diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index fd7a0d5e8..3409c8721 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -29,16 +29,16 @@ import java.util.Map; */ public final class HMCLGameLauncher extends DefaultLauncher { - public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { - this(repository, versionId, authInfo, options, null); + public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) { + this(repository, version, authInfo, options, null); } - public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { - this(repository, versionId, authInfo, options, listener, true); + public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { + this(repository, version, authInfo, options, listener, true); } - public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { - super(repository, versionId, authInfo, options, listener, daemon); + public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { + super(repository, version, authInfo, options, listener, daemon); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java index 1fcc1e2f5..db8a70190 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java @@ -20,7 +20,6 @@ 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.game.VersionJsonSaveTask; import org.jackhuang.hmcl.mod.MinecraftInstanceTask; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.ModpackConfiguration; @@ -90,7 +89,7 @@ public final class HMCLModpackInstallTask extends Task { 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(new VersionJsonSaveTask(repository, version)); + dependencies.add(repository.save(version)); dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name))); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index a559552b8..ae3538458 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -26,15 +26,22 @@ 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.*; +import org.jackhuang.hmcl.launch.NotDecompressingNativesException; +import org.jackhuang.hmcl.launch.PermissionException; +import org.jackhuang.hmcl.launch.ProcessCreationException; +import org.jackhuang.hmcl.launch.ProcessListener; import org.jackhuang.hmcl.mod.CurseCompletionException; import org.jackhuang.hmcl.mod.CurseCompletionTask; import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.setting.LauncherVisibility; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.VersionSetting; -import org.jackhuang.hmcl.task.*; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.task.TaskListener; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.DialogController; import org.jackhuang.hmcl.ui.LogWindow; @@ -56,7 +63,13 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.*; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -119,7 +132,7 @@ public final class LauncherHelper { private void launch0() { HMCLGameRepository repository = profile.getRepository(); DefaultDependencyManager dependencyManager = profile.getDependency(); - Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion)); + Version version = MaintainTask.maintain(LibrariesUniqueTask.unique(repository.getResolvedVersion(selectedVersion))); Optional gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)) @@ -159,7 +172,7 @@ public final class LauncherHelper { }) .thenApplyAsync(authInfo -> new HMCLGameLauncher( repository, - selectedVersion, + version.getPatches().isEmpty() ? repository.getResolvedVersion(selectedVersion) : version, authInfo, setting.toLaunchOptions(profile.getGameDir()), launcherVisibility == LauncherVisibility.CLOSE diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java index ce6f974d9..f0bc7b7cb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java @@ -24,7 +24,6 @@ import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.game.LibraryDownloadException; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; -import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.DownloadException; @@ -56,10 +55,10 @@ public final class InstallerWizardProvider implements WizardProvider { this.gameVersion = gameVersion; this.version = version; - LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version); - forge = analyzer.get(FORGE).map(Library::getVersion).orElse(null); - liteLoader = analyzer.get(LITELOADER).map(Library::getVersion).orElse(null); - optiFine = analyzer.get(OPTIFINE).map(Library::getVersion).orElse(null); + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolve(profile.getRepository())); + forge = analyzer.getVersion(FORGE).orElse(null); + liteLoader = analyzer.getVersion(LITELOADER).orElse(null); + optiFine = analyzer.getVersion(OPTIFINE).orElse(null); } public Profile getProfile() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java index ea14cc595..066213a31 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java @@ -19,16 +19,13 @@ package org.jackhuang.hmcl.ui.download; import javafx.scene.Node; import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.RemoteVersion; -import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardProvider; -import java.util.LinkedList; import java.util.Map; import static org.jackhuang.hmcl.ui.download.InstallerWizardProvider.alertFailureMessage; @@ -39,14 +36,14 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { private final String gameVersion; private final Version version; private final String libraryId; - private final Library oldLibrary; + private final String oldLibraryVersion; - public UpdateInstallerWizardProvider(Profile profile, String gameVersion, Version version, String libraryId, Library oldLibrary) { + public UpdateInstallerWizardProvider(Profile profile, String gameVersion, Version version, String libraryId, String oldLibraryVersion) { this.profile = profile; this.gameVersion = gameVersion; this.version = version; this.libraryId = libraryId; - this.oldLibrary = oldLibrary; + this.oldLibraryVersion = oldLibraryVersion; } @Override @@ -60,9 +57,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { // We remove library but not save it, // so if installation failed will not break down current version. - LinkedList newList = new LinkedList<>(version.getLibraries()); - newList.remove(oldLibrary); - return new MaintainTask(version.setLibraries(newList)) + return profile.getDependency().removeLibraryWithoutSavingAsync(version.getId(), libraryId) .thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get(libraryId))) .thenComposeAsync(profile.getRepository().refreshVersionsAsync()); } @@ -73,7 +68,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { switch (step) { case 0: return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> { - Controllers.confirmDialog(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibrary.getVersion(), ((RemoteVersion) settings.get(libraryId)).getSelfVersion()), + Controllers.confirmDialog(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibraryVersion, ((RemoteVersion) settings.get(libraryId)).getSelfVersion()), i18n("install.change_version"), controller::onFinish, controller::onCancel); }); default: diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java index 7330a21f9..9f13a2b5f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java @@ -18,7 +18,10 @@ package org.jackhuang.hmcl.ui.versions; import javafx.application.Platform; -import javafx.beans.property.*; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.scene.control.Control; import javafx.scene.control.Skin; import javafx.scene.image.Image; @@ -26,6 +29,10 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.setting.Profile; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.util.Lang.handleUncaught; import static org.jackhuang.hmcl.util.Lang.threadPool; @@ -33,10 +40,6 @@ import static org.jackhuang.hmcl.util.StringUtils.removePrefix; import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - public class GameItem extends Control { private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool("VersionResolve", true, 1, 1, TimeUnit.SECONDS); @@ -56,9 +59,9 @@ public class GameItem extends Control { .thenAcceptAsync(game -> { StringBuilder libraries = new StringBuilder(game); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id)); - analyzer.get(FORGE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", "")))); - analyzer.get(LITELOADER).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", "")))); - analyzer.get(OPTIFINE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", "")))); + analyzer.getVersion(FORGE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)forge", "")))); + analyzer.getVersion(LITELOADER).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)liteloader", "")))); + analyzer.getVersion(OPTIFINE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)optifine", "")))); subtitle.set(libraries.toString()); }, Platform::runLater) .exceptionally(handleUncaught); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index 7bb6588e7..7731b4fb3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -21,24 +21,25 @@ import javafx.scene.Node; import javafx.scene.control.Skin; import javafx.stage.FileChooser; import org.jackhuang.hmcl.download.LibraryAnalyzer; -import org.jackhuang.hmcl.download.MaintainTask; -import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; import org.jackhuang.hmcl.game.GameVersion; -import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskListener; -import org.jackhuang.hmcl.ui.*; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.InstallerItem; +import org.jackhuang.hmcl.ui.ListPageBase; +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.io.FileUtils; import java.io.File; import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; @@ -68,38 +69,35 @@ public class InstallerListPage extends ListPageBase { public void loadVersion(Profile profile, String versionId) { this.profile = profile; this.versionId = versionId; - this.version = profile.getRepository().getResolvedVersion(versionId); + this.version = profile.getRepository().getVersion(versionId); this.gameVersion = null; Task.supplyAsync(() -> { gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null); - return LibraryAnalyzer.analyze(version); + return LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(versionId)); }).thenAcceptAsync(Schedulers.javafx(), analyzer -> { - Function> removeAction = library -> x -> { - LinkedList newList = new LinkedList<>(version.getLibraries()); - newList.remove(library); - new MaintainTask(version.setLibraries(newList)) - .thenComposeAsync(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion)) + Function> removeAction = libraryId -> x -> { + profile.getDependency().removeLibraryAsync(version.getId(), libraryId) .withComposeAsync(profile.getRepository().refreshVersionsAsync()) .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)) .start(); }; itemsProperty().clear(); - analyzer.get(FORGE).ifPresent(library -> itemsProperty().add( - new InstallerItem("Forge", library.getVersion(), () -> { - Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", library)); - }, removeAction.apply(library)))); - analyzer.get(LITELOADER).ifPresent(library -> itemsProperty().add( - new InstallerItem("LiteLoader", library.getVersion(), () -> { - Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", library)); - }, removeAction.apply(library)))); - analyzer.get(OPTIFINE).ifPresent(library -> itemsProperty().add( - new InstallerItem("OptiFine", library.getVersion(), () -> { - Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", library)); - }, removeAction.apply(library)))); - analyzer.get(FABRIC).ifPresent(library -> itemsProperty().add(new InstallerItem("Fabric", library.getVersion(), null, null))); + analyzer.getVersion(FORGE).ifPresent(libraryVersion -> itemsProperty().add( + new InstallerItem("Forge", libraryVersion, () -> { + Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", libraryVersion)); + }, removeAction.apply("forge")))); + analyzer.getVersion(LITELOADER).ifPresent(libraryVersion -> itemsProperty().add( + new InstallerItem("LiteLoader", libraryVersion, () -> { + Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", libraryVersion)); + }, removeAction.apply("liteloader")))); + analyzer.getVersion(OPTIFINE).ifPresent(libraryVersion -> itemsProperty().add( + new InstallerItem("OptiFine", libraryVersion, () -> { + Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", libraryVersion)); + }, removeAction.apply("optifine")))); + analyzer.getVersion(FABRIC).ifPresent(libraryVersion -> itemsProperty().add(new InstallerItem("Fabric", libraryVersion, null, null))); }).start(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index 14fea233e..a7a4b462c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -19,18 +19,22 @@ package org.jackhuang.hmcl.download; import org.jackhuang.hmcl.download.forge.ForgeInstallTask; import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion; -import org.jackhuang.hmcl.download.game.*; +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.liteloader.LiteLoaderInstallTask; import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; 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. @@ -90,6 +94,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager { @Override public Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) { + if (version.isResolved()) throw new IllegalArgumentException("Version should not be resolved"); + VersionList versionList = getVersionList(libraryId); return versionList.loadAsync(gameVersion, getDownloadProvider()) .thenComposeAsync(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion) @@ -98,6 +104,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager { @Override public Task installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) { + if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved"); + Task task; if (libraryVersion instanceof ForgeRemoteVersion) task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion); @@ -108,9 +116,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager { else throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized."); return task - .thenComposeAsync(LibrariesUniqueTask::new) - .thenComposeAsync(MaintainTask::new) - .thenComposeAsync(newVersion -> new VersionJsonSaveTask(repository, newVersion)); + .thenApplyAsync(oldVersion::addPatch) + .thenComposeAsync(repository::save); } @@ -118,7 +125,9 @@ public class DefaultDependencyManager extends AbstractDependencyManager { return version -> installLibraryAsync(version, libraryVersion); } - public Task installLibraryAsync(Version oldVersion, Path installer) { + public Task installLibraryAsync(Version oldVersion, Path installer) { + if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved"); + return Task .composeAsync(() -> { try { @@ -133,8 +142,54 @@ public class DefaultDependencyManager extends AbstractDependencyManager { throw new UnsupportedOperationException("Library cannot be recognized"); }) - .thenComposeAsync(LibrariesUniqueTask::new) - .thenComposeAsync(MaintainTask::new) - .thenComposeAsync(newVersion -> new VersionJsonSaveTask(repository, newVersion)); + .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 removeLibraryWithoutSavingAsync(String versionId, String libraryId) { + Version version = repository.getVersion(versionId); // to ensure version is not resolved + + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version); + LinkedList newList = new LinkedList<>(version.getLibraries()); + + switch (libraryId) { + case "forge": + analyzer.ifPresent(LibraryAnalyzer.LibraryType.FORGE, (library, libraryVersion) -> newList.remove(library)); + version = version.removePatchById("net.minecraftforge"); + break; + case "liteloader": + analyzer.ifPresent(LibraryAnalyzer.LibraryType.LITELOADER, (library, libraryVersion) -> newList.remove(library)); + version = version.removePatchById("com.mumfrey.liteloader"); + break; + case "optifine": + analyzer.ifPresent(LibraryAnalyzer.LibraryType.OPTIFINE, (library, libraryVersion) -> newList.remove(library)); + version = version.removePatchById("net.optifine"); + break; + case "fabric": + analyzer.ifPresent(LibraryAnalyzer.LibraryType.FABRIC, (library, libraryVersion) -> newList.remove(library)); + version = version.removePatchById("net.fabricmc"); + break; + } + return new MaintainTask(version.setLibraries(newList)); + } + + /** + * 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 removeLibraryAsync(String versionId, String libraryId) { + return removeLibraryWithoutSavingAsync(versionId, libraryId).thenComposeAsync(repository::save); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java index fe183a2b2..6ff7c1a98 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java @@ -17,7 +17,10 @@ */ package org.jackhuang.hmcl.download; -import org.jackhuang.hmcl.download.game.*; +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; @@ -53,7 +56,7 @@ public class DefaultGameBuilder extends GameBuilder { 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(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version))); // using [with] because download failure here are tolerant. + ).withComposeAsync(dependencyManager.getGameRepository().save(version))); // using [with] because download failure here are tolerant. Task libraryTask = vanillaTask.thenSupplyAsync(() -> version); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index ac69feb6a..a610eb51f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -19,22 +19,31 @@ package org.jackhuang.hmcl.download; import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.util.Pair; import java.util.Arrays; import java.util.EnumMap; import java.util.Map; import java.util.Optional; +import java.util.function.BiConsumer; import java.util.regex.Pattern; public final class LibraryAnalyzer { - private final Map libraries; + private final Map> libraries; - private LibraryAnalyzer(Map libraries) { + private LibraryAnalyzer(Map> libraries) { this.libraries = libraries; } - public Optional get(LibraryType type) { - return Optional.ofNullable(libraries.get(type)); + public Optional getVersion(LibraryType type) { + return Optional.ofNullable(libraries.get(type)).map(Pair::getValue); + } + + public void ifPresent(LibraryType type, BiConsumer consumer) { + if (libraries.containsKey(type)) { + Pair value = libraries.get(type); + consumer.accept(value.getKey(), value.getValue()); + } } public boolean has(LibraryType type) { @@ -48,7 +57,7 @@ public final class LibraryAnalyzer { } public static LibraryAnalyzer analyze(Version version) { - Map libraries = new EnumMap<>(LibraryType.class); + Map> libraries = new EnumMap<>(LibraryType.class); for (Library library : version.getLibraries()) { String groupId = library.getGroupId(); @@ -56,7 +65,16 @@ public final class LibraryAnalyzer { for (LibraryType type : LibraryType.values()) { if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) { - libraries.put(type, library); + libraries.put(type, Pair.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; } } @@ -66,16 +84,18 @@ public final class LibraryAnalyzer { } public enum LibraryType { - FORGE(true, Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")), - LITELOADER(true, Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")), - OPTIFINE(false, Pattern.compile("(net\\.)?optifine"), Pattern.compile(".*")), - FABRIC(true, Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")); + FORGE(true, "net.minecraftforge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")), + LITELOADER(true, "com.mumfrey.liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")), + OPTIFINE(false, "net.optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile(".*")), + FABRIC(true, "net.fabricmc", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")); - private final Pattern group, artifact; private final boolean modLoader; + private final String patchId; + private final Pattern group, artifact; - LibraryType(boolean modLoader, Pattern group, Pattern artifact) { + LibraryType(boolean modLoader, String patchId, Pattern group, Pattern artifact) { this.modLoader = modLoader; + this.patchId = patchId; this.group = group; this.artifact = artifact; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index d513779fb..5932240ad 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -79,7 +79,10 @@ public final class ForgeInstallTask extends Task { @Override public void postExecute() throws Exception { Files.deleteIfExists(installer); - setResult(dependency.getResult()); + setResult(dependency.getResult() + .setPriority(10000) + .setId("net.minecraftforge") + .setVersion(remote.getSelfVersion())); } @Override @@ -102,6 +105,7 @@ public final class ForgeInstallTask extends Task { /** * Install Forge library from existing local file. + * This method will try to identify this installer whether it is in old or new format. * * @param dependencyManager game repository * @param version version.json diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index 2fc56ca79..d453d2ca1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -20,7 +20,10 @@ package org.jackhuang.hmcl.download.forge; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; -import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.game.Artifact; +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.StringUtils; import org.jackhuang.hmcl.util.function.ExceptionalFunction; @@ -40,7 +43,14 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.stream.Collectors; @@ -258,15 +268,7 @@ public class ForgeNewInstallTask extends Task { } } - // resolve the version - SimpleVersionProvider provider = new SimpleVersionProvider(); - provider.addVersion(version); - - setResult(forgeVersion - .setInheritsFrom(version.getId()) - .resolve(provider).setJar(null) - .setId(version.getId()).setLogging(Collections.emptyMap())); - + setResult(forgeVersion); dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion)); FileUtils.deleteDirectory(temp.toFile()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java index b5e4921a8..6782bb112 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java @@ -18,15 +18,21 @@ package org.jackhuang.hmcl.download.forge; import org.jackhuang.hmcl.download.DefaultDependencyManager; -import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.IOUtils; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Path; -import java.util.*; +import java.util.LinkedList; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -75,15 +81,7 @@ public class ForgeOldInstallTask extends Task { IOUtils.copyTo(is, os); } - // resolve the version - SimpleVersionProvider provider = new SimpleVersionProvider(); - provider.addVersion(version); - - setResult(installProfile.getVersionInfo() - .setInheritsFrom(version.getId()) - .resolve(provider).setJar(null) - .setId(version.getId()).setLogging(Collections.emptyMap())); - + setResult(installProfile.getVersionInfo()); dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo())); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibrariesUniqueTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibrariesUniqueTask.java index 0ecece4c0..7913f5d16 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibrariesUniqueTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibrariesUniqueTask.java @@ -40,6 +40,10 @@ public class LibrariesUniqueTask extends Task { @Override public void execute() { + setResult(unique(version)); + } + + public static Version unique(Version version) { List libraries = new ArrayList<>(version.getLibraries()); SimpleMultimap multimap = new SimpleMultimap(HashMap::new, LinkedList::new); @@ -91,6 +95,6 @@ public class LibrariesUniqueTask extends Task { } } - setResult(version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList()))); + return version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList())); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java index 33cd7da2a..9fe763b16 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.download.liteloader; import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.game.LibrariesDownloadInfo; import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.LibraryDownloadInfo; @@ -67,16 +68,16 @@ public final class LiteLoaderInstallTask extends Task { new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl())) ); - Version tempVersion = version.setLibraries(Lang.merge(remote.getLibraries(), Collections.singleton(library))); - - // --tweakClass will be added in MaintainTask - setResult(version - .setMainClass("net.minecraft.launchwrapper.Launch") - .setLibraries(Lang.merge(tempVersion.getLibraries(), version.getLibraries())) + setResult(new Version("com.mumfrey.liteloader", + remote.getSelfVersion(), + 20000, + new Arguments().addGameArguments("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker"), + "net.minecraft.launchwrapper.Launch", + Lang.merge(remote.getLibraries(), Collections.singleton(library))) .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(tempVersion)); + dependencies.add(dependencyManager.checkLibraryCompletionAsync(version.setLibraries(getResult().getLibraries()))); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java index b7622bc46..fc318aa73 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java @@ -19,11 +19,17 @@ package org.jackhuang.hmcl.download.optifine; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.VersionMismatchException; -import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.GameVersion; +import org.jackhuang.hmcl.game.LibrariesDownloadInfo; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.LibraryDownloadInfo; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jenkinsci.constant_pool_scanner.ConstantPool; import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner; import org.jenkinsci.constant_pool_scanner.ConstantType; @@ -34,7 +40,12 @@ import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +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 static org.jackhuang.hmcl.util.Lang.getOrDefault; @@ -51,6 +62,9 @@ public final class OptiFineInstallTask extends Task { private final Path installer; private final List> dependents = new LinkedList<>(); private final List> dependencies = new LinkedList<>(); + private final File dest; + + private final Library optiFineLibrary; public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) { this(dependencyManager, version, remoteVersion, null); @@ -61,6 +75,39 @@ public final class OptiFineInstallTask extends Task { 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())) + ); + + dest = dependencyManager.getGameRepository().getLibraryFile(version, optiFineLibrary); + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + if (!Arrays.asList("net.minecraft.client.main.Main", + "net.minecraft.launchwrapper.Launch") + .contains(version.getMainClass())) + throw new UnsupportedOptiFineInstallationException(); + + + if (installer == null) { + dependents.add(new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), dest) + .setCacheRepository(dependencyManager.getCacheRepository()) + .setCaching(true)); + } else { + FileUtils.copyFile(installer, dest.toPath()); + } } @Override @@ -80,35 +127,42 @@ public final class OptiFineInstallTask extends Task { @Override public void execute() throws IOException { - if (!Arrays.asList("net.minecraft.client.main.Main", - "net.minecraft.launchwrapper.Launch") - .contains(version.getMainClass())) - throw new UnsupportedOptiFineInstallationException(); + List libraries = new LinkedList<>(); + libraries.add(optiFineLibrary); - String remoteVersion = remote.getGameVersion() + "_" + remote.getSelfVersion(); + // Install launch wrapper modified by OptiFine + boolean hasLaunchWrapper = false; + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest.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 library = new Library( - "optifine", "OptiFine", remoteVersion, null, null, - new LibrariesDownloadInfo(new LibraryDownloadInfo( - "optifine/OptiFine/" + remoteVersion + "/OptiFine-" + remoteVersion + ".jar", - remote.getUrl())) - ); + Library launchWrapper = new Library("optifine", "launchwrapper-of", launchWrapperVersion); - if (installer != null) { - FileUtils.copyFile(installer, dependencyManager.getGameRepository().getLibraryFile(version, library).toPath()); + if (Files.exists(launchWrapperJar)) { + File launchWrapperFile = dependencyManager.getGameRepository().getLibraryFile(version, launchWrapper); + FileUtils.makeDirectory(launchWrapperFile.getAbsoluteFile().getParentFile()); + FileUtils.copyFile(launchWrapperJar, launchWrapperFile.toPath()); + + hasLaunchWrapper = true; + libraries.add(0, launchWrapper); + } + } } - List libraries = new LinkedList<>(); - libraries.add(library); - - if (version.getMainClass() == null || !version.getMainClass().startsWith("net.minecraft.launchwrapper.")) + if (!hasLaunchWrapper) { libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12")); + } - // --tweakClass will be added in MaintainTask - setResult(version - .setLibraries(Lang.merge(version.getLibraries(), libraries)) - .setMainClass("net.minecraft.launchwrapper.Launch") - ); + setResult(new Version( + "net.optifine", + remote.getSelfVersion(), + 30000, + new Arguments().addGameArguments("--tweakClass", "optifine.OptiFineTweaker"), + "net.minecraft.launchwrapper.Launch", + libraries + )); dependencies.add(dependencyManager.checkLibraryCompletionAsync(version.setLibraries(libraries))); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java index 9220d1193..0273d0c81 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java @@ -21,8 +21,13 @@ import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -46,16 +51,18 @@ public final class Arguments { this.jvm = jvm; } + @Nullable public List getGame() { - return game == null ? Collections.emptyList() : Collections.unmodifiableList(game); + return game == null ? null : Collections.unmodifiableList(game); } public Arguments withGame(List game) { return new Arguments(game, jvm); } + @Nullable public List getJvm() { - return jvm == null ? Collections.emptyList() : Collections.unmodifiableList(jvm); + return jvm == null ? null : Collections.unmodifiableList(jvm); } public Arguments withJvm(List jvm) { @@ -86,7 +93,9 @@ public final class Arguments { else if (b == null) return a; else - return new Arguments(Lang.merge(a.game, b.game), Lang.merge(a.jvm, b.jvm)); + return new Arguments( + a.game == null && b.game == null ? null : Lang.merge(a.game, b.game), + a.jvm == null && b.jvm == null ? null : Lang.merge(a.jvm, b.jvm)); } public static List parseStringArguments(List arguments, Map keys) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java index 4579eab54..b3d07568f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java @@ -29,10 +29,10 @@ import java.util.Date; public class ClassicVersion extends Version { public ClassicVersion() { - super(true, "Classic", "${auth_player_name} ${auth_session} --workDir ${game_directory}", + 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, ReleaseType.UNKNOWN, new Date(), new Date(), 0, false, null); } private static class ClassicLibrary extends Library { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index e9da804d9..6a69195a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -18,8 +18,17 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import org.jackhuang.hmcl.event.*; +import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; +import org.jackhuang.hmcl.event.Event; +import org.jackhuang.hmcl.event.EventBus; +import org.jackhuang.hmcl.event.GameJsonParseFailedEvent; +import org.jackhuang.hmcl.event.LoadedOneVersionEvent; +import org.jackhuang.hmcl.event.RefreshedVersionsEvent; +import org.jackhuang.hmcl.event.RefreshingVersionsEvent; +import org.jackhuang.hmcl.event.RemoveVersionEvent; +import org.jackhuang.hmcl.event.RenameVersionEvent; import org.jackhuang.hmcl.mod.ModManager; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -27,7 +36,13 @@ import org.jackhuang.hmcl.util.io.FileUtils; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; import java.util.logging.Level; import java.util.stream.Stream; @@ -368,6 +383,10 @@ public class DefaultGameRepository implements GameRepository { return assetsDir; } + public Task save(Version version) { + return new VersionJsonSaveTask(this, version); + } + public boolean isLoaded() { return versions != null; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index 9fc25e11a..d975745a8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -18,11 +18,28 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import org.jackhuang.hmcl.util.*; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.gson.Validation; +import org.jetbrains.annotations.Nullable; -import java.util.*; +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; +import java.util.Optional; +import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; /** * @@ -32,6 +49,8 @@ import java.util.logging.Level; public class Version implements Comparable, Validation { private final String id; + private final String version; + private final Integer priority; private final String minecraftArguments; private final Arguments arguments; private final String mainClass; @@ -47,13 +66,20 @@ public class Version implements Comparable, Validation { private final Date time; private final Date releaseTime; private final int minimumLauncherVersion; - private final boolean hidden; + private final Boolean hidden; + private final List patches; private transient final boolean resolved; - public Version(boolean resolved, String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List libraries, List compatibilityRules, Map downloads, Map logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion, boolean hidden) { + public Version(String id, String version, int priority, Arguments arguments, String mainClass, List libraries) { + this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, 0, 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 libraries, List compatibilityRules, Map downloads, Map logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion, Boolean hidden, List patches) { this.resolved = resolved; this.id = id; + this.version = version; + this.priority = priority; this.minecraftArguments = minecraftArguments; this.arguments = arguments; this.mainClass = mainClass; @@ -61,15 +87,16 @@ public class Version implements Comparable, Validation { this.jar = jar; this.assetIndex = assetIndex; this.assets = assets; - this.libraries = libraries == null ? new LinkedList<>() : new LinkedList<>(libraries); + this.libraries = libraries == null ? Collections.emptyList() : new LinkedList<>(libraries); this.compatibilityRules = compatibilityRules == null ? null : new LinkedList<>(compatibilityRules); this.downloads = downloads == null ? null : new HashMap<>(downloads); this.logging = logging == null ? null : new HashMap<>(logging); this.type = type; - this.time = time == null ? new Date() : (Date) time.clone(); - this.releaseTime = releaseTime == null ? new Date() : (Date) releaseTime.clone(); + this.time = time == null ? null : (Date) time.clone(); + 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); } public Optional getMinecraftArguments() { @@ -92,8 +119,22 @@ public class Version implements Comparable, Validation { return id; } + /** + * Version of the patch. + * Exists only when this version object represents a patch. + * Example: 0.5.0.33 for fabric-loader, 28.0.46 for minecraft-forge. + */ + @Nullable + public String getVersion() { + return version; + } + + public int getPriority() { + return priority == null ? Integer.MIN_VALUE : priority; + } + public ReleaseType getType() { - return type; + return type == null ? ReleaseType.UNKNOWN : type; } public Date getReleaseTime() { @@ -113,7 +154,15 @@ public class Version implements Comparable, Validation { } public boolean isHidden() { - return hidden; + return hidden == null ? false : hidden; + } + + public boolean isResolved() { + return resolved; + } + + public List getPatches() { + return patches == null ? Collections.emptyList() : patches; } public Map getLogging() { @@ -153,22 +202,12 @@ public class Version implements Comparable, Validation { return resolve(provider, new HashSet<>()).setResolved(); } - protected Version resolve(VersionProvider provider, Set resolvedSoFar) throws VersionNotFoundException { - if (inheritsFrom == null) { - return this.jar == null ? this.setJar(id) : this; - } - - // To maximize the compatibility. - if (!resolvedSoFar.add(id)) { - Logging.LOG.log(Level.WARNING, "Found circular dependency versions: " + resolvedSoFar); - return this; - } - - // It is supposed to auto install an version in getVersion. - Version parent = provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar); + protected Version merge(Version parent) { return new Version( true, id, + null, + null, minecraftArguments == null ? parent.minecraftArguments : minecraftArguments, Arguments.merge(parent.arguments, arguments), mainClass == null ? parent.mainClass : mainClass, @@ -184,43 +223,90 @@ public class Version implements Comparable, Validation { time, releaseTime, Math.max(minimumLauncherVersion, parent.minimumLauncherVersion), - hidden); + hidden, + Lang.merge(parent.patches, patches)); + } + + protected Version resolve(VersionProvider provider, Set resolvedSoFar) throws VersionNotFoundException { + Version thisVersion; + + if (inheritsFrom == null) { + thisVersion = this.jar == null ? this.setJar(id) : this; + } else { + // To maximize the compatibility. + if (!resolvedSoFar.add(id)) { + Logging.LOG.log(Level.WARNING, "Found circular dependency versions: " + resolvedSoFar); + 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)); + } + } + + if (patches != null && !patches.isEmpty()) { + // Assume patches themselves do not have patches recursively. + List sortedPatches = patches.stream() + .sorted(Comparator.comparing(Version::getPriority)) + .collect(Collectors.toList()); + for (Version patch : sortedPatches) { + thisVersion = patch.setJar(null).merge(thisVersion); + } + } + + return thisVersion.setId(id); } private Version setResolved() { - return new Version(true, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden); + return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches); } public Version setId(String id) { - return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden); + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, 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); + } + + 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); } public Version setMinecraftArguments(String minecraftArguments) { - return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden); + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches); } public Version setArguments(Arguments arguments) { - return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden); + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches); } public Version setMainClass(String mainClass) { - return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden); + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches); } public Version setInheritsFrom(String inheritsFrom) { - return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden); + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches); } public Version setJar(String jar) { - return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden); + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches); } public Version setLibraries(List libraries) { - return new Version(resolved, id, 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, patches); } public Version setLogging(Map logging) { - return new Version(resolved, id, 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, 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 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, + patches == null ? null : patches.stream().filter(patch -> !patchId.equals(patch.getId())).collect(Collectors.toList())); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java index 6fa20de1b..0e51f43fe 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java @@ -21,8 +21,6 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -43,13 +41,13 @@ public final class VersionLibraryBuilder { } public Version build() { + Version ret = version; if (useMcArgs) { // Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping, // so we regenerate the minecraftArgument without escaping. - return version.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString()); - } else { - return version.setArguments(version.getArguments().map(args -> args.withGame(game)).orElse(new Arguments(game, Collections.emptyList()))); + ret = ret.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString()); } + return ret.setArguments(ret.getArguments().map(args -> args.withGame(game)).orElse(new Arguments(game, null))); } public void removeTweakClass(String target) { @@ -63,30 +61,26 @@ public final class VersionLibraryBuilder { --i; } } - } else { - for (int i = 0; i + 1 < game.size(); ++i) { - Argument arg0 = game.get(i); - Argument arg1 = game.get(i + 1); - if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) { - // We need to preserve the tokens - String arg0Str = arg0.toString(); - String arg1Str = arg1.toString(); - if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) { - game.remove(i); - game.remove(i); - --i; - } + } + + for (int i = 0; i + 1 < game.size(); ++i) { + Argument arg0 = game.get(i); + Argument arg1 = game.get(i + 1); + if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) { + // We need to preserve the tokens + String arg0Str = arg0.toString(); + String arg1Str = arg1.toString(); + if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) { + game.remove(i); + game.remove(i); + --i; } } } } public void addArgument(String... args) { - if (useMcArgs) { - mcArgs.addAll(Arrays.asList(args)); - } else { - for (String arg : args) - game.add(new StringArgument(arg)); - } + for (String arg : args) + game.add(new StringArgument(arg)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 3913d94fb..2eba4448e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -18,8 +18,15 @@ package org.jackhuang.hmcl.launch; import org.jackhuang.hmcl.auth.AuthInfo; -import org.jackhuang.hmcl.game.*; -import org.jackhuang.hmcl.util.*; +import org.jackhuang.hmcl.game.Argument; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.game.LaunchOptions; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Log4jLevel; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.Unzipper; @@ -29,9 +36,18 @@ import org.jackhuang.hmcl.util.platform.ManagedProcess; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.Platform; -import java.io.*; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; import java.nio.file.Files; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Supplier; import static org.jackhuang.hmcl.util.Lang.mapOf; @@ -43,16 +59,16 @@ import static org.jackhuang.hmcl.util.Pair.pair; */ public class DefaultLauncher extends Launcher { - public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { - this(repository, versionId, authInfo, options, null); + public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) { + this(repository, version, authInfo, options, null); } - public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { - this(repository, versionId, authInfo, options, listener, true); + public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { + this(repository, version, authInfo, options, listener, true); } - public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { - super(repository, versionId, authInfo, options, listener, daemon); + public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { + super(repository, version, authInfo, options, listener, daemon); } private CommandBuilder generateCommandLine(File nativeFolder) throws IOException { @@ -144,13 +160,13 @@ public class DefaultLauncher extends Launcher { res.add(version.getMainClass()); + res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(LinkedList::new), configuration)); + Map features = getFeatures(); res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getGame).orElseGet(this::getDefaultGameArguments), configuration, features)); if (authInfo.getArguments() != null && authInfo.getArguments().getGame() != null && !authInfo.getArguments().getGame().isEmpty()) res.addAll(Arguments.parseArguments(authInfo.getArguments().getGame(), configuration, features)); - res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(LinkedList::new), configuration)); - if (StringUtils.isNotBlank(options.getServerIp())) { String[] args = options.getServerIp().split(":"); res.add("--server"); @@ -255,7 +271,7 @@ public class DefaultLauncher extends Launcher { @Override public ManagedProcess launch() throws IOException, InterruptedException { - File nativeFolder = repository.getNativeDirectory(versionId); + File nativeFolder = repository.getNativeDirectory(version.getId()); // To guarantee that when failed to generate launch command line, we will not call pre-launch command List rawCommandLine = generateCommandLine(nativeFolder).asList(); @@ -287,7 +303,7 @@ public class DefaultLauncher extends Launcher { public void makeLaunchScript(File scriptFile) throws IOException { boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS; - File nativeFolder = repository.getNativeDirectory(versionId); + File nativeFolder = repository.getNativeDirectory(version.getId()); decompressNatives(nativeFolder); if (isWindows && !FileUtils.getExtension(scriptFile).equals("bat")) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java index 41aa91a13..b96a316b0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java @@ -33,30 +33,27 @@ import java.io.IOException; public abstract class Launcher { protected final GameRepository repository; - protected final String versionId; protected final Version version; protected final AuthInfo authInfo; protected final LaunchOptions options; protected final ProcessListener listener; protected final boolean daemon; - public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { - this(repository, versionId, authInfo, options, null); + public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) { + this(repository, version, authInfo, options, null); } - public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { - this(repository, versionId, authInfo, options, listener, true); + public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { + this(repository, version, authInfo, options, listener, true); } - public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { + public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { this.repository = repository; - this.versionId = versionId; + this.version = version; this.authInfo = authInfo; this.options = options; this.listener = listener; this.daemon = daemon; - - version = repository.getResolvedVersion(versionId); } /** diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java index 127bb3eb9..7e50fc1ff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java @@ -22,7 +22,6 @@ 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.download.game.VersionJsonSaveTask; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.VersionLibraryBuilder; @@ -155,7 +154,7 @@ public final class MultiMCModpackInstallTask extends Task { } } - dependencies.add(new MaintainTask(version).thenComposeAsync(maintainedVersion -> new VersionJsonSaveTask(repository, maintainedVersion))); + dependencies.add(new MaintainTask(version).thenComposeAsync(repository::save)); dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name))); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/CommandBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/CommandBuilder.java index a31f5d4bf..d26460f9d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/CommandBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/CommandBuilder.java @@ -17,14 +17,14 @@ */ package org.jackhuang.hmcl.util.platform; +import org.jackhuang.hmcl.util.StringUtils; + import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; -import org.jackhuang.hmcl.util.StringUtils; - public final class CommandBuilder { private final OperatingSystem os; private List raw = new LinkedList<>(); @@ -39,9 +39,9 @@ public final class CommandBuilder { private String parse(String s) { if (OperatingSystem.WINDOWS == os) { - return parseWindows(s); + return parseBatch(s); } else { - return parseBash(s); + return parseShell(s); } } @@ -96,9 +96,14 @@ public final class CommandBuilder { this.arg = arg; this.parse = parse; } + + @Override + public String toString() { + return parse ? (OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS ? parseBatch(arg) : parseShell(arg)) : arg; + } } - private static String parseWindows(String s) { + private static String parseBatch(String s) { String escape = " \t\"^&<>|"; if (StringUtils.containsOne(s, escape.toCharArray())) // The argument has not been quoted, add quotes. @@ -111,7 +116,7 @@ public final class CommandBuilder { } } - private static String parseBash(String s) { + private static String parseShell(String s) { String escaping = " \t\"!#$&'()*,;<=>?[\\]^`{|}~"; String escaped = "\"$&`"; if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0 || StringUtils.containsOne(s, escaping.toCharArray())) {