Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf1a66e858 | ||
|
|
5eba896e6d | ||
|
|
e69d149c34 | ||
|
|
2f9955febd | ||
|
|
df39be708f | ||
|
|
ce3b9e3b1f | ||
|
|
a9feb65fc8 | ||
|
|
93837a28fa | ||
|
|
678f3b8eb7 | ||
|
|
0833fa9e7d | ||
|
|
369b9bb6cf | ||
|
|
c9b6f2f62f | ||
|
|
e2787af044 | ||
|
|
30c27bbb67 | ||
|
|
7aee7e33d0 | ||
|
|
a26061c388 | ||
|
|
11e66ac13c | ||
|
|
a2ac3c1f18 | ||
|
|
e7142e4032 | ||
|
|
0dd9dc6c17 | ||
|
|
a53bfed060 | ||
|
|
c5919f2719 | ||
|
|
fb24516b90 | ||
|
|
60fa4eb0a1 | ||
|
|
536576084d | ||
|
|
c62cc499c5 | ||
|
|
c11bbf8836 | ||
|
|
71c0cede24 | ||
|
|
ad16bbc96d | ||
|
|
520ffe4c09 |
No files matched your search
@@ -20,8 +20,12 @@ package org.jackhuang.hmcl;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.AuthlibInjectorServers;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
@@ -40,6 +44,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -53,6 +58,14 @@ public final class Launcher extends Application {
|
||||
try {
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
AuthlibInjectorServers.init();
|
||||
|
||||
if (ConfigHolder.isNewlyCreated() && !AuthlibInjectorServers.getConfigInstance().getUrls().isEmpty()) {
|
||||
config().setPreferredLoginType(Accounts.getLoginType(Accounts.FACTORY_AUTHLIB_INJECTOR));
|
||||
AuthlibInjectorServers.getConfigInstance().getUrls().stream()
|
||||
.map(AuthlibInjectorServer::new)
|
||||
.forEach(config().getAuthlibInjectorServers()::add);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Main.showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
@@ -77,6 +90,7 @@ public final class Launcher extends Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
TaskExecutor.setUncaughtExceptionHandler(new CrashReporter(false));
|
||||
|
||||
try {
|
||||
LOG.info("*** " + Metadata.TITLE + " ***");
|
||||
@@ -142,5 +156,5 @@ public final class Launcher extends Application {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter(true);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -73,9 +74,9 @@ public class HMCLModpackExportTask extends Task<Void> {
|
||||
return false;
|
||||
});
|
||||
|
||||
Version mv = repository.getResolvedVersion(version);
|
||||
Version mv = repository.getResolvedPreservingPatchesVersion(version);
|
||||
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
|
||||
.orElseThrow(() -> new IOException("Cannot parse the version of " + version));
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DependencyManager;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
@@ -39,12 +40,13 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
private final File zipFile;
|
||||
private final String name;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final Modpack modpack;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
|
||||
DependencyManager dependency = profile.getDependency();
|
||||
dependency = profile.getDependency();
|
||||
repository = profile.getRepository();
|
||||
this.zipFile = zipFile;
|
||||
this.name = name;
|
||||
@@ -88,8 +90,18 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
dependencies.add(repository.save(version));
|
||||
Version originalVersion = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion);
|
||||
Task<Version> libraryTask = Task.supplyAsync(() -> originalVersion);
|
||||
// reinstall libraries
|
||||
// libraries of Forge and OptiFine should be obtained by installation.
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
if (LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(mark.getLibraryId()))
|
||||
continue;
|
||||
libraryTask = libraryTask.thenComposeAsync(version -> dependency.installLibraryAsync(modpack.getGameVersion(), version, mark.getLibraryId(), mark.getLibraryVersion()));
|
||||
}
|
||||
|
||||
dependencies.add(libraryTask.thenComposeAsync(repository::save));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.LibrariesUniqueTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.launch.NotDecompressingNativesException;
|
||||
import org.jackhuang.hmcl.launch.PermissionException;
|
||||
@@ -132,7 +131,7 @@ public final class LauncherHelper {
|
||||
private void launch0() {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(LibrariesUniqueTask.unique(repository.getResolvedVersion(selectedVersion)));
|
||||
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
|
||||
@@ -123,7 +123,7 @@ public final class ModpackHelper {
|
||||
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
|
||||
else throw new IllegalArgumentException("Unrecognized modpack: " + modpack);
|
||||
}
|
||||
|
||||
public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {
|
||||
|
||||
@@ -121,6 +121,9 @@ public final class TexturesLoader {
|
||||
try (InputStream in = Files.newInputStream(file)) {
|
||||
img = ImageIO.read(in);
|
||||
}
|
||||
if (img == null)
|
||||
throw new IOException("Texture is malformed");
|
||||
|
||||
Map<String, String> metadata = texture.getMetadata();
|
||||
if (metadata == null) {
|
||||
metadata = emptyMap();
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class AuthlibInjectorServers implements Validation {
|
||||
|
||||
public static final String CONFIG_FILENAME = "authlib-injectors.json";
|
||||
|
||||
private final List<String> urls;
|
||||
|
||||
public AuthlibInjectorServers(List<String> urls) {
|
||||
this.urls = urls;
|
||||
}
|
||||
|
||||
public List<String> getUrls() {
|
||||
return urls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (urls == null)
|
||||
throw new JsonParseException("authlib-injectors.json -> urls cannot be null");
|
||||
}
|
||||
|
||||
private static final Path configLocation = Paths.get(CONFIG_FILENAME);
|
||||
private static AuthlibInjectorServers configInstance;
|
||||
|
||||
public synchronized static void init() {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("AuthlibInjectorServers is already loaded");
|
||||
}
|
||||
|
||||
configInstance = new AuthlibInjectorServers(Collections.emptyList());
|
||||
|
||||
if (Files.exists(configLocation)) {
|
||||
try {
|
||||
String content = FileUtils.readText(configLocation);
|
||||
configInstance = JsonUtils.GSON.fromJson(content, AuthlibInjectorServers.class);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Malformed authlib-injectors.json", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthlibInjectorServers getConfigInstance() {
|
||||
return configInstance;
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,10 @@ public final class ConfigHolder {
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
public static boolean isNewlyCreated() {
|
||||
return newlyCreated;
|
||||
}
|
||||
|
||||
public synchronized static void init() throws IOException {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("Configuration is already loaded");
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -482,7 +483,7 @@ public final class VersionSetting {
|
||||
else if (isUsesCustomJavaDir()) {
|
||||
try {
|
||||
return JavaVersion.fromExecutable(Paths.get(getJavaDir()));
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | InvalidPathException e) {
|
||||
return null; // Custom Java Directory not found,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
@@ -537,6 +538,7 @@ public final class VersionSetting {
|
||||
.setGameDir(gameDir)
|
||||
.setJava(javaVersion)
|
||||
.setVersionName(Metadata.TITLE)
|
||||
.setVersionType(Metadata.TITLE)
|
||||
.setProfileName(Metadata.TITLE)
|
||||
.setMinecraftArgs(getMinecraftArgs())
|
||||
.setJavaArgs(getJavaArgs())
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -40,11 +41,16 @@ public class InstallerItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
|
||||
{
|
||||
if (version != null) {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.setTitle(artifact);
|
||||
item.setSubtitle(i18n("archive.version") + ": " + version);
|
||||
setCenter(item);
|
||||
} else {
|
||||
Label label = new Label(artifact);
|
||||
label.setStyle("-fx-font-size: 15px;");
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
setCenter(label);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -23,7 +23,12 @@ import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -113,7 +118,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchButton.setPrefHeight(55);
|
||||
launchButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
launchButton.getStyleClass().add("jfx-button-raised");
|
||||
launchButton.setOnMouseClicked(e -> launch());
|
||||
launchButton.setOnAction(e -> launch());
|
||||
launchButton.setDefaultButton(true);
|
||||
launchButton.setClip(new Rectangle(-100, -100, 310, 200));
|
||||
{
|
||||
VBox graphic = new VBox();
|
||||
|
||||
@@ -26,11 +26,14 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameInstallTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackUpdateTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||
@@ -86,6 +89,8 @@ public final class TaskListPane extends StackPane {
|
||||
|
||||
if (task instanceof GameAssetDownloadTask) {
|
||||
task.setName(i18n("assets.download_all"));
|
||||
} else if (task instanceof GameInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.game")));
|
||||
} else if (task instanceof ForgeInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
|
||||
} else if (task instanceof LiteLoaderInstallTask) {
|
||||
|
||||
@@ -107,22 +107,20 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(provider.getVersion().resolve(provider.getProfile().getRepository()));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(provider.getVersion().resolvePreservingPatches(provider.getProfile().getRepository()));
|
||||
String fabric = analyzer.getVersion(FABRIC).orElse(null);
|
||||
String forge = analyzer.getVersion(FORGE).orElse(null);
|
||||
String liteLoader = analyzer.getVersion(LITELOADER).orElse(null);
|
||||
String optiFine = analyzer.getVersion(OPTIFINE).orElse(null);
|
||||
|
||||
JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine};
|
||||
Label[] labels = new Label[]{lblFabric, lblForge, lblLiteLoader, lblOptiFine};
|
||||
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
|
||||
String[] versions = new String[]{fabric, forge, liteLoader, optiFine};
|
||||
|
||||
for (int i = 0; i < libraryIds.length; ++i) {
|
||||
String libraryId = libraryIds[i];
|
||||
buttons[i].setDisable(versions[i] != null);
|
||||
if (versions[i] != null || controller.getSettings().containsKey(libraryId))
|
||||
labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + Lang.nonNull(versions[i], getVersion(libraryId)));
|
||||
labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + Lang.nonNull(getVersion(libraryId), versions[i]));
|
||||
else
|
||||
labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionMismatchException;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -114,9 +116,10 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", ((DownloadException) exception).getUrl()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
}
|
||||
} else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException) {
|
||||
} else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException ||
|
||||
exception instanceof FabricInstallTask.UnsupportedFabricInstallationException) {
|
||||
Controllers.dialog(i18n("install.failed.optifine_conflict"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof UnsupportedOperationException) {
|
||||
} else if (exception instanceof DefaultDependencyManager.UnsupportedLibraryInstallerException) {
|
||||
Controllers.dialog(i18n("install.failed.install_online"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof VersionMismatchException) {
|
||||
VersionMismatchException e = ((VersionMismatchException) exception);
|
||||
|
||||
@@ -30,8 +30,11 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -83,7 +86,9 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
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);
|
||||
Validator nameValidator = new Validator(OperatingSystem::isNameValid);
|
||||
nameValidator.setMessage(i18n("install.new_game.malformed"));
|
||||
txtName.getValidators().addAll(hasVersion, nameValidator);
|
||||
txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate()));
|
||||
txtName.setText(gameVersion);
|
||||
|
||||
|
||||
+3
-3
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,7 +39,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
private final String libraryId;
|
||||
private final String oldLibraryVersion;
|
||||
|
||||
public UpdateInstallerWizardProvider(Profile profile, String gameVersion, Version version, String libraryId, String oldLibraryVersion) {
|
||||
public UpdateInstallerWizardProvider(@NotNull Profile profile, @NotNull String gameVersion, @NotNull Version version, @NotNull String libraryId, @NotNull String oldLibraryVersion) {
|
||||
this.profile = profile;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
@@ -57,8 +58,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
|
||||
// We remove library but not save it,
|
||||
// so if installation failed will not break down current version.
|
||||
return profile.getDependency().removeLibraryWithoutSavingAsync(version.getId(), libraryId)
|
||||
.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get(libraryId)))
|
||||
return profile.getDependency().installLibraryAsync(version, (RemoteVersion) settings.get(libraryId))
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,13 @@ import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
|
||||
import static org.jackhuang.hmcl.util.Lang.handleUncaught;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
@@ -57,12 +59,17 @@ public class GameItem extends Control {
|
||||
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE)
|
||||
.thenAcceptAsync(game -> {
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id));
|
||||
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values())
|
||||
analyzer.getVersion(type).ifPresent(library ->
|
||||
libraries
|
||||
.append(", ").append(i18n("install.installer." + type.name().toLowerCase()))
|
||||
.append(": ").append(modifyVersion(game, library.replaceAll("(?i)" + type.name().toLowerCase(), ""))));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
if (libraryId.equals(MINECRAFT.getPatchId())) continue;
|
||||
if (I18n.hasKey("install.installer." + libraryId)) {
|
||||
libraries.append(", ").append(i18n("install.installer." + libraryId));
|
||||
if (libraryVersion != null)
|
||||
libraries.append(": ").append(modifyVersion("", libraryVersion.replaceAll("(?i)" + libraryId, "")));
|
||||
}
|
||||
}
|
||||
|
||||
subtitle.set(libraries.toString());
|
||||
}, Platform::runLater)
|
||||
|
||||
@@ -22,7 +22,9 @@ import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -49,22 +51,24 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
chkSelected.setToggleGroup(skinnable.getToggleGroup());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
root.setCenter(new GameItem(skinnable.getProfile(), skinnable.getVersion()));
|
||||
GameItem gameItem = new GameItem(skinnable.getProfile(), skinnable.getVersion());
|
||||
gameItem.setMouseTransparent(true);
|
||||
root.setCenter(gameItem);
|
||||
|
||||
PopupMenu menu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.manage"), FXUtils.withJFXPopupClosing(skinnable::modifyGameSettings, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(skinnable::rename, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(skinnable::remove, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(skinnable::export, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)));
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)));
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
@@ -91,5 +95,16 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
|
||||
getChildren().setAll(root);
|
||||
|
||||
root.setCursor(Cursor.HAND);
|
||||
root.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
if (e.getClickCount() == 1) {
|
||||
skinnable.modifyGameSettings();
|
||||
}
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.ToolbarListPageSkin;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -74,22 +76,29 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
Task.supplyAsync(() -> {
|
||||
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
|
||||
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(versionId));
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
|
||||
}).thenAcceptAsync(Schedulers.javafx(), analyzer -> {
|
||||
Function<String, Consumer<InstallerItem>> removeAction = libraryId -> x -> {
|
||||
profile.getDependency().removeLibraryAsync(version.getId(), libraryId)
|
||||
profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||
.thenComposeAsync(profile.getRepository()::save)
|
||||
.withComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
.withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
|
||||
.start();
|
||||
};
|
||||
|
||||
itemsProperty().clear();
|
||||
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
|
||||
String libraryId = type.getPatchId();
|
||||
analyzer.getVersion(type).ifPresent(libraryVersion -> itemsProperty().add(
|
||||
new InstallerItem(i18n("install.installer." + libraryId), libraryVersion, () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
|
||||
}, removeAction.apply(libraryId))));
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
String title = I18n.hasKey("install.installer." + libraryId) ? i18n("install.installer." + libraryId) : libraryId;
|
||||
Consumer<InstallerItem> action = "game".equals(libraryId) ? null : removeAction.apply(libraryId);
|
||||
if (libraryVersion != null && Lang.test(() -> profile.getDependency().getVersionList(libraryId)))
|
||||
itemsProperty().add(
|
||||
new InstallerItem(title, libraryVersion, () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
|
||||
}, action));
|
||||
else
|
||||
itemsProperty().add(new InstallerItem(title, libraryVersion, null, action));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id));
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
modded.set(libraryAnalyzer.hasModLoader());
|
||||
loadMods(profile.getRepository().getModManager(id));
|
||||
}
|
||||
@@ -142,6 +142,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
public void removeSelected(ObservableList<TreeItem<ModListPageSkin.ModInfoObject>> selectedItems) {
|
||||
try {
|
||||
modManager.removeMods(selectedItems.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(TreeItem::getValue)
|
||||
.filter(Objects::nonNull)
|
||||
.map(ModListPageSkin.ModInfoObject::getModInfo)
|
||||
|
||||
@@ -21,7 +21,6 @@ import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -77,8 +76,7 @@ public class Versions {
|
||||
}
|
||||
|
||||
public static void updateGameAssets(Profile profile, String version) {
|
||||
Version resolvedVersion = profile.getRepository().getResolvedVersion(version);
|
||||
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), resolvedVersion, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY)
|
||||
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY)
|
||||
.executor();
|
||||
Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"));
|
||||
executor.start();
|
||||
|
||||
@@ -55,6 +55,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
put("java.lang.VerifyError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchMethodError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchFieldError", i18n("crash.NoClassDefFound"));
|
||||
put("javax.imageio.IIOException", i18n("crash.NoClassDefFound"));
|
||||
put("netscape.javascript.JSException", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.IncompatibleClassChangeError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.ClassFormatError", i18n("crash.NoClassDefFound"));
|
||||
@@ -86,6 +87,12 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static Set<String> CAUGHT_EXCEPTIONS = newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
private final boolean showCrashWindow;
|
||||
|
||||
public CrashReporter(boolean showCrashWindow) {
|
||||
this.showCrashWindow = showCrashWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Uncaught exception in thread " + t.getName(), e);
|
||||
@@ -116,7 +123,9 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
LOG.log(Level.SEVERE, text);
|
||||
|
||||
if (checkThrowable(e)) {
|
||||
Platform.runLater(() -> new CrashWindow(text).show());
|
||||
if (showCrashWindow) {
|
||||
Platform.runLater(() -> new CrashWindow(text).show());
|
||||
}
|
||||
if (!UpdateChecker.isOutdated() && IntegrityChecker.isSelfVerified()) {
|
||||
reportToServer(text);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ install.failed.downloading=Failed to install due to some files not downloaded su
|
||||
install.failed.downloading.detail=Failed to download file: %s
|
||||
install.failed.downloading.timeout=Download timed out: %s
|
||||
install.failed.install_online=Unable to recognize what you provided installer file is
|
||||
install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13
|
||||
install.failed.optifine_conflict=Fabric, OptiFine and Forge are both installed simultaneously on Minecraft 1.13
|
||||
install.failed.version_mismatch=The library requires game version %s, but actual version is %s.
|
||||
install.installer.choose=Choose a %s version
|
||||
install.installer.fabric=Fabric
|
||||
@@ -152,6 +152,7 @@ install.modpack=Install a modpack
|
||||
install.new_game=Install a New Game
|
||||
install.new_game.already_exists=This version has already been existing.
|
||||
install.new_game.current_game_version=Current Game Version
|
||||
install.new_game.malformed=Name malformed
|
||||
install.select=Select an operation
|
||||
install.success=Installed successfully
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ install.modpack=安裝整合包
|
||||
install.new_game=安裝新遊戲版本
|
||||
install.new_game.already_exists=此版本已經存在,請換一個名字
|
||||
install.new_game.current_game_version=目前遊戲版本
|
||||
install.new_game.malformed=名字不合法
|
||||
install.select=請選擇安裝方式
|
||||
install.success=安裝成功
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ install.modpack=安装整合包
|
||||
install.new_game=安装新游戏版本
|
||||
install.new_game.already_exists=此版本已经存在,请换一个名字
|
||||
install.new_game.current_game_version=当前游戏版本
|
||||
install.new_game.malformed=名字不合法
|
||||
install.select=请选择安装方式
|
||||
install.success=安装成功
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -214,7 +217,7 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
* // assets and versions will not be included in index.
|
||||
* }
|
||||
*/
|
||||
private class Index {
|
||||
private class Index implements Validation {
|
||||
private final Set<LibraryIndex> libraries;
|
||||
|
||||
public Index() {
|
||||
@@ -222,12 +225,19 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
}
|
||||
|
||||
public Index(Set<LibraryIndex> libraries) {
|
||||
this.libraries = libraries;
|
||||
this.libraries = Objects.requireNonNull(libraries);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<LibraryIndex> getLibraries() {
|
||||
return libraries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (libraries == null)
|
||||
throw new JsonParseException("Index.libraries cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
private class LibraryIndex {
|
||||
|
||||
@@ -23,14 +23,12 @@ import org.jackhuang.hmcl.download.game.GameDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Note: This class has no state.
|
||||
@@ -70,7 +68,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> checkGameCompletionAsync(Version version) {
|
||||
public Task<?> checkGameCompletionAsync(Version original) {
|
||||
Version version = original.resolve(repository);
|
||||
return Task.allOf(
|
||||
Task.composeAsync(() -> {
|
||||
if (!repository.getVersionJar(version).exists())
|
||||
@@ -85,7 +84,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
|
||||
@Override
|
||||
public Task<?> checkLibraryCompletionAsync(Version version) {
|
||||
return new GameLibrariesTask(this, version);
|
||||
return new GameLibrariesTask(this, version, version.getLibraries());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,19 +94,19 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
VersionList<?> versionList = getVersionList(libraryId);
|
||||
return versionList.loadAsync(gameVersion, getDownloadProvider())
|
||||
.thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)
|
||||
.orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion))));
|
||||
.orElseThrow(() -> new IOException("Remote library " + libraryId + " has no version " + libraryVersion))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion) {
|
||||
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
|
||||
|
||||
return libraryVersion.getInstallTask(this, baseVersion)
|
||||
return removeLibraryAsync(baseVersion.resolvePreservingPatches(repository), libraryVersion.getLibraryId())
|
||||
.thenComposeAsync(version -> libraryVersion.getInstallTask(this, version))
|
||||
.thenApplyAsync(baseVersion::addPatch)
|
||||
.thenComposeAsync(repository::save);
|
||||
}
|
||||
|
||||
|
||||
public ExceptionalFunction<Version, Task<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
|
||||
return version -> installLibraryAsync(version, libraryVersion);
|
||||
}
|
||||
@@ -127,56 +126,32 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Library cannot be recognized");
|
||||
throw new UnsupportedLibraryInstallerException();
|
||||
})
|
||||
.thenApplyAsync(oldVersion::addPatch)
|
||||
.thenComposeAsync(repository::save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove installed library.
|
||||
* Will try to remove libraries and patches.
|
||||
*
|
||||
* @param versionId version id
|
||||
* @param libraryId forge/liteloader/optifine
|
||||
* @return task to remove the specified library
|
||||
*/
|
||||
public Task<Version> removeLibraryWithoutSavingAsync(String versionId, String libraryId) {
|
||||
Version version = repository.getVersion(versionId); // to ensure version is not resolved
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
|
||||
switch (libraryId) {
|
||||
case "forge":
|
||||
analyzer.ifPresent(LibraryAnalyzer.LibraryType.FORGE, (library, libraryVersion) -> newList.remove(library));
|
||||
version = version.removePatchById(LibraryAnalyzer.LibraryType.FORGE.getPatchId());
|
||||
break;
|
||||
case "liteloader":
|
||||
analyzer.ifPresent(LibraryAnalyzer.LibraryType.LITELOADER, (library, libraryVersion) -> newList.remove(library));
|
||||
version = version.removePatchById(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId());
|
||||
break;
|
||||
case "optifine":
|
||||
analyzer.ifPresent(LibraryAnalyzer.LibraryType.OPTIFINE, (library, libraryVersion) -> newList.remove(library));
|
||||
version = version.removePatchById(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId());
|
||||
break;
|
||||
case "fabric":
|
||||
analyzer.ifPresent(LibraryAnalyzer.LibraryType.FABRIC, (library, libraryVersion) -> newList.remove(library));
|
||||
version = version.removePatchById(LibraryAnalyzer.LibraryType.FABRIC.getPatchId());
|
||||
break;
|
||||
}
|
||||
return new MaintainTask(version.setLibraries(newList));
|
||||
public static class UnsupportedLibraryInstallerException extends Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove installed library.
|
||||
* Will try to remove libraries and patches.
|
||||
*
|
||||
* @param versionId version id
|
||||
* @param libraryId forge/liteloader/optifine
|
||||
* @param version not resolved version
|
||||
* @param libraryId forge/liteloader/optifine/fabric
|
||||
* @return task to remove the specified library
|
||||
*/
|
||||
public Task<Version> removeLibraryAsync(String versionId, String libraryId) {
|
||||
return removeLibraryWithoutSavingAsync(versionId, libraryId).thenComposeAsync(repository::save);
|
||||
public Task<Version> removeLibraryAsync(Version version, String libraryId) {
|
||||
// MaintainTask requires version that does not inherits from any version.
|
||||
// If we want to remove a library in dependent version, we should keep the dependents not changed
|
||||
// So resolving this game version to preserve all information in this version.json is necessary.
|
||||
if (version.isResolved())
|
||||
throw new IllegalArgumentException("removeLibraryWithoutSavingAsync requires non-resolved version");
|
||||
Version independentVersion = version.resolvePreservingPatches(repository);
|
||||
|
||||
return Task.supplyAsync(() -> LibraryAnalyzer.analyze(independentVersion).removeLibrary(libraryId).build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,14 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -52,24 +47,16 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
|
||||
@Override
|
||||
public Task<?> buildAsync() {
|
||||
return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenComposeAsync(rawJson -> {
|
||||
Version original = JsonUtils.GSON.fromJson(rawJson, Version.class);
|
||||
Version version = original.setId(name).setJar(null);
|
||||
Task<?> vanillaTask = downloadGameAsync(gameVersion, version).thenComposeAsync(Task.allOf(
|
||||
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
|
||||
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
|
||||
).withComposeAsync(dependencyManager.getGameRepository().save(version))); // using [with] because download failure here are tolerant.
|
||||
Task<Version> libraryTask = Task.supplyAsync(() -> new Version(name));
|
||||
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "game", gameVersion));
|
||||
|
||||
Task<Version> libraryTask = vanillaTask.thenSupplyAsync(() -> version);
|
||||
for (Map.Entry<String, String> entry : toolVersions.entrySet())
|
||||
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
|
||||
|
||||
for (Map.Entry<String, String> entry : toolVersions.entrySet())
|
||||
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
|
||||
for (RemoteVersion remoteVersion : remoteVersions)
|
||||
libraryTask = libraryTask.thenComposeAsync(dependencyManager.installLibraryAsync(remoteVersion));
|
||||
|
||||
for (RemoteVersion remoteVersion : remoteVersions)
|
||||
libraryTask = libraryTask.thenComposeAsync(dependencyManager.installLibraryAsync(remoteVersion));
|
||||
|
||||
return libraryTask;
|
||||
}).whenComplete(exception -> {
|
||||
return libraryTask.whenComplete(exception -> {
|
||||
if (exception != null)
|
||||
dependencyManager.getGameRepository().removeVersionFromDisk(name);
|
||||
});
|
||||
@@ -78,9 +65,4 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
private ExceptionalFunction<Version, Task<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId, String libraryVersion) {
|
||||
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, libraryVersion);
|
||||
}
|
||||
|
||||
protected Task<Void> downloadGameAsync(String gameVersion, Version version) {
|
||||
return new GameDownloadTask(dependencyManager, gameVersion, version);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The service provider that provides Minecraft online file downloads.
|
||||
*
|
||||
@@ -39,11 +42,18 @@ public interface DownloadProvider {
|
||||
*/
|
||||
String injectURL(String baseURL);
|
||||
|
||||
default Stream<String> injectURLs(String[] baseURLs) {
|
||||
Stream<String> urls = Arrays.stream(baseURLs);
|
||||
Stream<String> jsonURLs = Arrays.stream(baseURLs).map(this::injectURL);
|
||||
return Stream.concat(jsonURLs, urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* the specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
|
||||
*
|
||||
* @param id the id of specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
|
||||
* @return the version list
|
||||
* @throws IllegalArgumentException if the version list does not exist
|
||||
*/
|
||||
VersionList<?> getVersionListById(String id);
|
||||
}
|
||||
@@ -20,70 +20,136 @@ package org.jackhuang.hmcl.download;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class LibraryAnalyzer {
|
||||
private final Map<LibraryType, Pair<Library, String>> libraries;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
private LibraryAnalyzer(Map<LibraryType, Pair<Library, String>> libraries) {
|
||||
public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMark> {
|
||||
private Version version;
|
||||
private final Map<String, Pair<Library, String>> libraries;
|
||||
|
||||
private LibraryAnalyzer(Version version, Map<String, Pair<Library, String>> libraries) {
|
||||
this.version = version;
|
||||
this.libraries = libraries;
|
||||
}
|
||||
|
||||
public Optional<String> getVersion(LibraryType type) {
|
||||
return getVersion(type.getPatchId());
|
||||
}
|
||||
|
||||
public Optional<String> getVersion(String type) {
|
||||
return Optional.ofNullable(libraries.get(type)).map(Pair::getValue);
|
||||
}
|
||||
|
||||
public void ifPresent(LibraryType type, BiConsumer<Library, String> consumer) {
|
||||
if (libraries.containsKey(type)) {
|
||||
Pair<Library, String> value = libraries.get(type);
|
||||
consumer.accept(value.getKey(), value.getValue());
|
||||
}
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<LibraryMark> iterator() {
|
||||
return new Iterator<LibraryMark>() {
|
||||
Iterator<Map.Entry<String, Pair<Library, String>>> impl = libraries.entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return impl.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LibraryMark next() {
|
||||
Map.Entry<String, Pair<Library, String>> entry = impl.next();
|
||||
return new LibraryMark(entry.getKey(), entry.getValue().getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean has(LibraryType type) {
|
||||
return has(type.getPatchId());
|
||||
}
|
||||
|
||||
public boolean has(String type) {
|
||||
return libraries.containsKey(type);
|
||||
}
|
||||
|
||||
public boolean hasModLoader() {
|
||||
return Arrays.stream(LibraryType.values())
|
||||
.filter(LibraryType::isModLoader)
|
||||
.anyMatch(this::has);
|
||||
return libraries.keySet().stream().map(LibraryType::fromPatchId)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(LibraryType::isModLoader);
|
||||
}
|
||||
|
||||
public boolean hasModLauncher() {
|
||||
final String modLauncher = "cpw.mods.modlauncher.Launcher";
|
||||
return modLauncher.equals(version.getMainClass()) || version.getPatches().stream().anyMatch(patch -> modLauncher.equals(patch.getMainClass()));
|
||||
}
|
||||
|
||||
private Version removingMatchedLibrary(Version version, String libraryId) {
|
||||
LibraryType type = LibraryType.fromPatchId(libraryId);
|
||||
if (type == null) return version;
|
||||
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
for (Library library : version.getLibraries()) {
|
||||
String groupId = library.getGroupId();
|
||||
String artifactId = library.getArtifactId();
|
||||
|
||||
if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) {
|
||||
// skip
|
||||
} else {
|
||||
libraries.add(library);
|
||||
}
|
||||
}
|
||||
return version.setLibraries(libraries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove library by library id
|
||||
* @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"
|
||||
* @return this
|
||||
*/
|
||||
public LibraryAnalyzer removeLibrary(String libraryId) {
|
||||
if (!has(libraryId)) return this;
|
||||
version = removingMatchedLibrary(version, libraryId)
|
||||
.setPatches(version.getPatches().stream()
|
||||
.filter(patch -> !libraryId.equals(patch.getId()))
|
||||
.map(patch -> removingMatchedLibrary(patch, libraryId))
|
||||
.collect(Collectors.toList()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Version build() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public static LibraryAnalyzer analyze(Version version) {
|
||||
Map<LibraryType, Pair<Library, String>> libraries = new EnumMap<>(LibraryType.class);
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("LibraryAnalyzer can only analyze independent game version");
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
Map<String, Pair<Library, String>> libraries = new HashMap<>();
|
||||
|
||||
for (Library library : version.resolve(null).getLibraries()) {
|
||||
String groupId = library.getGroupId();
|
||||
String artifactId = library.getArtifactId();
|
||||
|
||||
for (LibraryType type : LibraryType.values()) {
|
||||
if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) {
|
||||
libraries.put(type, Pair.pair(library, library.getVersion()));
|
||||
libraries.put(type.getPatchId(), pair(library, library.getVersion()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Version patch : version.getPatches()) {
|
||||
for (LibraryType type : LibraryType.values()) {
|
||||
if (type.patchId.equals(patch.getId())) {
|
||||
libraries.put(type, Pair.pair(null, patch.getVersion()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (patch.isHidden()) continue;
|
||||
libraries.put(patch.getId(), pair(null, patch.getVersion()));
|
||||
}
|
||||
|
||||
return new LibraryAnalyzer(libraries);
|
||||
return new LibraryAnalyzer(version, libraries);
|
||||
}
|
||||
|
||||
public enum LibraryType {
|
||||
MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$")),
|
||||
FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")),
|
||||
FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
|
||||
LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
|
||||
@@ -107,5 +173,32 @@ public final class LibraryAnalyzer {
|
||||
public String getPatchId() {
|
||||
return patchId;
|
||||
}
|
||||
|
||||
public static LibraryType fromPatchId(String patchId) {
|
||||
for (LibraryType type : values())
|
||||
if (type.getPatchId().equals(patchId))
|
||||
return type;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LibraryMark {
|
||||
private final String libraryId;
|
||||
private final String libraryVersion;
|
||||
|
||||
public LibraryMark(@NotNull String libraryId, @Nullable String libraryVersion) {
|
||||
this.libraryId = libraryId;
|
||||
this.libraryVersion = libraryVersion;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getLibraryId() {
|
||||
return libraryId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLibraryVersion() {
|
||||
return libraryVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,37 +17,63 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.CompatibilityRule;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
|
||||
public class MaintainTask extends Task<Version> {
|
||||
|
||||
private final GameRepository repository;
|
||||
private final Version version;
|
||||
|
||||
public MaintainTask(Version version) {
|
||||
public MaintainTask(GameRepository repository, Version version) {
|
||||
this.repository = repository;
|
||||
this.version = version;
|
||||
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("MaintainTask requires independent game version");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
setResult(maintain(version));
|
||||
setResult(maintain(repository, version));
|
||||
}
|
||||
|
||||
public static Version maintain(Version version) {
|
||||
if (version.getMainClass().contains("launchwrapper")) {
|
||||
return maintainGameWithLaunchWrapper(version);
|
||||
public static Version maintain(GameRepository repository, Version version) {
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("MaintainTask requires independent game version");
|
||||
|
||||
String mainClass = version.resolve(null).getMainClass();
|
||||
|
||||
if (mainClass != null && mainClass.contains("launchwrapper")) {
|
||||
return maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version)));
|
||||
} else {
|
||||
// Vanilla Minecraft does not need maintain
|
||||
// Forge 1.13 support not implemented, not compatible with OptiFine currently.
|
||||
// Fabric does not need maintain, nothing compatible with fabric now.
|
||||
return version;
|
||||
return maintainOptiFineLibrary(repository, unique(version));
|
||||
}
|
||||
}
|
||||
|
||||
private static Version maintainGameWithLaunchWrapper(Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
String mainClass = null;
|
||||
|
||||
if (!libraryAnalyzer.has(FORGE)) {
|
||||
builder.removeTweakClass("forge");
|
||||
@@ -56,7 +82,7 @@ public class MaintainTask extends Task<Version> {
|
||||
// Installing Forge will override the Minecraft arguments in json, so LiteLoader and OptiFine Tweaker are being re-added.
|
||||
|
||||
builder.removeTweakClass("liteloader");
|
||||
if (libraryAnalyzer.has(LITELOADER)) {
|
||||
if (libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.hasModLauncher()) {
|
||||
builder.addArgument("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker");
|
||||
}
|
||||
|
||||
@@ -65,11 +91,101 @@ public class MaintainTask extends Task<Version> {
|
||||
if (!libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.has(FORGE)) {
|
||||
builder.addArgument("--tweakClass", "optifine.OptiFineTweaker");
|
||||
} else {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
builder.addArgument("--tweakClass", "optifine.OptiFineForgeTweaker");
|
||||
if (libraryAnalyzer.hasModLauncher()) {
|
||||
// If ModLauncher installed, we use ModLauncher in place of LaunchWrapper.
|
||||
mainClass = "cpw.mods.modlauncher.Launcher";
|
||||
} else {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
builder.addArgument("--tweakClass", "optifine.OptiFineForgeTweaker");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
Version ret = builder.build();
|
||||
return mainClass == null ? ret : ret.setMainClass(mainClass);
|
||||
}
|
||||
|
||||
private static Version maintainOptiFineLibrary(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
List<Library> libraries = new ArrayList<>(version.getLibraries());
|
||||
|
||||
if (libraryAnalyzer.has(OPTIFINE)) {
|
||||
if (libraryAnalyzer.has(LITELOADER) || libraryAnalyzer.has(FORGE)) {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
// And we should load the installer jar instead of patch jar.
|
||||
if (repository != null)
|
||||
for (int i = 0; i < version.getLibraries().size(); ++i) {
|
||||
Library library = libraries.get(i);
|
||||
if (library.is("optifine", "OptiFine")) {
|
||||
Library newLibrary = new Library(new Artifact("optifine", "OptiFine", library.getVersion(), "installer"));
|
||||
if (repository.getLibraryFile(version, newLibrary).exists()) {
|
||||
libraries.set(i, null);
|
||||
// OptiFine should be loaded after Forge in classpath.
|
||||
// Although we have altered priority of OptiFine higher than Forge,
|
||||
// there still exists a situation that Forge is installed without patch.
|
||||
// Here we manually alter the position of OptiFine library in classpath.
|
||||
libraries.add(newLibrary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(libraries.stream().filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static Version unique(Version version) {
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
|
||||
SimpleMultimap<String, Integer> multimap = new SimpleMultimap<String, Integer>(HashMap::new, LinkedList::new);
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
String id = library.getGroupId() + ":" + library.getArtifactId();
|
||||
VersionNumber number = VersionNumber.asVersion(library.getVersion());
|
||||
String serialized = JsonUtils.GSON.toJson(library);
|
||||
|
||||
if (multimap.containsKey(id)) {
|
||||
boolean duplicate = false;
|
||||
for (int otherLibraryIndex : multimap.get(id)) {
|
||||
Library otherLibrary = libraries.get(otherLibraryIndex);
|
||||
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
|
||||
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
|
||||
boolean flag = true;
|
||||
if (number.compareTo(otherNumber) > 0) { // if this library is newer
|
||||
// replace [otherLibrary] with [library]
|
||||
libraries.set(otherLibraryIndex, library);
|
||||
} else if (number.compareTo(otherNumber) == 0) { // same library id.
|
||||
// prevent from duplicated libraries
|
||||
if (library.equals(otherLibrary)) {
|
||||
String otherSerialized = JsonUtils.GSON.toJson(otherLibrary);
|
||||
// A trick, the library that has more information is better, which can be
|
||||
// considered whose serialized JSON text will be longer.
|
||||
if (serialized.length() > otherSerialized.length()) {
|
||||
libraries.set(otherLibraryIndex, library);
|
||||
}
|
||||
} else {
|
||||
// for text2speech, which have same library id as well as version number,
|
||||
// but its library and native library does not equal
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicate) {
|
||||
multimap.put(id, libraries.size());
|
||||
libraries.add(library);
|
||||
}
|
||||
} else {
|
||||
multimap.put(id, libraries.size());
|
||||
libraries.add(library);
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(libraries);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -31,9 +32,10 @@ import java.util.Objects;
|
||||
*/
|
||||
public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
|
||||
private final String libraryId;
|
||||
private final String gameVersion;
|
||||
private final String selfVersion;
|
||||
private final String url;
|
||||
private final String[] url;
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
@@ -43,8 +45,8 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
* @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) {
|
||||
this(gameVersion, selfVersion, url, Type.UNCATEGORIZED);
|
||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String... url) {
|
||||
this(libraryId, gameVersion, selfVersion, Type.UNCATEGORIZED, url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,13 +56,18 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
* @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) {
|
||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Type type, String... url) {
|
||||
this.libraryId = Objects.requireNonNull(libraryId);
|
||||
this.gameVersion = Objects.requireNonNull(gameVersion);
|
||||
this.selfVersion = Objects.requireNonNull(selfVersion);
|
||||
this.url = Objects.requireNonNull(url);
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
public String getLibraryId() {
|
||||
return libraryId;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
@@ -69,7 +76,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
return selfVersion;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
public String[] getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
@@ -64,8 +65,8 @@ public final class FabricInstallTask extends Task<Version> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() {
|
||||
if (!Objects.equals("net.minecraft.client.main.Main", version.getMainClass()))
|
||||
public void preExecute() throws Exception {
|
||||
if (!Objects.equals("net.minecraft.client.main.Main", version.resolve(dependencyManager.getGameRepository()).getMainClass()))
|
||||
throw new UnsupportedFabricInstallationException();
|
||||
}
|
||||
|
||||
@@ -121,12 +122,12 @@ public final class FabricInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
libraries.add(new Library("net.fabricmc", "intermediary", gameVersion, null, "https://maven.fabricmc.net/", null));
|
||||
libraries.add(new Library("net.fabricmc", "fabric-loader", loaderVersion, null, "https://maven.fabricmc.net/", null));
|
||||
libraries.add(new Library(new Artifact("net.fabricmc", "intermediary", gameVersion), "https://maven.fabricmc.net/", null));
|
||||
libraries.add(new Library(new Artifact("net.fabricmc", "fabric-loader", loaderVersion), "https://maven.fabricmc.net/", null));
|
||||
|
||||
return new Version("net.fabricmc", loaderVersion, 30000, arguments, mainClass, libraries);
|
||||
}
|
||||
|
||||
public static class UnsupportedFabricInstallationException extends UnsupportedOperationException {
|
||||
public static class UnsupportedFabricInstallationException extends Exception {
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -31,7 +32,7 @@ public class FabricRemoteVersion extends RemoteVersion {
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
FabricRemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
super(gameVersion, selfVersion, url);
|
||||
super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,11 +28,17 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
|
||||
public static final ForgeBMCLVersionList INSTANCE = new ForgeBMCLVersionList();
|
||||
@@ -76,19 +82,28 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
for (ForgeVersion version : forgeVersions) {
|
||||
if (version == null)
|
||||
continue;
|
||||
String jar = null;
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (ForgeVersion.File file : version.getFiles())
|
||||
if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) {
|
||||
String classifier = gameVersion + "-" + version.getVersion()
|
||||
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
|
||||
String fileName = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat();
|
||||
jar = "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "/" + fileName;
|
||||
String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat();
|
||||
String fileName2 = "forge-" + classifier + "-" + gameVersion + "-" + file.getCategory() + "." + file.getFormat();
|
||||
urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf(
|
||||
pair("mcversion", version.getGameVersion()),
|
||||
pair("version", version.getVersion()),
|
||||
pair("branch", version.getBranch()),
|
||||
pair("category", file.getCategory()),
|
||||
pair("format", file.getFormat())
|
||||
)));
|
||||
urls.add("https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1);
|
||||
urls.add("https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
|
||||
}
|
||||
|
||||
if (jar == null)
|
||||
if (urls.isEmpty())
|
||||
continue;
|
||||
versions.put(gameVersion, new ForgeRemoteVersion(
|
||||
version.getGameVersion(), version.getVersion(), jar
|
||||
version.getGameVersion(), version.getVersion(), urls.toArray(new String[0])
|
||||
));
|
||||
}
|
||||
} finally {
|
||||
@@ -121,18 +136,22 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getBranch() {
|
||||
return branch;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getGameVersion() {
|
||||
return mcversion;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<File> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
/**
|
||||
@@ -28,7 +29,7 @@ public final class ForgeInstall {
|
||||
|
||||
private final String profileName;
|
||||
private final String target;
|
||||
private final String path;
|
||||
private final Artifact path;
|
||||
private final String version;
|
||||
private final String filePath;
|
||||
private final String welcome;
|
||||
@@ -40,7 +41,7 @@ public final class ForgeInstall {
|
||||
this(null, null, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public ForgeInstall(String profileName, String target, String path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {
|
||||
public ForgeInstall(String profileName, String target, Artifact path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {
|
||||
this.profileName = profileName;
|
||||
this.target = target;
|
||||
this.path = path;
|
||||
@@ -60,7 +61,7 @@ public final class ForgeInstall {
|
||||
return target;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
public Artifact getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.VersionMismatchException;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -34,10 +33,11 @@ import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -67,7 +67,11 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
public void preExecute() throws Exception {
|
||||
installer = Files.createTempFile("forge-installer", ".jar");
|
||||
|
||||
dependent = new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer.toFile())
|
||||
dependent = new FileDownloadTask(
|
||||
Arrays.stream(remote.getUrl())
|
||||
.map(NetworkUtils::toURL)
|
||||
.collect(Collectors.toList()),
|
||||
installer.toFile(), null)
|
||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
||||
.setCaching(true);
|
||||
}
|
||||
@@ -80,10 +84,7 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void postExecute() throws Exception {
|
||||
Files.deleteIfExists(installer);
|
||||
setResult(dependency.getResult()
|
||||
.setPriority(30000)
|
||||
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
|
||||
.setVersion(remote.getSelfVersion()));
|
||||
setResult(dependency.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,9 +100,9 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() {
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare("1.13", remote.getGameVersion()) <= 0)
|
||||
dependency = new ForgeNewInstallTask(dependencyManager, version, installer);
|
||||
dependency = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
|
||||
else
|
||||
dependency = new ForgeOldInstallTask(dependencyManager, version, installer);
|
||||
dependency = new ForgeOldInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,15 +126,19 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
|
||||
if (!gameVersion.get().equals(profile.getMinecraft()))
|
||||
throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());
|
||||
return new ForgeNewInstallTask(dependencyManager, version, installer);
|
||||
return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getPath().getVersion().replaceAll("(?i)forge", "")), installer);
|
||||
} else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) {
|
||||
ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);
|
||||
if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))
|
||||
throw new VersionMismatchException(profile.getInstall().getMinecraft(), gameVersion.get());
|
||||
return new ForgeOldInstallTask(dependencyManager, version, installer);
|
||||
return new ForgeOldInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getInstall().getPath().getVersion().replaceAll("(?i)forge", "")), installer);
|
||||
} else {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
@@ -34,11 +35,11 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -70,12 +71,14 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
|
||||
private ForgeNewInstallProfile profile;
|
||||
private Version forgeVersion;
|
||||
private final String selfVersion;
|
||||
|
||||
ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
|
||||
ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameRepository = dependencyManager.getGameRepository();
|
||||
this.version = version;
|
||||
this.installer = installer;
|
||||
this.selfVersion = selfVersion;
|
||||
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
@@ -86,7 +89,7 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
else if (StringUtils.isSurrounded(literal, "'", "'"))
|
||||
return StringUtils.removeSurrounding(literal, "'");
|
||||
else if (StringUtils.isSurrounded(literal, "[", "]"))
|
||||
return gameRepository.getArtifactFile(version, new Artifact(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
|
||||
return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
|
||||
else
|
||||
return plainConverter.apply(literal);
|
||||
}
|
||||
@@ -228,25 +231,20 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
|
||||
command.add(mainClass);
|
||||
|
||||
List<String> args = processor.getArgs().stream().map(arg -> {
|
||||
List<String> args = new ArrayList<>(processor.getArgs().size());
|
||||
for (String arg : processor.getArgs()) {
|
||||
String parsed = parseLiteral(arg, data, ExceptionalFunction.identity());
|
||||
if (parsed == null)
|
||||
throw new IllegalStateException("Invalid forge installation configuration");
|
||||
return parsed;
|
||||
}).collect(Collectors.toList());
|
||||
throw new IOException("Invalid forge installation configuration");
|
||||
args.add(parsed);
|
||||
}
|
||||
|
||||
command.addAll(args);
|
||||
|
||||
LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString());
|
||||
Process process = new ProcessBuilder(command).start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
for (String line; (line = reader.readLine()) != null;) {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
int exitCode = process.waitFor();
|
||||
int exitCode = SystemUtils.callExternalProcess(command);
|
||||
if (exitCode != 0)
|
||||
throw new IllegalStateException("Game processor exited abnormally");
|
||||
throw new IOException("Game processor exited abnormally");
|
||||
|
||||
for (Map.Entry<String, String> entry : outputs.entrySet()) {
|
||||
Path artifact = Paths.get(entry.getKey());
|
||||
@@ -268,7 +266,10 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
setResult(forgeVersion);
|
||||
setResult(forgeVersion
|
||||
.setPriority(30000)
|
||||
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
|
||||
.setVersion(selfVersion));
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion));
|
||||
|
||||
FileUtils.deleteDirectory(temp.toFile());
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -41,12 +42,14 @@ public class ForgeOldInstallTask extends Task<Version> {
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final Path installer;
|
||||
private final String selfVersion;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
|
||||
ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.installer = installer;
|
||||
this.selfVersion = selfVersion;
|
||||
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
@@ -71,7 +74,7 @@ public class ForgeOldInstallTask extends Task<Version> {
|
||||
ForgeInstallProfile installProfile = JsonUtils.fromNonNullJson(json, ForgeInstallProfile.class);
|
||||
|
||||
// unpack the universal jar in the installer file.
|
||||
Library forgeLibrary = Library.fromName(installProfile.getInstall().getPath());
|
||||
Library forgeLibrary = new Library(installProfile.getInstall().getPath());
|
||||
File forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary);
|
||||
if (!FileUtils.makeFile(forgeFile))
|
||||
throw new IOException("Cannot make directory " + forgeFile.getParent());
|
||||
@@ -81,7 +84,10 @@ public class ForgeOldInstallTask extends Task<Version> {
|
||||
IOUtils.copyTo(is, os);
|
||||
}
|
||||
|
||||
setResult(installProfile.getVersionInfo());
|
||||
setResult(installProfile.getVersionInfo()
|
||||
.setPriority(30000)
|
||||
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
|
||||
.setVersion(selfVersion));
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -30,8 +31,8 @@ public class ForgeRemoteVersion extends RemoteVersion {
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
super(gameVersion, selfVersion, url);
|
||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, String... url) {
|
||||
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,16 +51,16 @@ public final class GameAssetDownloadTask extends Task<Void> {
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
* @param version the game version
|
||||
*/
|
||||
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version, boolean forceDownloadingIndex) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.assetIndexInfo = version.getAssetIndex();
|
||||
this.version = version.resolve(dependencyManager.getGameRepository());
|
||||
this.assetIndexInfo = this.version.getAssetIndex();
|
||||
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
|
||||
|
||||
if (!assetIndexFile.exists() || forceDownloadingIndex)
|
||||
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, version));
|
||||
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,7 +43,7 @@ public final class GameDownloadTask extends Task<Void> {
|
||||
public GameDownloadTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.version = version.resolve(dependencyManager.getGameRepository());
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
|
||||
|
||||
public class GameInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultGameRepository gameRepository;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final GameRemoteVersion remote;
|
||||
private final VersionJsonDownloadTask downloadTask;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
public GameInstallTask(DefaultDependencyManager dependencyManager, Version version, GameRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameRepository = dependencyManager.getGameRepository();
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
this.downloadTask = new VersionJsonDownloadTask(remoteVersion.getGameVersion(), dependencyManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return Collections.singleton(downloadTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Version patch = JsonUtils.fromNonNullJson(downloadTask.getResult(), Version.class)
|
||||
.setId(MINECRAFT.getPatchId()).setVersion(remote.getGameVersion()).setJar(null).setPriority(0);
|
||||
setResult(patch);
|
||||
|
||||
Version version = new Version(this.version.getId()).addPatch(patch);
|
||||
dependencies.add(new GameDownloadTask(dependencyManager, remote.getGameVersion(), version)
|
||||
.thenComposeAsync(Task.allOf(
|
||||
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
|
||||
new GameLibrariesTask(dependencyManager, version)
|
||||
).withComposeAsync(gameRepository.save(version))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,17 +43,17 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
* @param version the game version
|
||||
*/
|
||||
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
this(dependencyManager, version, version.getLibraries());
|
||||
this(dependencyManager, version, version.resolve(dependencyManager.getGameRepository()).getLibraries());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
* @param version the game version
|
||||
*/
|
||||
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version, List<Library> libraries) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.ReleaseType;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -34,7 +38,7 @@ public final class GameRemoteVersion extends RemoteVersion {
|
||||
private final Date time;
|
||||
|
||||
public GameRemoteVersion(String gameVersion, String selfVersion, String url, ReleaseType type, Date time) {
|
||||
super(gameVersion, selfVersion, url, getReleaseType(type));
|
||||
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, getReleaseType(type), url);
|
||||
this.type = type;
|
||||
this.time = time;
|
||||
}
|
||||
@@ -47,6 +51,11 @@ public final class GameRemoteVersion extends RemoteVersion {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
return new GameInstallTask(dependencyManager, baseVersion, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RemoteVersion o) {
|
||||
if (!(o instanceof GameRemoteVersion))
|
||||
|
||||
@@ -21,7 +21,6 @@ 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.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
@@ -48,7 +47,7 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
|
||||
protected Collection<GameRemoteVersion> getVersionsImpl(String gameVersion) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return StringUtils.isBlank(gameVersion) ? versions.values() : versions.get(gameVersion);
|
||||
return versions.values();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.game.CompatibilityRule;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LibrariesUniqueTask extends Task<Version> {
|
||||
private final Version version;
|
||||
|
||||
public LibrariesUniqueTask(Version version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
setResult(unique(version));
|
||||
}
|
||||
|
||||
public static Version unique(Version version) {
|
||||
List<Library> libraries = new ArrayList<>(version.getLibraries());
|
||||
|
||||
SimpleMultimap<String, Library> multimap = new SimpleMultimap<String, Library>(HashMap::new, LinkedList::new);
|
||||
|
||||
for (Library library : libraries) {
|
||||
String id = library.getGroupId() + ":" + library.getArtifactId();
|
||||
VersionNumber number = VersionNumber.asVersion(library.getVersion());
|
||||
String serialized = JsonUtils.GSON.toJson(library);
|
||||
|
||||
if (multimap.containsKey(id)) {
|
||||
boolean duplicate = false;
|
||||
for (Library otherLibrary : multimap.get(id)) {
|
||||
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
|
||||
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
|
||||
boolean flag = true;
|
||||
if (number.compareTo(otherNumber) > 0) { // if this library is newer
|
||||
multimap.removeValue(otherLibrary);
|
||||
multimap.put(id, library);
|
||||
break;
|
||||
} else if (number.compareTo(otherNumber) == 0) { // same library id.
|
||||
// prevent from duplicated libraries
|
||||
if (library.equals(otherLibrary)) {
|
||||
String otherSerialized = JsonUtils.GSON.toJson(otherLibrary);
|
||||
// A trick, the library that has more information is better, which can be
|
||||
// considered whose serialized JSON text will be longer.
|
||||
if (serialized.length() > otherSerialized.length()) {
|
||||
multimap.removeValue(id, otherLibrary);
|
||||
multimap.put(id, library);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// for text2speech, which have same library id as well as version number,
|
||||
// but its library and native library does not equal
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicate) {
|
||||
multimap.put(id, library);
|
||||
}
|
||||
} else {
|
||||
multimap.put(id, library);
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
+13
-5
@@ -24,9 +24,15 @@ import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -61,10 +67,12 @@ public final class VersionJsonDownloadTask extends Task<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
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)).storeTo(this::setResult));
|
||||
public void execute() throws IOException {
|
||||
RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion)
|
||||
.orElseThrow(() -> new IOException("Cannot find specific version " + gameVersion + " in remote repository"));
|
||||
dependencies.add(new GetTask(
|
||||
dependencyManager.getDownloadProvider().injectURLs(remoteVersion.getUrl())
|
||||
.map(NetworkUtils::toURL).collect(Collectors.toList()),
|
||||
UTF_8).storeTo(this::setResult));
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download.liteloader;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
|
||||
@@ -64,9 +65,9 @@ public final class LiteLoaderInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() {
|
||||
Library library = new Library(
|
||||
"com.mumfrey", "liteloader", remote.getSelfVersion(), null,
|
||||
new Artifact("com.mumfrey", "liteloader", remote.getSelfVersion()),
|
||||
"http://dl.liteloader.com/versions/",
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()[0]))
|
||||
);
|
||||
|
||||
setResult(new Version(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(),
|
||||
@@ -78,7 +79,7 @@ public final class LiteLoaderInstallTask extends Task<Version> {
|
||||
.setLogging(Collections.emptyMap()) // Mods may log in malformed format, causing XML parser to crash. So we suppress using official log4j configuration
|
||||
);
|
||||
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(version.setLibraries(getResult().getLibraries())));
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult()));
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.download.liteloader;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -36,7 +37,7 @@ public class LiteLoaderRemoteVersion extends RemoteVersion {
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, String url, String tweakClass, Collection<Library> libraries) {
|
||||
super(gameVersion, selfVersion, url);
|
||||
super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, url);
|
||||
|
||||
this.tweakClass = tweakClass;
|
||||
this.libraries = libraries;
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
|
||||
continue;
|
||||
|
||||
String gameVersion = VersionNumber.normalize(element.getGameVersion());
|
||||
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror, isPre));
|
||||
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, mirror, isPre));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+54
-32
@@ -21,6 +21,8 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.VersionMismatchException;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
@@ -31,6 +33,8 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantPool;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantType;
|
||||
@@ -41,12 +45,8 @@ import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.getOrDefault;
|
||||
|
||||
@@ -57,15 +57,17 @@ import static org.jackhuang.hmcl.util.Lang.getOrDefault;
|
||||
*/
|
||||
public final class OptiFineInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultGameRepository gameRepository;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final OptiFineRemoteVersion remote;
|
||||
private final Path installer;
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final File dest;
|
||||
private Path dest;
|
||||
|
||||
private final Library optiFineLibrary;
|
||||
private final Library optiFineInstallerLibrary;
|
||||
|
||||
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {
|
||||
this(dependencyManager, version, remoteVersion, null);
|
||||
@@ -73,20 +75,21 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
|
||||
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion, Path installer) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameRepository = dependencyManager.getGameRepository();
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
this.installer = installer;
|
||||
|
||||
String mavenVersion = remote.getGameVersion() + "_" + remote.getSelfVersion();
|
||||
|
||||
optiFineLibrary = new Library(
|
||||
"optifine", "OptiFine", mavenVersion, null, null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(
|
||||
"optifine/OptiFine/" + mavenVersion + "/OptiFine-" + mavenVersion + ".jar",
|
||||
remote.getUrl()))
|
||||
);
|
||||
optiFineLibrary = new Library(new Artifact("optifine", "OptiFine", mavenVersion));
|
||||
|
||||
dest = dependencyManager.getGameRepository().getLibraryFile(version, optiFineLibrary);
|
||||
optiFineInstallerLibrary = new Library(
|
||||
new Artifact("optifine", "OptiFine", mavenVersion, "installer"), null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(
|
||||
"optifine/OptiFine/" + mavenVersion + "/OptiFine-" + mavenVersion + "-installer.jar",
|
||||
remote.getUrl()[0]))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,18 +99,16 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
if (!Arrays.asList("net.minecraft.client.main.Main",
|
||||
"net.minecraft.launchwrapper.Launch")
|
||||
.contains(version.resolve(dependencyManager.getGameRepository()).getMainClass()))
|
||||
throw new UnsupportedOptiFineInstallationException();
|
||||
|
||||
dest = Files.createTempFile("optifine-installer", ".jar");
|
||||
|
||||
if (installer == null) {
|
||||
dependents.add(new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), dest)
|
||||
dependents.add(new FileDownloadTask(
|
||||
Arrays.stream(remote.getUrl()).map(NetworkUtils::toURL).collect(Collectors.toList()),
|
||||
dest.toFile(), null)
|
||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
||||
.setCaching(true));
|
||||
} else {
|
||||
FileUtils.copyFile(installer, dest.toPath());
|
||||
FileUtils.copyFile(installer, dest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,48 +128,66 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
public void execute() throws Exception {
|
||||
List<Library> libraries = new LinkedList<>();
|
||||
libraries.add(optiFineLibrary);
|
||||
|
||||
FileUtils.copyFile(dest, gameRepository.getLibraryFile(version, optiFineInstallerLibrary).toPath());
|
||||
|
||||
// Install launch wrapper modified by OptiFine
|
||||
boolean hasLaunchWrapper = false;
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest.toPath())) {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest)) {
|
||||
if (Files.exists(fs.getPath("optifine/Patcher.class"))) {
|
||||
int exitCode = SystemUtils.callExternalProcess(
|
||||
JavaVersion.fromCurrentEnvironment().getBinary().toString(),
|
||||
"-cp",
|
||||
dest.toString(),
|
||||
"optifine.Patcher",
|
||||
gameRepository.getVersionJar(version).getAbsolutePath(),
|
||||
dest.toString(),
|
||||
gameRepository.getLibraryFile(version, optiFineLibrary).toString()
|
||||
);
|
||||
if (exitCode != 0)
|
||||
throw new IOException("OptiFine patcher failed");
|
||||
} else {
|
||||
FileUtils.copyFile(dest, gameRepository.getLibraryFile(version, optiFineLibrary).toPath());
|
||||
}
|
||||
|
||||
Path launchWrapperVersionText = fs.getPath("launchwrapper-of.txt");
|
||||
if (Files.exists(launchWrapperVersionText)) {
|
||||
String launchWrapperVersion = FileUtils.readText(launchWrapperVersionText).trim();
|
||||
Path launchWrapperJar = fs.getPath("launchwrapper-of-" + launchWrapperVersion + ".jar");
|
||||
|
||||
Library launchWrapper = new Library("optifine", "launchwrapper-of", launchWrapperVersion);
|
||||
Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper-of", launchWrapperVersion));
|
||||
|
||||
if (Files.exists(launchWrapperJar)) {
|
||||
File launchWrapperFile = dependencyManager.getGameRepository().getLibraryFile(version, launchWrapper);
|
||||
File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);
|
||||
FileUtils.makeDirectory(launchWrapperFile.getAbsoluteFile().getParentFile());
|
||||
FileUtils.copyFile(launchWrapperJar, launchWrapperFile.toPath());
|
||||
|
||||
hasLaunchWrapper = true;
|
||||
libraries.add(0, launchWrapper);
|
||||
libraries.add(launchWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasLaunchWrapper) {
|
||||
libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12"));
|
||||
libraries.add(new Library(new Artifact("net.minecraft", "launchwrapper", "1.12")));
|
||||
}
|
||||
|
||||
setResult(new Version(
|
||||
LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(),
|
||||
remote.getSelfVersion(),
|
||||
90000,
|
||||
10000,
|
||||
new Arguments().addGameArguments("--tweakClass", "optifine.OptiFineTweaker"),
|
||||
"net.minecraft.launchwrapper.Launch",
|
||||
libraries
|
||||
));
|
||||
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(version.setLibraries(libraries)));
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult()));
|
||||
}
|
||||
|
||||
public static class UnsupportedOptiFineInstallationException extends UnsupportedOperationException {
|
||||
public static class UnsupportedOptiFineInstallationException extends Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +205,10 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(jar);
|
||||
if (!gameVersion.isPresent()) throw new IOException();
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(fs.getPath("Config.class")), ConstantType.UTF8);
|
||||
Path configClass = fs.getPath("Config.class");
|
||||
if (!Files.exists(configClass)) configClass = fs.getPath("net/optifine/Config.class");
|
||||
if (!Files.exists(configClass)) throw new IOException("Unrecognized installer");
|
||||
ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(configClass), ConstantType.UTF8);
|
||||
List<String> constants = new ArrayList<>();
|
||||
pool.list(Utf8Constant.class).forEach(utf8 -> constants.add(utf8.get()));
|
||||
String mcVersion = getOrDefault(constants, constants.indexOf("MC_VERSION") + 1, null);
|
||||
@@ -200,7 +222,7 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
throw new VersionMismatchException(mcVersion, gameVersion.get());
|
||||
|
||||
return new OptiFineInstallTask(dependencyManager, version,
|
||||
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, () -> null, false), installer);
|
||||
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, "", false), installer);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-10
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.download.optifine;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -25,17 +26,9 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class OptiFineRemoteVersion extends RemoteVersion {
|
||||
private final Supplier<String> url;
|
||||
|
||||
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url, boolean snapshot) {
|
||||
super(gameVersion, selfVersion, "", snapshot ? Type.SNAPSHOT : Type.RELEASE);
|
||||
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return url.get();
|
||||
public OptiFineRemoteVersion(String gameVersion, String selfVersion, String url, boolean snapshot) {
|
||||
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, snapshot ? Type.SNAPSHOT : Type.RELEASE, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.event;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -27,12 +28,11 @@ import java.util.HashMap;
|
||||
*/
|
||||
public final class EventBus {
|
||||
|
||||
private final HashMap<Class<?>, EventManager<?>> events = new HashMap<>();
|
||||
private final ConcurrentHashMap<Class<?>, EventManager<?>> events = new ConcurrentHashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Event> EventManager<T> channel(Class<T> clazz) {
|
||||
if (!events.containsKey(clazz))
|
||||
events.put(clazz, new EventManager<>());
|
||||
events.putIfAbsent(clazz, new EventManager<>());
|
||||
return (EventManager<T>) events.get(clazz);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -31,7 +32,7 @@ import java.util.function.Consumer;
|
||||
public final class EventManager<T extends Event> {
|
||||
|
||||
private final SimpleMultimap<EventPriority, Consumer<T>> handlers
|
||||
= new SimpleMultimap<>(() -> new EnumMap<>(EventPriority.class), HashSet::new);
|
||||
= new SimpleMultimap<>(() -> new EnumMap<>(EventPriority.class), CopyOnWriteArraySet::new);
|
||||
|
||||
public Consumer<T> registerWeak(Consumer<T> consumer) {
|
||||
register(new WeakListener(consumer));
|
||||
|
||||
@@ -17,7 +17,14 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
@@ -38,9 +45,34 @@ public final class Artifact {
|
||||
private final String fileName;
|
||||
private final String path;
|
||||
|
||||
public Artifact(String descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
public Artifact(String group, String name, String version) {
|
||||
this(group, name, version, null);
|
||||
}
|
||||
|
||||
public Artifact(String group, String name, String version, String classifier) {
|
||||
this(group, name, version, classifier, null);
|
||||
}
|
||||
|
||||
public Artifact(String group, String name, String version, String classifier, String extension) {
|
||||
this.group = group;
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.classifier = classifier;
|
||||
this.extension = extension == null ? "jar" : extension;
|
||||
|
||||
String fileName = this.name + "-" + this.version;
|
||||
if (classifier != null) fileName += "-" + this.classifier;
|
||||
this.fileName = fileName + "." + this.extension;
|
||||
this.path = String.format("%s/%s/%s/%s", this.group.replace(".", "/"), this.name, this.version, this.fileName);
|
||||
|
||||
// group:name:version:classifier@extension
|
||||
String descriptor = String.format("%s:%s:%s", group, name, version);
|
||||
if (classifier != null) descriptor += ":" + classifier;
|
||||
if (!"jar".equals(this.extension)) descriptor += "@" + this.extension;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
public static Artifact fromDescriptor(String descriptor) {
|
||||
String[] arr = descriptor.split(":", 4);
|
||||
if (arr.length != 3 && arr.length != 4)
|
||||
throw new IllegalArgumentException("Artifact name is malformed");
|
||||
@@ -55,16 +87,7 @@ public final class Artifact {
|
||||
throw new IllegalArgumentException("Artifact name is malformed");
|
||||
}
|
||||
|
||||
this.group = arr[0].replace("\\", "/");
|
||||
this.name = arr[1];
|
||||
this.version = arr[2];
|
||||
this.classifier = arr.length >= 4 ? arr[3] : null;
|
||||
this.extension = ext == null ? "jar" : ext;
|
||||
|
||||
String fileName = this.name + "-" + this.version;
|
||||
if (classifier != null) fileName += "-" + this.classifier;
|
||||
this.fileName = fileName + "." + this.extension;
|
||||
this.path = String.format("%s/%s/%s/%s", this.group.replace(".", "/"), this.name, this.version, this.fileName);
|
||||
return new Artifact(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, ext);
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
@@ -83,6 +106,10 @@ public final class Artifact {
|
||||
return classifier;
|
||||
}
|
||||
|
||||
public Artifact setClassifier(String classifier) {
|
||||
return new Artifact(group, name, version, classifier, extension);
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
return extension;
|
||||
}
|
||||
@@ -91,6 +118,8 @@ public final class Artifact {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getPath() { return path; }
|
||||
|
||||
public Path getPath(Path root) {
|
||||
return root.resolve(path);
|
||||
}
|
||||
@@ -108,7 +137,7 @@ public final class Artifact {
|
||||
|
||||
@Override
|
||||
public Artifact deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return json.isJsonPrimitive() ? new Artifact(json.getAsJsonPrimitive().getAsString()) : null;
|
||||
return json.isJsonPrimitive() ? fromDescriptor(json.getAsJsonPrimitive().getAsString()) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,6 @@ public final class AssetObject implements Validation {
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (StringUtils.isBlank(hash) || hash.length() < 2)
|
||||
throw new IllegalStateException("AssetObject hash cannot be blank.");
|
||||
throw new JsonParseException("AssetObject hash cannot be blank.");
|
||||
}
|
||||
}
|
||||
@@ -32,15 +32,15 @@ public class ClassicVersion extends Version {
|
||||
super(true, "Classic", null, null, "${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, false, null);
|
||||
null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0, false, false, null);
|
||||
}
|
||||
|
||||
private static class ClassicLibrary extends Library {
|
||||
|
||||
public ClassicLibrary(String name) {
|
||||
super("", "", "", null, null,
|
||||
super(new Artifact("", "", ""), null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
|
||||
null, null, null, null);
|
||||
null, null, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,10 @@ public class DefaultGameRepository implements GameRepository {
|
||||
|
||||
@Override
|
||||
public File getLibraryFile(Version version, Library lib) {
|
||||
return new File(getBaseDirectory(), "libraries/" + lib.getPath());
|
||||
if ("local".equals(lib.getHint()))
|
||||
return new File(getVersionRoot(version.getId()), "libraries/" + lib.getFileName());
|
||||
else
|
||||
return new File(getBaseDirectory(), "libraries/" + lib.getPath());
|
||||
}
|
||||
|
||||
public Path getArtifactFile(Version version, Artifact artifact) {
|
||||
@@ -160,7 +163,7 @@ public class DefaultGameRepository implements GameRepository {
|
||||
}
|
||||
|
||||
if (fromVersion.getId().equals(fromVersion.getJar()))
|
||||
fromVersion = fromVersion.setJar(to);
|
||||
fromVersion = fromVersion.setJar(null);
|
||||
FileUtils.writeText(toJson, JsonUtils.GSON.toJson(fromVersion.setId(to)));
|
||||
return true;
|
||||
} catch (IOException | JsonParseException | VersionNotFoundException e) {
|
||||
|
||||
@@ -56,6 +56,10 @@ public interface GameRepository extends VersionProvider {
|
||||
return getVersion(id).resolve(this);
|
||||
}
|
||||
|
||||
default Version getResolvedPreservingPatchesVersion(String id) throws VersionNotFoundException {
|
||||
return getVersion(id).resolvePreservingPatches(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* How many version are there?
|
||||
*/
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -31,6 +31,7 @@ public class LaunchOptions implements Serializable {
|
||||
private File gameDir;
|
||||
private JavaVersion java;
|
||||
private String versionName;
|
||||
private String versionType;
|
||||
private String profileName;
|
||||
private String minecraftArgs;
|
||||
private String javaArgs;
|
||||
@@ -71,6 +72,14 @@ public class LaunchOptions implements Serializable {
|
||||
return versionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will shown in the left bottom corner of the main menu of Minecraft.
|
||||
* null if use Version.versionType.
|
||||
*/
|
||||
public String getVersionType() {
|
||||
return versionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't know what the hell this is.
|
||||
*/
|
||||
@@ -215,6 +224,11 @@ public class LaunchOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setVersionType(String versionType) {
|
||||
options.versionType = versionType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileName(String profileName) {
|
||||
options.profileName = profileName;
|
||||
return this;
|
||||
|
||||
@@ -34,7 +34,7 @@ public final class LibrariesDownloadInfo {
|
||||
private final Map<String, LibraryDownloadInfo> classifiers;
|
||||
|
||||
public LibrariesDownloadInfo(LibraryDownloadInfo artifact) {
|
||||
this(artifact, Collections.emptyMap());
|
||||
this(artifact, null);
|
||||
}
|
||||
|
||||
public LibrariesDownloadInfo(LibraryDownloadInfo artifact, Map<String, LibraryDownloadInfo> classifiers) {
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -36,83 +37,68 @@ import java.util.Optional;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@JsonAdapter(Library.Serializer.class)
|
||||
public class Library implements Comparable<Library> {
|
||||
@Immutable
|
||||
public class Library implements Comparable<Library>, Validation {
|
||||
|
||||
private final String groupId;
|
||||
private final String artifactId;
|
||||
private final String version;
|
||||
private final String classifier;
|
||||
@SerializedName("name")
|
||||
private final Artifact artifact;
|
||||
private final String url;
|
||||
private final LibrariesDownloadInfo downloads;
|
||||
private transient final LibraryDownloadInfo download;
|
||||
private final ExtractRules extract;
|
||||
private final Map<OperatingSystem, String> natives;
|
||||
private final List<CompatibilityRule> rules;
|
||||
private final List<String> checksums;
|
||||
|
||||
private transient final String path;
|
||||
@SerializedName(value = "hint", alternate = {"MMC-hint"})
|
||||
private final String hint;
|
||||
|
||||
public Library(String groupId, String artifactId, String version) {
|
||||
this(groupId, artifactId, version, null, null, null);
|
||||
@SerializedName(value = "filename", alternate = {"MMC-filename"})
|
||||
private final String fileName;
|
||||
|
||||
public Library(Artifact artifact) {
|
||||
this(artifact, null, null);
|
||||
}
|
||||
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads) {
|
||||
this(groupId, artifactId, version, classifier, url, downloads, null, null, null, null);
|
||||
public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads) {
|
||||
this(artifact, url, downloads, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
if (classifier == null)
|
||||
if (natives != null && natives.containsKey(OperatingSystem.CURRENT_OS))
|
||||
this.classifier = natives.get(OperatingSystem.CURRENT_OS).replace("${arch}", Platform.PLATFORM.getBit());
|
||||
else
|
||||
this.classifier = null;
|
||||
else
|
||||
this.classifier = classifier;
|
||||
public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules, String hint, String filename) {
|
||||
this.artifact = artifact;
|
||||
this.url = url;
|
||||
this.downloads = downloads;
|
||||
this.extract = extract;
|
||||
this.natives = natives;
|
||||
this.rules = rules;
|
||||
this.checksums = checksums;
|
||||
|
||||
LibraryDownloadInfo temp = null;
|
||||
if (downloads != null)
|
||||
if (isNative())
|
||||
temp = downloads.getClassifiers().get(this.classifier);
|
||||
else
|
||||
temp = downloads.getArtifact();
|
||||
|
||||
if (temp != null && temp.getPath() != null)
|
||||
path = temp.getPath();
|
||||
else
|
||||
path = String.format("%s/%s/%s/%s-%s", groupId.replace(".", "/"), artifactId, version, artifactId, version)
|
||||
+ (this.classifier == null ? "" : "-" + this.classifier) + ".jar";
|
||||
|
||||
download = new LibraryDownloadInfo(path,
|
||||
Optional.ofNullable(temp).map(LibraryDownloadInfo::getUrl).orElse(Optional.ofNullable(url).orElse(Constants.DEFAULT_LIBRARY_URL) + path),
|
||||
temp != null ? temp.getSha1() : null,
|
||||
temp != null ? temp.getSize() : 0
|
||||
);
|
||||
this.hint = hint;
|
||||
this.fileName = filename;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
return artifact.getGroup();
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
return artifact.getName();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return groupId + ":" + artifactId + ":" + version;
|
||||
return artifact.toString();
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
return artifact.getVersion();
|
||||
}
|
||||
|
||||
public String getClassifier() {
|
||||
if (artifact.getClassifier() == null)
|
||||
if (natives != null && natives.containsKey(OperatingSystem.CURRENT_OS))
|
||||
return natives.get(OperatingSystem.CURRENT_OS).replace("${arch}", Platform.PLATFORM.getBit());
|
||||
else
|
||||
return null;
|
||||
else
|
||||
return artifact.getClassifier();
|
||||
}
|
||||
|
||||
public ExtractRules getExtract() {
|
||||
@@ -127,12 +113,33 @@ public class Library implements Comparable<Library> {
|
||||
return natives != null && appliesToCurrentEnvironment();
|
||||
}
|
||||
|
||||
protected LibraryDownloadInfo getRawDownloadInfo() {
|
||||
if (downloads != null) {
|
||||
if (isNative())
|
||||
return downloads.getClassifiers().get(getClassifier());
|
||||
else
|
||||
return downloads.getArtifact();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
LibraryDownloadInfo temp = getRawDownloadInfo();
|
||||
if (temp != null && temp.getPath() != null)
|
||||
return temp.getPath();
|
||||
else
|
||||
return artifact.setClassifier(getClassifier()).getPath();
|
||||
}
|
||||
|
||||
public LibraryDownloadInfo getDownload() {
|
||||
return download;
|
||||
LibraryDownloadInfo temp = getRawDownloadInfo();
|
||||
String path = getPath();
|
||||
return new LibraryDownloadInfo(path,
|
||||
Optional.ofNullable(temp).map(LibraryDownloadInfo::getUrl).orElse(Optional.ofNullable(url).orElse(Constants.DEFAULT_LIBRARY_URL) + path),
|
||||
temp != null ? temp.getSha1() : null,
|
||||
temp != null ? temp.getSize() : 0
|
||||
);
|
||||
}
|
||||
|
||||
public List<String> getChecksums() {
|
||||
@@ -143,8 +150,24 @@ public class Library implements Comparable<Library> {
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hint for how to locate the library file.
|
||||
* @return null for default, "local" for location in version/<version>/libraries/filename
|
||||
*/
|
||||
public String getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available when hint is "local"
|
||||
* @return the filename of the local library in version/<version>/libraries/$filename
|
||||
*/
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public boolean is(String groupId, String artifactId) {
|
||||
return this.groupId.equals(groupId) && this.artifactId.equals(artifactId);
|
||||
return getGroupId().equals(groupId) && getArtifactId().equals(artifactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -175,58 +198,12 @@ public class Library implements Comparable<Library> {
|
||||
}
|
||||
|
||||
public Library setClassifier(String classifier) {
|
||||
return new Library(groupId, artifactId, version, classifier, url, downloads, checksums, extract, natives, rules);
|
||||
return new Library(artifact.setClassifier(classifier), url, downloads, checksums, extract, natives, rules, hint, fileName);
|
||||
}
|
||||
|
||||
public static Library fromName(String name) {
|
||||
return fromName(name, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public static Library fromName(String name, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
String[] arr = name.split(":", 4);
|
||||
if (arr.length != 3 && arr.length != 4)
|
||||
throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier).");
|
||||
|
||||
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, checksums, extract, natives, rules);
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonDeserializer<Library>, JsonSerializer<Library> {
|
||||
@Override
|
||||
public Library deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json == null || json == JsonNull.INSTANCE)
|
||||
return null;
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
if (!jsonObject.has("name"))
|
||||
throw new JsonParseException("Library name not found.");
|
||||
return fromName(
|
||||
jsonObject.get("name").getAsString(),
|
||||
jsonObject.has("url") ? jsonObject.get("url").getAsString() : null,
|
||||
context.deserialize(jsonObject.get("downloads"), LibrariesDownloadInfo.class),
|
||||
context.deserialize(jsonObject.get("checksums"), new TypeToken<List<String>>() {
|
||||
}.getType()),
|
||||
context.deserialize(jsonObject.get("extract"), ExtractRules.class),
|
||||
context.deserialize(jsonObject.get("natives"), new TypeToken<Map<OperatingSystem, String>>() {
|
||||
}.getType()),
|
||||
context.deserialize(jsonObject.get("rules"), new TypeToken<List<CompatibilityRule>>() {
|
||||
}.getType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Library src, Type type, JsonSerializationContext context) {
|
||||
if (src == null)
|
||||
return JsonNull.INSTANCE;
|
||||
JsonObject obj = new JsonObject();
|
||||
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()));
|
||||
obj.add("rules", context.serialize(src.rules, new TypeToken<List<CompatibilityRule>>() {
|
||||
}.getType()));
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (artifact == null)
|
||||
throw new JsonParseException("Library.name cannot be null");
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,12 @@ import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -66,16 +66,30 @@ public class Version implements Comparable<Version>, Validation {
|
||||
private final Date time;
|
||||
private final Date releaseTime;
|
||||
private final Integer minimumLauncherVersion;
|
||||
private final Boolean root;
|
||||
private final Boolean hidden;
|
||||
private final List<Version> patches;
|
||||
|
||||
private transient final boolean resolved;
|
||||
|
||||
public Version(String id, String version, int priority, Arguments arguments, String mainClass, List<Library> libraries) {
|
||||
this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null);
|
||||
public Version(String id) {
|
||||
this(false, id, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, false, true, null);
|
||||
}
|
||||
|
||||
public Version(boolean resolved, String id, String version, Integer priority, 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, Integer minimumLauncherVersion, Boolean hidden, List<Version> patches) {
|
||||
/**
|
||||
* Constructor for patch
|
||||
* @param id patch id
|
||||
* @param version patch version
|
||||
* @param priority patch priority
|
||||
* @param arguments patch additional arguments
|
||||
* @param mainClass main class to override
|
||||
* @param libraries additional libraries
|
||||
*/
|
||||
public Version(String id, String version, int priority, Arguments arguments, String mainClass, List<Library> libraries) {
|
||||
this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public Version(boolean resolved, String id, String version, Integer priority, 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, Integer minimumLauncherVersion, Boolean hidden, Boolean root, List<Version> patches) {
|
||||
this.resolved = resolved;
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
@@ -87,8 +101,8 @@ public class Version implements Comparable<Version>, Validation {
|
||||
this.jar = jar;
|
||||
this.assetIndex = assetIndex;
|
||||
this.assets = assets;
|
||||
this.libraries = libraries == null ? Collections.emptyList() : new LinkedList<>(libraries);
|
||||
this.compatibilityRules = compatibilityRules == null ? null : new LinkedList<>(compatibilityRules);
|
||||
this.libraries = Lang.copyList(libraries);
|
||||
this.compatibilityRules = Lang.copyList(compatibilityRules);
|
||||
this.downloads = downloads == null ? null : new HashMap<>(downloads);
|
||||
this.logging = logging == null ? null : new HashMap<>(logging);
|
||||
this.type = type;
|
||||
@@ -96,7 +110,8 @@ public class Version implements Comparable<Version>, Validation {
|
||||
this.releaseTime = releaseTime == null ? null : (Date) releaseTime.clone();
|
||||
this.minimumLauncherVersion = minimumLauncherVersion;
|
||||
this.hidden = hidden;
|
||||
this.patches = patches == null ? null : patches.isEmpty() ? null : new LinkedList<>(patches);
|
||||
this.root = root;
|
||||
this.patches = Lang.copyList(patches);
|
||||
}
|
||||
|
||||
public Optional<String> getMinecraftArguments() {
|
||||
@@ -157,10 +172,16 @@ public class Version implements Comparable<Version>, Validation {
|
||||
return hidden == null ? false : hidden;
|
||||
}
|
||||
|
||||
public boolean isRoot() { return root == null ? false : root; }
|
||||
|
||||
public boolean isResolved() {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public boolean isResolvedPreservingPatches() {
|
||||
return inheritsFrom == null && !resolved;
|
||||
}
|
||||
|
||||
public List<Version> getPatches() {
|
||||
return patches == null ? Collections.emptyList() : patches;
|
||||
}
|
||||
@@ -202,7 +223,7 @@ public class Version implements Comparable<Version>, Validation {
|
||||
return resolve(provider, new HashSet<>()).setResolved();
|
||||
}
|
||||
|
||||
protected Version merge(Version parent) {
|
||||
protected Version merge(Version parent, boolean isPatch) {
|
||||
return new Version(
|
||||
true,
|
||||
id,
|
||||
@@ -224,7 +245,8 @@ public class Version implements Comparable<Version>, Validation {
|
||||
releaseTime,
|
||||
Lang.merge(minimumLauncherVersion, parent.minimumLauncherVersion, Math::max),
|
||||
hidden,
|
||||
Lang.merge(parent.patches, patches));
|
||||
false,
|
||||
isPatch ? parent.patches : Lang.merge(Lang.merge(parent.patches, Collections.singleton(toPatch())), patches));
|
||||
}
|
||||
|
||||
protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
|
||||
@@ -239,7 +261,7 @@ public class Version implements Comparable<Version>, Validation {
|
||||
thisVersion = this.jar == null ? this.setJar(id) : this;
|
||||
} else {
|
||||
// It is supposed to auto install an version in getVersion.
|
||||
thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar));
|
||||
thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +271,41 @@ public class Version implements Comparable<Version>, Validation {
|
||||
.sorted(Comparator.comparing(Version::getPriority))
|
||||
.collect(Collectors.toList());
|
||||
for (Version patch : sortedPatches) {
|
||||
thisVersion = patch.setJar(null).merge(thisVersion);
|
||||
thisVersion = patch.setJar(null).merge(thisVersion, true);
|
||||
}
|
||||
}
|
||||
|
||||
return thisVersion.setId(id);
|
||||
}
|
||||
|
||||
private Version toPatch() {
|
||||
return this.clearPatches().setHidden(true).setId("resolved." + getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the version preserving all dependencies and patches.
|
||||
*/
|
||||
public Version resolvePreservingPatches(VersionProvider provider) throws VersionNotFoundException {
|
||||
return resolvePreservingPatches(provider, new HashSet<>());
|
||||
}
|
||||
|
||||
protected Version mergePreservingPatches(Version parent) {
|
||||
return parent.addPatch(toPatch()).addPatches(patches);
|
||||
}
|
||||
|
||||
protected Version resolvePreservingPatches(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
|
||||
Version thisVersion = isRoot() ? this : new Version(id).addPatch(toPatch()).addPatches(getPatches());
|
||||
|
||||
if (inheritsFrom == null) {
|
||||
// keep thisVersion
|
||||
} else {
|
||||
// To maximize the compatibility.
|
||||
if (!resolvedSoFar.add(id)) {
|
||||
Logging.LOG.log(Level.WARNING, "Found circular dependency versions: " + resolvedSoFar);
|
||||
// keep thisVersion
|
||||
} else {
|
||||
// It is supposed to auto install an version in getVersion.
|
||||
thisVersion = mergePreservingPatches(provider.getVersion(inheritsFrom).resolvePreservingPatches(provider, resolvedSoFar));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,58 +313,78 @@ public class Version implements Comparable<Version>, Validation {
|
||||
}
|
||||
|
||||
private Version setResolved() {
|
||||
return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
private Version setHidden(Boolean hidden) {
|
||||
return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setId(String id) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setVersion(String version) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setPriority(Integer priority) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setMinecraftArguments(String minecraftArguments) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setArguments(Arguments arguments) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setMainClass(String mainClass) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setInheritsFrom(String inheritsFrom) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setJar(String jar) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setLibraries(List<Library> libraries) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version setLogging(Map<DownloadType, LoggingInfo> logging) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version addPatch(Version patch) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, Lang.merge(patches, Collections.singleton(patch)));
|
||||
public Version setPatches(List<Version> patches) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
|
||||
}
|
||||
|
||||
public Version addPatch(Version... additional) {
|
||||
return addPatches(Arrays.asList(additional));
|
||||
}
|
||||
|
||||
public Version addPatches(List<Version> additional) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, Lang.merge(patches, additional));
|
||||
}
|
||||
|
||||
public Version clearPatches() {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root,null);
|
||||
}
|
||||
|
||||
public Version removePatchById(String patchId) {
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden,
|
||||
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root,
|
||||
patches == null ? null : patches.stream().filter(patch -> !patchId.equals(patch.getId())).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public boolean hasPatch(String patchId) {
|
||||
return patches != null && patches.stream().anyMatch(patch -> patchId.equals(patch.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
|
||||
@@ -91,7 +91,7 @@ public class World {
|
||||
private void loadFromZipImpl(Path root) throws IOException {
|
||||
Path levelDat = root.resolve("level.dat");
|
||||
if (!Files.exists(levelDat))
|
||||
throw new IllegalArgumentException("Not a valid world zip file since level.dat cannot be found.");
|
||||
throw new IOException("Not a valid world zip file since level.dat cannot be found.");
|
||||
|
||||
getWorldName(levelDat);
|
||||
}
|
||||
@@ -116,6 +116,9 @@ public class World {
|
||||
CompoundTag nbt = parseLevelDat(levelDat);
|
||||
|
||||
CompoundTag data = nbt.get("Data");
|
||||
if (data == null)
|
||||
throw new IOException("level.dat missing Data");
|
||||
|
||||
if (data.get("LevelName") instanceof StringTag)
|
||||
worldName = data.<StringTag>get("LevelName").getValue();
|
||||
else
|
||||
@@ -209,7 +212,7 @@ public class World {
|
||||
return Files.list(savesDir).flatMap(world -> {
|
||||
try {
|
||||
return Stream.of(new World(world));
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read world " + world, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ public class DefaultLauncher extends Launcher {
|
||||
pair("${auth_uuid}", UUIDTypeAdapter.fromUUID(authInfo.getUUID())),
|
||||
pair("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
|
||||
pair("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
|
||||
pair("${version_type}", version.getType().getId()),
|
||||
pair("${version_type}", Optional.ofNullable(options.getVersionType()).orElse(version.getType().getId())),
|
||||
pair("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()),
|
||||
pair("${user_type}", "mojang"),
|
||||
pair("${assets_index_name}", version.getAssetIndex().getId()),
|
||||
|
||||
@@ -72,6 +72,8 @@ public class ModpackUpdateTask extends Task<Void> {
|
||||
repository.removeVersionFromDisk(id);
|
||||
|
||||
FileUtils.copyDirectory(backupFolder, repository.getVersionRoot(id).toPath());
|
||||
|
||||
repository.refreshVersionsAsync().start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -78,9 +79,9 @@ public class MultiMCModpackExportTask extends Task<Void> {
|
||||
return false;
|
||||
});
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedVersion(versionId));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
|
||||
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId))
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + versionId));
|
||||
.orElseThrow(() -> new IOException("Cannot parse the version of " + versionId));
|
||||
List<MultiMCManifest.MultiMCManifestComponent> components = new ArrayList<>();
|
||||
components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, "net.minecraft", gameVersion));
|
||||
analyzer.getVersion(FORGE).ifPresent(forgeVersion ->
|
||||
|
||||
+25
-11
@@ -21,16 +21,14 @@ import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -41,6 +39,7 @@ import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -84,6 +83,12 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
if (c.getVersion() != null)
|
||||
builder.version("liteloader", c.getVersion());
|
||||
});
|
||||
|
||||
Optional<MultiMCManifest.MultiMCManifestComponent> fabric = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("net.fabricmc.fabric-loader")).findAny();
|
||||
fabric.ifPresent(c -> {
|
||||
if (c.getVersion() != null)
|
||||
builder.version("fabric", c.getVersion());
|
||||
});
|
||||
}
|
||||
|
||||
dependents.add(builder.buildAsync());
|
||||
@@ -145,20 +150,29 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
for (Path patchJson : Files.newDirectoryStream(patches)) {
|
||||
if (patchJson.toString().endsWith(".json")) {
|
||||
// If json is malformed, we should stop installing this modpack instead of skipping it.
|
||||
MultiMCInstancePatch patch = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
|
||||
MultiMCInstancePatch multiMCPatch = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
|
||||
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
for (String arg : patch.getTweakers())
|
||||
builder.addArgument("--tweakClass", arg);
|
||||
List<String> arguments = new ArrayList<>();
|
||||
for (String arg : multiMCPatch.getTweakers()) {
|
||||
arguments.add("--tweakClass");
|
||||
arguments.add(arg);
|
||||
}
|
||||
|
||||
version = builder.build()
|
||||
.setLibraries(Lang.merge(version.getLibraries(), patch.getLibraries()))
|
||||
.setMainClass(patch.getMainClass());
|
||||
Version patch = new Version(multiMCPatch.getName(), multiMCPatch.getVersion(), 1, new Arguments().addGameArguments(arguments), multiMCPatch.getMainClass(), multiMCPatch.getLibraries());
|
||||
version = version.addPatch(patch);
|
||||
}
|
||||
}
|
||||
|
||||
Path libraries = root.resolve("libraries");
|
||||
if (Files.exists(libraries))
|
||||
FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries"));
|
||||
|
||||
Path jarmods = root.resolve("jarmods");
|
||||
if (Files.exists(jarmods))
|
||||
FileUtils.copyDirectory(jarmods, repository.getVersionRoot(name).toPath().resolve("jarmods"));
|
||||
}
|
||||
|
||||
dependencies.add(new MaintainTask(version).thenComposeAsync(repository::save));
|
||||
dependencies.add(repository.save(version));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -80,11 +83,10 @@ public class FileDownloadTask extends Task<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
private final URL url;
|
||||
private final List<URL> urls;
|
||||
private final File file;
|
||||
private final IntegrityCheck integrityCheck;
|
||||
private final int retry;
|
||||
private final EventManager<FailedEvent<URL>> onFailed = new EventManager<>();
|
||||
private Path candidate;
|
||||
private boolean caching;
|
||||
private CacheRepository repository = CacheRepository.getInstance();
|
||||
@@ -115,7 +117,7 @@ public class FileDownloadTask extends Task<Void> {
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) {
|
||||
this.url = url;
|
||||
this.urls = Collections.singletonList(url);
|
||||
this.file = file;
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.retry = retry;
|
||||
@@ -124,6 +126,25 @@ public class FileDownloadTask extends Task<Void> {
|
||||
setExecutor(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param urls urls of remote file, will be attempted in order.
|
||||
* @param file the location that download to.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
*/
|
||||
public FileDownloadTask(List<URL> urls, File file, IntegrityCheck integrityCheck) {
|
||||
if (urls == null || urls.isEmpty())
|
||||
throw new IllegalArgumentException("At least one URL is required");
|
||||
|
||||
this.urls = new ArrayList<>(urls);
|
||||
this.file = file;
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.retry = urls.size();
|
||||
|
||||
setName(file.getName());
|
||||
setExecutor(Schedulers.io());
|
||||
}
|
||||
|
||||
private void closeFiles() {
|
||||
if (rFile != null)
|
||||
try {
|
||||
@@ -143,14 +164,6 @@ public class FileDownloadTask extends Task<Void> {
|
||||
stream = null;
|
||||
}
|
||||
|
||||
public EventManager<FailedEvent<URL>> getOnFailed() {
|
||||
return onFailed;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
@@ -172,8 +185,6 @@ public class FileDownloadTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
URL currentURL = url;
|
||||
|
||||
boolean checkETag;
|
||||
// Check cache
|
||||
if (integrityCheck != null && caching) {
|
||||
@@ -182,7 +193,7 @@ public class FileDownloadTask extends Task<Void> {
|
||||
if (cache.isPresent()) {
|
||||
try {
|
||||
FileUtils.copyFile(cache.get().toFile(), file);
|
||||
Logging.LOG.log(Level.FINER, "Successfully verified file " + file + " from " + currentURL);
|
||||
Logging.LOG.log(Level.FINER, "Successfully verified file " + file + " from " + urls.get(0));
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to copy cache files", e);
|
||||
@@ -192,15 +203,11 @@ public class FileDownloadTask extends Task<Void> {
|
||||
checkETag = true;
|
||||
}
|
||||
|
||||
Logging.LOG.log(Level.FINER, "Downloading " + currentURL + " to " + file);
|
||||
Logging.LOG.log(Level.FINER, "Downloading " + urls.get(0) + " to " + file);
|
||||
Exception exception = null;
|
||||
|
||||
for (int repeat = 0; repeat < retry; repeat++) {
|
||||
if (repeat > 0) {
|
||||
FailedEvent<URL> event = new FailedEvent<>(this, repeat, currentURL);
|
||||
onFailed.fireEvent(event);
|
||||
currentURL = event.getNewResult();
|
||||
}
|
||||
URL url = urls.get(repeat % urls.size());
|
||||
if (Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
@@ -226,7 +233,7 @@ public class FileDownloadTask extends Task<Void> {
|
||||
repository.removeRemoteEntry(con);
|
||||
}
|
||||
} else if (con.getResponseCode() / 100 != 2) {
|
||||
throw new ResponseCodeException(currentURL, con.getResponseCode());
|
||||
throw new ResponseCodeException(url, con.getResponseCode());
|
||||
}
|
||||
|
||||
int contentLength = con.getContentLength();
|
||||
@@ -292,7 +299,7 @@ public class FileDownloadTask extends Task<Void> {
|
||||
}
|
||||
|
||||
if (downloaded != contentLength)
|
||||
throw new IllegalStateException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||
throw new IOException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||
|
||||
// Integrity check
|
||||
if (integrityCheck != null) {
|
||||
@@ -312,17 +319,18 @@ public class FileDownloadTask extends Task<Void> {
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
} catch (IOException e) {
|
||||
if (temp != null)
|
||||
temp.toFile().delete();
|
||||
exception = e;
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + repeat + 1, e);
|
||||
} finally {
|
||||
closeFiles();
|
||||
}
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw new DownloadException(currentURL, exception);
|
||||
throw new DownloadException(urls.get(0), exception);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,6 +30,9 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -40,7 +43,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
*/
|
||||
public final class GetTask extends Task<String> {
|
||||
|
||||
private final URL url;
|
||||
private final List<URL> urls;
|
||||
private final Charset charset;
|
||||
private final int retry;
|
||||
private CacheRepository repository = CacheRepository.getInstance();
|
||||
@@ -54,7 +57,7 @@ public final class GetTask extends Task<String> {
|
||||
}
|
||||
|
||||
public GetTask(URL url, Charset charset, int retry) {
|
||||
this.url = url;
|
||||
this.urls = Collections.singletonList(url);
|
||||
this.charset = charset;
|
||||
this.retry = retry;
|
||||
|
||||
@@ -62,6 +65,15 @@ public final class GetTask extends Task<String> {
|
||||
setExecutor(Schedulers.io());
|
||||
}
|
||||
|
||||
public GetTask(List<URL> urls, Charset charset) {
|
||||
this.urls = new ArrayList<>(urls);
|
||||
this.charset = charset;
|
||||
this.retry = urls.size();
|
||||
|
||||
setName(urls.get(0).toString());
|
||||
setExecutor(Schedulers.io());
|
||||
}
|
||||
|
||||
public GetTask setCacheRepository(CacheRepository repository) {
|
||||
this.repository = repository;
|
||||
return this;
|
||||
@@ -72,8 +84,7 @@ public final class GetTask extends Task<String> {
|
||||
Exception exception = null;
|
||||
boolean checkETag = true;
|
||||
for (int time = 0; time < retry; ++time) {
|
||||
if (time > 0)
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download, repeat times: " + time);
|
||||
URL url = urls.get(time % urls.size());
|
||||
try {
|
||||
updateProgress(0);
|
||||
HttpURLConnection conn = NetworkUtils.createConnection(url);
|
||||
@@ -122,10 +133,11 @@ public final class GetTask extends Task<String> {
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
exception = ex;
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + time + 1, ex);
|
||||
}
|
||||
}
|
||||
if (exception != null)
|
||||
throw new DownloadException(url, exception);
|
||||
throw new DownloadException(urls.get(0), exception);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -190,6 +190,7 @@ public abstract class Task<T> {
|
||||
*/
|
||||
public Task<T> storeTo(Consumer<T> action) {
|
||||
this.resultConsumer = action;
|
||||
action.accept(getResult());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
@@ -62,6 +63,15 @@ public final class TaskExecutor {
|
||||
if (!success) {
|
||||
// We log exception stacktrace because some of exceptions occurred because of bugs.
|
||||
Logging.LOG.log(Level.WARNING, "An exception occurred in task execution", exception);
|
||||
|
||||
Throwable resolvedException = resolveException(exception);
|
||||
if (resolvedException instanceof RuntimeException &&
|
||||
!(resolvedException instanceof CancellationException) &&
|
||||
!(resolvedException instanceof JsonParseException)) {
|
||||
// Track uncaught RuntimeException which are thrown mostly by our mistake
|
||||
if (uncaughtExceptionHandler != null)
|
||||
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), resolvedException);
|
||||
}
|
||||
}
|
||||
|
||||
taskListeners.forEach(it -> it.onStop(success, this));
|
||||
@@ -253,4 +263,10 @@ public final class TaskExecutor {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null;
|
||||
|
||||
public static void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
|
||||
TaskExecutor.uncaughtExceptionHandler = uncaughtExceptionHandler;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
|
||||
import java.util.*;
|
||||
@@ -57,6 +58,15 @@ public final class Lang {
|
||||
return Collections.unmodifiableList(Arrays.asList(elements));
|
||||
}
|
||||
|
||||
public static boolean test(ExceptionalRunnable<?> r) {
|
||||
try {
|
||||
r.run();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends Exception> boolean test(ExceptionalSupplier<Boolean, E> r) {
|
||||
try {
|
||||
return r.get();
|
||||
@@ -119,6 +129,10 @@ public final class Lang {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> copyList(List<T> list) {
|
||||
return list == null ? null : list.isEmpty() ? null : new ArrayList<>(list);
|
||||
}
|
||||
|
||||
public static void executeDelayed(Runnable runnable, TimeUnit timeUnit, long timeout, boolean isDaemon) {
|
||||
thread(() -> {
|
||||
try {
|
||||
|
||||
@@ -179,6 +179,8 @@ public final class CompressingUtils {
|
||||
throw new ZipException(error.getMessage());
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
throw new IOException("Not a zip file", ex);
|
||||
} catch (FileSystemNotFoundException ex) {
|
||||
throw new IOException("Java Environment is broken");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ public final class FileUtils {
|
||||
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Path destFile = dest.resolve(src.relativize(file));
|
||||
Path destFile = dest.resolve(src.relativize(file).toString());
|
||||
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
@@ -207,7 +207,7 @@ public final class FileUtils {
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
Path destDir = dest.resolve(src.relativize(dir));
|
||||
Path destDir = dest.resolve(src.relativize(dir).toString());
|
||||
Files.createDirectories(destDir);
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
|
||||
@@ -40,6 +40,8 @@ public final class NetworkUtils {
|
||||
StringBuilder sb = new StringBuilder(baseUrl);
|
||||
boolean first = true;
|
||||
for (Entry<String, String> param : params.entrySet()) {
|
||||
if (param.getValue() == null)
|
||||
continue;
|
||||
if (first) {
|
||||
sb.append('?');
|
||||
first = false;
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -35,8 +34,9 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
/**
|
||||
* Represents a Java installation.
|
||||
@@ -115,11 +115,6 @@ public final class JavaVersion {
|
||||
Platform platform = Platform.BIT_32;
|
||||
String version = null;
|
||||
|
||||
// javaw is only used on windows
|
||||
if ("javaw.exe".equalsIgnoreCase(executable.getFileName().toString())) {
|
||||
executable = executable.resolveSibling("java.exe");
|
||||
}
|
||||
|
||||
executable = executable.toRealPath();
|
||||
|
||||
Process process = new ProcessBuilder(executable.toString(), "-version").start();
|
||||
|
||||
@@ -28,8 +28,10 @@ import java.lang.management.ManagementFactory;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents the operating system.
|
||||
@@ -98,6 +100,10 @@ public enum OperatingSystem {
|
||||
*/
|
||||
public static final String SYSTEM_ARCHITECTURE;
|
||||
|
||||
public static final Pattern INVALID_RESOURCE_CHARACTERS;
|
||||
private static final String[] INVALID_RESOURCE_BASENAMES;
|
||||
private static final String[] INVALID_RESOURCE_FULLNAMES;
|
||||
|
||||
static {
|
||||
String name = System.getProperty("os.name").toLowerCase(Locale.US);
|
||||
if (name.contains("win"))
|
||||
@@ -119,6 +125,24 @@ public enum OperatingSystem {
|
||||
if (arch == null)
|
||||
arch = System.getProperty("os.arch");
|
||||
SYSTEM_ARCHITECTURE = arch;
|
||||
|
||||
// setup the invalid names
|
||||
if (CURRENT_OS == WINDOWS) {
|
||||
// valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
|
||||
INVALID_RESOURCE_CHARACTERS = Pattern.compile("[/\"<>|?*:\\\\]");
|
||||
INVALID_RESOURCE_BASENAMES = new String[]{"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
|
||||
"com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
|
||||
"lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$
|
||||
Arrays.sort(INVALID_RESOURCE_BASENAMES);
|
||||
//CLOCK$ may be used if an extension is provided
|
||||
INVALID_RESOURCE_FULLNAMES = new String[]{"clock$"}; //$NON-NLS-1$
|
||||
} else {
|
||||
//only front slash and null char are invalid on UNIXes
|
||||
//taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html
|
||||
INVALID_RESOURCE_CHARACTERS = null;
|
||||
INVALID_RESOURCE_BASENAMES = null;
|
||||
INVALID_RESOURCE_FULLNAMES = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<Long> getTotalPhysicalMemorySize() {
|
||||
@@ -154,4 +178,38 @@ public enum OperatingSystem {
|
||||
return Paths.get(home, folder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given name is a valid resource name on this operating system,
|
||||
* and false otherwise.
|
||||
*/
|
||||
public static boolean isNameValid(String name) {
|
||||
//. and .. have special meaning on all platforms
|
||||
if (name.equals(".") || name.equals("..") || name.indexOf('/') == 0 || name.indexOf('\0') >= 0) //$NON-NLS-1$ //$NON-NLS-2$
|
||||
return false;
|
||||
if (CURRENT_OS == WINDOWS) {
|
||||
//empty names are not valid
|
||||
final int length = name.length();
|
||||
if (length == 0)
|
||||
return false;
|
||||
final char lastChar = name.charAt(length - 1);
|
||||
// filenames ending in dot are not valid
|
||||
if (lastChar == '.')
|
||||
return false;
|
||||
// file names ending with whitespace are truncated (bug 118997)
|
||||
if (Character.isWhitespace(lastChar))
|
||||
return false;
|
||||
int dot = name.indexOf('.');
|
||||
// on windows, filename suffixes are not relevant to name validity
|
||||
String basename = dot == -1 ? name : name.substring(0, dot);
|
||||
if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0)
|
||||
return false;
|
||||
if (Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) >= 0)
|
||||
return false;
|
||||
if (INVALID_RESOURCE_CHARACTERS != null && INVALID_RESOURCE_CHARACTERS.matcher(name).find())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class SystemUtils {
|
||||
private SystemUtils() {}
|
||||
|
||||
public static int callExternalProcess(String... command) throws IOException, InterruptedException {
|
||||
return callExternalProcess(Arrays.asList(command));
|
||||
}
|
||||
|
||||
public static int callExternalProcess(List<String> command) throws IOException, InterruptedException {
|
||||
Process process = new ProcessBuilder(command).start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
for (String line; (line = reader.readLine()) != null;) {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
return process.waitFor();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
@@ -57,7 +74,6 @@ public class TaskTest {
|
||||
Assert.assertTrue("withRunAsync should be executed", bool.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThenAccept() {
|
||||
new JFXPanel(); // init JavaFX Toolkit
|
||||
AtomicBoolean flag = new AtomicBoolean();
|
||||
@@ -89,7 +105,6 @@ public class TaskTest {
|
||||
Assert.assertNull("CancellationException should not be recorded.", task.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectedExecutionException() {
|
||||
Schedulers.defaultScheduler();
|
||||
Schedulers.shutdown();
|
||||
|
||||
Reference in new issue
Block a user