Skip to content
Open
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,75 @@ public Task<GameInstanceManifest> installLibraryAsync(String gameVersion, GameIn
.withStage(String.format("hmcl.install.%s:%s", libraryId, libraryVersion));
}

/// Checks whether a resolved [Library] belongs to the given loader type.
///
/// The mapping must stay consistent with [LibraryAnalyzer.LibraryType]'s
/// group-id patterns so that the file-integrity check covers the correct jars.
///
/// @param library a library entry from the resolved launch manifest
/// @param libraryId the patch id of the loader (e.g. `"forge"`, `"fabric"`)
/// @return `true` if the library's groupId matches the loader
private static boolean isLoaderLibrary(Library library, String libraryId) {
if (library == null || library.groupId() == null) return false;
String groupId = library.groupId().toLowerCase(java.util.Locale.ROOT);
String targetId = libraryId.toLowerCase(java.util.Locale.ROOT);
return switch (targetId) {
case "forge" -> groupId.startsWith("net.minecraftforge");
case "neoforge" -> groupId.startsWith("net.neoforged");
case "optifine" -> "optifine".equals(groupId) || "net.optifine".equals(groupId);
case "fabric" -> groupId.startsWith("net.fabricmc");
case "quilt" -> groupId.startsWith("org.quiltmc");
case "legacyfabric" -> groupId.startsWith("net.legacyfabric") || groupId.startsWith("net.fabricmc");
case "liteloader" -> "com.mumfrey".equals(groupId);
case "cleanroom" -> groupId.startsWith("com.cleanroommc") || groupId.startsWith("zone.rong");
default -> groupId.contains(targetId);
};
}

/// 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 (!repository.hasInstance(baseVersion.id())) {
return null;
}

try {
GameInstanceManifest existingManifest = repository.getInstanceManifest(baseVersion.id());
String currentGameVersion = repository.getGameVersion(existingManifest).orElse(null);
if (!java.util.Objects.equals(currentGameVersion, libraryVersion.getGameVersion())) {
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;
}

GameInstanceManifest.Resolved resolved = repository.getResolvedInstanceManifest(baseVersion.id());
boolean needsRepair = resolved.launchManifest().getLibraries().stream()
.filter(lib -> isLoaderLibrary(lib, libraryVersion.getLibraryId()))
.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