Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4f89fac1f | ||
|
|
5262150d3c | ||
|
|
b5f56eb2b5 | ||
|
|
9a375fd0d1 | ||
|
|
6ca4aeb5a2 | ||
|
|
8ed4a9a5f2 | ||
|
|
a4578405cf | ||
|
|
73f147b7e2 | ||
|
|
5c3d735315 | ||
|
|
200621996d | ||
|
|
a912a0a07e | ||
|
|
4146ceb048 | ||
|
|
ad568d2b9f | ||
|
|
c7bfb9a11d | ||
|
|
c9391181bd | ||
|
|
b08eb519bf | ||
|
|
47c4fc2f7e | ||
|
|
dfd177433a | ||
|
|
27381e9696 | ||
|
|
1c7827c53c | ||
|
|
9512bdb5d6 | ||
|
|
33d1f1d692 | ||
|
|
c6b49545ff | ||
|
|
a4a833bfdb | ||
|
|
2e1b6e4630 | ||
|
|
6911cfa387 | ||
|
|
aa29f890de | ||
|
|
0f4e6a01bc | ||
|
|
1d3d8d6710 | ||
|
|
005e06c56f | ||
|
|
0a7c32b3cb | ||
|
|
82916a04ea | ||
|
|
ba2e419894 | ||
|
|
0812c9025d |
No files matched your search
@@ -17,23 +17,20 @@
|
||||
*/
|
||||
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;
|
||||
import java.net.URLClassLoader;
|
||||
@@ -41,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 {
|
||||
|
||||
@@ -57,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) {
|
||||
@@ -101,6 +80,9 @@ public final class Launcher extends Application {
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
LOG.info("Current Directory: " + Paths.get("").toAbsolutePath());
|
||||
LOG.info("HMCL Directory: " + HMCL_DIRECTORY);
|
||||
LOG.info("Memory: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");
|
||||
ManagementFactory.getMemoryPoolMXBeans().stream().filter(bean -> bean.getName().equals("Metaspace")).findAny()
|
||||
.ifPresent(bean -> LOG.info("Metaspace: " + bean.getUsage().getUsed() / 1024 / 1024 + "MB"));
|
||||
|
||||
launch(args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
@@ -35,6 +36,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class InstallerController {
|
||||
private Profile profile;
|
||||
@@ -43,9 +45,6 @@ public class InstallerController {
|
||||
@FXML
|
||||
private ScrollPane scrollPane;
|
||||
@FXML private VBox contentPane;
|
||||
private String forge;
|
||||
private String liteLoader;
|
||||
private String optiFine;
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
@@ -58,32 +57,22 @@ public class InstallerController {
|
||||
this.version = profile.getRepository().getResolvedVersion(versionId);
|
||||
|
||||
contentPane.getChildren().clear();
|
||||
forge = liteLoader = optiFine = null;
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
Consumer<InstallerItem> removeAction = x -> {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new MaintainTask(version.setLibraries(newList))
|
||||
.then(variables -> new VersionJsonSaveTask(profile.getRepository(), variables.get(MaintainTask.ID)))
|
||||
.with(profile.getRepository().refreshVersionsAsync())
|
||||
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
|
||||
.start();
|
||||
};
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
contentPane.getChildren().add(new InstallerItem("Forge", library.getVersion(), removeAction));
|
||||
forge = library.getVersion();
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
contentPane.getChildren().add(new InstallerItem("LiteLoader", library.getVersion(), removeAction));
|
||||
liteLoader = library.getVersion();
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
contentPane.getChildren().add(new InstallerItem("OptiFine", library.getVersion(), removeAction));
|
||||
optiFine = library.getVersion();
|
||||
}
|
||||
}
|
||||
Function<Library, Consumer<InstallerItem>> removeAction = library -> x -> {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new MaintainTask(version.setLibraries(newList))
|
||||
.then(variables -> new VersionJsonSaveTask(profile.getRepository(), variables.get(MaintainTask.ID)))
|
||||
.with(profile.getRepository().refreshVersionsAsync())
|
||||
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
|
||||
.start();
|
||||
};
|
||||
|
||||
analyzer.getForge().ifPresent(library -> contentPane.getChildren().add(new InstallerItem("Forge", library.getVersion(), removeAction.apply(library))));
|
||||
analyzer.getLiteLoader().ifPresent(library -> contentPane.getChildren().add(new InstallerItem("LiteLoader", library.getVersion(), removeAction.apply(library))));
|
||||
analyzer.getOptiFine().ifPresent(library -> contentPane.getChildren().add(new InstallerItem("OptiFine", library.getVersion(), removeAction.apply(library))));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -93,6 +82,6 @@ public class InstallerController {
|
||||
if (!gameVersion.isPresent())
|
||||
Controllers.dialog(i18n("version.cannot_read"));
|
||||
else
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version));
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
@@ -119,17 +120,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
item.setGameVersion(game);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
libraries.append(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(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(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
|
||||
}
|
||||
}
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
analyzer.getForge().ifPresent(library -> libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n"));
|
||||
analyzer.getLiteLoader().ifPresent(library -> libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n"));
|
||||
analyzer.getOptiFine().ifPresent(library -> libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n"));
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
});
|
||||
@@ -236,6 +230,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -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,16 +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);
|
||||
|
||||
@@ -186,7 +187,12 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
Controllers.confirmDialog(i18n("version.manage.remove.confirm", version), i18n("message.confirm"), () -> {
|
||||
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == EnumGameDirectory.VERSION_FOLDER;
|
||||
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);
|
||||
|
||||
@@ -103,7 +103,7 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
return Optional.ofNullable(controller.getSettings().get(id)).map(it -> (RemoteVersion<?>) it).map(RemoteVersion::getSelfVersion).orElse(null);
|
||||
return Optional.ofNullable(controller.getSettings().get(id)).map(it -> (RemoteVersion) it).map(RemoteVersion::getSelfVersion).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -49,16 +49,16 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
GameBuilder builder = profile.getDependency().gameBuilder();
|
||||
|
||||
builder.name((String) settings.get("name"));
|
||||
builder.gameVersion(((RemoteVersion<?>) settings.get("game")).getGameVersion());
|
||||
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
builder.version((RemoteVersion<?>) settings.get("forge"));
|
||||
builder.version((RemoteVersion) settings.get("forge"));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
builder.version((RemoteVersion<?>) settings.get("liteloader"));
|
||||
builder.version((RemoteVersion) settings.get("liteloader"));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version((RemoteVersion<?>) settings.get("optifine"));
|
||||
builder.version((RemoteVersion) settings.get("optifine"));
|
||||
|
||||
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions());
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ import javafx.scene.Node;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -40,16 +42,14 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
private final String optiFine;
|
||||
|
||||
public InstallerWizardProvider(Profile profile, String gameVersion, Version version) {
|
||||
this(profile, gameVersion, version, null, null, null);
|
||||
}
|
||||
|
||||
public InstallerWizardProvider(Profile profile, String gameVersion, Version version, String forge, String liteLoader, String optiFine) {
|
||||
this.profile = profile;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.forge = forge;
|
||||
this.liteLoader = liteLoader;
|
||||
this.optiFine = optiFine;
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
forge = analyzer.getForge().map(Library::getVersion).orElse(null);
|
||||
liteLoader = analyzer.getLiteLoader().map(Library::getVersion).orElse(null);
|
||||
optiFine = analyzer.getOptiFine().map(Library::getVersion).orElse(null);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
@@ -85,16 +85,16 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
Task ret = Task.empty();
|
||||
Task ret = Task.ofResult("version", () -> version);
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("forge")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("liteloader")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("optifine")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
|
||||
|
||||
return ret.then(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/installers.fxml");
|
||||
|
||||
String gameVersion = ((RemoteVersion<?>) controller.getSettings().get("game")).getGameVersion();
|
||||
String gameVersion = ((RemoteVersion) controller.getSettings().get("game")).getGameVersion();
|
||||
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
|
||||
hasVersion.setMessage(i18n("install.new_game.already_exists"));
|
||||
txtName.getValidators().add(hasVersion);
|
||||
@@ -103,7 +103,7 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
return ((RemoteVersion<?>) controller.getSettings().get(id)).getSelfVersion();
|
||||
return ((RemoteVersion) controller.getSettings().get(id)).getSelfVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,9 +27,8 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
@@ -48,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 WizardController controller;
|
||||
|
||||
@FXML
|
||||
private JFXListView<VersionsPageItem> list;
|
||||
@@ -56,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;
|
||||
@@ -75,11 +77,12 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
this.gameVersion = gameVersion;
|
||||
this.downloadProvider = downloadProvider;
|
||||
this.libraryId = libraryId;
|
||||
this.controller = controller;
|
||||
this.versionList = downloadProvider.getVersionListById(libraryId);
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||
|
||||
if (versionList instanceof GameVersionList) {
|
||||
if (versionList.hasType()) {
|
||||
centrePane.getChildren().setAll(checkPane, list);
|
||||
} else
|
||||
centrePane.getChildren().setAll(list);
|
||||
@@ -99,21 +102,19 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
}
|
||||
|
||||
private List<VersionsPageItem> loadVersions() {
|
||||
boolean isGameVersionList = versionList instanceof GameVersionList;
|
||||
return versionList.getVersions(gameVersion).stream()
|
||||
.filter(it -> {
|
||||
if (isGameVersionList)
|
||||
switch (((GameRemoteVersionTag) it.getTag()).getType()) {
|
||||
case RELEASE:
|
||||
return chkRelease.isSelected();
|
||||
case SNAPSHOT:
|
||||
return chkSnapshot.isSelected();
|
||||
default:
|
||||
return chkOld.isSelected();
|
||||
}
|
||||
else return true;
|
||||
switch (it.getVersionType()) {
|
||||
case RELEASE:
|
||||
return chkRelease.isSelected();
|
||||
case SNAPSHOT:
|
||||
return chkSnapshot.isSelected();
|
||||
case OLD:
|
||||
return chkOld.isSelected();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted()
|
||||
.map(VersionsPageItem::new).collect(Collectors.toList());
|
||||
}
|
||||
@@ -126,8 +127,18 @@ 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 (versionList.getVersions(gameVersion).isEmpty()) {
|
||||
transitionHandler.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
} else {
|
||||
if (items.isEmpty()) {
|
||||
chkRelease.setSelected(true);
|
||||
chkSnapshot.setSelected(true);
|
||||
chkOld.setSelected(true);
|
||||
} else {
|
||||
list.getItems().setAll(items);
|
||||
}
|
||||
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
@@ -153,4 +164,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
private void onRefresh() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onBack() { controller.onPrev(true); }
|
||||
}
|
||||
@@ -25,8 +25,9 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -37,7 +38,7 @@ import java.util.Objects;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class VersionsPageItem extends StackPane {
|
||||
private final RemoteVersion<?> remoteVersion;
|
||||
private final RemoteVersion remoteVersion;
|
||||
@FXML
|
||||
private Label lblSelfVersion;
|
||||
@FXML
|
||||
@@ -49,14 +50,14 @@ public final class VersionsPageItem extends StackPane {
|
||||
@FXML
|
||||
private StackPane imageViewContainer;
|
||||
|
||||
public VersionsPageItem(RemoteVersion<?> remoteVersion) {
|
||||
public VersionsPageItem(RemoteVersion remoteVersion) {
|
||||
this.remoteVersion = Objects.requireNonNull(remoteVersion);
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions-list-item.fxml");
|
||||
lblSelfVersion.setText(remoteVersion.getSelfVersion());
|
||||
|
||||
if (remoteVersion.getTag() instanceof GameRemoteVersionTag) {
|
||||
switch (((GameRemoteVersionTag) remoteVersion.getTag()).getType()) {
|
||||
if (remoteVersion instanceof GameRemoteVersion) {
|
||||
switch (remoteVersion.getVersionType()) {
|
||||
case RELEASE:
|
||||
lblGameVersion.setText(i18n("version.game.release"));
|
||||
imageView.setImage(new Image("/assets/img/icon.png", 32, 32, false, true));
|
||||
@@ -70,13 +71,13 @@ public final class VersionsPageItem extends StackPane {
|
||||
imageView.setImage(new Image("/assets/img/grass.png", 32, 32, false, true));
|
||||
break;
|
||||
}
|
||||
} else if (remoteVersion.getTag() instanceof LiteLoaderRemoteVersionTag) {
|
||||
} else if (remoteVersion instanceof LiteLoaderRemoteVersion) {
|
||||
imageView.setImage(new Image("/assets/img/chicken.png", 32, 32, false, true));
|
||||
lblGameVersion.setText(remoteVersion.getGameVersion());
|
||||
} else if (remoteVersion instanceof OptiFineRemoteVersion) {
|
||||
// optifine has no icon.
|
||||
lblGameVersion.setText(remoteVersion.getGameVersion());
|
||||
} else { // forge
|
||||
} else if (remoteVersion instanceof ForgeRemoteVersion) {
|
||||
imageView.setImage(new Image("/assets/img/forge.png", 32, 32, false, true));
|
||||
lblGameVersion.setText(remoteVersion.getGameVersion());
|
||||
}
|
||||
@@ -84,7 +85,7 @@ public final class VersionsPageItem extends StackPane {
|
||||
leftPane.getChildren().remove(imageViewContainer);
|
||||
}
|
||||
|
||||
public RemoteVersion<?> getRemoteVersion() {
|
||||
public RemoteVersion getRemoteVersion() {
|
||||
return remoteVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.upgrade;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
class HMCLDownloadTask extends FileDownloadTask {
|
||||
|
||||
private RemoteVersion.Type archiveFormat;
|
||||
|
||||
public HMCLDownloadTask(RemoteVersion version, Path target) {
|
||||
super(NetworkUtils.toURL(version.getUrl()), target.toFile(), version.getIntegrityCheck());
|
||||
archiveFormat = version.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
super.execute();
|
||||
|
||||
try {
|
||||
Path target = getFile().toPath();
|
||||
|
||||
switch (archiveFormat) {
|
||||
case JAR:
|
||||
break;
|
||||
|
||||
case PACK_XZ:
|
||||
byte[] raw = Files.readAllBytes(target);
|
||||
try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw));
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
|
||||
Pack200.newUnpacker().unpack(in, out);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown format: " + archiveFormat);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
getFile().delete();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import java.util.zip.ZipFile;
|
||||
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.JarUtils;
|
||||
|
||||
/**
|
||||
* A class that checks the integrity of HMCL.
|
||||
@@ -123,8 +124,7 @@ public final class IntegrityChecker {
|
||||
}
|
||||
|
||||
private static void verifySelf() throws IOException {
|
||||
Path self = LocalVersion.current().orElseThrow(() -> new IOException("Failed to find myself"))
|
||||
.getLocation();
|
||||
Path self = JarUtils.thisJar().orElseThrow(() -> new IOException("Failed to find current HMCL location"));
|
||||
requireVerifiedJar(self);
|
||||
}
|
||||
}
|
||||
@@ -1,162 +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.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.JarUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
/**
|
||||
* A class used to manage the local HMCL repository.
|
||||
*
|
||||
* @author yushijinhun
|
||||
*/
|
||||
final class LocalRepository {
|
||||
private LocalRepository() {}
|
||||
|
||||
private static Path localStorage = Launcher.HMCL_DIRECTORY.toPath().resolve("hmcl.jar");
|
||||
private static Path hmclVersionJson = Launcher.HMCL_DIRECTORY.toPath().resolve("hmclver.json");
|
||||
|
||||
/**
|
||||
* Gets the current stored executable in local repository.
|
||||
*/
|
||||
public static Optional<LocalVersion> getStored() {
|
||||
if (!Files.isRegularFile(localStorage)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(localStorage)
|
||||
.flatMap(JarUtils::getImplementationVersion)
|
||||
.map(version -> new LocalVersion(version, localStorage));
|
||||
}
|
||||
|
||||
private static void writeToStorage(Path source, boolean checkHeaders) throws IOException {
|
||||
IntegrityChecker.requireVerifiedJar(source);
|
||||
Files.createDirectories(localStorage.getParent());
|
||||
if (checkHeaders) {
|
||||
ExecutableHeaderHelper.copyWithoutHeader(source, localStorage);
|
||||
} else {
|
||||
Files.copy(source, localStorage, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
Optional<LocalVersion> stored = getStored();
|
||||
if (stored.isPresent()) {
|
||||
Map<String, String> json = new HashMap<>();
|
||||
json.put("ver", stored.get().getVersion());
|
||||
json.put("loc", stored.get().getLocation().toString());
|
||||
FileUtils.writeText(hmclVersionJson.toFile(), Constants.GSON.toJson(json));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a task that downloads the given version to local repository.
|
||||
*/
|
||||
public static FileDownloadTask downloadFromRemote(RemoteVersion version) throws IOException {
|
||||
Path downloaded = Files.createTempFile("hmcl-update-", null);
|
||||
return new FileDownloadTask(new URL(version.getUrl()), downloaded.toFile(), version.getIntegrityCheck()) {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
super.execute();
|
||||
|
||||
try {
|
||||
switch (version.getType()) {
|
||||
case JAR:
|
||||
writeToStorage(downloaded, false);
|
||||
break;
|
||||
|
||||
case PACK_XZ:
|
||||
Path unpacked = Files.createTempFile("hmcl-update-unpack-", null);
|
||||
try {
|
||||
try (InputStream in = new XZInputStream(Files.newInputStream(downloaded));
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(unpacked))) {
|
||||
Pack200.newUnpacker().unpack(in, out);
|
||||
}
|
||||
writeToStorage(unpacked, false);
|
||||
} finally {
|
||||
Files.deleteIfExists(unpacked);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown type: " + version.getType());
|
||||
}
|
||||
} finally {
|
||||
Files.deleteIfExists(downloaded);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the current HMCL executable to local repository.
|
||||
*/
|
||||
public static void downloadFromCurrent() {
|
||||
Optional<LocalVersion> current = LocalVersion.current();
|
||||
if (current.isPresent()) {
|
||||
Path currentPath = current.get().getLocation();
|
||||
if (!Files.isRegularFile(currentPath)) {
|
||||
LOG.warning("Failed to download " + current.get() + ", it isn't a file");
|
||||
return;
|
||||
}
|
||||
if (isSameAsLocalStorage(currentPath)) {
|
||||
LOG.warning("Trying to download from self, ignored");
|
||||
return;
|
||||
}
|
||||
LOG.info("Downloading " + current.get());
|
||||
try {
|
||||
writeToStorage(current.get().getLocation(), true);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to download " + current.get(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the executable stored in local repository to the given location.
|
||||
*/
|
||||
public static void applyTo(Path target) throws IOException {
|
||||
if (isSameAsLocalStorage(target)) {
|
||||
throw new IOException("Cannot apply update to self");
|
||||
}
|
||||
|
||||
LOG.info("Applying update to " + target);
|
||||
IntegrityChecker.requireVerifiedJar(localStorage);
|
||||
ExecutableHeaderHelper.copyWithHeader(localStorage, target);
|
||||
}
|
||||
|
||||
private static boolean isSameAsLocalStorage(Path path) {
|
||||
return path.toAbsolutePath().equals(localStorage.toAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.JarUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
class LocalVersion {
|
||||
|
||||
public static Optional<LocalVersion> current() {
|
||||
return JarUtils.thisJar().map(path -> new LocalVersion(Metadata.VERSION, path));
|
||||
}
|
||||
|
||||
private String version;
|
||||
private Path location;
|
||||
|
||||
public LocalVersion(String version, Path location) {
|
||||
this.version = version;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Path getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + version + " at " + location + "]";
|
||||
}
|
||||
}
|
||||
+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();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.checkFxUserThread;
|
||||
import static org.jackhuang.hmcl.util.IntVersionNumber.isIntVersionNumber;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.VersionNumber.asVersion;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -30,6 +29,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -45,9 +45,12 @@ import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.JarUtils;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
@@ -58,9 +61,7 @@ public final class UpdateHandler {
|
||||
* @return whether to exit
|
||||
*/
|
||||
public static boolean processArguments(String[] args) {
|
||||
if (!isIntVersionNumber(Metadata.VERSION)) {
|
||||
return false;
|
||||
}
|
||||
breakForceUpdateFeature();
|
||||
|
||||
if (isNestedApplication()) {
|
||||
// updated from old versions
|
||||
@@ -88,33 +89,69 @@ public final class UpdateHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
Optional<LocalVersion> local = LocalRepository.getStored();
|
||||
if (local.isPresent()) {
|
||||
int difference = asVersion(local.get().getVersion()).compareTo(asVersion(Metadata.VERSION));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (difference < 0) {
|
||||
LocalRepository.downloadFromCurrent();
|
||||
public static void updateFrom(RemoteVersion version) {
|
||||
checkFxUserThread();
|
||||
|
||||
} else if (difference > 0) {
|
||||
Optional<LocalVersion> current = LocalVersion.current();
|
||||
if (current.isPresent() && IntegrityChecker.isSelfVerified()) {
|
||||
try {
|
||||
requestUpdate(local.get().getLocation(), current.get().getLocation());
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to update from local repository", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
LocalRepository.downloadFromCurrent();
|
||||
Path downloaded;
|
||||
try {
|
||||
downloaded = Files.createTempFile("hmcl-update-", ".jar");
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to create temp file", e);
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
Task task = new HMCLDownloadTask(version, downloaded);
|
||||
|
||||
TaskExecutor executor = task.executor();
|
||||
Region dialog = Controllers.taskDialog(executor, i18n("message.downloading"), "", null);
|
||||
thread(() -> {
|
||||
boolean success = executor.test();
|
||||
Platform.runLater(() -> dialog.fireEvent(new DialogCloseEvent()));
|
||||
|
||||
if (success) {
|
||||
try {
|
||||
if (!IntegrityChecker.isSelfVerified()) {
|
||||
throw new IOException("Current JAR is not verified");
|
||||
}
|
||||
|
||||
requestUpdate(downloaded, getCurrentLocation());
|
||||
System.exit(0);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
Throwable e = task.getLastException();
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void applyUpdate(Path target) throws IOException {
|
||||
LOG.info("Applying update to " + target);
|
||||
|
||||
Path self = getCurrentLocation();
|
||||
IntegrityChecker.requireVerifiedJar(self);
|
||||
ExecutableHeaderHelper.copyWithHeader(self, target);
|
||||
|
||||
Optional<Path> newFilename = tryRename(target, Metadata.VERSION);
|
||||
if (newFilename.isPresent()) {
|
||||
LOG.info("Move " + target + " to " + newFilename.get());
|
||||
try {
|
||||
Files.move(target, newFilename.get());
|
||||
target = newFilename.get();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to move target", e);
|
||||
}
|
||||
}
|
||||
|
||||
startJava(target);
|
||||
}
|
||||
|
||||
private static void requestUpdate(Path updateTo, Path self) throws IOException {
|
||||
@@ -122,28 +159,6 @@ public final class UpdateHandler {
|
||||
startJava(updateTo, "--apply-to", self.toString());
|
||||
}
|
||||
|
||||
private static void applyUpdate(Path target) throws IOException {
|
||||
LocalRepository.applyTo(target);
|
||||
|
||||
Optional<String> newVersion = LocalRepository.getStored().map(LocalVersion::getVersion);
|
||||
if (newVersion.isPresent()) {
|
||||
Optional<Path> newFilename = tryRename(target, newVersion.get());
|
||||
if (newFilename.isPresent()) {
|
||||
LOG.info("Move " + target + " to " + newFilename.get());
|
||||
try {
|
||||
Files.move(target, newFilename.get());
|
||||
target = newFilename.get();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to move target", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.warning("Failed to find local repository");
|
||||
}
|
||||
|
||||
startJava(target);
|
||||
}
|
||||
|
||||
private static void startJava(Path jar, String... appArgs) throws IOException {
|
||||
List<String> commandline = new ArrayList<>();
|
||||
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath());
|
||||
@@ -159,49 +174,6 @@ public final class UpdateHandler {
|
||||
.start();
|
||||
}
|
||||
|
||||
public static void updateFrom(RemoteVersion version) {
|
||||
checkFxUserThread();
|
||||
|
||||
Task task;
|
||||
try {
|
||||
task = LocalRepository.downloadFromRemote(version);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to create upgrade download task", e);
|
||||
return;
|
||||
}
|
||||
TaskExecutor executor = task.executor();
|
||||
Region dialog = Controllers.taskDialog(executor, i18n("message.downloading"), "", null);
|
||||
thread(() -> {
|
||||
boolean success = executor.test();
|
||||
Platform.runLater(() -> dialog.fireEvent(new DialogCloseEvent()));
|
||||
if (success) {
|
||||
try {
|
||||
Optional<LocalVersion> current = LocalVersion.current();
|
||||
Optional<LocalVersion> stored = LocalRepository.getStored();
|
||||
if (!current.isPresent()) {
|
||||
throw new IOException("Failed to find current HMCL location");
|
||||
}
|
||||
if (!stored.isPresent()) {
|
||||
throw new IOException("Failed to find local repository, this shouldn't happen");
|
||||
}
|
||||
if (!IntegrityChecker.isSelfVerified()) {
|
||||
throw new IOException("Current JAR is not verified");
|
||||
}
|
||||
requestUpdate(stored.get().getLocation(), current.get().getLocation());
|
||||
System.exit(0);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Throwable e = task.getLastException();
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Optional<Path> tryRename(Path path, String newVersion) {
|
||||
String filename = path.getFileName().toString();
|
||||
Matcher matcher = Pattern.compile("^(?<prefix>[hH][mM][cC][lL][.-])(?<version>\\d+(?:\\.\\d+)*)(?<suffix>\\.[^.]+)$").matcher(filename);
|
||||
@@ -214,6 +186,10 @@ public final class UpdateHandler {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Path getCurrentLocation() throws IOException {
|
||||
return JarUtils.thisJar().orElseThrow(() -> new IOException("Failed to find current HMCL location"));
|
||||
}
|
||||
|
||||
// ==== support for old versions ===
|
||||
private static void performMigration() throws IOException {
|
||||
LOG.info("Migrating from old versions");
|
||||
@@ -221,17 +197,7 @@ public final class UpdateHandler {
|
||||
Path location = getParentApplicationLocation()
|
||||
.orElseThrow(() -> new IOException("Failed to get parent application location"));
|
||||
|
||||
Optional<LocalVersion> local = LocalRepository.getStored();
|
||||
if (!local.isPresent() ||
|
||||
asVersion(local.get().getVersion()).compareTo(asVersion(Metadata.VERSION)) < 0) {
|
||||
LocalRepository.downloadFromCurrent();
|
||||
}
|
||||
local = LocalRepository.getStored();
|
||||
if (!local.isPresent()) {
|
||||
throw new IOException("Failed to find local repository");
|
||||
}
|
||||
|
||||
requestUpdate(local.get().getLocation(), location);
|
||||
requestUpdate(getCurrentLocation(), location);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,7 +231,7 @@ public final class UpdateHandler {
|
||||
}
|
||||
|
||||
private static boolean isFirstLaunchAfterUpgrade() {
|
||||
Optional<Path> currentPath = LocalVersion.current().map(LocalVersion::getLocation);
|
||||
Optional<Path> currentPath = JarUtils.thisJar();
|
||||
if (currentPath.isPresent()) {
|
||||
Path updated = Launcher.HMCL_DIRECTORY.toPath().resolve("HMCL-" + Metadata.VERSION + ".jar");
|
||||
if (currentPath.get().toAbsolutePath().equals(updated.toAbsolutePath())) {
|
||||
@@ -274,5 +240,21 @@ public final class UpdateHandler {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void breakForceUpdateFeature() {
|
||||
try {
|
||||
Path hmclVersionJson = Launcher.HMCL_DIRECTORY.toPath().resolve("hmclver.json");
|
||||
if (Files.isRegularFile(hmclVersionJson)) {
|
||||
Map<?, ?> content = new Gson().fromJson(new String(Files.readAllBytes(hmclVersionJson), UTF_8), Map.class);
|
||||
Object ver = content.get("ver");
|
||||
if (ver instanceof String && ((String) ver).startsWith("3.")) {
|
||||
Files.delete(hmclVersionJson);
|
||||
LOG.info("Successfully broke the force update feature");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to break the force update feature", e);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
}
|
||||
@@ -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.
|
||||
@@ -317,7 +319,7 @@ update.checking=Checking for updates
|
||||
update.failed=Failed to perform upgrade
|
||||
update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.note=Development version contains more functionality and bug fixes as well as more possible bugs. And this will affect all HMCL installations in your computer.
|
||||
update.note=Development version contains more functionality and bug fixes as well as more possible bugs.
|
||||
update.latest=This is latest Version.
|
||||
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
|
||||
update.tooltip=Update
|
||||
@@ -334,7 +336,9 @@ 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 version %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
|
||||
version.manage.rename.message=Please enter the new name
|
||||
|
||||
@@ -14,332 +14,336 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
#
|
||||
#author: huangyuhui
|
||||
# author: huangyuhui
|
||||
about.copyright=版權
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun ([email protected])
|
||||
about.thanks_to=鳴謝
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默認背景圖)\n所有通過 Issues、Pull Requests 等方式參與本項目的貢獻者
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (預設背景圖)\n所有通過 Issues、Pull Requests 等方式參與本項目的貢獻者
|
||||
about.dependency=依賴
|
||||
# 由於篇幅限制,僅列出第一作者
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
|
||||
about.claim=免責聲明
|
||||
about.claim.statement=Minecraft 版權歸 Mojang AB 所有,使用本軟件產生的版權問題,軟件製作方概不負責。請支持正版。
|
||||
about.claim.statement=Minecraft 版權歸 Mojang AB 所有,使用本軟體產生的版權問題,軟體製作方概不負責。請支援正版。
|
||||
about.open_source=開源
|
||||
about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
|
||||
account=賬戶
|
||||
account=帳戶
|
||||
account.character=角色
|
||||
account.choose=選擇一個角色
|
||||
account.create=新建賬戶
|
||||
account.email=郵箱
|
||||
account.failed.connect_authentication_server=無法連接認證服務器,可能是網絡問題
|
||||
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
|
||||
account.failed.invalid_credentials=您的用戶名或密碼錯誤,或者登錄次數過多被暫時禁止登錄,請稍後再試
|
||||
account.create=建立帳戶
|
||||
account.email=電子信箱
|
||||
account.failed.connect_authentication_server=無法連接認證伺服器,可能是網路問題
|
||||
account.failed.connect_injector_server=無法連接認證伺服器,可能是網路故障或 URL 輸入錯誤
|
||||
account.failed.invalid_credentials=您的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試
|
||||
account.failed.invalid_password=無效的密碼
|
||||
account.failed.invalid_token=請嘗試登出並重新輸入密碼登錄
|
||||
account.failed.invalid_token=請嘗試登出並重新輸入密碼登入
|
||||
account.failed.no_character=該帳號沒有角色
|
||||
account.failed.server_response_malformed=這個服務器不是合法的登錄驗證服務器
|
||||
account.injector.add=添加認證服務器
|
||||
account.injector.manage=管理認證服務器
|
||||
account.injector.manage.title=認證服務器
|
||||
account.injector.http=警告:此服務器使用不安全的 HTTP 協議,您的密碼在登錄時會被明文傳輸。
|
||||
account.injector.server=認證服務器
|
||||
account.injector.server_url=服務器地址
|
||||
account.injector.server_name=服務器名稱
|
||||
account.methods=登錄方式
|
||||
account.methods.authlib_injector=authlib-injector 登錄
|
||||
account.failed.server_response_malformed=這個伺服器不是合法的登入驗證伺服器
|
||||
account.injector.add=新增認證伺服器
|
||||
account.injector.manage=管理認證伺服器
|
||||
account.injector.manage.title=認證伺服器
|
||||
account.injector.http=警告:此伺服器使用不安全的 HTTP 協議,您的密碼在登入時會被明文傳輸。
|
||||
account.injector.server=認證伺服器
|
||||
account.injector.server_url=伺服器位址
|
||||
account.injector.server_name=伺服器名稱
|
||||
account.methods=登入方式
|
||||
account.methods.authlib_injector=authlib-injector 登入
|
||||
account.methods.offline=離線模式
|
||||
account.methods.yggdrasil=正版登錄
|
||||
account.missing=沒有賬戶
|
||||
account.methods.yggdrasil=正版登入
|
||||
account.missing=沒有帳戶
|
||||
account.password=密碼
|
||||
account.username=用戶名
|
||||
account.username=使用者名稱
|
||||
|
||||
archive.author=作者
|
||||
archive.game_version=遊戲版本
|
||||
archive.version=版本
|
||||
|
||||
assets.download=下載資源
|
||||
assets.download_all=檢查資源文件完整性
|
||||
assets.download_all=驗證資源檔案完整性
|
||||
|
||||
button.cancel=取消
|
||||
button.clear=清除
|
||||
button.delete=刪除
|
||||
button.edit=修改
|
||||
button.edit=編輯
|
||||
button.install=安裝
|
||||
button.no=否
|
||||
button.ok=確定
|
||||
button.refresh=刷新
|
||||
button.save=保存
|
||||
button.refresh=重新整理
|
||||
button.save=儲存
|
||||
button.yes=是
|
||||
|
||||
color.recent=推薦
|
||||
color.custom=自定義顏色
|
||||
color.custom=自訂顏色
|
||||
|
||||
crash.NoClassDefFound=請確認 HMCL 本體是否完整,或更新您的 Java。
|
||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟件崩潰,請檢查您的 Java 環境或您的電腦!可以嘗試重新安裝 Java。
|
||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體崩潰,請檢查您的 Java 環境或您的電腦!可以嘗試重新安裝 Java。
|
||||
|
||||
download=下載
|
||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.failed=下載失敗
|
||||
download.failed.refresh=加載版本列表失敗,點擊此處重試。
|
||||
download.mojang=官方
|
||||
download.failed.empty=沒有可供安裝的版本,點擊此處返回。
|
||||
download.failed.refresh=載入版本列表失敗,點擊此處重試。
|
||||
download.mojang=官方伺服器
|
||||
|
||||
extension.bat=Windows 腳本
|
||||
extension.mod=模組文件
|
||||
extension.png=圖片文件
|
||||
extension.mod=模組檔案
|
||||
extension.png=圖片檔案
|
||||
extension.sh=Bash 腳本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=當前 Java 平台缺少 DST Root CA X3 證書。\n您依然可以使用 HMCL,但將無法連接到部分站點(如使用 Let's Encrypt 證書的站點),這可能會使 HMCL 無法正常工作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
fatal.config_loading_failure=HMCL 無法加載配置文件。\n請確保 HMCL 對 "%s" 目錄及該目錄下的文件擁有讀寫權限。
|
||||
fatal.migration_requires_manual_reboot=HMCL 即將完成升級,請重新打開 HMCL。
|
||||
fatal.apply_update_failure=我們很抱歉 HMCL 無法自動完成升級,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。\n請考慮向我們反饋該問題。
|
||||
fatal.missing_javafx=找不到 JavaFX。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=目前的 Java 平台缺少 DST Root CA X3 憑證。\n您依然可以使用 HMCL,但將無法連線到部分網站(如使用 Lets Encrypt 證書的站點),這可能會使 HMCL 無法正常運作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
fatal.config_loading_failure=HMCL 無法載入設定檔案。\n請確保 HMCL 對 "%s" 目錄及該目錄下的檔案擁有讀寫權限。
|
||||
fatal.migration_requires_manual_reboot=HMCL 即將升級完成,請重新打開 HMCL。
|
||||
fatal.apply_update_failure=我們很抱歉 HMCL 無法自動完成升級程序,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。\n請考慮向我們回報該問題。
|
||||
|
||||
folder.config=配置文件夾
|
||||
folder.coremod=核心MOD文件夾
|
||||
folder.game=遊戲文件夾
|
||||
folder.mod=MOD文件夾
|
||||
folder.resourcepacks=資源包文件夾
|
||||
folder.saves=存檔文件夾
|
||||
folder.screenshots=截圖文件夾
|
||||
folder.config=設定資料夾
|
||||
folder.coremod=核心 MOD 模組資料夾
|
||||
folder.game=遊戲資料夾
|
||||
folder.mod=MOD 模組資料夾
|
||||
folder.resourcepacks=資源包資料夾
|
||||
folder.saves=遊戲存檔資料夾
|
||||
folder.screenshots=截圖資料夾
|
||||
|
||||
general.prompt.empty=(無)
|
||||
general.prompt.empty=(無)
|
||||
|
||||
input.email=用戶名必須是郵箱
|
||||
input.email=「使用者名稱」必須是電子信箱格式
|
||||
input.number=必須是數字
|
||||
input.not_empty=必填項
|
||||
input.url=必須是合法的鏈接
|
||||
input.not_empty=必填
|
||||
input.url=必須為有效連結
|
||||
|
||||
install=添加遊戲
|
||||
install=新增遊戲
|
||||
install.failed=安裝失敗
|
||||
install.installer.choose=選擇 %s 版本
|
||||
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=安裝整合包
|
||||
install.new_game=安裝新遊戲版本
|
||||
install.new_game.already_exists=此版本已經存在,請換一個名字
|
||||
install.new_game.current_game_version=當前遊戲版本
|
||||
install.new_game.current_game_version=目前遊戲版本
|
||||
install.select=請選擇安裝方式
|
||||
install.success=安裝成功
|
||||
|
||||
lang=繁體中文
|
||||
lang=正體中文
|
||||
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.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,所以可能影響遊戲體驗或無法啓動遊戲,啓動器仍會嘗試啓動。
|
||||
launch.advice.too_large_memory_for_32bit=您設置的內存大小過大,由於可能超過了 32 位 Java 的內存分配限制,所以可能無法啓動遊戲,請將內存調至 1024MB 或更小,啓動器仍會嘗試啓動。
|
||||
launch.failed=啓動失敗
|
||||
launch.failed.cannot_create_jvm=截獲到無法創建 Java 虛擬機,可能是 Java 參數有問題,可以在設置中開啓無參數模式啓動。
|
||||
launch.failed.creating_process=啓動失敗,在創建新進程時發生錯誤,可能是 Java 路徑錯誤。
|
||||
launch.failed.decompressing_natives=未能解壓遊戲本地庫。
|
||||
launch.failed.download_library=未能下载游戏依赖 %s.
|
||||
launch.failed.executable_permission=未能爲啓動文件添加執行權限。
|
||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看日志文件,或聯繫他人尋求幫助。
|
||||
launch.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,所以可能影響遊戲體驗或無法啟動遊戲。啟動器仍會嘗試啟動。
|
||||
launch.advice.too_large_memory_for_32bit=您設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。啟動器仍會嘗試啟動。
|
||||
launch.failed=啟動失敗
|
||||
launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。
|
||||
launch.failed.creating_process=啟動失敗,在建立新執行程序時發生錯誤。可能是 Java 路徑錯誤。
|
||||
launch.failed.decompressing_natives=無法解壓縮遊戲資源庫。
|
||||
launch.failed.download_library=無法下載遊戲依賴 %s.
|
||||
launch.failed.executable_permission=無法為啟動檔案新增執行權限。
|
||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。
|
||||
launch.state.dependencies=正在處理遊戲依賴
|
||||
launch.state.done=啓動完成
|
||||
launch.state.logging_in=登錄中
|
||||
launch.state.modpack=正在下載必要文件
|
||||
launch.state.waiting_launching=等待遊戲啓動
|
||||
launch.wrong_javadir=錯誤的 Java 路徑,將自動重置爲默認 Java 路徑。
|
||||
launch.state.done=啟動完成
|
||||
launch.state.logging_in=登入中
|
||||
launch.state.modpack=正在下載必要檔案
|
||||
launch.state.waiting_launching=等待遊戲啟動
|
||||
launch.wrong_javadir=錯誤的 Java 路徑,將自動重設為預設 Java 路徑。
|
||||
|
||||
launcher=啓動器
|
||||
launcher.background=背景地址
|
||||
launcher=啟動器
|
||||
launcher.background=背景位址
|
||||
launcher.background.choose=選擇背景路徑
|
||||
launcher.background.default=默認(自動檢索啓動器同目錄下的 background.png/jpg 及 bg 文件夾內的圖片)
|
||||
launcher.common_directory=公共文件夾
|
||||
launcher.common_directory.choose=選擇公共文件夾
|
||||
launcher.common_directory.default=默認
|
||||
launcher.common_directory.disabled=禁用
|
||||
launcher.contact=聯繫我們
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並通過 MCBBS、貼吧、GitHub 或 Minecraft Forum 反饋 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已檢測到您的啓動器不是最新版本,請更新後再試!
|
||||
launcher.background.default=預設(自動尋找啟動器同目錄下的 background.png/jpg 及 bg 資料夾內的圖片)
|
||||
launcher.common_directory=公共資料夾
|
||||
launcher.common_directory.choose=選擇公共資料夾
|
||||
launcher.common_directory.default=預設
|
||||
launcher.common_directory.disabled=停用
|
||||
launcher.contact=聯絡我們
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並透過 MCBBS、貼吧、GitHub 或 Minecraft Forum 回報 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已偵測到您的啟動器不是最新版本,請更新後重試!
|
||||
launcher.update_java=請更新您的 Java
|
||||
|
||||
login.empty_username=你還未設置用戶名!
|
||||
login.empty_username=你還未設定使用者名稱!
|
||||
login.enter_password=請輸入您的密碼
|
||||
|
||||
logwindow.show_lines=顯示行數
|
||||
logwindow.terminate_game=結束遊戲進程
|
||||
logwindow.title=日誌
|
||||
logwindow.terminate_game=結束遊戲執行程序
|
||||
logwindow.title=記錄
|
||||
|
||||
main_page=主頁
|
||||
main_page=首頁
|
||||
|
||||
message.confirm=提示
|
||||
message.doing=請耐心等待
|
||||
message.downloading=正在下載...
|
||||
message.downloading=正在下載…
|
||||
message.error=錯誤
|
||||
message.info=提示
|
||||
message.success=已完成
|
||||
message.info=資訊
|
||||
message.success=完成
|
||||
message.unknown=未知
|
||||
message.warning=警告
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=選擇要安裝的遊戲整合包文件
|
||||
modpack.desc=描述你要製作的整合包,比如整合包注意事項和更新記錄,支持 Markdown(圖片請用網絡圖)。
|
||||
modpack.enter_name=給遊戲起個你喜歡的名字
|
||||
modpack.export=導出整合包
|
||||
modpack.choose=選擇要安裝的遊戲整合包檔案
|
||||
modpack.desc=描述你要製作的整合包,比如整合包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。
|
||||
modpack.enter_name=給遊戲取個你喜歡的名字
|
||||
modpack.export=匯出整合包
|
||||
modpack.files.blueprints=BuildCraft 藍圖
|
||||
modpack.files.config=Mod 配置文件
|
||||
modpack.files.dumps=NEI 調試輸出
|
||||
modpack.files.liteconfig=Mod 配置文件
|
||||
modpack.files.mods=Mod
|
||||
modpack.files.mods.voxelmods=VoxelMods 配置,如小地圖
|
||||
modpack.files.config=Mod 模組設定檔案
|
||||
modpack.files.dumps=NEI 調校輸出
|
||||
modpack.files.liteconfig=Mod 模組設定檔案
|
||||
modpack.files.mods=Mod 模組
|
||||
modpack.files.mods.voxelmods=VoxelMods 設定,如小地圖
|
||||
modpack.files.options_txt=遊戲設定
|
||||
modpack.files.optionsshaders_txt=光影設定
|
||||
modpack.files.resourcepacks=資源包(材質包)
|
||||
modpack.files.resourcepacks=資源包 (材質包)
|
||||
modpack.files.saves=遊戲存檔
|
||||
modpack.files.scripts=MineTweaker 配置
|
||||
modpack.files.servers_dat=多人遊戲服務器列表
|
||||
modpack.install=安裝%s整合包
|
||||
modpack.files.scripts=MineTweaker 設定
|
||||
modpack.files.servers_dat=多人遊戲伺服器列表
|
||||
modpack.install=安裝 %s 整合包
|
||||
modpack.installing=正在安裝整合包
|
||||
modpack.introduction=支持 Curse、MultiMC、HMCL、MCBBS 整合包。
|
||||
modpack.invalid=無效的整合包升級文件,可能是下載時出現問題。
|
||||
modpack.mismatched_type=整合包類型不匹配,當前遊戲是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。
|
||||
modpack.introduction=支援 Curse、MultiMC、HMCL、MCBBS 整合包。
|
||||
modpack.invalid=無效的整合包升級檔案,可能是下載時出現問題。
|
||||
modpack.mismatched_type=整合包類型不匹配,目前遊戲是 %s 整合包,但是提供的整合包更新檔案是 %s 整合包。
|
||||
modpack.name=整合包名稱
|
||||
modpack.not_a_valid_name=不是一個有效的整合包名稱
|
||||
modpack.not_a_valid_name=整合包名稱無效
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=導入整合包
|
||||
modpack.task.install.error=安裝失敗,可能是整合包格式不正確或操作文件失敗。
|
||||
modpack.task.install=匯入整合包
|
||||
modpack.task.install.error=安裝失敗,可能是整合包格式錯誤或操作檔案失敗。
|
||||
modpack.task.install.will=將會安裝整合包:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=下載 Curse 整合包相關文件
|
||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||
modpack.type.curse.completion=下載 Curse 整合包相關檔案
|
||||
modpack.type.curse.error=無法完成 Curse 整合包的下載,請多次重試或設定代理
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=該整合包不被支持。僅 HMCL、MultiMC、Curse 整合包受支持。
|
||||
modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。
|
||||
modpack.update=正在升級整合包
|
||||
modpack.wizard=導出整合包嚮導
|
||||
modpack.wizard.step.1=基本設置
|
||||
modpack.wizard.step.1.title=設置整合包的主要信息
|
||||
modpack.wizard.step.2=文件選擇
|
||||
modpack.wizard.step.2.title=選中你想加到整合包中的文件或文件夾
|
||||
modpack.wizard=匯出整合包引導
|
||||
modpack.wizard.step.1=基本設定
|
||||
modpack.wizard.step.1.title=設定整合包的主要訊息
|
||||
modpack.wizard.step.2=檔案選擇
|
||||
modpack.wizard.step.2.title=選中你想加到整合包中的檔案或資料夾
|
||||
modpack.wizard.step.3=整合包描述
|
||||
modpack.wizard.step.initialization.exported_version=要導出的遊戲版本
|
||||
modpack.wizard.step.initialization.include_launcher=包含啓動器
|
||||
modpack.wizard.step.initialization.save=選擇要導出到的遊戲整合包位置
|
||||
modpack.wizard.step.initialization.warning=在製作整合包前,請您確認您選擇的版本可以正常啓動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應當將不允許非官方途徑傳播的 Mod、材質包等納入整合包。\n整合包會保存您目前的下載源設置
|
||||
modpack.wizard.step.initialization.exported_version=要匯出的遊戲版本
|
||||
modpack.wizard.step.initialization.include_launcher=包含啟動器
|
||||
modpack.wizard.step.initialization.save=選擇要匯出到的遊戲整合包位置
|
||||
modpack.wizard.step.initialization.warning=在製作整合包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入整合包。\n整合包會儲存您目前的下載來源設定
|
||||
|
||||
mods=模組管理
|
||||
mods.add=添加模組
|
||||
mods.add.failed=添加模組 %s 失敗。
|
||||
mods.add.success=成功添加模組 %s。
|
||||
mods.add=新增模組
|
||||
mods.add.failed=新增模組 %s 失敗。
|
||||
mods.add.success=成功新增模組 %s。
|
||||
mods.choose_mod=選擇模組
|
||||
mods.remove=刪除
|
||||
|
||||
|
||||
profile=配置
|
||||
profile.default=當前目錄
|
||||
profile.home=主文件夾
|
||||
profile=設定
|
||||
profile.default=目前目錄
|
||||
profile.home=主資料夾
|
||||
profile.instance_directory=遊戲路徑
|
||||
profile.instance_directory.choose=選擇遊戲路徑
|
||||
profile.new=新建配置
|
||||
profile.new=建立設定
|
||||
profile.title=遊戲目錄
|
||||
profile.selected=已選中
|
||||
profile.use_relative_path=若可能,遊戲目錄使用相對路徑
|
||||
profile.selected=已選取
|
||||
profile.use_relative_path=若可能,則在遊戲目錄使用相對路徑
|
||||
|
||||
selector.choose=選擇
|
||||
selector.choose_file=選擇文件
|
||||
selector.custom=自定義
|
||||
selector.choose_file=選擇檔案
|
||||
selector.custom=自訂
|
||||
|
||||
settings=普通設置
|
||||
settings=普通設定
|
||||
|
||||
settings.advanced=高級設置
|
||||
settings.advanced=進階設定
|
||||
settings.advanced.dont_check_game_completeness=不檢查遊戲完整性
|
||||
settings.advanced.game_dir.default=默認(.minecraft/)
|
||||
settings.advanced.game_dir.default=預設(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本獨立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_args_default=啓動器默認添加的參數(請不要重複添加):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=內存永久保存區域(不必填寫,MB)
|
||||
settings.advanced.java_args_default=啟動器預設新增的參數(請勿重複新增):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=記憶體永久儲存區域(不必填寫,格式:MB)
|
||||
settings.advanced.jvm_args=Java 虛擬機參數(不必填寫)
|
||||
settings.advanced.launcher_visibility.close=遊戲啓動後結束啓動器
|
||||
settings.advanced.launcher_visibility.hide=遊戲啓動後隱藏啓動器
|
||||
settings.advanced.launcher_visibility.hide_reopen=隱藏啓動器並在遊戲結束後重新打開
|
||||
settings.advanced.launcher_visibility.keep=保持啓動器可見
|
||||
settings.advanced.launcher_visible=啓動器可見性
|
||||
settings.advanced.launcher_visibility.close=遊戲啟動後結束啟動器
|
||||
settings.advanced.launcher_visibility.hide=遊戲啟動後隱藏啟動器
|
||||
settings.advanced.launcher_visibility.hide_reopen=隱藏啟動器並在遊戲結束後重新開啟
|
||||
settings.advanced.launcher_visibility.keep=不隱藏啟動器
|
||||
settings.advanced.launcher_visible=啟動器可見性
|
||||
settings.advanced.minecraft_arguments=Minecraft 額外參數(不必填寫)
|
||||
settings.advanced.no_common=不使用公共路徑
|
||||
settings.advanced.no_jvm_args=不添加默認的 JVM 參數(使用 Java 9 時必勾)
|
||||
settings.advanced.precall_command=啓動前執行命令(不必填寫,將在遊戲啓動前調用)
|
||||
settings.advanced.server_ip=直入服務器 IP 地址(不必填寫,啓動遊戲後直接進入對應服務器)
|
||||
settings.advanced.no_jvm_args=不新增預設的 JVM 參數(使用 Java 9 時必勾)
|
||||
settings.advanced.precall_command=啟動前執行命令(不必填寫,將在遊戲啟動前呼叫使用)
|
||||
settings.advanced.server_ip=直接進入伺服器 IP 位址(不必填寫,啟動遊戲後直接進入對應伺服器)
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填寫,如 optirun)
|
||||
|
||||
settings.custom=自定義
|
||||
settings.custom=自訂
|
||||
|
||||
settings.game=遊戲設置
|
||||
settings.game.dimension=遊戲窗口分辨率
|
||||
settings.game=遊戲設定
|
||||
settings.game.dimension=遊戲介面解析度大小
|
||||
settings.game.exploration=瀏覽
|
||||
settings.game.fullscreen=全屏
|
||||
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=管理
|
||||
|
||||
settings.icon=遊戲圖標
|
||||
settings.icon=遊戲圖示
|
||||
|
||||
settings.launcher=啓動器設置
|
||||
settings.launcher.common_path.tooltip=啓動器將所有遊戲資源及依賴庫文件放於此集中管理,如果遊戲文件夾內有現成的將不會使用公共庫文件
|
||||
settings.launcher.download_source=下載源
|
||||
settings.launcher=啟動器設定
|
||||
settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及依賴庫檔案放於此集中管理,如果遊戲資料夾內有現成的將不會使用公共庫檔案
|
||||
settings.launcher.download_source=下載來源
|
||||
settings.launcher.language=語言
|
||||
settings.launcher.log_font=日誌字體
|
||||
settings.launcher.log_font=記錄字體
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=身份驗證
|
||||
settings.launcher.proxy.disable=使用系統代理
|
||||
settings.launcher.proxy.host=主機
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密碼
|
||||
settings.launcher.proxy.port=端口
|
||||
settings.launcher.proxy.port=埠 (Port)
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=賬戶
|
||||
settings.launcher.proxy.username=帳戶
|
||||
settings.launcher.theme=主題
|
||||
|
||||
settings.max_memory=最大內存(MB)
|
||||
settings.physical_memory=物理內存大小
|
||||
settings.show_log=查看日誌
|
||||
settings.max_memory=最大記憶體(MB)
|
||||
settings.physical_memory=實體記憶體大小
|
||||
settings.show_log=查看記錄
|
||||
settings.tabs.installers=自動安裝
|
||||
settings.type.global=全局版本設置(使用該設置的版本共用一套設定)
|
||||
settings.type=版本設置類型
|
||||
settings.type.special=單獨版本設置(不會影響到其他版本的設定)
|
||||
settings.type.global=全域版本設定(使用該設定的版本共用一套設定)
|
||||
settings.type=版本設定類型
|
||||
settings.type.special=單獨版本設定(不會影響到其他版本的設定)
|
||||
|
||||
update=啓動器更新
|
||||
update=啟動器更新
|
||||
update.channel.dev=更新到開發版
|
||||
update.channel.stable=更新到推薦版本
|
||||
update.checking=正在檢查更新
|
||||
update.failed=更新失敗
|
||||
update.found=發現更新
|
||||
update.newest_version=最新版本爲:%s
|
||||
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。選擇更新到開發版導致你電腦中所有的HMCL更新至開發版
|
||||
update.latest=當前版本爲最新版本
|
||||
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼板了,您可以手動粘貼網址打開頁面
|
||||
update.found=發現到更新
|
||||
update.newest_version=最新版本為:%s
|
||||
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.latest=目前版本為最新版本
|
||||
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址打開頁面
|
||||
update.tooltip=更新
|
||||
|
||||
version.cannot_read=讀取遊戲版本失敗,無法進行自動安裝
|
||||
version.forbidden_name=此版本名稱不受支持,請換一個名字
|
||||
version.game.old=遠古版
|
||||
version.game.release=穩定版
|
||||
version.game.snapshot=測試版
|
||||
version.launch=啓動遊戲
|
||||
version.launch_script=生成啓動腳本
|
||||
version.launch_script.failed=生成啓動腳本失敗
|
||||
version.launch_script.save=保存啓動腳本
|
||||
version.launch_script.success=啓動腳本已生成完畢:%s
|
||||
version.manage.redownload_assets_index=重新下載資源配置(assets_index.json)
|
||||
version.forbidden_name=此版本名稱不受支援,請換一個名字
|
||||
version.game.old=老舊版本
|
||||
version.game.release=穩定版本
|
||||
version.game.snapshot=測試版本
|
||||
version.launch=啟動遊戲
|
||||
version.launch_script=生成啟動腳本
|
||||
version.launch_script.failed=生成啟動腳本失敗
|
||||
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_libraries=刪除所有庫文件
|
||||
version.manage.rename=重命名該版本
|
||||
version.manage.rename.message=請輸入要改成的名字
|
||||
version.manage.rename.fail=重命名版本失敗,可能文件被佔用或者名字有特殊字符
|
||||
version.settings=遊戲設置
|
||||
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=重新命名該版本
|
||||
version.manage.rename.message=請輸入新名稱
|
||||
version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名字有特殊字元
|
||||
version.settings=遊戲設定
|
||||
version.update=更新
|
||||
|
||||
wizard.prev=< 上一步
|
||||
|
||||
@@ -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=官方
|
||||
|
||||
@@ -92,13 +93,13 @@ extension.png=图片文件
|
||||
extension.sh=Bash 脚本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Let's Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Lets Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.config_loading_failure=HMCL 无法加载配置文件。\n请确保 HMCL 对 "%s" 目录及该目录下的文件拥有读写权限。
|
||||
fatal.migration_requires_manual_reboot=HMCL 即将完成升级,请重新打开 HMCL。
|
||||
fatal.apply_update_failure=我们很抱歉 HMCL 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 HMCL 来完成升级。\n请考虑向我们反馈该问题。
|
||||
|
||||
folder.config=配置文件夹
|
||||
folder.coremod=核心MOD文件夹
|
||||
folder.coremod=核心 MOD 文件夹
|
||||
folder.game=游戏文件夹
|
||||
folder.mod=MOD文件夹
|
||||
folder.resourcepacks=资源包文件夹
|
||||
@@ -132,10 +133,11 @@ install.success=安装成功
|
||||
lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice.different_platform=您的系统是 64 位的但是 Java 是 32 位的,推荐您安装 64 位 Java。
|
||||
launch.advice.different_platform=您的系统是 64 位,但是 Java 是 32 位的,建议您安装 64 位 Java。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 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.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,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小,启动器仍会尝试启动。
|
||||
launch.failed=启动失败
|
||||
@@ -258,7 +260,7 @@ settings.advanced.dont_check_game_completeness=不检查游戏完整性
|
||||
settings.advanced.game_dir.default=默认(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_args_default=启动器默认添加的参数(请不要重复添加):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,MB)
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,单位 MB)
|
||||
settings.advanced.jvm_args=Java 虚拟机参数(不必填写)
|
||||
settings.advanced.launcher_visibility.close=游戏启动后结束启动器
|
||||
settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器
|
||||
@@ -317,7 +319,7 @@ update.checking=正在检查更新
|
||||
update.failed=更新失败
|
||||
update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。选择更新到开发版导致你电脑中所有的HMCL更新至开发版
|
||||
update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
|
||||
update.tooltip=更新
|
||||
@@ -334,7 +336,9 @@ 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=重命名该版本
|
||||
version.manage.rename.message=请输入要改成的名字
|
||||
|
||||
@@ -64,8 +64,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
return baseURL
|
||||
.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")
|
||||
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven")
|
||||
.replace("https://libraries.minecraft.net", "http://bmclapi2.bangbang93.com/libraries")
|
||||
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "http://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")
|
||||
.replace("https://authlib-injector.yushi.moe", "https://bmclapi2.bangbang93.com/mirrors/authlib-injector");
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap;
|
||||
import org.jackhuang.hmcl.util.ExceptionalFunction;
|
||||
|
||||
/**
|
||||
* Note: This class has no state.
|
||||
@@ -63,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)
|
||||
);
|
||||
}
|
||||
@@ -77,7 +78,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task installLibraryAsync(Version version, RemoteVersion<?> libraryVersion) {
|
||||
public Task installLibraryAsync(Version version, RemoteVersion libraryVersion) {
|
||||
if (libraryVersion instanceof ForgeRemoteVersion)
|
||||
return new ForgeInstallTask(this, version, (ForgeRemoteVersion) libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
@@ -97,4 +98,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
|
||||
}
|
||||
|
||||
|
||||
public ExceptionalFunction<AutoTypingMap<String>, Task, ?> installLibraryAsync(RemoteVersion libraryVersion) {
|
||||
return var -> installLibraryAsync(var.get("version"), libraryVersion);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -67,8 +66,8 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
if (toolVersions.containsKey("optifine"))
|
||||
result = result.then(libraryTaskHelper(gameVersion, "optifine"));
|
||||
|
||||
for (RemoteVersion<?> remoteVersion : remoteVersions)
|
||||
result = result.then(var -> dependencyManager.installLibraryAsync(var.get("version"), remoteVersion));
|
||||
for (RemoteVersion remoteVersion : remoteVersions)
|
||||
result = result.then(dependencyManager.installLibraryAsync(remoteVersion));
|
||||
|
||||
return result;
|
||||
}).finalized((variables, isDependentsSucceeded) -> {
|
||||
|
||||
@@ -67,7 +67,7 @@ public interface DependencyManager {
|
||||
* @param libraryVersion the remote version of being installed library.
|
||||
* @return the task to install the specific library.
|
||||
*/
|
||||
Task installLibraryAsync(Version version, RemoteVersion<?> libraryVersion);
|
||||
Task installLibraryAsync(Version version, RemoteVersion libraryVersion);
|
||||
|
||||
/**
|
||||
* Get registered version list.
|
||||
|
||||
@@ -31,7 +31,7 @@ public abstract class GameBuilder {
|
||||
protected String name = "";
|
||||
protected String gameVersion = "";
|
||||
protected final Map<String, String> toolVersions = new HashMap<>();
|
||||
protected final Set<RemoteVersion<?>> remoteVersions = new HashSet<>();
|
||||
protected final Set<RemoteVersion> remoteVersions = new HashSet<>();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -64,7 +64,7 @@ public abstract class GameBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public GameBuilder version(RemoteVersion<?> remoteVersion) {
|
||||
public GameBuilder version(RemoteVersion remoteVersion) {
|
||||
remoteVersions.add(remoteVersion);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (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;
|
||||
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class LibraryAnalyzer {
|
||||
private final Library forge;
|
||||
private final Library liteLoader;
|
||||
private final Library optiFine;
|
||||
|
||||
public LibraryAnalyzer(Library forge, Library liteLoader, Library optiFine) {
|
||||
this.forge = forge;
|
||||
this.liteLoader = liteLoader;
|
||||
this.optiFine = optiFine;
|
||||
}
|
||||
|
||||
public Optional<Library> getForge() {
|
||||
return Optional.ofNullable(forge);
|
||||
}
|
||||
|
||||
public boolean hasForge() {
|
||||
return forge != null;
|
||||
}
|
||||
|
||||
public Optional<Library> getLiteLoader() {
|
||||
return Optional.ofNullable(liteLoader);
|
||||
}
|
||||
|
||||
public boolean hasLiteLoader() {
|
||||
return liteLoader != null;
|
||||
}
|
||||
|
||||
public Optional<Library> getOptiFine() {
|
||||
return Optional.ofNullable(optiFine);
|
||||
}
|
||||
|
||||
public boolean hasOptiFine() {
|
||||
return optiFine != null;
|
||||
}
|
||||
|
||||
public static LibraryAnalyzer analyze(Version version) {
|
||||
Library forge = null, liteLoader = null, optiFine = null;
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
String groupId = library.getGroupId();
|
||||
String artifactId = library.getArtifactId();
|
||||
if (groupId.equalsIgnoreCase("net.minecraftforge") && artifactId.equalsIgnoreCase("forge"))
|
||||
forge = library;
|
||||
|
||||
if (groupId.equalsIgnoreCase("com.mumfrey") && artifactId.equalsIgnoreCase("liteloader"))
|
||||
liteLoader = library;
|
||||
|
||||
if ((groupId.equalsIgnoreCase("optifine") || groupId.equalsIgnoreCase("net.optifine")) && artifactId.equalsIgnoreCase("optifine"))
|
||||
optiFine = library;
|
||||
}
|
||||
|
||||
return new LibraryAnalyzer(forge, liteLoader, optiFine);
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public class MaintainTask extends TaskResult<Version> {
|
||||
|
||||
@@ -46,55 +44,41 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
}
|
||||
|
||||
public static Version maintain(Version version) {
|
||||
Library forge = null, liteLoader = null, optiFine = null;
|
||||
List<String> args = new ArrayList<>(StringUtils.tokenize(version.getMinecraftArguments().orElse("")));
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
List<String> mcArgs = version.getMinecraftArguments().map(StringUtils::tokenize).map(ArrayList::new).orElse(null);
|
||||
List<Argument> game = version.getArguments().map(Arguments::getGame).map(ArrayList::new).orElseGet(ArrayList::new);
|
||||
boolean useMcArgs = mcArgs != null;
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge"))
|
||||
forge = library;
|
||||
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader"))
|
||||
liteLoader = library;
|
||||
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine"))
|
||||
optiFine = library;
|
||||
|
||||
}
|
||||
|
||||
if (forge == null) {
|
||||
removeTweakClass(args, "forge");
|
||||
if (!libraryAnalyzer.hasForge()) {
|
||||
removeTweakClass(useMcArgs, mcArgs, game, "forge");
|
||||
}
|
||||
|
||||
// Installing Forge will override the Minecraft arguments in json, so LiteLoader and OptiFine Tweaker are being re-added.
|
||||
|
||||
if (liteLoader == null) {
|
||||
removeTweakClass(args, "liteloader");
|
||||
removeTweakClass(useMcArgs, mcArgs, game, "liteloader");
|
||||
if (libraryAnalyzer.hasLiteLoader()) {
|
||||
addArgument(useMcArgs, mcArgs, game, "--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker");
|
||||
}
|
||||
|
||||
removeTweakClass(useMcArgs, mcArgs, game, "optifine");
|
||||
if (libraryAnalyzer.hasOptiFine()) {
|
||||
if (!libraryAnalyzer.hasLiteLoader() && !libraryAnalyzer.hasForge()) {
|
||||
addArgument(useMcArgs, mcArgs, game, "--tweakClass", "optifine.OptiFineTweaker");
|
||||
} else {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
addArgument(useMcArgs, mcArgs, game, "--tweakClass", "optifine.OptiFineForgeTweaker");
|
||||
}
|
||||
}
|
||||
|
||||
Version result;
|
||||
if (useMcArgs) {
|
||||
// Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping,
|
||||
// so we regenerate the minecraftArgument without escaping.
|
||||
result = version.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString());
|
||||
} else {
|
||||
if (!StringUtils.containsOne(args, "LiteLoaderTweaker")) {
|
||||
args.add("--tweakClass");
|
||||
args.add("com.mumfrey.liteloader.launch.LiteLoaderTweaker");
|
||||
}
|
||||
result = version.setArguments(version.getArguments().map(args -> args.withGame(game)).orElse(new Arguments(game, Collections.emptyList())));
|
||||
}
|
||||
|
||||
if (optiFine == null) {
|
||||
removeTweakClass(args, "optifine");
|
||||
}
|
||||
|
||||
if (liteLoader == null && forge == null && optiFine != null) {
|
||||
if (!StringUtils.containsOne(args, "optifine.OptiFineTweaker")) {
|
||||
args.add("--tweakClass");
|
||||
args.add("optifine.OptiFineTweaker");
|
||||
}
|
||||
}
|
||||
|
||||
if ((liteLoader != null || forge != null) && optiFine != null) {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
removeTweakClass(args, "optifine");
|
||||
args.add("--tweakClass");
|
||||
args.add("optifine.OptiFineForgeTweaker");
|
||||
}
|
||||
|
||||
return version.setMinecraftArguments(new CommandBuilder().addAll(args).toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,17 +86,43 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void removeTweakClass(List<String> args, String target) {
|
||||
for (int i = 0; i < args.size(); ++i) {
|
||||
if (args.get(i).toLowerCase().contains(target)) {
|
||||
if (i > 0 && args.get(i - 1).equals("--tweakClass")) {
|
||||
args.remove(i - 1);
|
||||
args.remove(i - 1);
|
||||
i -= 2;
|
||||
private static void removeTweakClass(boolean useMcArgs, List<String> mcArgs, List<Argument> game, String target) {
|
||||
if (useMcArgs) {
|
||||
for (int i = 0; i + 1 < mcArgs.size(); ++i) {
|
||||
String arg0Str = mcArgs.get(i);
|
||||
String arg1Str = mcArgs.get(i + 1);
|
||||
if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) {
|
||||
mcArgs.remove(i);
|
||||
mcArgs.remove(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i + 1 < game.size(); ++i) {
|
||||
Argument arg0 = game.get(i);
|
||||
Argument arg1 = game.get(i + 1);
|
||||
if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) {
|
||||
// We need to preserve the tokens
|
||||
String arg0Str = arg0.toString();
|
||||
String arg1Str = arg1.toString();
|
||||
if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) {
|
||||
game.remove(i);
|
||||
game.remove(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addArgument(boolean useMcArgs, List<String> mcArgs, List<Argument> game, String... args) {
|
||||
if (useMcArgs) {
|
||||
mcArgs.addAll(Arrays.asList(args));
|
||||
} else {
|
||||
for (String arg : args)
|
||||
game.add(new StringArgument(arg));
|
||||
}
|
||||
}
|
||||
|
||||
public static final String ID = "version";
|
||||
}
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.download;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineVersionList;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
||||
|
||||
/**
|
||||
* @see <a href="http://wiki.vg">http://wiki,vg</a>
|
||||
@@ -48,7 +48,7 @@ public class MojangDownloadProvider implements DownloadProvider {
|
||||
case "liteloader":
|
||||
return LiteLoaderVersionList.INSTANCE;
|
||||
case "optifine":
|
||||
return OptiFineVersionList.INSTANCE;
|
||||
return OptiFineBMCLVersionList.INSTANCE;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized version list id: " + id);
|
||||
}
|
||||
|
||||
@@ -27,26 +27,36 @@ import java.util.Objects;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
|
||||
public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
|
||||
private final String gameVersion;
|
||||
private final String selfVersion;
|
||||
private final String url;
|
||||
private final T tag;
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
* @param tag some necessary information for Installer Task.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public RemoteVersion(String gameVersion, String selfVersion, String url, T tag) {
|
||||
public RemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
this(gameVersion, selfVersion, url, Type.UNCATEGORIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public RemoteVersion(String gameVersion, String selfVersion, String url, Type type) {
|
||||
this.gameVersion = Objects.requireNonNull(gameVersion);
|
||||
this.selfVersion = Objects.requireNonNull(selfVersion);
|
||||
this.url = Objects.requireNonNull(url);
|
||||
this.tag = tag;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
@@ -57,14 +67,14 @@ public class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
|
||||
return selfVersion;
|
||||
}
|
||||
|
||||
public T getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Type getVersionType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion);
|
||||
@@ -80,13 +90,19 @@ public class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
|
||||
return new ToStringBuilder(this)
|
||||
.append("selfVersion", selfVersion)
|
||||
.append("gameVersion", gameVersion)
|
||||
.append("tag", tag)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RemoteVersion<T> o) {
|
||||
public int compareTo(RemoteVersion o) {
|
||||
// newer versions are smaller than older versions
|
||||
return VersionNumber.asVersion(o.selfVersion).compareTo(VersionNumber.asVersion(selfVersion));
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
UNCATEGORIZED,
|
||||
RELEASE,
|
||||
SNAPSHOT,
|
||||
OLD
|
||||
}
|
||||
}
|
||||
@@ -26,18 +26,18 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
/**
|
||||
* The remote version list.
|
||||
*
|
||||
* @param <T> The type of {@code RemoteVersion<T>}, the type of tags.
|
||||
* @param <T> The subclass of {@code RemoteVersion}, the type of RemoteVersion.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class VersionList<T> {
|
||||
public abstract class VersionList<T extends RemoteVersion> {
|
||||
|
||||
/**
|
||||
* the remote version list.
|
||||
* key: game version.
|
||||
* values: corresponding remote versions.
|
||||
*/
|
||||
protected final SimpleMultimap<String, RemoteVersion<T>> versions = new SimpleMultimap<String, RemoteVersion<T>>(HashMap::new, TreeSet::new);
|
||||
protected final SimpleMultimap<String, T> versions = new SimpleMultimap<String, T>(HashMap::new, TreeSet::new);
|
||||
|
||||
/**
|
||||
* True if the version list has been loaded.
|
||||
@@ -46,6 +46,8 @@ public abstract class VersionList<T> {
|
||||
return !versions.isEmpty();
|
||||
}
|
||||
|
||||
public abstract boolean hasType();
|
||||
|
||||
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
/**
|
||||
@@ -68,11 +70,10 @@ public abstract class VersionList<T> {
|
||||
});
|
||||
}
|
||||
|
||||
private Collection<RemoteVersion<T>> getVersionsImpl(String gameVersion) {
|
||||
protected Collection<T> getVersionsImpl(String gameVersion) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
Collection<RemoteVersion<T>> ans = versions.get(gameVersion);
|
||||
return ans.isEmpty() ? versions.values() : ans;
|
||||
return versions.get(gameVersion);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
@@ -84,7 +85,7 @@ public abstract class VersionList<T> {
|
||||
* @param gameVersion the Minecraft version that remote versions belong to
|
||||
* @return the collection of specific remote versions
|
||||
*/
|
||||
public final Collection<RemoteVersion<T>> getVersions(String gameVersion) {
|
||||
public final Collection<T> getVersions(String gameVersion) {
|
||||
return Collections.unmodifiableCollection(getVersionsImpl(gameVersion));
|
||||
}
|
||||
|
||||
@@ -95,11 +96,11 @@ public abstract class VersionList<T> {
|
||||
* @param remoteVersion the version of the remote version.
|
||||
* @return the specific remote version, null if it is not found.
|
||||
*/
|
||||
public final Optional<RemoteVersion<T>> getVersion(String gameVersion, String remoteVersion) {
|
||||
public final Optional<T> getVersion(String gameVersion, String remoteVersion) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
RemoteVersion<T> result = null;
|
||||
for (RemoteVersion<T> it : versions.get(gameVersion))
|
||||
T result = null;
|
||||
for (T it : versions.get(gameVersion))
|
||||
if (remoteVersion.equals(it.getSelfVersion()))
|
||||
result = it;
|
||||
return Optional.ofNullable(result);
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
|
||||
public class ForgeRemoteVersion extends RemoteVersion<Void> {
|
||||
public class ForgeRemoteVersion extends RemoteVersion {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -28,6 +28,6 @@ public class ForgeRemoteVersion extends RemoteVersion<Void> {
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
super(gameVersion, selfVersion, url, null);
|
||||
super(gameVersion, selfVersion, url);
|
||||
}
|
||||
}
|
||||
@@ -32,13 +32,18 @@ import java.util.*;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ForgeVersionList extends VersionList<Void> {
|
||||
public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
|
||||
public static final ForgeVersionList INSTANCE = new ForgeVersionList();
|
||||
|
||||
private ForgeVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST)));
|
||||
|
||||
@@ -75,8 +75,6 @@ public final class GameAssetDownloadTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
int size = refreshTask.getResult().size();
|
||||
int downloaded = 0;
|
||||
for (Map.Entry<File, AssetObject> entry : refreshTask.getResult()) {
|
||||
if (Thread.interrupted())
|
||||
throw new InterruptedException();
|
||||
@@ -88,29 +86,11 @@ public final class GameAssetDownloadTask extends Task {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to create new file " + file + ", because parent directory cannot be created");
|
||||
continue;
|
||||
}
|
||||
if (file.isDirectory())
|
||||
if (file.isDirectory() || file.isFile() && file.exists())
|
||||
continue;
|
||||
boolean flag = true;
|
||||
try {
|
||||
// check the checksum of file to ensure that the file is not need to re-download.
|
||||
if (file.exists()) {
|
||||
String sha1 = encodeHex(digest("SHA-1", FileUtils.readBytes(file)));
|
||||
if (sha1.equals(assetObject.getHash())) {
|
||||
++downloaded;
|
||||
Logging.LOG.finest("File $file has been downloaded successfully, skipped downloading");
|
||||
updateProgress(downloaded, size);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to get hash code of file " + file, e);
|
||||
flag = !file.exists();
|
||||
}
|
||||
if (flag) {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
dependencies.add(task);
|
||||
}
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
dependencies.add(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,12 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.ReleaseType;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.Validation;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -30,64 +27,45 @@ import java.util.Date;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameRemoteVersion implements Validation {
|
||||
@Immutable
|
||||
public final class GameRemoteVersion extends RemoteVersion {
|
||||
|
||||
@SerializedName("id")
|
||||
private final String gameVersion;
|
||||
|
||||
@SerializedName("time")
|
||||
private final ReleaseType type;
|
||||
private final Date time;
|
||||
|
||||
@SerializedName("releaseTime")
|
||||
private final Date releaseTime;
|
||||
|
||||
@SerializedName("type")
|
||||
private final ReleaseType type;
|
||||
|
||||
@SerializedName("url")
|
||||
private final String url;
|
||||
|
||||
public GameRemoteVersion() {
|
||||
this("", new Date(), new Date(), ReleaseType.UNKNOWN);
|
||||
}
|
||||
|
||||
public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type) {
|
||||
this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json");
|
||||
}
|
||||
|
||||
public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type, String url) {
|
||||
this.gameVersion = gameVersion;
|
||||
this.time = time;
|
||||
this.releaseTime = releaseTime;
|
||||
public GameRemoteVersion(String gameVersion, String selfVersion, String url, ReleaseType type, Date time) {
|
||||
super(gameVersion, selfVersion, url, getReleaseType(type));
|
||||
this.type = type;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return gameVersion;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public Date getReleaseTime() {
|
||||
return releaseTime;
|
||||
}
|
||||
|
||||
public ReleaseType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (StringUtils.isBlank(gameVersion))
|
||||
throw new JsonParseException("GameRemoteVersion id cannot be blank");
|
||||
if (StringUtils.isBlank(url))
|
||||
throw new JsonParseException("GameRemoteVersion url cannot be blank");
|
||||
public int compareTo(RemoteVersion o) {
|
||||
if (!(o instanceof GameRemoteVersion))
|
||||
return 0;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.game.ReleaseType;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.Validation;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameRemoteVersionInfo implements Validation {
|
||||
|
||||
@SerializedName("id")
|
||||
private final String gameVersion;
|
||||
|
||||
@SerializedName("time")
|
||||
private final Date time;
|
||||
|
||||
@SerializedName("releaseTime")
|
||||
private final Date releaseTime;
|
||||
|
||||
@SerializedName("type")
|
||||
private final ReleaseType type;
|
||||
|
||||
@SerializedName("url")
|
||||
private final String url;
|
||||
|
||||
public GameRemoteVersionInfo() {
|
||||
this("", new Date(), new Date(), ReleaseType.UNKNOWN);
|
||||
}
|
||||
|
||||
public GameRemoteVersionInfo(String gameVersion, Date time, Date releaseTime, ReleaseType type) {
|
||||
this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json");
|
||||
}
|
||||
|
||||
public GameRemoteVersionInfo(String gameVersion, Date time, Date releaseTime, ReleaseType type, String url) {
|
||||
this.gameVersion = gameVersion;
|
||||
this.time = time;
|
||||
this.releaseTime = releaseTime;
|
||||
this.type = type;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public Date getReleaseTime() {
|
||||
return releaseTime;
|
||||
}
|
||||
|
||||
public ReleaseType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (StringUtils.isBlank(gameVersion))
|
||||
throw new JsonParseException("GameRemoteVersion id cannot be blank");
|
||||
if (StringUtils.isBlank(url))
|
||||
throw new JsonParseException("GameRemoteVersion url cannot be blank");
|
||||
}
|
||||
}
|
||||
@@ -1,55 +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.game.ReleaseType;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@Immutable
|
||||
public final class GameRemoteVersionTag {
|
||||
|
||||
private final ReleaseType type;
|
||||
private final Date time;
|
||||
|
||||
/**
|
||||
* No-arg constructor for Gson.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public GameRemoteVersionTag() {
|
||||
this(ReleaseType.UNKNOWN, new Date());
|
||||
}
|
||||
|
||||
public GameRemoteVersionTag(ReleaseType type, Date time) {
|
||||
this.type = type;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public ReleaseType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import java.util.List;
|
||||
public final class GameRemoteVersions {
|
||||
|
||||
@SerializedName("versions")
|
||||
private final List<GameRemoteVersion> versions;
|
||||
private final List<GameRemoteVersionInfo> versions;
|
||||
|
||||
@SerializedName("latest")
|
||||
private final GameRemoteLatestVersions latest;
|
||||
@@ -44,7 +44,7 @@ public final class GameRemoteVersions {
|
||||
this(Collections.emptyList(), null);
|
||||
}
|
||||
|
||||
public GameRemoteVersions(List<GameRemoteVersion> versions, GameRemoteLatestVersions latest) {
|
||||
public GameRemoteVersions(List<GameRemoteVersionInfo> versions, GameRemoteLatestVersions latest) {
|
||||
this.versions = versions;
|
||||
this.latest = latest;
|
||||
}
|
||||
@@ -53,7 +53,7 @@ public final class GameRemoteVersions {
|
||||
return latest;
|
||||
}
|
||||
|
||||
public List<GameRemoteVersion> getVersions() {
|
||||
public List<GameRemoteVersionInfo> getVersions() {
|
||||
return versions;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -32,13 +33,28 @@ import java.util.Collections;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
|
||||
public final class GameVersionList extends VersionList<GameRemoteVersion> {
|
||||
|
||||
public static final GameVersionList INSTANCE = new GameVersionList();
|
||||
|
||||
private GameVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<GameRemoteVersion> getVersionsImpl(String gameVersion) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return StringUtils.isBlank(gameVersion) ? versions.values() : versions.get(gameVersion);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL()));
|
||||
@@ -56,12 +72,12 @@ public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
|
||||
versions.clear();
|
||||
|
||||
GameRemoteVersions root = Constants.GSON.fromJson(task.getResult(), GameRemoteVersions.class);
|
||||
for (GameRemoteVersion remoteVersion : root.getVersions()) {
|
||||
versions.put(remoteVersion.getGameVersion(), new RemoteVersionGame(
|
||||
for (GameRemoteVersionInfo remoteVersion : root.getVersions()) {
|
||||
versions.put(remoteVersion.getGameVersion(), new GameRemoteVersion(
|
||||
remoteVersion.getGameVersion(),
|
||||
remoteVersion.getGameVersion(),
|
||||
remoteVersion.getUrl(),
|
||||
new GameRemoteVersionTag(remoteVersion.getType(), remoteVersion.getReleaseTime()))
|
||||
remoteVersion.getType(), remoteVersion.getReleaseTime())
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
@@ -70,15 +86,4 @@ public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class RemoteVersionGame extends RemoteVersion<GameRemoteVersionTag> {
|
||||
public RemoteVersionGame(String gameVersion, String selfVersion, String url, GameRemoteVersionTag tag) {
|
||||
super(gameVersion, selfVersion, url, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RemoteVersion<GameRemoteVersionTag> o) {
|
||||
return o.getTag().getTime().compareTo(getTag().getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,10 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
@@ -23,15 +26,15 @@ import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
|
||||
public final class LibraryDownloadTask extends Task {
|
||||
private final FileDownloadTask xzTask;
|
||||
private final FileDownloadTask task;
|
||||
private final LibraryDownloadTaskHelper helperTask;
|
||||
private final File jar;
|
||||
private final File xzFile;
|
||||
private final Library library;
|
||||
|
||||
private boolean downloaded = false;
|
||||
private boolean xz;
|
||||
|
||||
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) {
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
|
||||
if (library.is("net.minecraftforge", "forge"))
|
||||
library = library.setClassifier("universal");
|
||||
|
||||
@@ -42,20 +45,12 @@ public final class LibraryDownloadTask extends Task {
|
||||
|
||||
xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz");
|
||||
|
||||
xzTask = new FileDownloadTask(NetworkUtils.toURL(url + ".pack.xz"),
|
||||
xzFile, null, 1);
|
||||
xzTask.setSignificance(TaskSignificance.MINOR);
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
|
||||
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
||||
file,
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
helperTask = new LibraryDownloadTaskHelper(file, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return library.getChecksums() != null ? Collections.singleton(xzTask) : Collections.singleton(task);
|
||||
return Collections.singleton(helperTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,32 +61,75 @@ public final class LibraryDownloadTask extends Task {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (!isDependentsSucceeded()) {
|
||||
// Since FileDownloadTask wraps the actual exception with another IOException.
|
||||
// We should extract it letting the error message clearer.
|
||||
Throwable t = library.getChecksums() != null ? xzTask.getLastException() : task.getLastException();
|
||||
if (t.getCause() != null && t.getCause() != t)
|
||||
throw new LibraryDownloadException(library, t.getCause());
|
||||
else
|
||||
throw new LibraryDownloadException(library, t);
|
||||
if (helperTask.task == null) {
|
||||
// NetworkUtils.URLExists failed.
|
||||
throw new LibraryDownloadException(library, helperTask.getLastException());
|
||||
} else {
|
||||
// Since FileDownloadTask wraps the actual exception with another IOException.
|
||||
// We should extract it letting the error message clearer.
|
||||
Throwable t = helperTask.task.getLastException();
|
||||
if (t.getCause() != null && t.getCause() != t)
|
||||
throw new LibraryDownloadException(library, t.getCause());
|
||||
else
|
||||
throw new LibraryDownloadException(library, t);
|
||||
}
|
||||
} else {
|
||||
if (xz) unpackLibrary(jar, FileUtils.readBytes(xzFile));
|
||||
if (!checksumValid(jar, library.getChecksums())) {
|
||||
jar.delete();
|
||||
throw new IOException("Checksum failed for " + library);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LibraryDownloadTaskHelper extends Task {
|
||||
private FileDownloadTask task;
|
||||
private final File file;
|
||||
private final String url;
|
||||
|
||||
public LibraryDownloadTaskHelper(File file, String url) {
|
||||
this.file = file;
|
||||
this.url = url;
|
||||
|
||||
setName(library.getName());
|
||||
}
|
||||
|
||||
if (library.getChecksums() != null) {
|
||||
unpackLibrary(jar, FileUtils.readBytes(xzFile));
|
||||
if (!checksumValid(jar, library.getChecksums()))
|
||||
throw new IOException("Checksum failed for " + library);
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return Collections.singleton(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
URL packXz = NetworkUtils.toURL(url + ".pack.xz");
|
||||
if (NetworkUtils.URLExists(packXz)) {
|
||||
task = new FileDownloadTask(packXz, xzFile, null);
|
||||
xz = true;
|
||||
} else {
|
||||
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
||||
file,
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
xz = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checksumValid(File libPath, List<String> checksums) {
|
||||
try {
|
||||
if ((checksums == null) || (checksums.isEmpty())) {
|
||||
if (checksums == null || checksums.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
byte[] fileData = FileUtils.readBytes(libPath);
|
||||
boolean valid = checksums.contains(encodeHex(digest("SHA-1", fileData)));
|
||||
if ((!valid) && (libPath.getName().endsWith(".jar"))) {
|
||||
if (!valid && libPath.getName().endsWith(".jar")) {
|
||||
valid = validateJar(fileData, checksums);
|
||||
}
|
||||
return validateJar(fileData, checksums);
|
||||
return valid;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -104,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");
|
||||
}
|
||||
@@ -115,8 +153,8 @@ public final class LibraryDownloadTask extends Task {
|
||||
}
|
||||
jar.close();
|
||||
if (hashes != null) {
|
||||
boolean passed = !checksums.contains(files.get("checksums.sha1"));
|
||||
if (passed) {
|
||||
boolean failed = !checksums.contains(files.get("checksums.sha1"));
|
||||
if (!failed) {
|
||||
for (String hash : hashes) {
|
||||
if ((!hash.trim().equals("")) && (hash.contains(" "))) {
|
||||
String[] e = hash.split(" ");
|
||||
@@ -125,17 +163,17 @@ public final class LibraryDownloadTask extends Task {
|
||||
String checksum = files.get(target);
|
||||
if ((!files.containsKey(target)) || (checksum == null)) {
|
||||
Logging.LOG.warning(" " + target + " : missing");
|
||||
passed = false;
|
||||
failed = true;
|
||||
break;
|
||||
} else if (!checksum.equals(validChecksum)) {
|
||||
Logging.LOG.warning(" " + target + " : failed (" + checksum + ", " + validChecksum + ")");
|
||||
passed = false;
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return passed;
|
||||
return !failed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public final class VersionJsonDownloadTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
RemoteVersion<?> remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
|
||||
RemoteVersion remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot find specific version " + gameVersion + " in remote repository"));
|
||||
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl());
|
||||
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL), ID));
|
||||
|
||||
+6
-1
@@ -39,13 +39,18 @@ import java.util.Optional;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersionTag> {
|
||||
public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersion> {
|
||||
|
||||
public static final LiteLoaderBMCLVersionList INSTANCE = new LiteLoaderBMCLVersionList();
|
||||
|
||||
private LiteLoaderBMCLVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
|
||||
|
||||
+2
-11
@@ -19,10 +19,7 @@ package org.jackhuang.hmcl.download.liteloader;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -74,18 +71,12 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
|
||||
);
|
||||
|
||||
Version tempVersion = version.setLibraries(Lang.merge(remote.getTag().getLibraries(), Collections.singleton(library)));
|
||||
|
||||
String mcArg = version.getMinecraftArguments().orElse("");
|
||||
if (mcArg.contains("--tweakClass optifine.OptiFineTweaker"))
|
||||
mcArg = mcArg.replace("--tweakClass optifine.OptiFineTweaker", "");
|
||||
Version tempVersion = version.setLibraries(Lang.merge(remote.getLibraries(), Collections.singleton(library)));
|
||||
|
||||
setResult(version
|
||||
.setMainClass("net.minecraft.launchwrapper.Launch")
|
||||
.setLibraries(Lang.merge(tempVersion.getLibraries(), version.getLibraries()))
|
||||
.setLogging(Collections.emptyMap())
|
||||
.setMinecraftArguments(mcArg + " --tweakClass " + remote.getTag().getTweakClass())
|
||||
//.setArguments(Arguments.addGameArguments(Lang.get(version.getArguments()), "--tweakClass", remote.getTag().getTweakClass()))
|
||||
);
|
||||
|
||||
dependencies.add(new GameLibrariesTask(dependencyManager, tempVersion));
|
||||
|
||||
+16
-2
@@ -22,7 +22,9 @@ import org.jackhuang.hmcl.game.Library;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class LiteLoaderRemoteVersion extends RemoteVersion<LiteLoaderRemoteVersionTag> {
|
||||
public class LiteLoaderRemoteVersion extends RemoteVersion {
|
||||
private final String tweakClass;
|
||||
private final Collection<Library> libraries;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -31,6 +33,18 @@ public class LiteLoaderRemoteVersion extends RemoteVersion<LiteLoaderRemoteVersi
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, String url, String tweakClass, Collection<Library> libraries) {
|
||||
super(gameVersion, selfVersion, url, new LiteLoaderRemoteVersionTag(tweakClass, libraries));
|
||||
super(gameVersion, selfVersion, url);
|
||||
|
||||
this.tweakClass = tweakClass;
|
||||
this.libraries = libraries;
|
||||
}
|
||||
|
||||
public Collection<Library> getLibraries() {
|
||||
return libraries;
|
||||
}
|
||||
|
||||
public String getTweakClass() {
|
||||
return tweakClass;
|
||||
}
|
||||
|
||||
}
|
||||
-54
@@ -1,54 +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.liteloader;
|
||||
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class LiteLoaderRemoteVersionTag {
|
||||
private final String tweakClass;
|
||||
private final Collection<Library> libraries;
|
||||
|
||||
/**
|
||||
* No-arg constructor for Gson.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public LiteLoaderRemoteVersionTag() {
|
||||
this("", Collections.emptySet());
|
||||
}
|
||||
|
||||
public LiteLoaderRemoteVersionTag(String tweakClass, Collection<Library> libraries) {
|
||||
this.tweakClass = tweakClass;
|
||||
this.libraries = libraries;
|
||||
}
|
||||
|
||||
public Collection<Library> getLibraries() {
|
||||
return libraries;
|
||||
}
|
||||
|
||||
public String getTweakClass() {
|
||||
return tweakClass;
|
||||
}
|
||||
|
||||
}
|
||||
+6
-1
@@ -39,13 +39,18 @@ import java.util.Optional;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVersionTag> {
|
||||
public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVersion> {
|
||||
|
||||
public static final LiteLoaderVersionList INSTANCE = new LiteLoaderVersionList();
|
||||
|
||||
private LiteLoaderVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
|
||||
|
||||
+9
-4
@@ -33,13 +33,18 @@ import java.util.*;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class OptiFineBMCLVersionList extends VersionList<Void> {
|
||||
public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVersion> {
|
||||
|
||||
public static final OptiFineBMCLVersionList INSTANCE = new OptiFineBMCLVersionList();
|
||||
|
||||
private OptiFineBMCLVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi2.bangbang93.com/optifine/versionlist"));
|
||||
@@ -57,16 +62,16 @@ public final class OptiFineBMCLVersionList extends VersionList<Void> {
|
||||
}.getType());
|
||||
for (OptiFineVersion element : root) {
|
||||
String version = element.getType() + "_" + element.getPatch();
|
||||
if (element.getType() == null || "pre".equals(element.getPatch()))
|
||||
continue;
|
||||
String mirror = "http://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch();
|
||||
if (!duplicates.add(mirror))
|
||||
continue;
|
||||
|
||||
boolean isPre = element.getPatch() != null && (element.getPatch().equals("pre") || element.getPatch().startsWith("alpha"));
|
||||
|
||||
if (StringUtils.isBlank(element.getGameVersion()))
|
||||
continue;
|
||||
VersionNumber.parseVersion(element.getGameVersion())
|
||||
.ifPresent(gameVersion -> versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror)));
|
||||
.ifPresent(gameVersion -> versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror, isPre)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+5
-28
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -70,46 +69,24 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
String remoteVersion = remote.getGameVersion() + "-" + remote.getSelfVersion();
|
||||
String remoteVersion = remote.getGameVersion() + "_" + remote.getSelfVersion();
|
||||
|
||||
Library library = new Library(
|
||||
"net.optifine", "optifine", remoteVersion, null, null,
|
||||
"optifine", "OptiFine", remoteVersion, null, null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(
|
||||
"net/optifine/optifine/" + remoteVersion + "/optifine-" + remoteVersion + ".jar",
|
||||
"optifine/OptiFine/" + remoteVersion + "/OptiFine-" + remoteVersion + ".jar",
|
||||
remote.getUrl()))
|
||||
);
|
||||
|
||||
List<Library> libraries = new LinkedList<>();
|
||||
libraries.add(library);
|
||||
|
||||
boolean hasFMLTweaker = false;
|
||||
if (version.getMinecraftArguments().isPresent() && version.getMinecraftArguments().get().contains("FMLTweaker"))
|
||||
hasFMLTweaker = true;
|
||||
if (version.getArguments().isPresent()) {
|
||||
List<Argument> game = version.getArguments().get().getGame();
|
||||
if (game.stream().anyMatch(arg -> arg.toString(Collections.emptyMap(), Collections.emptyMap()).contains("FMLTweaker")))
|
||||
hasFMLTweaker = true;
|
||||
}
|
||||
|
||||
/*Arguments arguments = Lang.get(version.getArguments());
|
||||
|
||||
if (!hasFMLTweaker)
|
||||
arguments = Arguments.addGameArguments(arguments, "--tweakClass", "optifine.OptiFineTweaker");
|
||||
*/
|
||||
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"));
|
||||
|
||||
setResult(version
|
||||
.setLibraries(Lang.merge(version.getLibraries(), libraries))
|
||||
.setMainClass("net.minecraft.launchwrapper.Launch")
|
||||
.setMinecraftArguments(minecraftArguments)
|
||||
//.setArguments(arguments)
|
||||
.setLibraries(Lang.merge(version.getLibraries(), libraries))
|
||||
.setMainClass("net.minecraft.launchwrapper.Launch")
|
||||
);
|
||||
|
||||
dependencies.add(new GameLibrariesTask(dependencyManager, version.setLibraries(libraries)));
|
||||
|
||||
+3
-3
@@ -21,11 +21,11 @@ import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class OptiFineRemoteVersion extends RemoteVersion<Void> {
|
||||
public class OptiFineRemoteVersion extends RemoteVersion {
|
||||
private final Supplier<String> url;
|
||||
|
||||
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url) {
|
||||
super(gameVersion, selfVersion, "", null);
|
||||
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url, boolean snapshot) {
|
||||
super(gameVersion, selfVersion, "", snapshot ? Type.SNAPSHOT : Type.RELEASE);
|
||||
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@@ -1,121 +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.optifine;
|
||||
|
||||
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.Lang;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class OptiFineVersionList extends VersionList<Void> {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("OptiFine (.*?) ");
|
||||
private static final Pattern LINK_PATTERN = Pattern.compile("\"downloadx\\?f=OptiFine(.*)\"");
|
||||
|
||||
public static final OptiFineVersionList INSTANCE = new OptiFineVersionList();
|
||||
|
||||
private OptiFineVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL("https://optifine.net/downloads"));
|
||||
return new Task() {
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
versions.clear();
|
||||
|
||||
String html = task.getResult().replace(" ", " ").replace(">", ">").replace("<", "<").replace("<br>", "<br />");
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db = factory.newDocumentBuilder();
|
||||
Document doc = db.parse(new ByteArrayInputStream(html.getBytes("UTF-8")));
|
||||
Element r = doc.getDocumentElement();
|
||||
NodeList tables = r.getElementsByTagName("table");
|
||||
for (int i = 0; i < tables.getLength(); i++) {
|
||||
Element e = (Element) tables.item(i);
|
||||
if ("downloadTable".equals(e.getAttribute("class"))) {
|
||||
NodeList tr = e.getElementsByTagName("tr");
|
||||
for (int k = 0; k < tr.getLength(); k++) {
|
||||
NodeList downloadLine = ((Element) tr.item(k)).getElementsByTagName("td");
|
||||
String url = null, version = null, gameVersion = null;
|
||||
for (int j = 0; j < downloadLine.getLength(); j++) {
|
||||
Element td = (Element) downloadLine.item(j);
|
||||
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineMirror"))
|
||||
url = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href");
|
||||
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineFile"))
|
||||
version = td.getTextContent();
|
||||
}
|
||||
if (version == null || url == null)
|
||||
continue;
|
||||
|
||||
Matcher matcher = PATTERN.matcher(version);
|
||||
while (matcher.find())
|
||||
gameVersion = matcher.group(1);
|
||||
if (gameVersion == null)
|
||||
continue;
|
||||
|
||||
String finalURL = url;
|
||||
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Lang.hideException(() -> getLink(finalURL))));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String getLink(String url) throws IOException {
|
||||
String result = null;
|
||||
String content = NetworkUtils.doGet(NetworkUtils.toURL(url));
|
||||
Matcher m = LINK_PATTERN.matcher(content);
|
||||
while (m.find())
|
||||
result = m.group(1);
|
||||
if (result == null)
|
||||
throw new IllegalStateException("Cannot find version in " + content);
|
||||
return "https://optifine.net/downloadx?f=OptiFine" + result;
|
||||
}
|
||||
}
|
||||
@@ -50,10 +50,18 @@ public final class Arguments {
|
||||
return game == null ? Collections.emptyList() : Collections.unmodifiableList(game);
|
||||
}
|
||||
|
||||
public Arguments withGame(List<Argument> game) {
|
||||
return new Arguments(game, jvm);
|
||||
}
|
||||
|
||||
public List<Argument> getJvm() {
|
||||
return jvm == null ? Collections.emptyList() : Collections.unmodifiableList(jvm);
|
||||
}
|
||||
|
||||
public Arguments withJvm(List<Argument> jvm) {
|
||||
return new Arguments(game, jvm);
|
||||
}
|
||||
|
||||
public Arguments addGameArguments(String... gameArguments) {
|
||||
return addGameArguments(Arrays.asList(gameArguments));
|
||||
}
|
||||
@@ -82,7 +90,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) {
|
||||
|
||||
@@ -32,7 +32,7 @@ public class ClassicVersion extends Version {
|
||||
super(true, "Classic", "${auth_player_name} ${auth_session} --workDir ${game_directory}",
|
||||
null, "net.minecraft.client.Minecraft", null, null, null, null,
|
||||
Arrays.asList(new ClassicLibrary("lwjgl"), new ClassicLibrary("jinput"), new ClassicLibrary("lwjgl_util")),
|
||||
null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0);
|
||||
null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0, false);
|
||||
}
|
||||
|
||||
private static class ClassicLibrary extends Library {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -54,4 +54,8 @@ public final class StringArgument implements Argument {
|
||||
return Collections.singletonList(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return argument;
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,11 @@ public class Version implements Comparable<Version>, Validation {
|
||||
private final Date time;
|
||||
private final Date releaseTime;
|
||||
private final int minimumLauncherVersion;
|
||||
private final boolean hidden;
|
||||
|
||||
private transient final boolean resolved;
|
||||
|
||||
public Version(boolean resolved, String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion) {
|
||||
public Version(boolean resolved, String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion, boolean hidden) {
|
||||
this.resolved = resolved;
|
||||
this.id = id;
|
||||
this.minecraftArguments = minecraftArguments;
|
||||
@@ -67,6 +68,7 @@ public class Version implements Comparable<Version>, Validation {
|
||||
this.time = time == null ? new Date() : (Date) time.clone();
|
||||
this.releaseTime = releaseTime == null ? new Date() : (Date) releaseTime.clone();
|
||||
this.minimumLauncherVersion = minimumLauncherVersion;
|
||||
this.hidden = hidden;
|
||||
}
|
||||
|
||||
public Optional<String> getMinecraftArguments() {
|
||||
@@ -109,6 +111,10 @@ public class Version implements Comparable<Version>, Validation {
|
||||
return minimumLauncherVersion;
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return hidden;
|
||||
}
|
||||
|
||||
public Map<DownloadType, LoggingInfo> getLogging() {
|
||||
return logging == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(logging);
|
||||
}
|
||||
@@ -139,10 +145,6 @@ public class Version implements Comparable<Version>, Validation {
|
||||
return CompatibilityRule.appliesToCurrentEnvironment(compatibilityRules);
|
||||
}
|
||||
|
||||
public boolean install(String id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve given version
|
||||
*/
|
||||
@@ -180,43 +182,44 @@ public class Version implements Comparable<Version>, Validation {
|
||||
type,
|
||||
time,
|
||||
releaseTime,
|
||||
Math.max(minimumLauncherVersion, parent.minimumLauncherVersion));
|
||||
Math.max(minimumLauncherVersion, parent.minimumLauncherVersion),
|
||||
hidden);
|
||||
}
|
||||
|
||||
private Version setResolved() {
|
||||
return new Version(true, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(true, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setId(String id) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setMinecraftArguments(String minecraftArguments) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setArguments(Arguments arguments) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setMainClass(String mainClass) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setInheritsFrom(String inheritsFrom) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setJar(String jar) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setLibraries(List<Library> libraries) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
public Version setLogging(Map<DownloadType, LoggingInfo> logging) {
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
|
||||
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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 <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.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) {
|
||||
|
||||
@@ -108,7 +108,7 @@ public abstract class Task {
|
||||
return true;
|
||||
}
|
||||
|
||||
private String name = getClass().toString();
|
||||
private String name = getClass().getName();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
|
||||
@@ -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 <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
|
||||
import 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.
|
||||
*
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
@@ -40,7 +37,7 @@ public final class NetworkUtils {
|
||||
private NetworkUtils() {
|
||||
}
|
||||
|
||||
private static Supplier<String> userAgentSupplier = RandomUserAgent::randomUserAgent;
|
||||
private static Supplier<String> userAgentSupplier = () -> "HMCLCore";
|
||||
|
||||
public static String getUserAgent() {
|
||||
return userAgentSupplier.get();
|
||||
@@ -156,4 +153,12 @@ public final class NetworkUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean URLExists(URL url) throws IOException {
|
||||
try (InputStream stream = url.openStream()) {
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user