Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameInstanceManifest;
import org.jackhuang.hmcl.game.GameInstancePatch;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.file.Files;
Expand Down Expand Up @@ -155,8 +157,50 @@ public Task<GameInstanceManifest> installLibraryAsync(String gameVersion, GameIn
.withStage(String.format("hmcl.install.%s:%s", libraryId, libraryVersion));
}

/// Checks if a matching library patch is already installed for the target instance on disk and intact.
///
/// @param baseVersion the base game instance manifest
/// @param libraryVersion the remote library version to install
/// @return the existing intact matching patch, or `null` if the library is not installed, version mismatches, or files need repair
private @Nullable GameInstancePatch getIntactMatchingPatch(GameInstanceManifest baseVersion, RemoteVersion libraryVersion) {
if (LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(libraryVersion.getLibraryId()) || !repository.hasInstance(baseVersion.id())) {
return null;
}

try {
GameInstanceManifest existingManifest = repository.getInstanceManifest(baseVersion.id());
Comment thread
fatelove42 marked this conversation as resolved.
String currentGameVersion = repository.getGameVersion(existingManifest).orElse(null);
if (!java.util.Objects.equals(currentGameVersion, libraryVersion.getGameVersion())) {
Comment thread
fatelove42 marked this conversation as resolved.
return null;
}

GameInstancePatch matchingPatch = existingManifest.getPatches().stream()
.filter(patch -> libraryVersion.getLibraryId().equals(patch.id())
&& java.util.Objects.equals(patch.version(), libraryVersion.getSelfVersion()))
.findFirst()
.orElse(null);
if (matchingPatch == null) {
return null;
}

List<Library> patchLibraries = matchingPatch.libraries();
boolean needsRepair = patchLibraries != null && patchLibraries.stream()
.filter(Library::appliesToCurrentEnvironment)
.anyMatch(lib -> GameLibrariesTask.shouldDownloadLibrary(repository, existingManifest, lib, true));

return needsRepair ? null : matchingPatch;
} catch (Exception ignored) {
return null;
}
}

@Override
public Task<GameInstanceManifest> installLibraryAsync(GameInstanceManifest baseVersion, RemoteVersion libraryVersion) {
GameInstancePatch matchingPatch = getIntactMatchingPatch(baseVersion, libraryVersion);
if (matchingPatch != null) {
return Task.completed(baseVersion.addPatch(matchingPatch));
}

AtomicReference<GameInstanceManifest> removedLibraryVersion = new AtomicReference<>();

return removeLibraryAsync(baseVersion, libraryVersion.getLibraryId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@
*/
package org.jackhuang.hmcl.download;

import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.game.GameInstanceManifest;
import org.jackhuang.hmcl.game.GameInstancePatch;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
*
Expand All @@ -44,32 +50,78 @@ public DefaultDependencyManager getDependencyManager() {
public Task<?> buildAsync() {
var hints = new ArrayList<Task.StagesHint>();

var repository = dependencyManager.getGameRepository();
boolean isUpdate = repository.hasInstance(name);
GameInstanceManifest preUpdateManifest = null;
String preUpdateGameVersion = null;
if (isUpdate) {
try {
preUpdateManifest = repository.getInstanceManifest(name);
preUpdateGameVersion = repository.getGameVersion(preUpdateManifest).orElse(null);
} catch (Exception ignored) {
}
}

Task<GameInstanceManifest> libraryTask = Task.supplyAsync(() -> new GameInstanceManifest(name));
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "game", gameVersion));
hints.add(new Task.StagesHint("hmcl.install.game:" + gameVersion));
hints.add(new Task.StagesHint("hmcl.install.libraries"));
hints.add(new Task.StagesHint("hmcl.install.assets"));

for (Map.Entry<String, String> entry : toolVersions.entrySet()) {
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
GameInstancePatch matchingPatch = getIntactLoaderPatch(preUpdateManifest, preUpdateGameVersion, entry.getKey(), entry.getValue());
if (matchingPatch != null) {
libraryTask = libraryTask.thenApplyAsync(version -> version.addPatch(matchingPatch));
} else {
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
}
hints.add(new Task.StagesHint(String.format("hmcl.install.%s:%s", entry.getKey(), entry.getValue())));
}

for (RemoteVersion remoteVersion : remoteVersions) {
libraryTask = libraryTask.thenComposeAsync(version -> dependencyManager.installLibraryAsync(version, remoteVersion));
GameInstancePatch matchingPatch = getIntactLoaderPatch(preUpdateManifest, preUpdateGameVersion, remoteVersion.getLibraryId(), remoteVersion.getSelfVersion());
if (matchingPatch != null) {
libraryTask = libraryTask.thenApplyAsync(version -> version.addPatch(matchingPatch));
} else {
libraryTask = libraryTask.thenComposeAsync(version -> dependencyManager.installLibraryAsync(version, remoteVersion));
}
hints.add(new Task.StagesHint(String.format("hmcl.install.%s:%s", remoteVersion.getLibraryId(), remoteVersion.getSelfVersion())));
}

var repository = dependencyManager.getGameRepository();
boolean isUpdate = repository.hasInstance(name);

return libraryTask.thenComposeAsync(repository::saveAsync).whenComplete(exception -> {
if (exception != null && !isUpdate) {
repository.removeInstanceFromDisk(name);
}
}).withStagesHints(hints);
}

/// Checks if a matching loader patch was installed prior to the rebuild for the same game version and is intact.
private @Nullable GameInstancePatch getIntactLoaderPatch(
@Nullable GameInstanceManifest preUpdateManifest,
@Nullable String preUpdateGameVersion,
String libraryId,
String libraryVersion) {
if (preUpdateManifest == null || !Objects.equals(gameVersion, preUpdateGameVersion)) {
return null;
}

GameInstancePatch matchingPatch = preUpdateManifest.getPatches().stream()
.filter(patch -> libraryId.equals(patch.id()) && Objects.equals(patch.version(), libraryVersion))
.findFirst()
.orElse(null);
if (matchingPatch == null) {
return null;
}

var repository = dependencyManager.getGameRepository();
List<Library> patchLibraries = matchingPatch.libraries();
boolean needsRepair = patchLibraries != null && patchLibraries.stream()
.filter(Library::appliesToCurrentEnvironment)
.anyMatch(lib -> GameLibrariesTask.shouldDownloadLibrary(repository, preUpdateManifest, lib, true));

return needsRepair ? null : matchingPatch;
}

private ExceptionalFunction<GameInstanceManifest, Task<GameInstanceManifest>, ?> libraryTaskHelper(String gameVersion, String libraryId, String libraryVersion) {
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, libraryVersion);
}
Expand Down