Compare commits

..
18 Commits
Author SHA1 Message Date
huanghongxun e2787af044 no longer force using javaw 2019-08-20 00:54:12 +08:00
huanghongxun 30c27bbb67 MultiMC fabric and local libraries 2019-08-20 00:53:59 +08:00
huanghongxun 7aee7e33d0 Making versions whose library being removed pure patched 2019-08-20 00:53:36 +08:00
huanghongxun a26061c388 alter order if game item context menu 2019-08-19 16:08:48 +08:00
huanghongxun 11e66ac13c Support changing game version 2019-08-19 16:05:40 +08:00
huanghongxun a2ac3c1f18 Hide generated resolved patches in InstallerListPage 2019-08-19 15:39:30 +08:00
huanghongxun e7142e4032 Fix displaying "null" in GameItem 2019-08-19 14:57:42 +08:00
huanghongxun 0dd9dc6c17 Now pressing enter when at main page will launch the game 2019-08-19 00:49:48 +08:00
huanghongxun a53bfed060 Add primary and secondary click action on GameListItem 2019-08-19 00:38:31 +08:00
huanghongxun c5919f2719 Fix OptiFine installation 2019-08-19 00:18:34 +08:00
huanghongxun fb24516b90 Show unrecognized patches in InstallerListPage 2019-08-19 00:17:03 +08:00
huanghongxun 60fa4eb0a1 Fix LibrariesUniqueTask changing order of classpath 2019-08-19 00:16:02 +08:00
huanghongxun 536576084d Fix forge local installation 2019-08-19 00:12:28 +08:00
huanghongxun c62cc499c5 Show HMCL version in game main menu 2019-08-18 10:54:05 +08:00
huanghongxun c11bbf8836 Fix failure of auto-installing more than one library 2019-08-18 10:27:48 +08:00
huanghongxun 71c0cede24 Fixed unrecognizing Optifine jar file 2019-08-18 10:27:12 +08:00
huanghongxun ad16bbc96d Disable failure test 2019-08-17 12:13:05 +08:00
huanghongxun 520ffe4c09 Add missing license header 2019-08-17 00:45:47 +08:00
50 changed files with 849 additions and 460 deletions

No files matched your search

@@ -26,7 +26,6 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.LibrariesUniqueTask;
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
import org.jackhuang.hmcl.launch.NotDecompressingNativesException;
import org.jackhuang.hmcl.launch.PermissionException;
@@ -132,7 +131,7 @@ public final class LauncherHelper {
private void launch0() {
HMCLGameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = MaintainTask.maintain(LibrariesUniqueTask.unique(repository.getResolvedVersion(selectedVersion)));
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
@@ -537,6 +537,7 @@ public final class VersionSetting {
.setGameDir(gameDir)
.setJava(javaVersion)
.setVersionName(Metadata.TITLE)
.setVersionType(Metadata.TITLE)
.setProfileName(Metadata.TITLE)
.setMinecraftArgs(getMinecraftArgs())
.setJavaArgs(getJavaArgs())
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.setting.Theme;
@@ -40,11 +41,16 @@ public class InstallerItem extends BorderPane {
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
{
if (version != null) {
TwoLineListItem item = new TwoLineListItem();
item.setTitle(artifact);
item.setSubtitle(i18n("archive.version") + ": " + version);
setCenter(item);
} else {
Label label = new Label(artifact);
label.setStyle("-fx-font-size: 15px;");
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
setCenter(label);
}
{
@@ -23,7 +23,12 @@ import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
@@ -113,7 +118,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
launchButton.setPrefHeight(55);
launchButton.setButtonType(JFXButton.ButtonType.RAISED);
launchButton.getStyleClass().add("jfx-button-raised");
launchButton.setOnMouseClicked(e -> launch());
launchButton.setOnAction(e -> launch());
launchButton.setDefaultButton(true);
launchButton.setClip(new Rectangle(-100, -100, 310, 200));
{
VBox graphic = new VBox();
@@ -26,11 +26,14 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameInstallTask;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
import org.jackhuang.hmcl.mod.*;
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
import org.jackhuang.hmcl.mod.ModpackInstallTask;
import org.jackhuang.hmcl.mod.ModpackUpdateTask;
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
@@ -86,6 +89,8 @@ public final class TaskListPane extends StackPane {
if (task instanceof GameAssetDownloadTask) {
task.setName(i18n("assets.download_all"));
} else if (task instanceof GameInstallTask) {
task.setName(i18n("install.installer.install", i18n("install.installer.game")));
} else if (task instanceof ForgeInstallTask) {
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
} else if (task instanceof LiteLoaderInstallTask) {
@@ -107,7 +107,7 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
public void onNavigate(Map<String, Object> settings) {
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(provider.getVersion().resolve(provider.getProfile().getRepository()));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(provider.getVersion().resolvePreservingPatches(provider.getProfile().getRepository()));
String fabric = analyzer.getVersion(FABRIC).orElse(null);
String forge = analyzer.getVersion(FORGE).orElse(null);
String liteLoader = analyzer.getVersion(LITELOADER).orElse(null);
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
@@ -38,7 +39,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
private final String libraryId;
private final String oldLibraryVersion;
public UpdateInstallerWizardProvider(Profile profile, String gameVersion, Version version, String libraryId, String oldLibraryVersion) {
public UpdateInstallerWizardProvider(@NotNull Profile profile, @NotNull String gameVersion, @NotNull Version version, @NotNull String libraryId, @NotNull String oldLibraryVersion) {
this.profile = profile;
this.gameVersion = gameVersion;
this.version = version;
@@ -28,11 +28,13 @@ import javafx.scene.image.Image;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.util.i18n.I18n;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
import static org.jackhuang.hmcl.util.Lang.handleUncaught;
import static org.jackhuang.hmcl.util.Lang.threadPool;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
@@ -57,12 +59,15 @@ public class GameItem extends Control {
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE)
.thenAcceptAsync(game -> {
StringBuilder libraries = new StringBuilder(game);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id));
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values())
analyzer.getVersion(type).ifPresent(library ->
libraries
.append(", ").append(i18n("install.installer." + type.name().toLowerCase()))
.append(": ").append(modifyVersion(game, library.replaceAll("(?i)" + type.name().toLowerCase(), ""))));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
analyzer.forEachLibrary((libraryId, libraryVersion) -> {
if (libraryId.equals(MINECRAFT.getPatchId())) return;
if (I18n.hasKey("install.installer." + libraryId)) {
libraries.append(", ").append(i18n("install.installer." + libraryId));
if (libraryVersion != null)
libraries.append(": ").append(modifyVersion("", libraryVersion.replaceAll("(?i)" + libraryId, "")));
}
});
subtitle.set(libraries.toString());
}, Platform::runLater)
@@ -22,7 +22,9 @@ import com.jfoenix.controls.JFXPopup;
import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.setting.Theme;
@@ -49,22 +51,24 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
chkSelected.setToggleGroup(skinnable.getToggleGroup());
root.setLeft(chkSelected);
root.setCenter(new GameItem(skinnable.getProfile(), skinnable.getVersion()));
GameItem gameItem = new GameItem(skinnable.getProfile(), skinnable.getVersion());
gameItem.setMouseTransparent(true);
root.setCenter(gameItem);
PopupMenu menu = new PopupMenu();
JFXPopup popup = new JFXPopup(menu);
menu.getContent().setAll(
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)),
new MenuSeparator(),
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.manage"), FXUtils.withJFXPopupClosing(skinnable::modifyGameSettings, popup)),
new MenuSeparator(),
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(skinnable::rename, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(skinnable::remove, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(skinnable::export, popup)),
new MenuSeparator(),
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)),
new MenuSeparator(),
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)));
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)));
HBox right = new HBox();
right.setAlignment(Pos.CENTER_RIGHT);
@@ -91,5 +95,16 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
JFXDepthManager.setDepth(root, 1);
getChildren().setAll(root);
root.setCursor(Cursor.HAND);
root.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
if (e.getClickCount() == 1) {
skinnable.modifyGameSettings();
}
} else if (e.getButton() == MouseButton.SECONDARY) {
popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
}
});
}
}
@@ -36,6 +36,8 @@ import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.ToolbarListPageSkin;
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
@@ -74,7 +76,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
Task.supplyAsync(() -> {
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(versionId));
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
}).thenAcceptAsync(Schedulers.javafx(), analyzer -> {
Function<String, Consumer<InstallerItem>> removeAction = libraryId -> x -> {
profile.getDependency().removeLibraryAsync(version.getId(), libraryId)
@@ -84,13 +86,17 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
};
itemsProperty().clear();
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
String libraryId = type.getPatchId();
analyzer.getVersion(type).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem(i18n("install.installer." + libraryId), libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
}, removeAction.apply(libraryId))));
}
analyzer.forEachLibrary((libraryId, libraryVersion) -> {
String title = I18n.hasKey("install.installer." + libraryId) ? i18n("install.installer." + libraryId) : libraryId;
Consumer<InstallerItem> action = "game".equals(libraryId) ? null : removeAction.apply(libraryId);
if (libraryVersion != null && Lang.test(() -> profile.getDependency().getVersionList(libraryId)))
itemsProperty().add(
new InstallerItem(title, libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
}, action));
else
itemsProperty().add(new InstallerItem(title, libraryVersion, null, action));
});
}).start();
}
@@ -79,7 +79,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
}
public void loadVersion(Profile profile, String id) {
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id));
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
modded.set(libraryAnalyzer.hasModLoader());
loadMods(profile.getRepository().getModManager(id));
}
@@ -21,7 +21,6 @@ import javafx.stage.FileChooser;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
@@ -77,8 +76,7 @@ public class Versions {
}
public static void updateGameAssets(Profile profile, String version) {
Version resolvedVersion = profile.getRepository().getResolvedVersion(version);
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), resolvedVersion, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY)
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY)
.executor();
Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"));
executor.start();
@@ -23,14 +23,12 @@ import org.jackhuang.hmcl.download.game.GameDownloadTask;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.io.IOException;
import java.nio.file.Path;
import java.util.LinkedList;
/**
* Note: This class has no state.
@@ -70,7 +68,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
}
@Override
public Task<?> checkGameCompletionAsync(Version version) {
public Task<?> checkGameCompletionAsync(Version original) {
Version version = original.resolve(repository);
return Task.allOf(
Task.composeAsync(() -> {
if (!repository.getVersionJar(version).exists())
@@ -138,34 +137,16 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
* Will try to remove libraries and patches.
*
* @param versionId version id
* @param libraryId forge/liteloader/optifine
* @param libraryId forge/liteloader/optifine/fabric
* @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
// MaintainTask requires version that does not inherits from any version.
// If we want to remove a library in dependent version, we should keep the dependents not changed
// So resolving this game version to preserve all information in this version.json is necessary.
Version version = repository.getResolvedPreservingPatchesVersion(versionId);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
switch (libraryId) {
case "forge":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.FORGE, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById(LibraryAnalyzer.LibraryType.FORGE.getPatchId());
break;
case "liteloader":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.LITELOADER, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId());
break;
case "optifine":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.OPTIFINE, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId());
break;
case "fabric":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.FABRIC, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById(LibraryAnalyzer.LibraryType.FABRIC.getPatchId());
break;
}
return new MaintainTask(version.setLibraries(newList));
return Task.supplyAsync(() -> LibraryAnalyzer.analyze(version).removeLibrary(libraryId).build());
}
/**
@@ -173,7 +154,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
* Will try to remove libraries and patches.
*
* @param versionId version id
* @param libraryId forge/liteloader/optifine
* @param libraryId forge/liteloader/optifine/fabric
* @return task to remove the specified library
*/
public Task<Version> removeLibraryAsync(String versionId, String libraryId) {
@@ -17,14 +17,9 @@
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameDownloadTask;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.util.Map;
@@ -52,24 +47,16 @@ public class DefaultGameBuilder extends GameBuilder {
@Override
public Task<?> buildAsync() {
return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenComposeAsync(rawJson -> {
Version original = JsonUtils.GSON.fromJson(rawJson, Version.class);
Version version = original.setId(name).setJar(null);
Task<?> vanillaTask = downloadGameAsync(gameVersion, version).thenComposeAsync(Task.allOf(
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
).withComposeAsync(dependencyManager.getGameRepository().save(version))); // using [with] because download failure here are tolerant.
Task<Version> libraryTask = Task.supplyAsync(() -> new Version(name));
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "game", gameVersion));
Task<Version> libraryTask = vanillaTask.thenSupplyAsync(() -> version);
for (Map.Entry<String, String> entry : toolVersions.entrySet())
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
for (Map.Entry<String, String> entry : toolVersions.entrySet())
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
for (RemoteVersion remoteVersion : remoteVersions)
libraryTask = libraryTask.thenComposeAsync(dependencyManager.installLibraryAsync(remoteVersion));
for (RemoteVersion remoteVersion : remoteVersions)
libraryTask = libraryTask.thenComposeAsync(dependencyManager.installLibraryAsync(remoteVersion));
return libraryTask;
}).whenComplete(exception -> {
return libraryTask.whenComplete(exception -> {
if (exception != null)
dependencyManager.getGameRepository().removeVersionFromDisk(name);
});
@@ -78,9 +65,4 @@ public class DefaultGameBuilder extends GameBuilder {
private ExceptionalFunction<Version, Task<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId, String libraryVersion) {
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, libraryVersion);
}
protected Task<Void> downloadGameAsync(String gameVersion, Version version) {
return new GameDownloadTask(dependencyManager, gameVersion, version);
}
}
@@ -44,6 +44,7 @@ public interface DownloadProvider {
*
* @param id the id of specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
* @return the version list
* @throws IllegalArgumentException if the version list does not exist
*/
VersionList<?> getVersionListById(String id);
}
@@ -20,70 +20,124 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.util.Pair;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class LibraryAnalyzer {
private final Map<LibraryType, Pair<Library, String>> libraries;
private Version version;
private final Map<String, Pair<Library, String>> libraries;
private LibraryAnalyzer(Map<LibraryType, Pair<Library, String>> libraries) {
private LibraryAnalyzer(Version version, Map<String, Pair<Library, String>> libraries) {
this.version = version;
this.libraries = libraries;
}
public Optional<String> getVersion(LibraryType type) {
return getVersion(type.getPatchId());
}
public Optional<String> getVersion(String type) {
return Optional.ofNullable(libraries.get(type)).map(Pair::getValue);
}
public void ifPresent(LibraryType type, BiConsumer<Library, String> consumer) {
if (libraries.containsKey(type)) {
Pair<Library, String> value = libraries.get(type);
consumer.accept(value.getKey(), value.getValue());
}
public void forEachLibrary(BiConsumer<String, @Nullable String> callback) {
for (Map.Entry<String, Pair<Library, String>> entry : libraries.entrySet())
callback.accept(entry.getKey(), entry.getValue().getValue());
}
public boolean has(LibraryType type) {
return has(type.getPatchId());
}
public boolean has(String type) {
return libraries.containsKey(type);
}
public boolean hasModLoader() {
return Arrays.stream(LibraryType.values())
.filter(LibraryType::isModLoader)
.anyMatch(this::has);
return libraries.keySet().stream().map(LibraryType::fromPatchId)
.filter(Objects::nonNull)
.anyMatch(LibraryType::isModLoader);
}
public boolean hasModLauncher() {
final String modLauncher = "cpw.mods.modlauncher.Launcher";
return modLauncher.equals(version.getMainClass()) || version.getPatches().stream().anyMatch(patch -> modLauncher.equals(patch.getMainClass()));
}
private Version removingMatchedLibrary(Version version, String libraryId) {
LibraryType type = LibraryType.fromPatchId(libraryId);
if (type == null) return version;
List<Library> libraries = new ArrayList<>();
for (Library library : version.getLibraries()) {
String groupId = library.getGroupId();
String artifactId = library.getArtifactId();
if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) {
// skip
} else {
libraries.add(library);
}
}
return version.setLibraries(libraries);
}
/**
* Remove library by library id
* @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"
* @return this
*/
public LibraryAnalyzer removeLibrary(String libraryId) {
if (!has(libraryId)) return this;
version = removingMatchedLibrary(version, libraryId)
.setPatches(version.getPatches().stream()
.filter(patch -> !libraryId.equals(patch.getId()))
.map(patch -> removingMatchedLibrary(patch, libraryId))
.collect(Collectors.toList()));
return this;
}
public Version build() {
return version;
}
public static LibraryAnalyzer analyze(Version version) {
Map<LibraryType, Pair<Library, String>> libraries = new EnumMap<>(LibraryType.class);
if (version.getInheritsFrom() != null)
throw new IllegalArgumentException("LibraryAnalyzer can only analyze independent game version");
for (Library library : version.getLibraries()) {
Map<String, Pair<Library, String>> libraries = new HashMap<>();
for (Library library : version.resolve(null).getLibraries()) {
String groupId = library.getGroupId();
String artifactId = library.getArtifactId();
for (LibraryType type : LibraryType.values()) {
if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) {
libraries.put(type, Pair.pair(library, library.getVersion()));
libraries.put(type.getPatchId(), Pair.pair(library, library.getVersion()));
break;
}
}
}
for (Version patch : version.getPatches()) {
for (LibraryType type : LibraryType.values()) {
if (type.patchId.equals(patch.getId())) {
libraries.put(type, Pair.pair(null, patch.getVersion()));
break;
}
}
if (patch.isHidden()) continue;
libraries.put(patch.getId(), Pair.pair(null, patch.getVersion()));
}
return new LibraryAnalyzer(libraries);
return new LibraryAnalyzer(version, libraries);
}
public enum LibraryType {
MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$")),
FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")),
FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
@@ -107,5 +161,12 @@ public final class LibraryAnalyzer {
public String getPatchId() {
return patchId;
}
public static LibraryType fromPatchId(String patchId) {
for (LibraryType type : values())
if (type.getPatchId().equals(patchId))
return type;
return null;
}
}
}
@@ -17,37 +17,63 @@
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.CompatibilityRule;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.SimpleMultimap;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
public class MaintainTask extends Task<Version> {
private final GameRepository repository;
private final Version version;
public MaintainTask(Version version) {
public MaintainTask(GameRepository repository, Version version) {
this.repository = repository;
this.version = version;
if (version.getInheritsFrom() != null)
throw new IllegalArgumentException("MaintainTask requires independent game version");
}
@Override
public void execute() {
setResult(maintain(version));
setResult(maintain(repository, version));
}
public static Version maintain(Version version) {
if (version.getMainClass().contains("launchwrapper")) {
return maintainGameWithLaunchWrapper(version);
public static Version maintain(GameRepository repository, Version version) {
if (version.getInheritsFrom() != null)
throw new IllegalArgumentException("MaintainTask requires independent game version");
String mainClass = version.resolve(null).getMainClass();
if (mainClass != null && mainClass.contains("launchwrapper")) {
return maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version)));
} else {
// Vanilla Minecraft does not need maintain
// Forge 1.13 support not implemented, not compatible with OptiFine currently.
// Fabric does not need maintain, nothing compatible with fabric now.
return version;
return maintainOptiFineLibrary(repository, unique(version));
}
}
private static Version maintainGameWithLaunchWrapper(Version version) {
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
String mainClass = null;
if (!libraryAnalyzer.has(FORGE)) {
builder.removeTweakClass("forge");
@@ -56,7 +82,7 @@ public class MaintainTask extends Task<Version> {
// Installing Forge will override the Minecraft arguments in json, so LiteLoader and OptiFine Tweaker are being re-added.
builder.removeTweakClass("liteloader");
if (libraryAnalyzer.has(LITELOADER)) {
if (libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.hasModLauncher()) {
builder.addArgument("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker");
}
@@ -65,11 +91,101 @@ public class MaintainTask extends Task<Version> {
if (!libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.has(FORGE)) {
builder.addArgument("--tweakClass", "optifine.OptiFineTweaker");
} else {
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
builder.addArgument("--tweakClass", "optifine.OptiFineForgeTweaker");
if (libraryAnalyzer.hasModLauncher()) {
// If ModLauncher installed, we use ModLauncher in place of LaunchWrapper.
mainClass = "cpw.mods.modlauncher.Launcher";
} else {
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
builder.addArgument("--tweakClass", "optifine.OptiFineForgeTweaker");
}
}
}
return builder.build();
Version ret = builder.build();
return mainClass == null ? ret : ret.setMainClass(mainClass);
}
private static Version maintainOptiFineLibrary(GameRepository repository, Version version) {
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
List<Library> libraries = new ArrayList<>(version.getLibraries());
if (libraryAnalyzer.has(OPTIFINE)) {
if (libraryAnalyzer.has(LITELOADER) || libraryAnalyzer.has(FORGE)) {
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
// And we should load the installer jar instead of patch jar.
if (repository != null)
for (int i = 0; i < version.getLibraries().size(); ++i) {
Library library = libraries.get(i);
if (library.is("optifine", "OptiFine")) {
Library newLibrary = new Library(new Artifact("optifine", "OptiFine", library.getVersion(), "installer"));
if (repository.getLibraryFile(version, newLibrary).exists()) {
libraries.set(i, null);
// OptiFine should be loaded after Forge in classpath.
// Although we have altered priority of OptiFine higher than Forge,
// there still exists a situation that Forge is installed without patch.
// Here we manually alter the position of OptiFine library in classpath.
libraries.add(newLibrary);
}
}
}
}
}
return version.setLibraries(libraries.stream().filter(Objects::nonNull).collect(Collectors.toList()));
}
public static Version unique(Version version) {
List<Library> libraries = new ArrayList<>();
SimpleMultimap<String, Integer> multimap = new SimpleMultimap<String, Integer>(HashMap::new, LinkedList::new);
for (Library library : version.getLibraries()) {
String id = library.getGroupId() + ":" + library.getArtifactId();
VersionNumber number = VersionNumber.asVersion(library.getVersion());
String serialized = JsonUtils.GSON.toJson(library);
if (multimap.containsKey(id)) {
boolean duplicate = false;
for (int otherLibraryIndex : multimap.get(id)) {
Library otherLibrary = libraries.get(otherLibraryIndex);
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
boolean flag = true;
if (number.compareTo(otherNumber) > 0) { // if this library is newer
// replace [otherLibrary] with [library]
libraries.set(otherLibraryIndex, library);
} else if (number.compareTo(otherNumber) == 0) { // same library id.
// prevent from duplicated libraries
if (library.equals(otherLibrary)) {
String otherSerialized = JsonUtils.GSON.toJson(otherLibrary);
// A trick, the library that has more information is better, which can be
// considered whose serialized JSON text will be longer.
if (serialized.length() > otherSerialized.length()) {
libraries.set(otherLibraryIndex, library);
}
} else {
// for text2speech, which have same library id as well as version number,
// but its library and native library does not equal
flag = false;
}
}
if (flag) {
duplicate = true;
break;
}
}
}
if (!duplicate) {
multimap.put(id, libraries.size());
libraries.add(library);
}
} else {
multimap.put(id, libraries.size());
libraries.add(library);
}
}
return version.setLibraries(libraries);
}
}
@@ -21,6 +21,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.GetTask;
@@ -65,7 +66,7 @@ public final class FabricInstallTask extends Task<Version> {
@Override
public void preExecute() {
if (!Objects.equals("net.minecraft.client.main.Main", version.getMainClass()))
if (!Objects.equals("net.minecraft.client.main.Main", version.resolve(dependencyManager.getGameRepository()).getMainClass()))
throw new UnsupportedFabricInstallationException();
}
@@ -121,8 +122,8 @@ public final class FabricInstallTask extends Task<Version> {
}
}
libraries.add(new Library("net.fabricmc", "intermediary", gameVersion, null, "https://maven.fabricmc.net/", null));
libraries.add(new Library("net.fabricmc", "fabric-loader", loaderVersion, null, "https://maven.fabricmc.net/", null));
libraries.add(new Library(new Artifact("net.fabricmc", "intermediary", gameVersion), "https://maven.fabricmc.net/", null));
libraries.add(new Library(new Artifact("net.fabricmc", "fabric-loader", loaderVersion), "https://maven.fabricmc.net/", null));
return new Version("net.fabricmc", loaderVersion, 30000, arguments, mainClass, libraries);
}
@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.util.Immutable;
/**
@@ -28,7 +29,7 @@ public final class ForgeInstall {
private final String profileName;
private final String target;
private final String path;
private final Artifact path;
private final String version;
private final String filePath;
private final String welcome;
@@ -40,7 +41,7 @@ public final class ForgeInstall {
this(null, null, null, null, null, null, null, null, null);
}
public ForgeInstall(String profileName, String target, String path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {
public ForgeInstall(String profileName, String target, Artifact path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {
this.profileName = profileName;
this.target = target;
this.path = path;
@@ -60,7 +61,7 @@ public final class ForgeInstall {
return target;
}
public String getPath() {
public Artifact getPath() {
return path;
}
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version;
@@ -39,6 +38,9 @@ import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
/**
*
* @author huangyuhui
@@ -80,10 +82,7 @@ public final class ForgeInstallTask extends Task<Version> {
@Override
public void postExecute() throws Exception {
Files.deleteIfExists(installer);
setResult(dependency.getResult()
.setPriority(30000)
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
.setVersion(remote.getSelfVersion()));
setResult(dependency.getResult());
}
@Override
@@ -99,9 +98,9 @@ public final class ForgeInstallTask extends Task<Version> {
@Override
public void execute() {
if (VersionNumber.VERSION_COMPARATOR.compare("1.13", remote.getGameVersion()) <= 0)
dependency = new ForgeNewInstallTask(dependencyManager, version, installer);
dependency = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
else
dependency = new ForgeOldInstallTask(dependencyManager, version, installer);
dependency = new ForgeOldInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
}
/**
@@ -125,15 +124,19 @@ public final class ForgeInstallTask extends Task<Version> {
ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
if (!gameVersion.get().equals(profile.getMinecraft()))
throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());
return new ForgeNewInstallTask(dependencyManager, version, installer);
return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getPath().getVersion().replaceAll("(?i)forge", "")), installer);
} else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) {
ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);
if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))
throw new VersionMismatchException(profile.getInstall().getMinecraft(), gameVersion.get());
return new ForgeOldInstallTask(dependencyManager, version, installer);
return new ForgeOldInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getInstall().getPath().getVersion().replaceAll("(?i)forge", "")), installer);
} else {
throw new IOException();
}
}
}
private static String modifyVersion(String gameVersion, String version) {
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
}
}
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.Artifact;
@@ -34,11 +35,10 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.CommandBuilder;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.SystemUtils;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -70,12 +70,14 @@ public class ForgeNewInstallTask extends Task<Version> {
private ForgeNewInstallProfile profile;
private Version forgeVersion;
private final String selfVersion;
ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {
this.dependencyManager = dependencyManager;
this.gameRepository = dependencyManager.getGameRepository();
this.version = version;
this.installer = installer;
this.selfVersion = selfVersion;
setSignificance(TaskSignificance.MINOR);
}
@@ -86,7 +88,7 @@ public class ForgeNewInstallTask extends Task<Version> {
else if (StringUtils.isSurrounded(literal, "'", "'"))
return StringUtils.removeSurrounding(literal, "'");
else if (StringUtils.isSurrounded(literal, "[", "]"))
return gameRepository.getArtifactFile(version, new Artifact(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
else
return plainConverter.apply(literal);
}
@@ -238,13 +240,7 @@ public class ForgeNewInstallTask extends Task<Version> {
command.addAll(args);
LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString());
Process process = new ProcessBuilder(command).start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
for (String line; (line = reader.readLine()) != null;) {
System.out.println(line);
}
}
int exitCode = process.waitFor();
int exitCode = SystemUtils.callExternalProcess(command);
if (exitCode != 0)
throw new IllegalStateException("Game processor exited abnormally");
@@ -268,7 +264,10 @@ public class ForgeNewInstallTask extends Task<Version> {
}
}
setResult(forgeVersion);
setResult(forgeVersion
.setPriority(30000)
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
.setVersion(selfVersion));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion));
FileUtils.deleteDirectory(temp.toFile());
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
@@ -41,12 +42,14 @@ public class ForgeOldInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager;
private final Version version;
private final Path installer;
private final String selfVersion;
private final List<Task<?>> dependencies = new LinkedList<>();
ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {
this.dependencyManager = dependencyManager;
this.version = version;
this.installer = installer;
this.selfVersion = selfVersion;
setSignificance(TaskSignificance.MINOR);
}
@@ -71,7 +74,7 @@ public class ForgeOldInstallTask extends Task<Version> {
ForgeInstallProfile installProfile = JsonUtils.fromNonNullJson(json, ForgeInstallProfile.class);
// unpack the universal jar in the installer file.
Library forgeLibrary = Library.fromName(installProfile.getInstall().getPath());
Library forgeLibrary = new Library(installProfile.getInstall().getPath());
File forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary);
if (!FileUtils.makeFile(forgeFile))
throw new IOException("Cannot make directory " + forgeFile.getParent());
@@ -81,7 +84,10 @@ public class ForgeOldInstallTask extends Task<Version> {
IOUtils.copyTo(is, os);
}
setResult(installProfile.getVersionInfo());
setResult(installProfile.getVersionInfo()
.setPriority(30000)
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
.setVersion(selfVersion));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo()));
}
}
@@ -51,16 +51,16 @@ public final class GameAssetDownloadTask extends Task<Void> {
* Constructor.
*
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
* @param version the game version
*/
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version, boolean forceDownloadingIndex) {
this.dependencyManager = dependencyManager;
this.version = version;
this.assetIndexInfo = version.getAssetIndex();
this.version = version.resolve(dependencyManager.getGameRepository());
this.assetIndexInfo = this.version.getAssetIndex();
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
if (!assetIndexFile.exists() || forceDownloadingIndex)
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, version));
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version));
}
@Override
@@ -43,7 +43,7 @@ public final class GameDownloadTask extends Task<Void> {
public GameDownloadTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version) {
this.dependencyManager = dependencyManager;
this.gameVersion = gameVersion;
this.version = version;
this.version = version.resolve(dependencyManager.getGameRepository());
setSignificance(TaskSignificance.MODERATE);
}
@@ -0,0 +1,79 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
public class GameInstallTask extends Task<Version> {
private final DefaultGameRepository gameRepository;
private final DefaultDependencyManager dependencyManager;
private final Version version;
private final GameRemoteVersion remote;
private final VersionJsonDownloadTask downloadTask;
private final List<Task<?>> dependencies = new LinkedList<>();
public GameInstallTask(DefaultDependencyManager dependencyManager, Version version, GameRemoteVersion remoteVersion) {
this.dependencyManager = dependencyManager;
this.gameRepository = dependencyManager.getGameRepository();
this.version = version;
this.remote = remoteVersion;
this.downloadTask = new VersionJsonDownloadTask(remoteVersion.getGameVersion(), dependencyManager);
}
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(downloadTask);
}
@Override
public Collection<Task<?>> getDependencies() {
return dependencies;
}
@Override
public boolean isRelyingOnDependencies() {
return false;
}
@Override
public void execute() throws Exception {
Version patch = JsonUtils.GSON.fromJson(downloadTask.getResult(), Version.class)
.setId(MINECRAFT.getPatchId()).setVersion(remote.getGameVersion()).setJar(null).setPriority(0);
setResult(patch);
Version version = new Version(this.version.getId()).addPatch(patch);
dependencies.add(new GameDownloadTask(dependencyManager, remote.getGameVersion(), version)
.thenComposeAsync(Task.allOf(
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
new GameLibrariesTask(dependencyManager, version)
).withComposeAsync(gameRepository.save(version))));
}
}
@@ -43,17 +43,17 @@ public final class GameLibrariesTask extends Task<Void> {
* Constructor.
*
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
* @param version the game version
*/
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
this(dependencyManager, version, version.getLibraries());
this(dependencyManager, version, version.resolve(dependencyManager.getGameRepository()).getLibraries());
}
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
* @param version the game version
*/
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version, List<Library> libraries) {
this.dependencyManager = dependencyManager;
@@ -17,8 +17,11 @@
*/
package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.ReleaseType;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Immutable;
import java.util.Date;
@@ -47,6 +50,11 @@ public final class GameRemoteVersion extends RemoteVersion {
return type;
}
@Override
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
return new GameInstallTask(dependencyManager, baseVersion, this);
}
@Override
public int compareTo(RemoteVersion o) {
if (!(o instanceof GameRemoteVersion))
@@ -21,7 +21,6 @@ import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
@@ -48,7 +47,7 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
protected Collection<GameRemoteVersion> getVersionsImpl(String gameVersion) {
lock.readLock().lock();
try {
return StringUtils.isBlank(gameVersion) ? versions.values() : versions.get(gameVersion);
return versions.values();
} finally {
lock.readLock().unlock();
}
@@ -1,100 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <[email protected]> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.game.CompatibilityRule;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.SimpleMultimap;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
public class LibrariesUniqueTask extends Task<Version> {
private final Version version;
public LibrariesUniqueTask(Version version) {
this.version = version;
}
@Override
public void execute() {
setResult(unique(version));
}
public static Version unique(Version version) {
List<Library> libraries = new ArrayList<>(version.getLibraries());
SimpleMultimap<String, Library> multimap = new SimpleMultimap<String, Library>(HashMap::new, LinkedList::new);
for (Library library : libraries) {
String id = library.getGroupId() + ":" + library.getArtifactId();
VersionNumber number = VersionNumber.asVersion(library.getVersion());
String serialized = JsonUtils.GSON.toJson(library);
if (multimap.containsKey(id)) {
boolean duplicate = false;
for (Library otherLibrary : multimap.get(id)) {
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
boolean flag = true;
if (number.compareTo(otherNumber) > 0) { // if this library is newer
multimap.removeValue(otherLibrary);
multimap.put(id, library);
break;
} else if (number.compareTo(otherNumber) == 0) { // same library id.
// prevent from duplicated libraries
if (library.equals(otherLibrary)) {
String otherSerialized = JsonUtils.GSON.toJson(otherLibrary);
// A trick, the library that has more information is better, which can be
// considered whose serialized JSON text will be longer.
if (serialized.length() > otherSerialized.length()) {
multimap.removeValue(id, otherLibrary);
multimap.put(id, library);
break;
}
} else {
// for text2speech, which have same library id as well as version number,
// but its library and native library does not equal
flag = false;
}
}
if (flag) {
duplicate = true;
break;
}
}
}
if (!duplicate) {
multimap.put(id, library);
}
} else {
multimap.put(id, library);
}
}
return version.setLibraries(multimap.values().stream().sorted().collect(Collectors.toList()));
}
}
@@ -62,7 +62,7 @@ public final class VersionJsonDownloadTask extends Task<String> {
@Override
public void execute() {
RemoteVersion remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion)
.orElseThrow(() -> new IllegalStateException("Cannot find specific version " + gameVersion + " in remote repository"));
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl());
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL)).storeTo(this::setResult));
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
@@ -64,7 +65,7 @@ public final class LiteLoaderInstallTask extends Task<Version> {
@Override
public void execute() {
Library library = new Library(
"com.mumfrey", "liteloader", remote.getSelfVersion(), null,
new Artifact("com.mumfrey", "liteloader", remote.getSelfVersion()),
"http://dl.liteloader.com/versions/",
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
);
@@ -21,6 +21,8 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library;
@@ -31,6 +33,8 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.SystemUtils;
import org.jenkinsci.constant_pool_scanner.ConstantPool;
import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
import org.jenkinsci.constant_pool_scanner.ConstantType;
@@ -42,7 +46,6 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -57,15 +60,17 @@ import static org.jackhuang.hmcl.util.Lang.getOrDefault;
*/
public final class OptiFineInstallTask extends Task<Version> {
private final DefaultGameRepository gameRepository;
private final DefaultDependencyManager dependencyManager;
private final Version version;
private final OptiFineRemoteVersion remote;
private final Path installer;
private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>();
private final File dest;
private Path dest;
private final Library optiFineLibrary;
private final Library optiFineInstallerLibrary;
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {
this(dependencyManager, version, remoteVersion, null);
@@ -73,20 +78,21 @@ public final class OptiFineInstallTask extends Task<Version> {
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion, Path installer) {
this.dependencyManager = dependencyManager;
this.gameRepository = dependencyManager.getGameRepository();
this.version = version;
this.remote = remoteVersion;
this.installer = installer;
String mavenVersion = remote.getGameVersion() + "_" + remote.getSelfVersion();
optiFineLibrary = new Library(
"optifine", "OptiFine", mavenVersion, null, null,
optiFineLibrary = new Library(new Artifact("optifine", "OptiFine", mavenVersion));
optiFineInstallerLibrary = new Library(
new Artifact("optifine", "OptiFine", mavenVersion, "installer"), null,
new LibrariesDownloadInfo(new LibraryDownloadInfo(
"optifine/OptiFine/" + mavenVersion + "/OptiFine-" + mavenVersion + ".jar",
"optifine/OptiFine/" + mavenVersion + "/OptiFine-" + mavenVersion + "-installer.jar",
remote.getUrl()))
);
dest = dependencyManager.getGameRepository().getLibraryFile(version, optiFineLibrary);
}
@Override
@@ -96,18 +102,14 @@ public final class OptiFineInstallTask extends Task<Version> {
@Override
public void preExecute() throws Exception {
if (!Arrays.asList("net.minecraft.client.main.Main",
"net.minecraft.launchwrapper.Launch")
.contains(version.resolve(dependencyManager.getGameRepository()).getMainClass()))
throw new UnsupportedOptiFineInstallationException();
dest = Files.createTempFile("optifine-installer", ".jar");
if (installer == null) {
dependents.add(new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), dest)
dependents.add(new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), dest.toFile())
.setCacheRepository(dependencyManager.getCacheRepository())
.setCaching(true));
} else {
FileUtils.copyFile(installer, dest.toPath());
FileUtils.copyFile(installer, dest);
}
}
@@ -127,39 +129,57 @@ public final class OptiFineInstallTask extends Task<Version> {
}
@Override
public void execute() throws IOException {
public void execute() throws Exception {
List<Library> libraries = new LinkedList<>();
libraries.add(optiFineLibrary);
FileUtils.copyFile(dest, gameRepository.getLibraryFile(version, optiFineInstallerLibrary).toPath());
// Install launch wrapper modified by OptiFine
boolean hasLaunchWrapper = false;
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest.toPath())) {
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest)) {
if (Files.exists(fs.getPath("optifine/Patcher.class"))) {
int exitCode = SystemUtils.callExternalProcess(
JavaVersion.fromCurrentEnvironment().getBinary().toString(),
"-cp",
dest.toString(),
"optifine.Patcher",
gameRepository.getVersionJar(version).getAbsolutePath(),
dest.toString(),
gameRepository.getLibraryFile(version, optiFineLibrary).toString()
);
if (exitCode != 0)
throw new IllegalStateException("OptiFine patcher failed");
} else {
FileUtils.copyFile(dest, gameRepository.getLibraryFile(version, optiFineLibrary).toPath());
}
Path launchWrapperVersionText = fs.getPath("launchwrapper-of.txt");
if (Files.exists(launchWrapperVersionText)) {
String launchWrapperVersion = FileUtils.readText(launchWrapperVersionText).trim();
Path launchWrapperJar = fs.getPath("launchwrapper-of-" + launchWrapperVersion + ".jar");
Library launchWrapper = new Library("optifine", "launchwrapper-of", launchWrapperVersion);
Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper-of", launchWrapperVersion));
if (Files.exists(launchWrapperJar)) {
File launchWrapperFile = dependencyManager.getGameRepository().getLibraryFile(version, launchWrapper);
File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);
FileUtils.makeDirectory(launchWrapperFile.getAbsoluteFile().getParentFile());
FileUtils.copyFile(launchWrapperJar, launchWrapperFile.toPath());
hasLaunchWrapper = true;
libraries.add(0, launchWrapper);
libraries.add(launchWrapper);
}
}
}
if (!hasLaunchWrapper) {
libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12"));
libraries.add(new Library(new Artifact("net.minecraft", "launchwrapper", "1.12")));
}
setResult(new Version(
LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(),
remote.getSelfVersion(),
90000,
10000,
new Arguments().addGameArguments("--tweakClass", "optifine.OptiFineTweaker"),
"net.minecraft.launchwrapper.Launch",
libraries
@@ -186,7 +206,10 @@ public final class OptiFineInstallTask extends Task<Version> {
Optional<String> gameVersion = GameVersion.minecraftVersion(jar);
if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(fs.getPath("Config.class")), ConstantType.UTF8);
Path configClass = fs.getPath("Config.class");
if (!Files.exists(configClass)) configClass = fs.getPath("net/optifine/Config.class");
if (!Files.exists(configClass)) throw new IOException("Unrecognized installer");
ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(configClass), ConstantType.UTF8);
List<String> constants = new ArrayList<>();
pool.list(Utf8Constant.class).forEach(utf8 -> constants.add(utf8.get()));
String mcVersion = getOrDefault(constants, constants.indexOf("MC_VERSION") + 1, null);
@@ -17,7 +17,14 @@
*/
package org.jackhuang.hmcl.game;
import com.google.gson.*;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.JsonAdapter;
import org.jackhuang.hmcl.util.Immutable;
@@ -38,9 +45,34 @@ public final class Artifact {
private final String fileName;
private final String path;
public Artifact(String descriptor) {
this.descriptor = descriptor;
public Artifact(String group, String name, String version) {
this(group, name, version, null);
}
public Artifact(String group, String name, String version, String classifier) {
this(group, name, version, classifier, null);
}
public Artifact(String group, String name, String version, String classifier, String extension) {
this.group = group;
this.name = name;
this.version = version;
this.classifier = classifier;
this.extension = extension == null ? "jar" : extension;
String fileName = this.name + "-" + this.version;
if (classifier != null) fileName += "-" + this.classifier;
this.fileName = fileName + "." + this.extension;
this.path = String.format("%s/%s/%s/%s", this.group.replace(".", "/"), this.name, this.version, this.fileName);
// group:name:version:classifier@extension
String descriptor = String.format("%s:%s:%s", group, name, version);
if (classifier != null) descriptor += ":" + classifier;
if (!"jar".equals(this.extension)) descriptor += "@" + this.extension;
this.descriptor = descriptor;
}
public static Artifact fromDescriptor(String descriptor) {
String[] arr = descriptor.split(":", 4);
if (arr.length != 3 && arr.length != 4)
throw new IllegalArgumentException("Artifact name is malformed");
@@ -55,16 +87,7 @@ public final class Artifact {
throw new IllegalArgumentException("Artifact name is malformed");
}
this.group = arr[0].replace("\\", "/");
this.name = arr[1];
this.version = arr[2];
this.classifier = arr.length >= 4 ? arr[3] : null;
this.extension = ext == null ? "jar" : ext;
String fileName = this.name + "-" + this.version;
if (classifier != null) fileName += "-" + this.classifier;
this.fileName = fileName + "." + this.extension;
this.path = String.format("%s/%s/%s/%s", this.group.replace(".", "/"), this.name, this.version, this.fileName);
return new Artifact(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, ext);
}
public String getGroup() {
@@ -83,6 +106,10 @@ public final class Artifact {
return classifier;
}
public Artifact setClassifier(String classifier) {
return new Artifact(group, name, version, classifier, extension);
}
public String getExtension() {
return extension;
}
@@ -91,6 +118,8 @@ public final class Artifact {
return fileName;
}
public String getPath() { return path; }
public Path getPath(Path root) {
return root.resolve(path);
}
@@ -108,7 +137,7 @@ public final class Artifact {
@Override
public Artifact deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return json.isJsonPrimitive() ? new Artifact(json.getAsJsonPrimitive().getAsString()) : null;
return json.isJsonPrimitive() ? fromDescriptor(json.getAsJsonPrimitive().getAsString()) : null;
}
}
}
@@ -32,15 +32,15 @@ public class ClassicVersion extends Version {
super(true, "Classic", null, null, "${auth_player_name} ${auth_session} --workDir ${game_directory}",
null, "net.minecraft.client.Minecraft", null, null, null, null,
Arrays.asList(new ClassicLibrary("lwjgl"), new ClassicLibrary("jinput"), new ClassicLibrary("lwjgl_util")),
null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0, false, null);
null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0, false, false, null);
}
private static class ClassicLibrary extends Library {
public ClassicLibrary(String name) {
super("", "", "", null, null,
super(new Artifact("", "", ""), null,
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
null, null, null, null);
null, null, null, null, null, null);
}
}
@@ -94,7 +94,10 @@ public class DefaultGameRepository implements GameRepository {
@Override
public File getLibraryFile(Version version, Library lib) {
return new File(getBaseDirectory(), "libraries/" + lib.getPath());
if ("local".equals(lib.getHint()))
return new File(getVersionRoot(version.getId()), "libraries/" + lib.getFileName());
else
return new File(getBaseDirectory(), "libraries/" + lib.getPath());
}
public Path getArtifactFile(Version version, Artifact artifact) {
@@ -160,7 +163,7 @@ public class DefaultGameRepository implements GameRepository {
}
if (fromVersion.getId().equals(fromVersion.getJar()))
fromVersion = fromVersion.setJar(to);
fromVersion = fromVersion.setJar(null);
FileUtils.writeText(toJson, JsonUtils.GSON.toJson(fromVersion.setId(to)));
return true;
} catch (IOException | JsonParseException | VersionNotFoundException e) {
@@ -56,6 +56,10 @@ public interface GameRepository extends VersionProvider {
return getVersion(id).resolve(this);
}
default Version getResolvedPreservingPatchesVersion(String id) throws VersionNotFoundException {
return getVersion(id).resolvePreservingPatches(this);
}
/**
* How many version are there?
*/
@@ -17,11 +17,11 @@
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import java.io.File;
import java.io.Serializable;
import org.jackhuang.hmcl.util.platform.JavaVersion;
/**
*
* @author huangyuhui
@@ -31,6 +31,7 @@ public class LaunchOptions implements Serializable {
private File gameDir;
private JavaVersion java;
private String versionName;
private String versionType;
private String profileName;
private String minecraftArgs;
private String javaArgs;
@@ -71,6 +72,14 @@ public class LaunchOptions implements Serializable {
return versionName;
}
/**
* Will shown in the left bottom corner of the main menu of Minecraft.
* null if use Version.versionType.
*/
public String getVersionType() {
return versionType;
}
/**
* Don't know what the hell this is.
*/
@@ -215,6 +224,11 @@ public class LaunchOptions implements Serializable {
return this;
}
public Builder setVersionType(String versionType) {
options.versionType = versionType;
return this;
}
public Builder setProfileName(String profileName) {
options.profileName = profileName;
return this;
@@ -34,7 +34,7 @@ public final class LibrariesDownloadInfo {
private final Map<String, LibraryDownloadInfo> classifiers;
public LibrariesDownloadInfo(LibraryDownloadInfo artifact) {
this(artifact, Collections.emptyMap());
this(artifact, null);
}
public LibrariesDownloadInfo(LibraryDownloadInfo artifact, Map<String, LibraryDownloadInfo> classifiers) {
@@ -17,15 +17,16 @@
*/
package org.jackhuang.hmcl.game;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
import org.jackhuang.hmcl.util.gson.Validation;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -36,83 +37,68 @@ import java.util.Optional;
*
* @author huangyuhui
*/
@JsonAdapter(Library.Serializer.class)
public class Library implements Comparable<Library> {
@Immutable
public class Library implements Comparable<Library>, Validation {
private final String groupId;
private final String artifactId;
private final String version;
private final String classifier;
@SerializedName("name")
private final Artifact artifact;
private final String url;
private final LibrariesDownloadInfo downloads;
private transient final LibraryDownloadInfo download;
private final ExtractRules extract;
private final Map<OperatingSystem, String> natives;
private final List<CompatibilityRule> rules;
private final List<String> checksums;
private transient final String path;
@SerializedName(value = "hint", alternate = {"MMC-hint"})
private final String hint;
public Library(String groupId, String artifactId, String version) {
this(groupId, artifactId, version, null, null, null);
@SerializedName(value = "filename", alternate = {"MMC-filename"})
private final String fileName;
public Library(Artifact artifact) {
this(artifact, null, null);
}
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads) {
this(groupId, artifactId, version, classifier, url, downloads, null, null, null, null);
public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads) {
this(artifact, url, downloads, null, null, null, null, null, null);
}
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
if (classifier == null)
if (natives != null && natives.containsKey(OperatingSystem.CURRENT_OS))
this.classifier = natives.get(OperatingSystem.CURRENT_OS).replace("${arch}", Platform.PLATFORM.getBit());
else
this.classifier = null;
else
this.classifier = classifier;
public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules, String hint, String filename) {
this.artifact = artifact;
this.url = url;
this.downloads = downloads;
this.extract = extract;
this.natives = natives;
this.rules = rules;
this.checksums = checksums;
LibraryDownloadInfo temp = null;
if (downloads != null)
if (isNative())
temp = downloads.getClassifiers().get(this.classifier);
else
temp = downloads.getArtifact();
if (temp != null && temp.getPath() != null)
path = temp.getPath();
else
path = String.format("%s/%s/%s/%s-%s", groupId.replace(".", "/"), artifactId, version, artifactId, version)
+ (this.classifier == null ? "" : "-" + this.classifier) + ".jar";
download = new LibraryDownloadInfo(path,
Optional.ofNullable(temp).map(LibraryDownloadInfo::getUrl).orElse(Optional.ofNullable(url).orElse(Constants.DEFAULT_LIBRARY_URL) + path),
temp != null ? temp.getSha1() : null,
temp != null ? temp.getSize() : 0
);
this.hint = hint;
this.fileName = filename;
}
public String getGroupId() {
return groupId;
return artifact.getGroup();
}
public String getArtifactId() {
return artifactId;
return artifact.getName();
}
public String getName() {
return groupId + ":" + artifactId + ":" + version;
return artifact.toString();
}
public String getVersion() {
return version;
return artifact.getVersion();
}
public String getClassifier() {
if (artifact.getClassifier() == null)
if (natives != null && natives.containsKey(OperatingSystem.CURRENT_OS))
return natives.get(OperatingSystem.CURRENT_OS).replace("${arch}", Platform.PLATFORM.getBit());
else
return null;
else
return artifact.getClassifier();
}
public ExtractRules getExtract() {
@@ -127,12 +113,33 @@ public class Library implements Comparable<Library> {
return natives != null && appliesToCurrentEnvironment();
}
protected LibraryDownloadInfo getRawDownloadInfo() {
if (downloads != null) {
if (isNative())
return downloads.getClassifiers().get(getClassifier());
else
return downloads.getArtifact();
} else {
return null;
}
}
public String getPath() {
return path;
LibraryDownloadInfo temp = getRawDownloadInfo();
if (temp != null && temp.getPath() != null)
return temp.getPath();
else
return artifact.setClassifier(getClassifier()).getPath();
}
public LibraryDownloadInfo getDownload() {
return download;
LibraryDownloadInfo temp = getRawDownloadInfo();
String path = getPath();
return new LibraryDownloadInfo(path,
Optional.ofNullable(temp).map(LibraryDownloadInfo::getUrl).orElse(Optional.ofNullable(url).orElse(Constants.DEFAULT_LIBRARY_URL) + path),
temp != null ? temp.getSha1() : null,
temp != null ? temp.getSize() : 0
);
}
public List<String> getChecksums() {
@@ -143,8 +150,24 @@ public class Library implements Comparable<Library> {
return rules;
}
/**
* Hint for how to locate the library file.
* @return null for default, "local" for location in version/&lt;version&gt;/libraries/filename
*/
public String getHint() {
return hint;
}
/**
* Available when hint is "local"
* @return the filename of the local library in version/&lt;version&gt;/libraries/$filename
*/
public String getFileName() {
return fileName;
}
public boolean is(String groupId, String artifactId) {
return this.groupId.equals(groupId) && this.artifactId.equals(artifactId);
return getGroupId().equals(groupId) && getArtifactId().equals(artifactId);
}
@Override
@@ -175,58 +198,12 @@ public class Library implements Comparable<Library> {
}
public Library setClassifier(String classifier) {
return new Library(groupId, artifactId, version, classifier, url, downloads, checksums, extract, natives, rules);
return new Library(artifact.setClassifier(classifier), url, downloads, checksums, extract, natives, rules, hint, fileName);
}
public static Library fromName(String name) {
return fromName(name, null, null, null, null, null, null);
}
public static Library fromName(String name, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
String[] arr = name.split(":", 4);
if (arr.length != 3 && arr.length != 4)
throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier).");
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, checksums, extract, natives, rules);
}
public static class Serializer implements JsonDeserializer<Library>, JsonSerializer<Library> {
@Override
public Library deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
if (json == null || json == JsonNull.INSTANCE)
return null;
JsonObject jsonObject = json.getAsJsonObject();
if (!jsonObject.has("name"))
throw new JsonParseException("Library name not found.");
return fromName(
jsonObject.get("name").getAsString(),
jsonObject.has("url") ? jsonObject.get("url").getAsString() : null,
context.deserialize(jsonObject.get("downloads"), LibrariesDownloadInfo.class),
context.deserialize(jsonObject.get("checksums"), new TypeToken<List<String>>() {
}.getType()),
context.deserialize(jsonObject.get("extract"), ExtractRules.class),
context.deserialize(jsonObject.get("natives"), new TypeToken<Map<OperatingSystem, String>>() {
}.getType()),
context.deserialize(jsonObject.get("rules"), new TypeToken<List<CompatibilityRule>>() {
}.getType()));
}
@Override
public JsonElement serialize(Library src, Type type, JsonSerializationContext context) {
if (src == null)
return JsonNull.INSTANCE;
JsonObject obj = new JsonObject();
obj.addProperty("name", src.getName());
obj.addProperty("url", src.url);
obj.add("downloads", context.serialize(src.downloads));
obj.add("checksums", context.serialize(src.getChecksums()));
obj.add("extract", context.serialize(src.extract));
obj.add("natives", context.serialize(src.natives, new TypeToken<Map<OperatingSystem, String>>() {
}.getType()));
obj.add("rules", context.serialize(src.rules, new TypeToken<List<CompatibilityRule>>() {
}.getType()));
return obj;
}
@Override
public void validate() throws JsonParseException, TolerableValidationException {
if (artifact == null)
throw new JsonParseException("Library.name cannot be null");
}
}
@@ -27,12 +27,12 @@ import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.gson.Validation;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -66,16 +66,30 @@ public class Version implements Comparable<Version>, Validation {
private final Date time;
private final Date releaseTime;
private final Integer minimumLauncherVersion;
private final Boolean root;
private final Boolean hidden;
private final List<Version> patches;
private transient final boolean resolved;
public Version(String id, String version, int priority, Arguments arguments, String mainClass, List<Library> libraries) {
this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null);
public Version(String id) {
this(false, id, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, false, true, null);
}
public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, Integer minimumLauncherVersion, Boolean hidden, List<Version> patches) {
/**
* Constructor for patch
* @param id patch id
* @param version patch version
* @param priority patch priority
* @param arguments patch additional arguments
* @param mainClass main class to override
* @param libraries additional libraries
*/
public Version(String id, String version, int priority, Arguments arguments, String mainClass, List<Library> libraries) {
this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null, null);
}
public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, Integer minimumLauncherVersion, Boolean hidden, Boolean root, List<Version> patches) {
this.resolved = resolved;
this.id = id;
this.version = version;
@@ -87,8 +101,8 @@ public class Version implements Comparable<Version>, Validation {
this.jar = jar;
this.assetIndex = assetIndex;
this.assets = assets;
this.libraries = libraries == null ? Collections.emptyList() : new LinkedList<>(libraries);
this.compatibilityRules = compatibilityRules == null ? null : new LinkedList<>(compatibilityRules);
this.libraries = Lang.copyList(libraries);
this.compatibilityRules = Lang.copyList(compatibilityRules);
this.downloads = downloads == null ? null : new HashMap<>(downloads);
this.logging = logging == null ? null : new HashMap<>(logging);
this.type = type;
@@ -96,7 +110,8 @@ public class Version implements Comparable<Version>, Validation {
this.releaseTime = releaseTime == null ? null : (Date) releaseTime.clone();
this.minimumLauncherVersion = minimumLauncherVersion;
this.hidden = hidden;
this.patches = patches == null ? null : patches.isEmpty() ? null : new LinkedList<>(patches);
this.root = root;
this.patches = Lang.copyList(patches);
}
public Optional<String> getMinecraftArguments() {
@@ -157,10 +172,16 @@ public class Version implements Comparable<Version>, Validation {
return hidden == null ? false : hidden;
}
public boolean isRoot() { return root == null ? false : root; }
public boolean isResolved() {
return resolved;
}
public boolean isResolvedPreservingPatches() {
return inheritsFrom == null && !resolved;
}
public List<Version> getPatches() {
return patches == null ? Collections.emptyList() : patches;
}
@@ -202,7 +223,7 @@ public class Version implements Comparable<Version>, Validation {
return resolve(provider, new HashSet<>()).setResolved();
}
protected Version merge(Version parent) {
protected Version merge(Version parent, boolean isPatch) {
return new Version(
true,
id,
@@ -224,7 +245,8 @@ public class Version implements Comparable<Version>, Validation {
releaseTime,
Lang.merge(minimumLauncherVersion, parent.minimumLauncherVersion, Math::max),
hidden,
Lang.merge(parent.patches, patches));
false,
isPatch ? parent.patches : Lang.merge(Lang.merge(parent.patches, Collections.singleton(toPatch())), patches));
}
protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
@@ -239,7 +261,7 @@ public class Version implements Comparable<Version>, Validation {
thisVersion = this.jar == null ? this.setJar(id) : this;
} else {
// It is supposed to auto install an version in getVersion.
thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar));
thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false);
}
}
@@ -249,7 +271,41 @@ public class Version implements Comparable<Version>, Validation {
.sorted(Comparator.comparing(Version::getPriority))
.collect(Collectors.toList());
for (Version patch : sortedPatches) {
thisVersion = patch.setJar(null).merge(thisVersion);
thisVersion = patch.setJar(null).merge(thisVersion, true);
}
}
return thisVersion.setId(id);
}
private Version toPatch() {
return this.clearPatches().setHidden(true).setId("resolved." + getId());
}
/**
* Resolve the version preserving all dependencies and patches.
*/
public Version resolvePreservingPatches(VersionProvider provider) throws VersionNotFoundException {
return resolvePreservingPatches(provider, new HashSet<>());
}
protected Version mergePreservingPatches(Version parent) {
return parent.addPatch(toPatch()).addPatches(patches);
}
protected Version resolvePreservingPatches(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
Version thisVersion = isRoot() ? this : new Version(id).addPatch(toPatch()).addPatches(getPatches());
if (inheritsFrom == null) {
// keep thisVersion
} else {
// To maximize the compatibility.
if (!resolvedSoFar.add(id)) {
Logging.LOG.log(Level.WARNING, "Found circular dependency versions: " + resolvedSoFar);
// keep thisVersion
} else {
// It is supposed to auto install an version in getVersion.
thisVersion = mergePreservingPatches(provider.getVersion(inheritsFrom).resolvePreservingPatches(provider, resolvedSoFar));
}
}
@@ -257,58 +313,78 @@ public class Version implements Comparable<Version>, Validation {
}
private Version setResolved() {
return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
private Version setHidden(Boolean hidden) {
return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setId(String id) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setVersion(String version) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setPriority(Integer priority) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setMinecraftArguments(String minecraftArguments) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setArguments(Arguments arguments) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setMainClass(String mainClass) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setInheritsFrom(String inheritsFrom) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setJar(String jar) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setLibraries(List<Library> libraries) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version setLogging(Map<DownloadType, LoggingInfo> logging) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, patches);
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version addPatch(Version patch) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, Lang.merge(patches, Collections.singleton(patch)));
public Version setPatches(List<Version> patches) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches);
}
public Version addPatch(Version... additional) {
return addPatches(Arrays.asList(additional));
}
public Version addPatches(List<Version> additional) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, Lang.merge(patches, additional));
}
public Version clearPatches() {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root,null);
}
public Version removePatchById(String patchId) {
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden,
return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root,
patches == null ? null : patches.stream().filter(patch -> !patchId.equals(patch.getId())).collect(Collectors.toList()));
}
public boolean hasPatch(String patchId) {
return patches != null && patches.stream().anyMatch(patch -> patchId.equals(patch.getId()));
}
@Override
public int hashCode() {
return id.hashCode();
@@ -259,7 +259,7 @@ public class DefaultLauncher extends Launcher {
pair("${auth_uuid}", UUIDTypeAdapter.fromUUID(authInfo.getUUID())),
pair("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
pair("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
pair("${version_type}", version.getType().getId()),
pair("${version_type}", Optional.ofNullable(options.getVersionType()).orElse(version.getType().getId())),
pair("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()),
pair("${user_type}", "mojang"),
pair("${assets_index_name}", version.getAssetIndex().getId()),
@@ -72,6 +72,8 @@ public class ModpackUpdateTask extends Task<Void> {
repository.removeVersionFromDisk(id);
FileUtils.copyDirectory(backupFolder, repository.getVersionRoot(id).toPath());
repository.refreshVersionsAsync().start();
}
}
}
@@ -78,7 +78,7 @@ public class MultiMCModpackExportTask extends Task<Void> {
return false;
});
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedVersion(versionId));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId))
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + versionId));
List<MultiMCManifest.MultiMCManifestComponent> components = new ArrayList<>();
@@ -21,16 +21,14 @@ import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.ModpackInstallTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -41,6 +39,7 @@ import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@@ -84,6 +83,12 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
if (c.getVersion() != null)
builder.version("liteloader", c.getVersion());
});
Optional<MultiMCManifest.MultiMCManifestComponent> fabric = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("net.fabricmc.fabric-loader")).findAny();
fabric.ifPresent(c -> {
if (c.getVersion() != null)
builder.version("fabric", c.getVersion());
});
}
dependents.add(builder.buildAsync());
@@ -145,20 +150,29 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
for (Path patchJson : Files.newDirectoryStream(patches)) {
if (patchJson.toString().endsWith(".json")) {
// If json is malformed, we should stop installing this modpack instead of skipping it.
MultiMCInstancePatch patch = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
MultiMCInstancePatch multiMCPatch = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
for (String arg : patch.getTweakers())
builder.addArgument("--tweakClass", arg);
List<String> arguments = new ArrayList<>();
for (String arg : multiMCPatch.getTweakers()) {
arguments.add("--tweakClass");
arguments.add(arg);
}
version = builder.build()
.setLibraries(Lang.merge(version.getLibraries(), patch.getLibraries()))
.setMainClass(patch.getMainClass());
Version patch = new Version(multiMCPatch.getName(), multiMCPatch.getVersion(), 1, new Arguments().addGameArguments(arguments), multiMCPatch.getMainClass(), multiMCPatch.getLibraries());
version = version.addPatch(patch);
}
}
Path libraries = root.resolve("libraries");
if (Files.exists(libraries))
FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries"));
Path jarmods = root.resolve("jarmods");
if (Files.exists(jarmods))
FileUtils.copyDirectory(jarmods, repository.getVersionRoot(name).toPath().resolve("jarmods"));
}
dependencies.add(new MaintainTask(version).thenComposeAsync(repository::save));
dependencies.add(repository.save(version));
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
}
@@ -190,6 +190,7 @@ public abstract class Task<T> {
*/
public Task<T> storeTo(Consumer<T> action) {
this.resultConsumer = action;
action.accept(getResult());
return this;
}
@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import java.util.*;
@@ -57,6 +58,15 @@ public final class Lang {
return Collections.unmodifiableList(Arrays.asList(elements));
}
public static boolean test(ExceptionalRunnable<?> r) {
try {
r.run();
return true;
} catch (Exception e) {
return false;
}
}
public static <E extends Exception> boolean test(ExceptionalSupplier<Boolean, E> r) {
try {
return r.get();
@@ -119,6 +129,10 @@ public final class Lang {
return result;
}
public static <T> List<T> copyList(List<T> list) {
return list == null ? null : list.isEmpty() ? null : new ArrayList<>(list);
}
public static void executeDelayed(Runnable runnable, TimeUnit timeUnit, long timeout, boolean isDaemon) {
thread(() -> {
try {
@@ -199,7 +199,7 @@ public final class FileUtils {
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path destFile = dest.resolve(src.relativize(file));
Path destFile = dest.resolve(src.relativize(file).toString());
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
@@ -207,7 +207,7 @@ public final class FileUtils {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path destDir = dest.resolve(src.relativize(dir));
Path destDir = dest.resolve(src.relativize(dir).toString());
Files.createDirectories(destDir);
return FileVisitResult.CONTINUE;
@@ -17,9 +17,8 @@
*/
package org.jackhuang.hmcl.util.platform;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.util.Logging.LOG;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.BufferedReader;
import java.io.IOException;
@@ -35,8 +34,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.util.Logging.LOG;
/**
* Represents a Java installation.
@@ -115,11 +115,6 @@ public final class JavaVersion {
Platform platform = Platform.BIT_32;
String version = null;
// javaw is only used on windows
if ("javaw.exe".equalsIgnoreCase(executable.getFileName().toString())) {
executable = executable.resolveSibling("java.exe");
}
executable = executable.toRealPath();
Process process = new ProcessBuilder(executable.toString(), "-version").start();
@@ -0,0 +1,42 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.platform;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
public final class SystemUtils {
private SystemUtils() {}
public static int callExternalProcess(String... command) throws IOException, InterruptedException {
return callExternalProcess(Arrays.asList(command));
}
public static int callExternalProcess(List<String> command) throws IOException, InterruptedException {
Process process = new ProcessBuilder(command).start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
for (String line; (line = reader.readLine()) != null;) {
System.out.println(line);
}
}
return process.waitFor();
}
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import javafx.embed.swing.JFXPanel;
@@ -57,7 +74,6 @@ public class TaskTest {
Assert.assertTrue("withRunAsync should be executed", bool.get());
}
@Test
public void testThenAccept() {
new JFXPanel(); // init JavaFX Toolkit
AtomicBoolean flag = new AtomicBoolean();
@@ -89,7 +105,6 @@ public class TaskTest {
Assert.assertNull("CancellationException should not be recorded.", task.getException());
}
@Test
public void testRejectedExecutionException() {
Schedulers.defaultScheduler();
Schedulers.shutdown();