Make auto-installing more flexible

This commit is contained in:
huanghongxun
2019-08-16 00:18:52 +08:00
parent 51aa0dd851
commit b11acb7762
25 changed files with 508 additions and 235 deletions

View File

@@ -29,16 +29,16 @@ import java.util.Map;
*/ */
public final class HMCLGameLauncher extends DefaultLauncher { public final class HMCLGameLauncher extends DefaultLauncher {
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
this(repository, versionId, authInfo, options, null); this(repository, version, authInfo, options, null);
} }
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
this(repository, versionId, authInfo, options, listener, true); this(repository, version, authInfo, options, listener, true);
} }
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
super(repository, versionId, authInfo, options, listener, daemon); super(repository, version, authInfo, options, listener, daemon);
} }
@Override @Override

View File

@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DependencyManager; import org.jackhuang.hmcl.download.DependencyManager;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.mod.MinecraftInstanceTask; import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackConfiguration;
@@ -90,7 +89,7 @@ public final class HMCLModpackInstallTask extends Task<Void> {
public void execute() throws Exception { public void execute() throws Exception {
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json"); String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
Version version = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null); 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))); dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
} }

View File

@@ -26,15 +26,22 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CredentialExpiredException; import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.LibrariesUniqueTask;
import org.jackhuang.hmcl.download.game.LibraryDownloadException; 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.CurseCompletionException;
import org.jackhuang.hmcl.mod.CurseCompletionTask; import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.setting.LauncherVisibility; import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting; 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.Controllers;
import org.jackhuang.hmcl.ui.DialogController; import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.LogWindow; import org.jackhuang.hmcl.ui.LogWindow;
@@ -56,7 +63,13 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; 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.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -119,7 +132,7 @@ public final class LauncherHelper {
private void launch0() { private void launch0() {
HMCLGameRepository repository = profile.getRepository(); HMCLGameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency(); DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion)); Version version = MaintainTask.maintain(LibrariesUniqueTask.unique(repository.getResolvedVersion(selectedVersion)));
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)) TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
@@ -159,7 +172,7 @@ public final class LauncherHelper {
}) })
.thenApplyAsync(authInfo -> new HMCLGameLauncher( .thenApplyAsync(authInfo -> new HMCLGameLauncher(
repository, repository,
selectedVersion, version.getPatches().isEmpty() ? repository.getResolvedVersion(selectedVersion) : version,
authInfo, authInfo,
setting.toLaunchOptions(profile.getGameDir()), setting.toLaunchOptions(profile.getGameDir()),
launcherVisibility == LauncherVisibility.CLOSE launcherVisibility == LauncherVisibility.CLOSE

View File

@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.download.game.LibraryDownloadException; import org.jackhuang.hmcl.download.game.LibraryDownloadException;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.DownloadException; import org.jackhuang.hmcl.task.DownloadException;
@@ -56,10 +55,10 @@ public final class InstallerWizardProvider implements WizardProvider {
this.gameVersion = gameVersion; this.gameVersion = gameVersion;
this.version = version; this.version = version;
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolve(profile.getRepository()));
forge = analyzer.get(FORGE).map(Library::getVersion).orElse(null); forge = analyzer.getVersion(FORGE).orElse(null);
liteLoader = analyzer.get(LITELOADER).map(Library::getVersion).orElse(null); liteLoader = analyzer.getVersion(LITELOADER).orElse(null);
optiFine = analyzer.get(OPTIFINE).map(Library::getVersion).orElse(null); optiFine = analyzer.getVersion(OPTIFINE).orElse(null);
} }
public Profile getProfile() { public Profile getProfile() {

View File

@@ -19,16 +19,13 @@ package org.jackhuang.hmcl.ui.download;
import javafx.scene.Node; import javafx.scene.Node;
import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import static org.jackhuang.hmcl.ui.download.InstallerWizardProvider.alertFailureMessage; 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 String gameVersion;
private final Version version; private final Version version;
private final String libraryId; 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.profile = profile;
this.gameVersion = gameVersion; this.gameVersion = gameVersion;
this.version = version; this.version = version;
this.libraryId = libraryId; this.libraryId = libraryId;
this.oldLibrary = oldLibrary; this.oldLibraryVersion = oldLibraryVersion;
} }
@Override @Override
@@ -60,9 +57,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
// We remove library but not save it, // We remove library but not save it,
// so if installation failed will not break down current version. // so if installation failed will not break down current version.
LinkedList<Library> newList = new LinkedList<>(version.getLibraries()); return profile.getDependency().removeLibraryWithoutSavingAsync(version.getId(), libraryId)
newList.remove(oldLibrary);
return new MaintainTask(version.setLibraries(newList))
.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get(libraryId))) .thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get(libraryId)))
.thenComposeAsync(profile.getRepository().refreshVersionsAsync()); .thenComposeAsync(profile.getRepository().refreshVersionsAsync());
} }
@@ -73,7 +68,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
switch (step) { switch (step) {
case 0: case 0:
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> { 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); i18n("install.change_version"), controller::onFinish, controller::onCancel);
}); });
default: default:

View File

@@ -18,7 +18,10 @@
package org.jackhuang.hmcl.ui.versions; package org.jackhuang.hmcl.ui.versions;
import javafx.application.Platform; 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.Control;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
import javafx.scene.image.Image; 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.game.GameVersion;
import org.jackhuang.hmcl.setting.Profile; 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.download.LibraryAnalyzer.LibraryType.*;
import static org.jackhuang.hmcl.util.Lang.handleUncaught; import static org.jackhuang.hmcl.util.Lang.handleUncaught;
import static org.jackhuang.hmcl.util.Lang.threadPool; 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.StringUtils.removeSuffix;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; 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 { public class GameItem extends Control {
private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool("VersionResolve", true, 1, 1, TimeUnit.SECONDS); 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 -> { .thenAcceptAsync(game -> {
StringBuilder libraries = new StringBuilder(game); StringBuilder libraries = new StringBuilder(game);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id)); 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.getVersion(FORGE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.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.getVersion(LITELOADER).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.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(OPTIFINE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)optifine", ""))));
subtitle.set(libraries.toString()); subtitle.set(libraries.toString());
}, Platform::runLater) }, Platform::runLater)
.exceptionally(handleUncaught); .exceptionally(handleUncaught);

View File

@@ -21,24 +21,25 @@ import javafx.scene.Node;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jackhuang.hmcl.download.LibraryAnalyzer; 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.GameVersion;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener; 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.InstallerWizardProvider;
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider; import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File; import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@@ -68,38 +69,35 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
public void loadVersion(Profile profile, String versionId) { public void loadVersion(Profile profile, String versionId) {
this.profile = profile; this.profile = profile;
this.versionId = versionId; this.versionId = versionId;
this.version = profile.getRepository().getResolvedVersion(versionId); this.version = profile.getRepository().getVersion(versionId);
this.gameVersion = null; this.gameVersion = null;
Task.supplyAsync(() -> { Task.supplyAsync(() -> {
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null); gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
return LibraryAnalyzer.analyze(version); return LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(versionId));
}).thenAcceptAsync(Schedulers.javafx(), analyzer -> { }).thenAcceptAsync(Schedulers.javafx(), analyzer -> {
Function<Library, Consumer<InstallerItem>> removeAction = library -> x -> { Function<String, Consumer<InstallerItem>> removeAction = libraryId -> x -> {
LinkedList<Library> newList = new LinkedList<>(version.getLibraries()); profile.getDependency().removeLibraryAsync(version.getId(), libraryId)
newList.remove(library);
new MaintainTask(version.setLibraries(newList))
.thenComposeAsync(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
.withComposeAsync(profile.getRepository().refreshVersionsAsync()) .withComposeAsync(profile.getRepository().refreshVersionsAsync())
.withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)) .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
.start(); .start();
}; };
itemsProperty().clear(); itemsProperty().clear();
analyzer.get(FORGE).ifPresent(library -> itemsProperty().add( analyzer.getVersion(FORGE).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem("Forge", library.getVersion(), () -> { new InstallerItem("Forge", libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", library)); Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", libraryVersion));
}, removeAction.apply(library)))); }, removeAction.apply("forge"))));
analyzer.get(LITELOADER).ifPresent(library -> itemsProperty().add( analyzer.getVersion(LITELOADER).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem("LiteLoader", library.getVersion(), () -> { new InstallerItem("LiteLoader", libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", library)); Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", libraryVersion));
}, removeAction.apply(library)))); }, removeAction.apply("liteloader"))));
analyzer.get(OPTIFINE).ifPresent(library -> itemsProperty().add( analyzer.getVersion(OPTIFINE).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem("OptiFine", library.getVersion(), () -> { new InstallerItem("OptiFine", libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", library)); Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", libraryVersion));
}, removeAction.apply(library)))); }, removeAction.apply("optifine"))));
analyzer.get(FABRIC).ifPresent(library -> itemsProperty().add(new InstallerItem("Fabric", library.getVersion(), null, null))); analyzer.getVersion(FABRIC).ifPresent(libraryVersion -> itemsProperty().add(new InstallerItem("Fabric", libraryVersion, null, null)));
}).start(); }).start();
} }

View File

@@ -19,18 +19,22 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask; import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion; 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.LiteLoaderInstallTask;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion; import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.LinkedList;
/** /**
* Note: This class has no state. * Note: This class has no state.
@@ -90,6 +94,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
@Override @Override
public Task<Version> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) { public Task<Version> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
if (version.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
VersionList<?> versionList = getVersionList(libraryId); VersionList<?> versionList = getVersionList(libraryId);
return versionList.loadAsync(gameVersion, getDownloadProvider()) return versionList.loadAsync(gameVersion, getDownloadProvider())
.thenComposeAsync(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion) .thenComposeAsync(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
@@ -98,6 +104,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
@Override @Override
public Task<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) { public Task<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) {
if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
Task<Version> task; Task<Version> task;
if (libraryVersion instanceof ForgeRemoteVersion) if (libraryVersion instanceof ForgeRemoteVersion)
task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion); task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion);
@@ -108,9 +116,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
else else
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized."); throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
return task return task
.thenComposeAsync(LibrariesUniqueTask::new) .thenApplyAsync(oldVersion::addPatch)
.thenComposeAsync(MaintainTask::new) .thenComposeAsync(repository::save);
.thenComposeAsync(newVersion -> new VersionJsonSaveTask(repository, newVersion));
} }
@@ -118,7 +125,9 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
return version -> installLibraryAsync(version, libraryVersion); return version -> installLibraryAsync(version, libraryVersion);
} }
public Task installLibraryAsync(Version oldVersion, Path installer) { public Task<Version> installLibraryAsync(Version oldVersion, Path installer) {
if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
return Task return Task
.composeAsync(() -> { .composeAsync(() -> {
try { try {
@@ -133,8 +142,54 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
throw new UnsupportedOperationException("Library cannot be recognized"); throw new UnsupportedOperationException("Library cannot be recognized");
}) })
.thenComposeAsync(LibrariesUniqueTask::new) .thenApplyAsync(oldVersion::addPatch)
.thenComposeAsync(MaintainTask::new) .thenComposeAsync(repository::save);
.thenComposeAsync(newVersion -> new VersionJsonSaveTask(repository, newVersion)); }
/**
* 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("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<Version> removeLibraryAsync(String versionId, String libraryId) {
return removeLibraryWithoutSavingAsync(versionId, libraryId).thenComposeAsync(repository::save);
} }
} }

View File

@@ -17,7 +17,10 @@
*/ */
package org.jackhuang.hmcl.download; 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.game.Version;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
@@ -53,7 +56,7 @@ public class DefaultGameBuilder extends GameBuilder {
Task<?> vanillaTask = downloadGameAsync(gameVersion, version).thenComposeAsync(Task.allOf( Task<?> vanillaTask = downloadGameAsync(gameVersion, version).thenComposeAsync(Task.allOf(
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY), 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. 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<Version> libraryTask = vanillaTask.thenSupplyAsync(() -> version); Task<Version> libraryTask = vanillaTask.thenSupplyAsync(() -> version);

View File

@@ -19,22 +19,31 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.util.Pair;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public final class LibraryAnalyzer { public final class LibraryAnalyzer {
private final Map<LibraryType, Library> libraries; private final Map<LibraryType, Pair<Library, String>> libraries;
private LibraryAnalyzer(Map<LibraryType, Library> libraries) { private LibraryAnalyzer(Map<LibraryType, Pair<Library, String>> libraries) {
this.libraries = libraries; this.libraries = libraries;
} }
public Optional<Library> get(LibraryType type) { public Optional<String> getVersion(LibraryType type) {
return Optional.ofNullable(libraries.get(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());
}
} }
public boolean has(LibraryType type) { public boolean has(LibraryType type) {
@@ -48,7 +57,7 @@ public final class LibraryAnalyzer {
} }
public static LibraryAnalyzer analyze(Version version) { public static LibraryAnalyzer analyze(Version version) {
Map<LibraryType, Library> libraries = new EnumMap<>(LibraryType.class); Map<LibraryType, Pair<Library, String>> libraries = new EnumMap<>(LibraryType.class);
for (Library library : version.getLibraries()) { for (Library library : version.getLibraries()) {
String groupId = library.getGroupId(); String groupId = library.getGroupId();
@@ -56,7 +65,16 @@ public final class LibraryAnalyzer {
for (LibraryType type : LibraryType.values()) { for (LibraryType type : LibraryType.values()) {
if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) { 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; break;
} }
} }
@@ -66,16 +84,18 @@ public final class LibraryAnalyzer {
} }
public enum LibraryType { public enum LibraryType {
FORGE(true, Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")), FORGE(true, "net.minecraftforge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
LITELOADER(true, Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")), LITELOADER(true, "com.mumfrey.liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
OPTIFINE(false, Pattern.compile("(net\\.)?optifine"), Pattern.compile(".*")), OPTIFINE(false, "net.optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile(".*")),
FABRIC(true, Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")); FABRIC(true, "net.fabricmc", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader"));
private final Pattern group, artifact;
private final boolean modLoader; 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.modLoader = modLoader;
this.patchId = patchId;
this.group = group; this.group = group;
this.artifact = artifact; this.artifact = artifact;
} }

View File

@@ -79,7 +79,10 @@ public final class ForgeInstallTask extends Task<Version> {
@Override @Override
public void postExecute() throws Exception { public void postExecute() throws Exception {
Files.deleteIfExists(installer); Files.deleteIfExists(installer);
setResult(dependency.getResult()); setResult(dependency.getResult()
.setPriority(10000)
.setId("net.minecraftforge")
.setVersion(remote.getSelfVersion()));
} }
@Override @Override
@@ -102,6 +105,7 @@ public final class ForgeInstallTask extends Task<Version> {
/** /**
* Install Forge library from existing local file. * 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 dependencyManager game repository
* @param version version.json * @param version version.json

View File

@@ -20,7 +20,10 @@ package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; 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.task.Task;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; 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.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; 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.Attributes;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -258,15 +268,7 @@ public class ForgeNewInstallTask extends Task<Version> {
} }
} }
// resolve the version setResult(forgeVersion);
SimpleVersionProvider provider = new SimpleVersionProvider();
provider.addVersion(version);
setResult(forgeVersion
.setInheritsFrom(version.getId())
.resolve(provider).setJar(null)
.setId(version.getId()).setLogging(Collections.emptyMap()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion)); dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion));
FileUtils.deleteDirectory(temp.toFile()); FileUtils.deleteDirectory(temp.toFile());

View File

@@ -18,15 +18,21 @@
package org.jackhuang.hmcl.download.forge; package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager; 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.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils; 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.nio.file.Path;
import java.util.*; import java.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@@ -75,15 +81,7 @@ public class ForgeOldInstallTask extends Task<Version> {
IOUtils.copyTo(is, os); IOUtils.copyTo(is, os);
} }
// resolve the version setResult(installProfile.getVersionInfo());
SimpleVersionProvider provider = new SimpleVersionProvider();
provider.addVersion(version);
setResult(installProfile.getVersionInfo()
.setInheritsFrom(version.getId())
.resolve(provider).setJar(null)
.setId(version.getId()).setLogging(Collections.emptyMap()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo())); dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo()));
} }
} }

View File

@@ -40,6 +40,10 @@ public class LibrariesUniqueTask extends Task<Version> {
@Override @Override
public void execute() { public void execute() {
setResult(unique(version));
}
public static Version unique(Version version) {
List<Library> libraries = new ArrayList<>(version.getLibraries()); List<Library> libraries = new ArrayList<>(version.getLibraries());
SimpleMultimap<String, Library> multimap = new SimpleMultimap<String, Library>(HashMap::new, LinkedList::new); SimpleMultimap<String, Library> multimap = new SimpleMultimap<String, Library>(HashMap::new, LinkedList::new);
@@ -91,6 +95,6 @@ public class LibrariesUniqueTask extends Task<Version> {
} }
} }
setResult(version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList()))); return version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList()));
} }
} }

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download.liteloader; package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo; import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo; import org.jackhuang.hmcl.game.LibraryDownloadInfo;
@@ -67,16 +68,16 @@ public final class LiteLoaderInstallTask extends Task<Version> {
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl())) new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
); );
Version tempVersion = version.setLibraries(Lang.merge(remote.getLibraries(), Collections.singleton(library))); setResult(new Version("com.mumfrey.liteloader",
remote.getSelfVersion(),
// --tweakClass will be added in MaintainTask 20000,
setResult(version new Arguments().addGameArguments("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker"),
.setMainClass("net.minecraft.launchwrapper.Launch") "net.minecraft.launchwrapper.Launch",
.setLibraries(Lang.merge(tempVersion.getLibraries(), version.getLibraries())) 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 .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())));
} }
} }

View File

@@ -19,11 +19,17 @@ package org.jackhuang.hmcl.download.optifine;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.VersionMismatchException; 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.task.Task;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils; 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.ConstantPool;
import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner; import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
import org.jenkinsci.constant_pool_scanner.ConstantType; import org.jenkinsci.constant_pool_scanner.ConstantType;
@@ -34,7 +40,12 @@ import java.io.IOException;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; 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; import static org.jackhuang.hmcl.util.Lang.getOrDefault;
@@ -51,6 +62,9 @@ public final class OptiFineInstallTask extends Task<Version> {
private final Path installer; private final Path installer;
private final List<Task<?>> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
private final File dest;
private final Library optiFineLibrary;
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) { public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {
this(dependencyManager, version, remoteVersion, null); this(dependencyManager, version, remoteVersion, null);
@@ -61,6 +75,39 @@ public final class OptiFineInstallTask extends Task<Version> {
this.version = version; this.version = version;
this.remote = remoteVersion; this.remote = remoteVersion;
this.installer = installer; 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 @Override
@@ -80,35 +127,42 @@ public final class OptiFineInstallTask extends Task<Version> {
@Override @Override
public void execute() throws IOException { public void execute() throws IOException {
if (!Arrays.asList("net.minecraft.client.main.Main", List<Library> libraries = new LinkedList<>();
"net.minecraft.launchwrapper.Launch") libraries.add(optiFineLibrary);
.contains(version.getMainClass()))
throw new UnsupportedOptiFineInstallationException();
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( Library launchWrapper = new Library("optifine", "launchwrapper-of", launchWrapperVersion);
"optifine", "OptiFine", remoteVersion, null, null,
new LibrariesDownloadInfo(new LibraryDownloadInfo(
"optifine/OptiFine/" + remoteVersion + "/OptiFine-" + remoteVersion + ".jar",
remote.getUrl()))
);
if (installer != null) { if (Files.exists(launchWrapperJar)) {
FileUtils.copyFile(installer, dependencyManager.getGameRepository().getLibraryFile(version, library).toPath()); File launchWrapperFile = dependencyManager.getGameRepository().getLibraryFile(version, launchWrapper);
FileUtils.makeDirectory(launchWrapperFile.getAbsoluteFile().getParentFile());
FileUtils.copyFile(launchWrapperJar, launchWrapperFile.toPath());
hasLaunchWrapper = true;
libraries.add(0, launchWrapper);
}
}
} }
List<Library> libraries = new LinkedList<>(); if (!hasLaunchWrapper) {
libraries.add(library);
if (version.getMainClass() == null || !version.getMainClass().startsWith("net.minecraft.launchwrapper."))
libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12")); libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12"));
}
// --tweakClass will be added in MaintainTask setResult(new Version(
setResult(version "net.optifine",
.setLibraries(Lang.merge(version.getLibraries(), libraries)) remote.getSelfVersion(),
.setMainClass("net.minecraft.launchwrapper.Launch") 30000,
); new Arguments().addGameArguments("--tweakClass", "optifine.OptiFineTweaker"),
"net.minecraft.launchwrapper.Launch",
libraries
));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(version.setLibraries(libraries))); dependencies.add(dependencyManager.checkLibraryCompletionAsync(version.setLibraries(libraries)));
} }

View File

@@ -21,8 +21,13 @@ import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.platform.OperatingSystem; 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; import java.util.stream.Collectors;
/** /**
@@ -46,16 +51,18 @@ public final class Arguments {
this.jvm = jvm; this.jvm = jvm;
} }
@Nullable
public List<Argument> getGame() { public List<Argument> getGame() {
return game == null ? Collections.emptyList() : Collections.unmodifiableList(game); return game == null ? null : Collections.unmodifiableList(game);
} }
public Arguments withGame(List<Argument> game) { public Arguments withGame(List<Argument> game) {
return new Arguments(game, jvm); return new Arguments(game, jvm);
} }
@Nullable
public List<Argument> getJvm() { public List<Argument> getJvm() {
return jvm == null ? Collections.emptyList() : Collections.unmodifiableList(jvm); return jvm == null ? null : Collections.unmodifiableList(jvm);
} }
public Arguments withJvm(List<Argument> jvm) { public Arguments withJvm(List<Argument> jvm) {
@@ -86,7 +93,9 @@ public final class Arguments {
else if (b == null) else if (b == null)
return a; return a;
else 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<String> parseStringArguments(List<String> arguments, Map<String, String> keys) { public static List<String> parseStringArguments(List<String> arguments, Map<String, String> keys) {

View File

@@ -29,10 +29,10 @@ import java.util.Date;
public class ClassicVersion extends Version { public class ClassicVersion extends Version {
public ClassicVersion() { 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, null, "net.minecraft.client.Minecraft", null, null, null, null,
Arrays.asList(new ClassicLibrary("lwjgl"), new ClassicLibrary("jinput"), new ClassicLibrary("lwjgl_util")), 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 { private static class ClassicLibrary extends Library {

View File

@@ -18,8 +18,17 @@
package org.jackhuang.hmcl.game; package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException; 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.mod.ModManager;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils; 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.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; 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.logging.Level;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -368,6 +383,10 @@ public class DefaultGameRepository implements GameRepository {
return assetsDir; return assetsDir;
} }
public Task<Version> save(Version version) {
return new VersionJsonSaveTask(this, version);
}
public boolean isLoaded() { public boolean isLoaded() {
return versions != null; return versions != null;
} }

View File

@@ -18,11 +18,28 @@
package org.jackhuang.hmcl.game; package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException; 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.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.logging.Level;
import java.util.stream.Collectors;
/** /**
* *
@@ -32,6 +49,8 @@ import java.util.logging.Level;
public class Version implements Comparable<Version>, Validation { public class Version implements Comparable<Version>, Validation {
private final String id; private final String id;
private final String version;
private final Integer priority;
private final String minecraftArguments; private final String minecraftArguments;
private final Arguments arguments; private final Arguments arguments;
private final String mainClass; private final String mainClass;
@@ -47,13 +66,20 @@ public class Version implements Comparable<Version>, Validation {
private final Date time; private final Date time;
private final Date releaseTime; private final Date releaseTime;
private final int minimumLauncherVersion; private final int minimumLauncherVersion;
private final boolean hidden; private final Boolean hidden;
private final List<Version> patches;
private transient final boolean resolved; private transient final boolean resolved;
public Version(boolean resolved, String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion, boolean hidden) { 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, 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<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion, Boolean hidden, List<Version> patches) {
this.resolved = resolved; this.resolved = resolved;
this.id = id; this.id = id;
this.version = version;
this.priority = priority;
this.minecraftArguments = minecraftArguments; this.minecraftArguments = minecraftArguments;
this.arguments = arguments; this.arguments = arguments;
this.mainClass = mainClass; this.mainClass = mainClass;
@@ -61,15 +87,16 @@ public class Version implements Comparable<Version>, Validation {
this.jar = jar; this.jar = jar;
this.assetIndex = assetIndex; this.assetIndex = assetIndex;
this.assets = assets; 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.compatibilityRules = compatibilityRules == null ? null : new LinkedList<>(compatibilityRules);
this.downloads = downloads == null ? null : new HashMap<>(downloads); this.downloads = downloads == null ? null : new HashMap<>(downloads);
this.logging = logging == null ? null : new HashMap<>(logging); this.logging = logging == null ? null : new HashMap<>(logging);
this.type = type; this.type = type;
this.time = time == null ? new Date() : (Date) time.clone(); this.time = time == null ? null : (Date) time.clone();
this.releaseTime = releaseTime == null ? new Date() : (Date) releaseTime.clone(); this.releaseTime = releaseTime == null ? null : (Date) releaseTime.clone();
this.minimumLauncherVersion = minimumLauncherVersion; this.minimumLauncherVersion = minimumLauncherVersion;
this.hidden = hidden; this.hidden = hidden;
this.patches = patches == null ? null : patches.isEmpty() ? null : new LinkedList<>(patches);
} }
public Optional<String> getMinecraftArguments() { public Optional<String> getMinecraftArguments() {
@@ -92,8 +119,22 @@ public class Version implements Comparable<Version>, Validation {
return id; 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() { public ReleaseType getType() {
return type; return type == null ? ReleaseType.UNKNOWN : type;
} }
public Date getReleaseTime() { public Date getReleaseTime() {
@@ -113,7 +154,15 @@ public class Version implements Comparable<Version>, Validation {
} }
public boolean isHidden() { public boolean isHidden() {
return hidden; return hidden == null ? false : hidden;
}
public boolean isResolved() {
return resolved;
}
public List<Version> getPatches() {
return patches == null ? Collections.emptyList() : patches;
} }
public Map<DownloadType, LoggingInfo> getLogging() { public Map<DownloadType, LoggingInfo> getLogging() {
@@ -153,22 +202,12 @@ public class Version implements Comparable<Version>, Validation {
return resolve(provider, new HashSet<>()).setResolved(); return resolve(provider, new HashSet<>()).setResolved();
} }
protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException { protected Version merge(Version parent) {
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);
return new Version( return new Version(
true, true,
id, id,
null,
null,
minecraftArguments == null ? parent.minecraftArguments : minecraftArguments, minecraftArguments == null ? parent.minecraftArguments : minecraftArguments,
Arguments.merge(parent.arguments, arguments), Arguments.merge(parent.arguments, arguments),
mainClass == null ? parent.mainClass : mainClass, mainClass == null ? parent.mainClass : mainClass,
@@ -184,43 +223,90 @@ public class Version implements Comparable<Version>, Validation {
time, time,
releaseTime, releaseTime,
Math.max(minimumLauncherVersion, parent.minimumLauncherVersion), Math.max(minimumLauncherVersion, parent.minimumLauncherVersion),
hidden); hidden,
Lang.merge(parent.patches, patches));
}
protected Version resolve(VersionProvider provider, Set<String> 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<Version> 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() { 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) { 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) { 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) { 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) { 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) { 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) { 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<Library> libraries) { public Version setLibraries(List<Library> libraries) {
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, 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<DownloadType, LoggingInfo> logging) { public Version setLogging(Map<DownloadType, LoggingInfo> logging) {
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, 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 @Override

View File

@@ -21,8 +21,6 @@ import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.platform.CommandBuilder; import org.jackhuang.hmcl.util.platform.CommandBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -43,13 +41,13 @@ public final class VersionLibraryBuilder {
} }
public Version build() { public Version build() {
Version ret = version;
if (useMcArgs) { if (useMcArgs) {
// Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping, // Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping,
// so we regenerate the minecraftArgument without escaping. // so we regenerate the minecraftArgument without escaping.
return version.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString()); ret = ret.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(mcArgs).toString());
} else {
return version.setArguments(version.getArguments().map(args -> args.withGame(game)).orElse(new Arguments(game, Collections.emptyList())));
} }
return ret.setArguments(ret.getArguments().map(args -> args.withGame(game)).orElse(new Arguments(game, null)));
} }
public void removeTweakClass(String target) { public void removeTweakClass(String target) {
@@ -63,30 +61,26 @@ public final class VersionLibraryBuilder {
--i; --i;
} }
} }
} else { }
for (int i = 0; i + 1 < game.size(); ++i) {
Argument arg0 = game.get(i); for (int i = 0; i + 1 < game.size(); ++i) {
Argument arg1 = game.get(i + 1); Argument arg0 = game.get(i);
if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) { Argument arg1 = game.get(i + 1);
// We need to preserve the tokens if (arg0 instanceof StringArgument && arg1 instanceof StringArgument) {
String arg0Str = arg0.toString(); // We need to preserve the tokens
String arg1Str = arg1.toString(); String arg0Str = arg0.toString();
if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) { String arg1Str = arg1.toString();
game.remove(i); if (arg0Str.equals("--tweakClass") && arg1Str.toLowerCase().contains(target)) {
game.remove(i); game.remove(i);
--i; game.remove(i);
} --i;
} }
} }
} }
} }
public void addArgument(String... args) { public void addArgument(String... args) {
if (useMcArgs) { for (String arg : args)
mcArgs.addAll(Arrays.asList(args)); game.add(new StringArgument(arg));
} else {
for (String arg : args)
game.add(new StringArgument(arg));
}
} }
} }

View File

@@ -18,8 +18,15 @@
package org.jackhuang.hmcl.launch; package org.jackhuang.hmcl.launch;
import org.jackhuang.hmcl.auth.AuthInfo; import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.game.Argument;
import org.jackhuang.hmcl.util.*; 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.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.Unzipper; 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.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform; 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.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 java.util.function.Supplier;
import static org.jackhuang.hmcl.util.Lang.mapOf; 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 class DefaultLauncher extends Launcher {
public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
this(repository, versionId, authInfo, options, null); this(repository, version, authInfo, options, null);
} }
public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
this(repository, versionId, authInfo, options, listener, true); this(repository, version, authInfo, options, listener, true);
} }
public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
super(repository, versionId, authInfo, options, listener, daemon); super(repository, version, authInfo, options, listener, daemon);
} }
private CommandBuilder generateCommandLine(File nativeFolder) throws IOException { private CommandBuilder generateCommandLine(File nativeFolder) throws IOException {
@@ -144,13 +160,13 @@ public class DefaultLauncher extends Launcher {
res.add(version.getMainClass()); res.add(version.getMainClass());
res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(LinkedList::new), configuration));
Map<String, Boolean> features = getFeatures(); Map<String, Boolean> features = getFeatures();
res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getGame).orElseGet(this::getDefaultGameArguments), configuration, features)); 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()) if (authInfo.getArguments() != null && authInfo.getArguments().getGame() != null && !authInfo.getArguments().getGame().isEmpty())
res.addAll(Arguments.parseArguments(authInfo.getArguments().getGame(), configuration, features)); 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())) { if (StringUtils.isNotBlank(options.getServerIp())) {
String[] args = options.getServerIp().split(":"); String[] args = options.getServerIp().split(":");
res.add("--server"); res.add("--server");
@@ -255,7 +271,7 @@ public class DefaultLauncher extends Launcher {
@Override @Override
public ManagedProcess launch() throws IOException, InterruptedException { 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 // To guarantee that when failed to generate launch command line, we will not call pre-launch command
List<String> rawCommandLine = generateCommandLine(nativeFolder).asList(); List<String> rawCommandLine = generateCommandLine(nativeFolder).asList();
@@ -287,7 +303,7 @@ public class DefaultLauncher extends Launcher {
public void makeLaunchScript(File scriptFile) throws IOException { public void makeLaunchScript(File scriptFile) throws IOException {
boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS; boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS;
File nativeFolder = repository.getNativeDirectory(versionId); File nativeFolder = repository.getNativeDirectory(version.getId());
decompressNatives(nativeFolder); decompressNatives(nativeFolder);
if (isWindows && !FileUtils.getExtension(scriptFile).equals("bat")) if (isWindows && !FileUtils.getExtension(scriptFile).equals("bat"))

View File

@@ -33,30 +33,27 @@ import java.io.IOException;
public abstract class Launcher { public abstract class Launcher {
protected final GameRepository repository; protected final GameRepository repository;
protected final String versionId;
protected final Version version; protected final Version version;
protected final AuthInfo authInfo; protected final AuthInfo authInfo;
protected final LaunchOptions options; protected final LaunchOptions options;
protected final ProcessListener listener; protected final ProcessListener listener;
protected final boolean daemon; protected final boolean daemon;
public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
this(repository, versionId, authInfo, options, null); this(repository, version, authInfo, options, null);
} }
public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { public Launcher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
this(repository, versionId, authInfo, options, listener, true); 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.repository = repository;
this.versionId = versionId; this.version = version;
this.authInfo = authInfo; this.authInfo = authInfo;
this.options = options; this.options = options;
this.listener = listener; this.listener = listener;
this.daemon = daemon; this.daemon = daemon;
version = repository.getResolvedVersion(versionId);
} }
/** /**

View File

@@ -22,7 +22,6 @@ import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.VersionLibraryBuilder; import org.jackhuang.hmcl.game.VersionLibraryBuilder;
@@ -155,7 +154,7 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
} }
} }
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))); dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
} }

View File

@@ -17,14 +17,14 @@
*/ */
package org.jackhuang.hmcl.util.platform; package org.jackhuang.hmcl.util.platform;
import org.jackhuang.hmcl.util.StringUtils;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jackhuang.hmcl.util.StringUtils;
public final class CommandBuilder { public final class CommandBuilder {
private final OperatingSystem os; private final OperatingSystem os;
private List<Item> raw = new LinkedList<>(); private List<Item> raw = new LinkedList<>();
@@ -39,9 +39,9 @@ public final class CommandBuilder {
private String parse(String s) { private String parse(String s) {
if (OperatingSystem.WINDOWS == os) { if (OperatingSystem.WINDOWS == os) {
return parseWindows(s); return parseBatch(s);
} else { } else {
return parseBash(s); return parseShell(s);
} }
} }
@@ -96,9 +96,14 @@ public final class CommandBuilder {
this.arg = arg; this.arg = arg;
this.parse = parse; 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\"^&<>|"; String escape = " \t\"^&<>|";
if (StringUtils.containsOne(s, escape.toCharArray())) if (StringUtils.containsOne(s, escape.toCharArray()))
// The argument has not been quoted, add quotes. // 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 escaping = " \t\"!#$&'()*,;<=>?[\\]^`{|}~";
String escaped = "\"$&`"; String escaped = "\"$&`";
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0 || StringUtils.containsOne(s, escaping.toCharArray())) { if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0 || StringUtils.containsOne(s, escaping.toCharArray())) {