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 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

View File

@@ -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<Void> {
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)));
}

View File

@@ -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<String> 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

View File

@@ -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() {

View File

@@ -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<Library> 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:

View File

@@ -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);

View File

@@ -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<InstallerItem> {
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<Library, Consumer<InstallerItem>> removeAction = library -> x -> {
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
newList.remove(library);
new MaintainTask(version.setLibraries(newList))
.thenComposeAsync(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
Function<String, Consumer<InstallerItem>> 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();
}

View File

@@ -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<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);
return versionList.loadAsync(gameVersion, getDownloadProvider())
.thenComposeAsync(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
@@ -98,6 +104,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
@Override
public Task<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) {
if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
Task<Version> 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<Version> 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<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;
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<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.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<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;
}
public Optional<Library> get(LibraryType type) {
return Optional.ofNullable(libraries.get(type));
public Optional<String> getVersion(LibraryType 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) {
@@ -48,7 +57,7 @@ public final class LibraryAnalyzer {
}
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()) {
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;
}

View File

@@ -79,7 +79,10 @@ public final class ForgeInstallTask extends Task<Version> {
@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<Version> {
/**
* 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

View File

@@ -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<Version> {
}
}
// 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());

View File

@@ -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<Version> {
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()));
}
}

View File

@@ -40,6 +40,10 @@ public class LibrariesUniqueTask extends Task<Version> {
@Override
public void execute() {
setResult(unique(version));
}
public static Version unique(Version version) {
List<Library> libraries = new ArrayList<>(version.getLibraries());
SimpleMultimap<String, Library> multimap = new SimpleMultimap<String, Library>(HashMap::new, LinkedList::new);
@@ -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;
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<Version> {
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())));
}
}

View File

@@ -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<Version> {
private final Path installer;
private final List<Task<?>> dependents = 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) {
this(dependencyManager, version, remoteVersion, null);
@@ -61,6 +75,39 @@ public final class OptiFineInstallTask extends Task<Version> {
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<Version> {
@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<Library> 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<Library> 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)));
}

View File

@@ -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<Argument> getGame() {
return game == null ? Collections.emptyList() : Collections.unmodifiableList(game);
return game == null ? null : Collections.unmodifiableList(game);
}
public Arguments withGame(List<Argument> game) {
return new Arguments(game, jvm);
}
@Nullable
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) {
@@ -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<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 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 {

View File

@@ -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<Version> save(Version version) {
return new VersionJsonSaveTask(this, version);
}
public boolean isLoaded() {
return versions != null;
}

View File

@@ -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<Version>, 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<Version>, Validation {
private final Date time;
private final Date releaseTime;
private final int minimumLauncherVersion;
private final boolean hidden;
private final Boolean hidden;
private final List<Version> 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<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.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<Version>, 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<String> getMinecraftArguments() {
@@ -92,8 +119,22 @@ public class Version implements Comparable<Version>, 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<Version>, Validation {
}
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() {
@@ -153,22 +202,12 @@ public class Version implements Comparable<Version>, Validation {
return resolve(provider, new HashSet<>()).setResolved();
}
protected Version resolve(VersionProvider provider, Set<String> 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<Version>, Validation {
time,
releaseTime,
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() {
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<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) {
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

View File

@@ -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));
}
}

View File

@@ -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<String, Boolean> 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<String> 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"))

View File

@@ -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);
}
/**

View File

@@ -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<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)));
}

View File

@@ -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<Item> 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())) {