From 34c172d9c6bf927d1e71d500e606ef0753127ac0 Mon Sep 17 00:00:00 2001 From: skidam Date: Fri, 16 Jan 2026 13:30:37 +0100 Subject: [PATCH 1/2] Unify dependency resolution with a single Directed Acyclic Graph algorithm --- .../loader/LoaderManagerService.java | 6 - .../loader/ModpackLoaderService.java | 3 - .../loader/NullLoaderManager.java | 15 +- .../loader/NullModpackLoader.java | 7 - .../utils/FileInspection.java | 595 +++++++----------- .../utils/WorkaroundUtil.java | 11 +- .../utils/sort/DependencyOptimizer.java | 131 ++++ .../client/ModpackUpdater.java | 57 +- .../client/ModpackUtils.java | 175 +----- .../loader/LoaderManager.java | 13 - .../mods/ModpackLoader.java | 6 - .../mods/ModpackLoader15.java | 175 ------ .../mods/ModpackLoader16.java | 174 ----- .../loader/LoaderManager.java | 77 --- .../mods/ModpackLoader.java | 6 - .../loader/LoaderManager.java | 83 --- .../mods/ModpackLoader.java | 7 +- .../loader/LoaderManager.java | 83 --- .../mods/ModpackLoader.java | 5 - .../loader/LoaderManager.java | 91 --- .../mods/ModpackLoader.java | 5 - .../loader/LoaderManager.java | 91 --- .../mods/ModpackLoader.java | 5 - .../loader/LoaderManager.java | 91 --- .../mods/ModpackLoader.java | 5 - 25 files changed, 399 insertions(+), 1518 deletions(-) create mode 100644 core/src/main/java/pl/skidam/automodpack_core/utils/sort/DependencyOptimizer.java diff --git a/core/src/main/java/pl/skidam/automodpack_core/loader/LoaderManagerService.java b/core/src/main/java/pl/skidam/automodpack_core/loader/LoaderManagerService.java index 78b2ac6a4..c6a516205 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/loader/LoaderManagerService.java +++ b/core/src/main/java/pl/skidam/automodpack_core/loader/LoaderManagerService.java @@ -1,16 +1,10 @@ package pl.skidam.automodpack_core.loader; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.util.Collection; - public interface LoaderManagerService { enum ModPlatform { FABRIC, QUILT, FORGE, NEOFORGE } enum EnvironmentType { CLIENT, SERVER, UNIVERSAL } ModPlatform getPlatformType(); - Collection getModList(); - boolean isModLoaded(String modId); String getLoaderVersion(); EnvironmentType getEnvironmentType(); boolean isDevelopmentEnvironment(); diff --git a/core/src/main/java/pl/skidam/automodpack_core/loader/ModpackLoaderService.java b/core/src/main/java/pl/skidam/automodpack_core/loader/ModpackLoaderService.java index c8a98183c..ffb0d7493 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/loader/ModpackLoaderService.java +++ b/core/src/main/java/pl/skidam/automodpack_core/loader/ModpackLoaderService.java @@ -1,11 +1,8 @@ package pl.skidam.automodpack_core.loader; -import pl.skidam.automodpack_core.utils.FileInspection; - import java.nio.file.Path; import java.util.List; public interface ModpackLoaderService { void loadModpack(List modpackMods); - List getModpackNestedConflicts(Path modpackDir); // Returns list of mods from the modpack Dir that are conflicting with the mods from standard mods dir } diff --git a/core/src/main/java/pl/skidam/automodpack_core/loader/NullLoaderManager.java b/core/src/main/java/pl/skidam/automodpack_core/loader/NullLoaderManager.java index 8adb15881..9f619d0ce 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/loader/NullLoaderManager.java +++ b/core/src/main/java/pl/skidam/automodpack_core/loader/NullLoaderManager.java @@ -1,25 +1,12 @@ package pl.skidam.automodpack_core.loader; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.util.Collection; - +// TODO remove this, wtf is this even for public class NullLoaderManager implements LoaderManagerService { @Override public ModPlatform getPlatformType() { return null; } - @Override - public boolean isModLoaded(String modId) { - return false; - } - - @Override - public Collection getModList() { - return null; - } - @Override public String getLoaderVersion() { return null; diff --git a/core/src/main/java/pl/skidam/automodpack_core/loader/NullModpackLoader.java b/core/src/main/java/pl/skidam/automodpack_core/loader/NullModpackLoader.java index 47d3c1fb1..1bb9f761c 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/loader/NullModpackLoader.java +++ b/core/src/main/java/pl/skidam/automodpack_core/loader/NullModpackLoader.java @@ -1,7 +1,5 @@ package pl.skidam.automodpack_core.loader; -import pl.skidam.automodpack_core.utils.FileInspection; - import java.nio.file.Path; import java.util.List; @@ -11,9 +9,4 @@ public class NullModpackLoader implements ModpackLoaderService { public void loadModpack(List modpackMods) { throw new AssertionError("Loader class not found"); } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - throw new AssertionError("Loader class not found"); - } } diff --git a/core/src/main/java/pl/skidam/automodpack_core/utils/FileInspection.java b/core/src/main/java/pl/skidam/automodpack_core/utils/FileInspection.java index 8a0bbb99a..cbe78179b 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/utils/FileInspection.java +++ b/core/src/main/java/pl/skidam/automodpack_core/utils/FileInspection.java @@ -10,10 +10,7 @@ import pl.skidam.automodpack_core.GlobalVariables; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -30,25 +27,14 @@ public class FileInspection { private static final Gson GSON = new Gson(); private static final String LOADER = GlobalVariables.LOADER; - public static boolean isMod(Path file) { - if (!file.getFileName().toString().endsWith(".jar") || !Files.exists(file)) { - return false; - } - - try (FileSystem fs = FileSystems.newFileSystem(file)) { - return getModID(fs) != null || hasSpecificServices(fs); - } catch (IOException e) { - return false; - } - } + public record HashPathPair(String hash, Path path) {} - public record Mod(String modID, String hash, Collection providesIDs, String modVersion, Path modPath, LoaderManagerService.EnvironmentType environmentType, Collection dependencies) {} - public record HashPathPair(String hash, Path path) { } - private static final Map modCache = new HashMap<>(); + public record Mod(Set IDs, String hash, String version, Path path, Set deps, Set nestedMods) {} + private record ModMetadata(String modId, String version, Set provides, Set deps, LoaderManagerService.EnvironmentType environment) {} + // TODO read from cache public static Mod getMod(Path file) { - if (!Files.isRegularFile(file)) return null; - if (!file.getFileName().toString().endsWith(".jar")) return null; + if (isJarInvalid(file)) return null; String hash = CustomFileUtils.getHash(file); if (hash == null) { @@ -56,435 +42,310 @@ public static Mod getMod(Path file) { return null; } - HashPathPair hashPathPair = new HashPathPair(hash, file); - if (modCache.containsKey(hashPathPair)) { - return modCache.get(hashPathPair); - } + try (FileSystem fs = FileSystems.newFileSystem(file)) { + ModMetadata meta = getModMetadata(fs); - for (Mod mod : GlobalVariables.LOADER_MANAGER.getModList()) { - if (hash.equals(mod.hash)) { - modCache.put(hashPathPair, mod); - return mod; - } - } + if (meta != null && meta.modId() != null) { + Set ids = new HashSet<>(meta.provides()); + ids.add(meta.modId()); - // Open FS once for all metadata extractions - try (FileSystem fs = FileSystems.newFileSystem(file)) { - String modId = (String) getModInfo(fs, "modId"); + Set nestedMods = scanForNestedMods(fs); - if (modId != null) { - String modVersion = (String) getModInfo(fs, "version"); - LoaderManagerService.EnvironmentType environmentType = (LoaderManagerService.EnvironmentType) getModInfo(fs, "environment"); - Set dependencies = getModDependencies(fs); - Set providesIDs = getProvidedIDs(fs); - - if (modVersion != null && dependencies != null) { - var mod = new Mod(modId, hash, providesIDs, modVersion, file, environmentType, dependencies); - modCache.put(hashPathPair, mod); - return mod; + if (meta.version() != null) { + return new Mod(ids, hash, meta.version(), file, meta.deps(), nestedMods); } - - LOGGER.error("Not enough mod information for file: {} modId: {}, modVersion: {}, dependencies: {}", file, modId, modVersion, dependencies); + LOGGER.error("Incomplete mod info for file: {} (ID: {}, Ver: {})", file, meta.modId(), meta.version()); } } catch (IOException e) { - LOGGER.debug("Failed to get mod info for file: {}", file); + LOGGER.debug("Failed to inspect mod file: {}", file); } - return null; } - private static final Set services = Set.of( - "META-INF/services/net.minecraftforge.forgespi.locating.IModLocator", - "META-INF/services/net.minecraftforge.forgespi.locating.IDependencyLocator", - "META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider", - "META-INF/services/net.neoforged.neoforgespi.locating.IModLocator", - "META-INF/services/net.neoforged.neoforgespi.locating.IDependencyLocator", - "META-INF/services/net.neoforged.neoforgespi.locating.IModLanguageLoader", - "META-INF/services/net.neoforged.neoforgespi.locating.IModFileCandidateLocator", - "META-INF/services/net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper" - ); - - // Checks for neo/forge mod locators - public static boolean isModCompatible(Path file) { - if (!file.getFileName().toString().endsWith(".jar") || !Files.exists(file)) { - return false; - } - + public static boolean isMod(Path file) { + if (isJarInvalid(file)) return false; try (FileSystem fs = FileSystems.newFileSystem(file)) { - String entryPathString = switch (LOADER) { - case "neoforge" -> "META-INF/neoforge.mods.toml"; - case "fabric" -> "fabric.mod.json"; - case "forge" -> "META-INF/mods.toml"; - case "quilt" -> "quilt.mod.json"; - default -> null; - }; - - if (entryPathString != null && Files.exists(fs.getPath(entryPathString))) { - return true; - } - - if ("forge".equals(LOADER) || "neoforge".equals(LOADER)) { - if (hasSpecificServices(fs)) { - return true; - } - } - + return getModMetadata(fs) != null || hasSpecificServices(fs); } catch (IOException e) { - // Ignore - } - - return false; - } - - public static boolean hasSpecificServices(Path file) { - if (!file.getFileName().toString().endsWith(".jar") || !Files.exists(file)) { return false; } - - try (FileSystem fs = FileSystems.newFileSystem(file)) { - return hasSpecificServices(fs); - } catch (IOException e) { - LOGGER.error("Error reading file {}: {}", file, e.getMessage()); - } - return false; } - public static boolean hasSpecificServices(FileSystem fs) { - // Direct Service Lookup (Fast) - for (String service : services) { - if (Files.exists(fs.getPath(service))) { - return true; - } - } - - // Jar-in-Jar Scan (Slower) - Path jarJarDir = fs.getPath("META-INF", "jarjar"); - if (!Files.exists(jarJarDir)) { - return false; - } - - try (Stream walk = Files.walk(jarJarDir, 1)) { - for (Path nestedJarPath : walk.toList()) { - // Skip non-jar entries - if (nestedJarPath.equals(jarJarDir) || !nestedJarPath.toString().endsWith(".jar")) { - continue; - } + public static boolean isModCompatible(Path file) { + if (isJarInvalid(file)) return false; - // Optimization: Use Files.newInputStream directly for nested zip entries - try (InputStream inputStream = Files.newInputStream(nestedJarPath); - ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + try (FileSystem fs = FileSystems.newFileSystem(file)) { + Path metaPath = getMetadataPath(fs); + if (metaPath != null) return true; - ZipEntry nestedEntry; - while ((nestedEntry = zipInputStream.getNextEntry()) != null) { - if (services.contains(nestedEntry.getName())) { - return true; - } - } - } catch (IOException e) { - LOGGER.error("Error reading nested JAR in {}: {}", nestedJarPath, e.getMessage()); - } + if ("forge".equals(LOADER) || "neoforge".equals(LOADER)) { + return hasSpecificServices(fs); } } catch (IOException e) { - LOGGER.error("Error examining JarJar in {}", fs, e); + LOGGER.error("Error examining JarJar in {}", e); } - return false; } - public static Path getMetadataPath(FileSystem fs) { - String preferredEntry = switch (LOADER) { - case "neoforge" -> "META-INF/neoforge.mods.toml"; - case "fabric" -> "fabric.mod.json"; - case "forge" -> "META-INF/mods.toml"; - case "quilt" -> "quilt.mod.json"; - default -> null; - }; - - if (preferredEntry != null) { - Path path = fs.getPath(preferredEntry); - if (Files.exists(path)) return path; - } - - String[] fallbackEntries = { - "META-INF/neoforge.mods.toml", - "fabric.mod.json", - "META-INF/mods.toml", - "quilt.mod.json" - }; - - for (String entryName : fallbackEntries) { - if (entryName.equals(preferredEntry)) continue; - - Path path = fs.getPath(entryName); - if (Files.exists(path)) return path; - } - - return null; - } - public static String getModVersion(Path file) { - return (String) getModInfo(file, "version"); + return extractBasicInfo(file, ModMetadata::version); } public static String getModID(Path file) { - return (String) getModInfo(file, "modId"); + return extractBasicInfo(file, ModMetadata::modId); } public static LoaderManagerService.EnvironmentType getModEnvironment(Path file) { - return (LoaderManagerService.EnvironmentType) getModInfo(file, "environment"); + return extractBasicInfo(file, ModMetadata::environment); } - private static String getModID(FileSystem fs) { - return (String) getModInfo(fs, "modId"); + private static boolean isJarInvalid(Path file) { + return file == null || !Files.exists(file) || !file.getFileName().toString().endsWith(".jar"); } - @SuppressWarnings("unchecked") - private static Set getProvidedIDs(FileSystem fs) { - return (Set) getModInfo(fs, "provides"); + private static T extractBasicInfo(Path file, java.util.function.Function extractor) { + if (isJarInvalid(file)) return null; + try (FileSystem fs = FileSystems.newFileSystem(file)) { + ModMetadata meta = getModMetadata(fs); + return meta != null ? extractor.apply(meta) : null; + } catch (IOException e) { + LOGGER.error("Error reading mod file {}: {}", file, e.getMessage()); + } + return null; } - @SuppressWarnings("unchecked") - private static Set getModDependencies(FileSystem fs) { - return (Set) getModInfo(fs, "dependencies"); + // TODO optimize it by caching and scanning only defined paths + private static Set scanForNestedMods(FileSystem parentFs) { + Set nestedMods = new HashSet<>(); + try (Stream walk = Files.walk(parentFs.getPath("/"))) { + for (Path path : walk.toList()) { + if (path.toString().endsWith(".jar") && !path.equals(parentFs.getPath("/"))) { + try (InputStream is = Files.newInputStream(path)) { + Mod nested = readModFromStream(path, is); + if (nested != null) nestedMods.add(nested); + } catch (IOException e) { + LOGGER.debug("Skipping unreadable nested jar: {}", path); + } + } + } + } catch (IOException e) { + LOGGER.error("Error scanning nested mods: {}", e.getMessage()); + } + return nestedMods; } - private static boolean isBasicInfo(String infoType) { - return "version".equals(infoType) || "modId".equals(infoType) || "environment".equals(infoType); - } + /** + * Reads a JAR from an InputStream (recursively) without mounting it as a FileSystem. + */ + private static Mod readModFromStream(Path virtualPath, InputStream is) { + // ZipInputStream must NOT close the underlying stream if it's a child stream + ZipInputStream zis = new ZipInputStream(is); + ZipEntry entry; + ModMetadata metadata = null; + Set nestedChildren = new HashSet<>(); - private static Object getModInfo(Path file, String infoType) { - if (!file.getFileName().toString().endsWith(".jar") || !Files.exists(file)) { - return isBasicInfo(infoType) ? null : Set.of(); - } + try { + while ((entry = zis.getNextEntry()) != null) { + String name = entry.getName(); - try (FileSystem fs = FileSystems.newFileSystem(file)) { - return getModInfo(fs, infoType); + if (isMetadataFilename(name)) { + // Prevent reader from closing the ZipInputStream + BufferedReader reader = new BufferedReader(new InputStreamReader(new FilterInputStream(zis) { + @Override public void close() {} + })); + + if (name.endsWith(".toml")) metadata = parseTomlMetadata(reader); + else metadata = parseJsonMetadata(reader); + } + else if (name.endsWith(".jar")) { + // Wrap ZIS to protect current stream position + Mod child = readModFromStream(virtualPath.resolve(name), new FilterInputStream(zis) { + @Override public void close() {} + }); + if (child != null) nestedChildren.add(child); + } + } } catch (IOException e) { - LOGGER.error("Error reading mod file {}: {}", file, e.getMessage()); + LOGGER.debug("Error processing stream for {}", virtualPath); } - return isBasicInfo(infoType) ? null : Set.of(); - } - private static Object getModInfo(FileSystem fs, String infoType) { - Path metadataPath = getMetadataPath(fs); - - if (metadataPath == null || !Files.exists(metadataPath)) { - return isBasicInfo(infoType) ? null : Set.of(); + if (metadata != null && metadata.modId() != null) { + Set ids = new HashSet<>(metadata.provides()); + ids.add(metadata.modId()); + // Investigate if we need hash or not + return new Mod(ids, null, metadata.version(), virtualPath, metadata.deps(), nestedChildren); } + return null; + } - try (InputStream stream = Files.newInputStream(metadataPath); - BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { + private static ModMetadata getModMetadata(FileSystem fs) { + Path metaPath = getMetadataPath(fs); + if (metaPath == null) return null; - if (metadataPath.getFileName().toString().endsWith("mods.toml")) { - return getModInfoFromToml(reader, infoType); + try (BufferedReader reader = Files.newBufferedReader(metaPath)) { + if (metaPath.toString().endsWith(".toml")) { + return parseTomlMetadata(reader); } else { - return getModInfoFromJson(reader, infoType); + return parseJsonMetadata(reader); } } catch (IOException e) { - LOGGER.error("Error reading metadata {}: {}", metadataPath, e.getMessage()); + LOGGER.error("Error parsing metadata {}: {}", metaPath, e.getMessage()); } - - return isBasicInfo(infoType) ? null : Set.of(); + return null; } - private static Object getModInfoFromToml(BufferedReader reader, String infoType) { + private static ModMetadata parseTomlMetadata(BufferedReader reader) { try { TomlParseResult result = Toml.parse(reader); - result.errors().forEach(error -> LOGGER.error(error.toString())); - - TomlArray modsArray = result.getArray("mods"); - if (modsArray == null) { - return isBasicInfo(infoType) ? null : Set.of(); - } + TomlArray mods = result.getArray("mods"); + if (mods == null || mods.isEmpty()) return null; - switch (infoType) { - case "version" -> { - String modVersion = null; - for (Object o : modsArray.toList()) { - TomlTable mod = (TomlTable) o; - if (mod != null) { - modVersion = mod.getString("version"); - } - } - return modVersion != null ? modVersion : "1"; - } - case "modId" -> { - String modID = null; - for (Object o : modsArray.toList()) { - TomlTable mod = (TomlTable) o; - if (mod != null) { - modID = mod.getString("modId"); - } - } - return modID; - } - case "provides" -> { - Set providedIDs = new HashSet<>(); - for (Object o : modsArray.toList()) { - TomlTable mod = (TomlTable) o; - if (mod != null) { - TomlArray providesArray = mod.getArray("provides"); - if (providesArray != null) { - for (int j = 0; j < providesArray.size(); j++) { - String id = providesArray.getString(j); - if (id != null && !id.isEmpty()) { - providedIDs.add(id); - } - } - } - } - } - return providedIDs; - } - case "dependencies" -> { - Set dependencies = new HashSet<>(); - - String modID = null; - for (Object o : modsArray.toList()) { - TomlTable mod = (TomlTable) o; - if (mod != null) { - modID = mod.getString("modId"); - } - } + String modId = null; + String version = "1"; + Set provides = new HashSet<>(); + Set deps = new HashSet<>(); + LoaderManagerService.EnvironmentType env = LoaderManagerService.EnvironmentType.UNIVERSAL; - if (modID == null) { - return dependencies; - } + for (int i = 0; i < mods.size(); i++) { + TomlTable modTable = mods.getTable(i); + if (modTable == null) continue; - TomlArray dependenciesArray = result.getArray("dependencies.\"" + modID + "\""); - if (dependenciesArray == null) { - return dependencies; - } + if (modId == null) modId = modTable.getString("modId"); - for (Object o : dependenciesArray.toList()) { - TomlTable mod = (TomlTable) o; - if (mod == null) continue; - String depId = mod.getString("modId"); - if (depId == null) continue; - dependencies.add(depId); - } + String v = modTable.getString("version"); + if (v != null && !v.equals("${file.jarVersion}")) version = v; - return dependencies; + TomlArray prov = modTable.getArray("provides"); + if (prov != null) { + for (int j = 0; j < prov.size(); j++) provides.add(prov.getString(j)); } - case "environment" -> { - LoaderManagerService.EnvironmentType environment = LoaderManagerService.EnvironmentType.UNIVERSAL; - - String modID = null; - for (Object o : modsArray.toList()) { - TomlTable mod = (TomlTable) o; - if (mod != null) { - modID = mod.getString("modId"); - } - } - - if (modID == null) { - return environment; - } + } - TomlArray dependenciesArray = result.getArray("dependencies.\"" + modID + "\""); - if (dependenciesArray == null) { - return environment; - } + if (modId != null) { + TomlArray depArray = result.getArray("deps.\"" + modId + "\""); + if (depArray != null) { + for (int i = 0; i < depArray.size(); i++) { + TomlTable depTable = depArray.getTable(i); + String depId = depTable.getString("modId"); + if (depId == null) continue; - for (Object o : dependenciesArray.toList()) { - TomlTable mod = (TomlTable) o; - if (mod == null) continue; - String depId = mod.getString("modId"); - if (depId == null) continue; // we only check for minecraft, neoforge and forge - if (!depId.equals("minecraft") && !depId.equals("neoforge") && !depId.equals("forge")) continue; - String depEnv = mod.getString("side"); - if (depEnv == null) continue; - switch (depEnv.toLowerCase()) { - case "client" -> environment = LoaderManagerService.EnvironmentType.CLIENT; - case "server" -> environment = LoaderManagerService.EnvironmentType.SERVER; - } + deps.add(depId); - if (environment != LoaderManagerService.EnvironmentType.UNIVERSAL) { - return environment; + // Determine Environment based on Minecraft/Forge side requirement + if (isPlatformId(depId)) { + String side = depTable.getString("side"); + if ("client".equalsIgnoreCase(side)) env = LoaderManagerService.EnvironmentType.CLIENT; + else if ("server".equalsIgnoreCase(side)) env = LoaderManagerService.EnvironmentType.SERVER; } } - - return environment; } } + return new ModMetadata(modId, version, provides, deps, env); } catch (Exception e) { - LOGGER.error("Error parsing TOML metadata: {}", e.getMessage()); + LOGGER.error("TOML Parse Error: {}", e.getMessage()); + return null; } - - return infoType.equals("version") || infoType.equals("modId") || infoType.equals("environment") ? null : Set.of(); } - private static Object getModInfoFromJson(BufferedReader reader, String infoType) { - JsonObject json = GSON.fromJson(reader, JsonObject.class); + private static ModMetadata parseJsonMetadata(BufferedReader reader) { + try { + JsonObject json = GSON.fromJson(reader, JsonObject.class); + JsonObject root = json; - switch (infoType) { - case "version" -> { - if (json.has("version")) { - return json.get("version").getAsString(); - } else if (json.has("quilt_loader") && json.get("quilt_loader").getAsJsonObject().has("version")) { - return json.get("quilt_loader").getAsJsonObject().get("version").getAsString(); - } + if (json.has("quilt_loader")) { + root = json.getAsJsonObject("quilt_loader"); } - case "modId" -> { - if (json.has("id")) { - return json.get("id").getAsString(); - } else if (json.has("quilt_loader") && json.get("quilt_loader").getAsJsonObject().has("id")) { - return json.get("quilt_loader").getAsJsonObject().get("id").getAsString(); - } - } - case "provides" -> { - Set providedIDs = new HashSet<>(); - if (json.has("provides")) { - for (JsonElement provides : json.get("provides").getAsJsonArray()) { - providedIDs.add(provides.getAsString()); - } - } else if (json.has("quilt_loader") && json.get("quilt_loader").getAsJsonObject().has("provides")) { - JsonObject quiltLoader = json.get("quilt_loader").getAsJsonObject(); - for (JsonElement provides : quiltLoader.get("provides").getAsJsonArray()) { - JsonObject providesObject = provides.getAsJsonObject(); - String id = providesObject.get("id").getAsString(); - providedIDs.add(id); - } + + String modId = getJsonString(root, "id"); + String version = getJsonString(root, "version"); + Set provides = new HashSet<>(); + Set deps = new HashSet<>(); + LoaderManagerService.EnvironmentType env = LoaderManagerService.EnvironmentType.UNIVERSAL; + + if (root.has("provides")) { + for (JsonElement e : root.get("provides").getAsJsonArray()) { + if (e.isJsonObject()) provides.add(e.getAsJsonObject().get("id").getAsString()); + else provides.add(e.getAsString()); } - return providedIDs; } - case "dependencies" -> { - Set dependencies = new HashSet<>(); - if (json.has("depends")) { - JsonObject depends = json.get("depends").getAsJsonObject(); - if (depends != null) { // Dont use asMap() since its only on gson 2.10^ - forge 1.18 - dependencies.addAll(depends.entrySet().stream().map(Map.Entry::getKey).toList()); - } - } else if (json.has("quilt_loader") && json.get("quilt_loader").getAsJsonObject().has("depends")) { - JsonObject depends = json.get("quilt_loader").getAsJsonObject().get("depends").getAsJsonObject(); - if (depends != null) { // Dont use asMap() since its only on gson 2.10^ - forge 1.18 - dependencies.addAll(depends.entrySet().stream().map(Map.Entry::getKey).toList()); + + if (root.has("depends")) { + JsonElement depends = root.get("depends"); + if (depends.isJsonObject()) { + deps.addAll(depends.getAsJsonObject().keySet()); + } else if (depends.isJsonArray()) { + for (JsonElement e : depends.getAsJsonArray()) { + if (e.isJsonObject()) deps.add(e.getAsJsonObject().get("id").getAsString()); + else deps.add(e.getAsString()); } } - return dependencies; } - case "environment" -> { - if (json.has("environment")) { - String environment = json.get("environment").getAsString(); - if (environment == null) return LoaderManagerService.EnvironmentType.UNIVERSAL; - return switch (environment.toLowerCase()) { - case "client" -> LoaderManagerService.EnvironmentType.CLIENT; - case "server" -> LoaderManagerService.EnvironmentType.SERVER; - default -> LoaderManagerService.EnvironmentType.UNIVERSAL; - }; - } else if (json.has("quilt_loader") && json.has("minecraft") && json.get("minecraft").getAsJsonObject().has("environment")) { - String environment = json.get("minecraft").getAsJsonObject().get("environment").getAsString(); - if (environment == null) return LoaderManagerService.EnvironmentType.UNIVERSAL; - return switch (environment.toLowerCase()) { - case "client" -> LoaderManagerService.EnvironmentType.CLIENT; - case "server" -> LoaderManagerService.EnvironmentType.SERVER; - default -> LoaderManagerService.EnvironmentType.UNIVERSAL; - }; - } + + if (root.has("environment")) { + String envStr = root.get("environment").getAsString(); + if ("client".equalsIgnoreCase(envStr)) env = LoaderManagerService.EnvironmentType.CLIENT; + else if ("server".equalsIgnoreCase(envStr)) env = LoaderManagerService.EnvironmentType.SERVER; } + + return new ModMetadata(modId, version, provides, deps, env); + } catch (Exception e) { + LOGGER.error("JSON Parse Error: {}", e.getMessage()); + return null; } + } + + private static boolean isPlatformId(String id) { + return "minecraft".equals(id) || "neoforge".equals(id) || "forge".equals(id); + } + + private static String getJsonString(JsonObject obj, String key) { + return obj.has(key) ? obj.get(key).getAsString() : null; + } + + public static Path getMetadataPath(FileSystem fs) { + String preferredEntry = switch (LOADER) { + case "neoforge" -> "META-INF/neoforge.mods.toml"; + case "fabric" -> "fabric.mod.json"; + case "forge" -> "META-INF/mods.toml"; + case "quilt" -> "quilt.mod.json"; + default -> null; + }; - return infoType.equals("version") || infoType.equals("modId") || infoType.equals("environment") ? null : Set.of(); + if (preferredEntry != null) { + Path p = fs.getPath(preferredEntry); + if (Files.exists(p)) return p; + } + + for (String fallback : List.of("META-INF/neoforge.mods.toml", "fabric.mod.json", "META-INF/mods.toml", "quilt.mod.json")) { + if (fallback.equals(preferredEntry)) continue; + Path p = fs.getPath(fallback); + if (Files.exists(p)) return p; + } + return null; + } + + private static boolean isMetadataFilename(String name) { + return name.endsWith("mods.toml") || name.endsWith("mod.json"); + } + + private static final Set KNOWN_SERVICES = Set.of( + "META-INF/services/net.minecraftforge.forgespi.locating.IModLocator", + "META-INF/services/net.minecraftforge.forgespi.locating.IDependencyLocator", + "META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider", + "META-INF/services/net.neoforged.neoforgespi.locating.IModLocator", + "META-INF/services/net.neoforged.neoforgespi.locating.IDependencyLocator", + "META-INF/services/net.neoforged.neoforgespi.locating.IModLanguageLoader", + "META-INF/services/net.neoforged.neoforgespi.locating.IModFileCandidateLocator", + "META-INF/services/net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper" + ); + + public static boolean hasSpecificServices(FileSystem fs) { + // Fast Check + for (String service : KNOWN_SERVICES) { + if (Files.exists(fs.getPath(service))) return true; + } + + return false; } private static final String forbiddenChars = "\\/:*\"<>|!?&%$;=+"; diff --git a/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java b/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java index 931fab77f..5a84b05c6 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java +++ b/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java @@ -3,6 +3,9 @@ import pl.skidam.automodpack_core.GlobalVariables; import pl.skidam.automodpack_core.config.Jsons; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.HashSet; import java.util.Set; @@ -17,7 +20,7 @@ public WorkaroundUtil(Path modapckPath) { // returns list of formatted modpack files which are mods with services (these mods need special treatment in order to work properly) // mods returned by this method should be installed in standard `~/mods/` directory - public Set getWorkaroundMods(Jsons.ModpackContentFields modpackContentFields) { + public Set getWorkaroundMods(Jsons.ModpackContentFields modpackContentFields) throws IOException { Set workaroundMods = new HashSet<>(); // this workaround is needed only for neo/forge mods @@ -28,8 +31,10 @@ public Set getWorkaroundMods(Jsons.ModpackContentFields modpackContentFi for (Jsons.ModpackContentFields.ModpackContentItem item : modpackContentFields.list) { if (item.type.equals("mod")) { Path modPath = CustomFileUtils.getPath(modpackPath, item.file); - if (FileInspection.hasSpecificServices(modPath)) { - workaroundMods.add(item.file); + try (FileSystem fs = FileSystems.newFileSystem(modPath)) { + if (FileInspection.hasSpecificServices(fs)) { + workaroundMods.add(item.file); + } } } } diff --git a/core/src/main/java/pl/skidam/automodpack_core/utils/sort/DependencyOptimizer.java b/core/src/main/java/pl/skidam/automodpack_core/utils/sort/DependencyOptimizer.java new file mode 100644 index 000000000..619078efb --- /dev/null +++ b/core/src/main/java/pl/skidam/automodpack_core/utils/sort/DependencyOptimizer.java @@ -0,0 +1,131 @@ +package pl.skidam.automodpack_core.utils.sort; + +import pl.skidam.automodpack_core.utils.FileInspection; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class DependencyOptimizer { + + /** + * Optimizes the standard mod set. + * Retains mods that are: + * 1. Seeds (Mods in Standard NOT in Modpack) + * 2. Dependencies of those Seeds (Recursively) + */ + public static Set optimize(Set standardMods, Set modpackMods) { + // Create a global lookup map: ID -> Top-Level Mod Container + Map idToContainerMap = new HashMap<>(); + + // Map Modpack first (Low Priority) + mapIdsToContainer(modpackMods, idToContainerMap); + // Map Standard second (High Priority - Overwrites Modpack entries) + // This ensures trace() prefers the existing Standard file if available. + mapIdsToContainer(standardMods, idToContainerMap); + + // Identify "Seeds" (User-added mods that are NOT in the modpack) + // We check if the modpack provides the IDs. If not, it's a seed. + Set modpackIds = new HashSet<>(); + collectAllIds(modpackMods, modpackIds); + + Set seeds = standardMods.stream() + .filter(mod -> mod.IDs().stream().noneMatch(modpackIds::contains)) + .collect(Collectors.toSet()); + + // Trace dependencies starting from Seeds + Set essentialMods = new HashSet<>(); + for (FileInspection.Mod seed : seeds) { + trace(seed, idToContainerMap, essentialMods); + } + + // Return the subset of standardMods that are essential + return standardMods.stream() + .filter(essentialMods::contains) + .collect(Collectors.toSet()); + } + + /** + * Finds standard mods that have a counterpart in the modpack with a DIFFERENT version. + * Returns Map: + */ + public static Map findMismatchedVersions(Set standardMods, Set modpackMods) { + Map mismatches = new HashMap<>(); + + // Map Modpack IDs for version lookup + Map modpackMap = new HashMap<>(); + mapIdsToContainer(modpackMods, modpackMap); + + for (FileInspection.Mod stdMod : standardMods) { + // Check IDs provided by this standard mod + for (String id : stdMod.IDs()) { + FileInspection.Mod packMod = modpackMap.get(id); + + // If modpack has this ID, but version differs + if (packMod != null && !areVersionsEqual(stdMod.version(), packMod.version())) { + mismatches.put(stdMod, packMod); + // Stop checking other IDs for this file, one mismatch is enough to trigger replacement + break; + } + } + } + return mismatches; + } + + // Depth-First Search to mark dependencies as essential + private static void trace(FileInspection.Mod current, Map lookup, Set visited) { + if (current == null || visited.contains(current)) return; + + visited.add(current); + + // Trace direct dependencies + if (current.deps() != null) { + for (String depId : current.deps()) { + FileInspection.Mod provider = lookup.get(depId); + trace(provider, lookup, visited); + } + } + } + + // Recursively maps IDs from a mod and its nested children to the TOP-LEVEL container file. + private static void mapIdsToContainer(Set mods, Map map) { + for (FileInspection.Mod container : mods) { + registerIdsRecursive(container, container, map); + } + } + + private static void registerIdsRecursive(FileInspection.Mod current, FileInspection.Mod topLevelContainer, Map map) { + // Register current mod's IDs + if (current.IDs() != null) { + for (String id : current.IDs()) { + map.put(id, topLevelContainer); + } + } + // Recurse into nested mods + if (current.nestedMods() != null) { + for (FileInspection.Mod nested : current.nestedMods()) { + registerIdsRecursive(nested, topLevelContainer, map); + } + } + } + + private static void collectAllIds(Set mods, Set targetSet) { + for (FileInspection.Mod mod : mods) { + collectIdsRecursive(mod, targetSet); + } + } + + private static void collectIdsRecursive(FileInspection.Mod mod, Set targetSet) { + if (mod.IDs() != null) targetSet.addAll(mod.IDs()); + if (mod.nestedMods() != null) { + mod.nestedMods().forEach(n -> collectIdsRecursive(n, targetSet)); + } + } + + private static boolean areVersionsEqual(String v1, String v2) { + if (v1 == null) return v2 == null; + return v1.equals(v2); + } +} \ No newline at end of file diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java index 76c6e86dd..c896305e6 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java @@ -6,6 +6,7 @@ import pl.skidam.automodpack_core.config.ConfigTools; import pl.skidam.automodpack_core.protocol.DownloadClient; import pl.skidam.automodpack_core.utils.*; +import pl.skidam.automodpack_core.utils.sort.DependencyOptimizer; import pl.skidam.automodpack_loader_core.ReLauncher; import pl.skidam.automodpack_loader_core.screen.ScreenManager; import pl.skidam.automodpack_loader_core.utils.*; @@ -419,25 +420,19 @@ private boolean applyModpack() throws Exception { } } - // Prepare modpack, analyze nested mods - List conflictingNestedMods = MODPACK_LOADER.getModpackNestedConflicts(modpackDir); - // delete old deleted files from the server modpack boolean needsRestart0 = deleteNonModpackFiles(modpackContent); Set workaroundMods = new WorkaroundUtil(modpackDir).getWorkaroundMods(modpackContent); Set filesNotToCopy = getFilesNotToCopy(modpackContent.list, workaroundMods); + boolean needsRestart1 = ModpackUtils.correctFilesLocations(modpackDir, modpackContent, filesNotToCopy); - workaroundMods = new WorkaroundUtil(modpackDir).getWorkaroundMods(modpackContent); - filesNotToCopy = getFilesNotToCopy(modpackContent.list, workaroundMods); - Set modpackMods = new HashSet<>(); - Collection modpackModList = new ArrayList<>(); + Set modpackModList = new HashSet<>(); Path modpackModsDir = modpackDir.resolve("mods"); if (Files.exists(modpackModsDir)) { try (Stream stream = Files.list(modpackModsDir)) { stream.forEach(path -> { - modpackMods.add(path); FileInspection.Mod mod = FileInspection.getMod(path); if (mod != null) { modpackModList.add(mod); @@ -446,7 +441,7 @@ private boolean applyModpack() throws Exception { } } - Collection standardModList = new ArrayList<>(); + Set standardModList = new HashSet<>(); Path standardModsDir = MODS_DIR; if (Files.exists(standardModsDir)) { try (Stream stream = Files.list(standardModsDir)) { @@ -459,31 +454,35 @@ private boolean applyModpack() throws Exception { } } - // Check if the conflicting mods still exits, they might have been deleted by methods above - conflictingNestedMods = conflictingNestedMods.stream() - .filter(conflictingMod -> modpackMods.contains(conflictingMod.modPath())) - .toList(); + Set optimizedStandardModList = DependencyOptimizer.optimize(standardModList, modpackModList); + Map mismatchedVersions = DependencyOptimizer.findMismatchedVersions(optimizedStandardModList, modpackModList); - if (!conflictingNestedMods.isEmpty()) { - LOGGER.warn("Found conflicting nested mods: {}", conflictingNestedMods); + if (!mismatchedVersions.isEmpty()) { + LOGGER.warn("Found mismatched versions: {}", mismatchedVersions); } - boolean needsRestart2 = ModpackUtils.fixNestedMods(conflictingNestedMods, standardModList); - Set ignoredFiles = ModpackUtils.getIgnoredFiles(conflictingNestedMods, workaroundMods); - - Set forceCopyFiles = modpackContent.list.stream() - .filter(item -> item.forceCopy) - .map(item -> item.file) - .collect(Collectors.toSet()); - - // Remove duplicate mods - ModpackUtils.RemoveDupeModsResult removeDupeModsResult = ModpackUtils.removeDupeMods(modpackDir, standardModList, modpackModList, ignoredFiles, workaroundMods, forceCopyFiles); - boolean needsRestart3 = removeDupeModsResult.requiresRestart(); + boolean needsRestart2 = false; - // Remove rest of mods not for standard mods directory - boolean needsRestart4 = ModpackUtils.removeRestModsNotToCopy(modpackContent, filesNotToCopy, removeDupeModsResult.modsToKeep()); + for (FileInspection.Mod standardMod : standardModList) { + if (!optimizedStandardModList.contains(standardMod)) { + LOGGER.info("Removing duplicated mod: {}", standardMod.path()); + CustomFileUtils.executeOrder66(standardMod.path(), false); + changelogs.changesDeletedList.put(standardMod.path().getFileName().toString(), null); + needsRestart2 = true; + } + else { + FileInspection.Mod replacement = mismatchedVersions.get(standardMod); + if (replacement != null) { + LOGGER.info("Replacing mismatched mod {} with modpack version {}", standardMod.path(), replacement.path()); + CustomFileUtils.executeOrder66(standardMod.path(), false); + changelogs.changesDeletedList.put(standardMod.path().getFileName().toString(), null); + CustomFileUtils.copyFile(replacement.path(), standardMod.path().getParent().resolve(replacement.path().getFileName())); + needsRestart2 = true; + } + } + } - return needsRestart0 || needsRestart1 || needsRestart2 || needsRestart3 || needsRestart4; + return needsRestart0 || needsRestart1 || needsRestart2; } // returns set of formated files which we should not copy to the cwd - let them stay in the modpack directory diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java index 0f3321a48..30d65d4ad 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java @@ -28,10 +28,10 @@ public class ModpackUtils { - // Modpack may require update even if theres no files to update, because some files may need to be deleted + // Modpack may require update even if there's no files to update, because some files may need to be deleted public record UpdateCheckResult(boolean requiresUpdate, Set filesToUpdate) {} - // Fast and freindly method to check if the modpack is up to date without modifying anything on disk + // Fast and friendly method to check if the modpack is up to date without modifying anything on disk public static UpdateCheckResult isUpdate(Jsons.ModpackContentFields serverModpackContent, Path modpackDir) { if (serverModpackContent == null || serverModpackContent.list == null) { throw new IllegalArgumentException("Server modpack content list is null"); @@ -167,177 +167,6 @@ public static boolean correctFilesLocations(Path modpackDir, Jsons.ModpackConten return needsRestart; } - public static boolean removeRestModsNotToCopy(Jsons.ModpackContentFields serverModpackContent, Set filesNotToCopy, Set modsToKeep) { - boolean needsRestart = false; - - for (Jsons.ModpackContentFields.ModpackContentItem contentItem : serverModpackContent.list) { - String formattedFile = contentItem.file; - Path runFile = CustomFileUtils.getPathFromCWD(formattedFile); - boolean isMod = "mod".equals(contentItem.type); - - if (isMod) { // Make it into standardized mods directory, for support custom launchers - runFile = CustomFileUtils.getPath(MODS_DIR, formattedFile.replaceFirst("/mods/", "")); - } - - if (modsToKeep.contains(runFile)) { - LOGGER.info("Keeping {} file in the standard mods directory", formattedFile); - continue; - } - - boolean runFileExists = Files.exists(runFile); - boolean runFileHashMatch = false; - if (runFileExists) runFileHashMatch = Objects.equals(contentItem.sha1, ClientCacheUtils.computeHashIfNeeded(runFile)); - - if (runFileHashMatch && isMod && filesNotToCopy.contains(formattedFile)) { - LOGGER.info("Deleting {} file from standard mods directory", formattedFile); - CustomFileUtils.executeOrder66(runFile); - needsRestart = true; - } - } - - return needsRestart; - } - - // Copies necessary nested mods from modpack mods to standard mods folder - // Returns true if requires client restart - public static boolean fixNestedMods(List conflictingNestedMods, Collection standardModList) throws IOException { - if (conflictingNestedMods.isEmpty()) - return false; - - final List standardModIDs = standardModList.stream().map(FileInspection.Mod::modID).toList(); - boolean needsRestart = false; - - for (FileInspection.Mod mod : conflictingNestedMods) { - // Check mods provides, if theres some mod which is named with the same id as some other mod 'provides' remove the mod which provides that id as well, otherwise loader will crash - if (standardModIDs.stream().anyMatch(mod.providesIDs()::contains)) - continue; - - Path modPath = mod.modPath(); - Path standardModPath = MODS_DIR.resolve(modPath.getFileName()); - if (!Files.exists(standardModPath) || !Objects.equals(ClientCacheUtils.computeHashIfNeeded(standardModPath), mod.hash())) { - needsRestart = true; - LOGGER.info("Copying nested mod {} to standard mods folder", standardModPath.getFileName()); - CustomFileUtils.copyFile(modPath, standardModPath); - var newMod = FileInspection.getMod(standardModPath); - if (newMod != null) standardModList.add(newMod); // important - } - } - - return needsRestart; - } - - // Returns ignored files list, which is conflicting nested mods + workarounds set - public static Set getIgnoredFiles(List conflictingNestedMods, Set workarounds) { - Set newIgnoredFiles = new HashSet<>(workarounds); - - for (FileInspection.Mod mod : conflictingNestedMods) { - newIgnoredFiles.add(CustomFileUtils.formatPath(mod.modPath(), modpacksDir)); - } - - return newIgnoredFiles; - } - - // Checks if in standard mods folder are any mods that are in modpack - // Returns map of modpack mods and standard mods that have the same mod id they dont necessarily have to be the same* - public static Map getDupeMods(Path modpackDir, Set ignoredMods, Collection standardModList, Collection modpackModList, Set forceCopyFiles) { - final Map duplicates = new HashMap<>(); - - for (FileInspection.Mod modpackMod : modpackModList) { - FileInspection.Mod standardMod = standardModList.stream().filter(mod -> mod.modID().equals(modpackMod.modID())).findFirst().orElse(null); // There might be super rare edge case if client would have for some reason more than one mod with the same mod id - if (standardMod != null) { - String formattedFile = CustomFileUtils.formatPath(modpackMod.modPath(), modpackDir); - if (ignoredMods.contains(formattedFile) || forceCopyFiles.contains(formattedFile)) - continue; - - duplicates.put(modpackMod, standardMod); - } - } - - return duplicates; - } - - public record RemoveDupeModsResult(boolean requiresRestart, Set modsToKeep) {} - - // Returns true if removed any mod from standard mods folder - // If the client mod is a duplicate of what modpack contains then it removes it from client so that you dont need to restart game just when you launched it and modpack get updated - basically having these mods separately allows for seamless updates - // If you have client mods which require specific mod which is also a duplicate of what modpack contains it should stay - public static RemoveDupeModsResult removeDupeMods(Path modpackDir, Collection standardModList, Collection modpackModList, Set ignoredMods, Set workaroundMods, Set forceCopyFiles) throws IOException { - var dupeMods = ModpackUtils.getDupeMods(modpackDir, ignoredMods, standardModList, modpackModList, forceCopyFiles); - - if (dupeMods.isEmpty()) { - return new RemoveDupeModsResult(false, Set.of()); - } - - Set modsToKeep = new HashSet<>(); - - // Fill out the sets with mods that are not duplicates and their dependencies - for (FileInspection.Mod standardMod : standardModList) { - if (!dupeMods.containsValue(standardMod)) { - modsToKeep.add(standardMod); - addDependenciesRecursively(standardMod, standardModList, modsToKeep); - } - } - - // Mods may provide more IDs - Set idsToKeep = new HashSet<>(); - modsToKeep.forEach(mod -> { - idsToKeep.add(mod.modID()); - idsToKeep.addAll(mod.providesIDs()); - }); - - boolean requiresRestart = false; - Set dependentMods = new HashSet<>(); - - // Remove dupe mods unless they need to stay - workaround mods - for (var dupeMod : dupeMods.entrySet()) { - FileInspection.Mod modpackMod = dupeMod.getKey(); - FileInspection.Mod standardMod = dupeMod.getValue(); - Path modpackModPath = modpackMod.modPath(); - Path standardModPath = standardMod.modPath(); - String modId = modpackMod.modID(); - String formatedPath = CustomFileUtils.formatPath(standardModPath, MODS_DIR.getParent()); - Collection providesIDs = modpackMod.providesIDs(); - List IDs = new ArrayList<>(providesIDs); - IDs.add(modId); - - boolean isDependent = IDs.stream().anyMatch(idsToKeep::contains); - boolean isWorkaround = workaroundMods.contains(formatedPath); - boolean isForceCopy = forceCopyFiles.contains(formatedPath); - - if (isDependent) { - Path newStandardModPath = standardModPath.getParent().resolve(modpackModPath.getFileName()); - dependentMods.add(newStandardModPath); - - // Check if hashes are the same, if not remove the mod and copy the modpack mod from modpack to make sure we achieve parity, - // If we break mod compat there that's up to the user to fix it, because they added their own mods, we need to guarantee that server modpack is working. - if (!Objects.equals(modpackMod.hash(), standardMod.hash())) { - LOGGER.warn("Changing duplicated mod {} - {} to modpack version - {}", modId, standardMod.modVersion(), modpackMod.modVersion()); - CustomFileUtils.executeOrder66(standardModPath, false); - CustomFileUtils.copyFile(modpackModPath, newStandardModPath); // TODO make sure we dont copy an empty invalid file there - requiresRestart = true; - } - } else if (!isWorkaround && !isForceCopy) { - LOGGER.warn("Removing {} mod. It is duplicated modpack mod and no other mods are dependent on it!", modId); - CustomFileUtils.executeOrder66(standardModPath, false); - requiresRestart = true; - } - } - - ClientCacheUtils.saveDummyFiles(); - - return new RemoveDupeModsResult(requiresRestart, dependentMods); - } - - private static void addDependenciesRecursively(FileInspection.Mod mod, Collection modList, Set modsToKeep) { - for (String depId : mod.dependencies()) { - for (FileInspection.Mod modItem : modList) { - if ((modItem.modID().equals(depId) || modItem.providesIDs().contains(depId)) && modsToKeep.add(modItem)) { - addDependenciesRecursively(modItem, modList, modsToKeep); - } - } - } - } - public static Path renameModpackDir(Jsons.ModpackContentFields serverModpackContent, Path modpackDir) { String currentName = clientConfig.selectedModpack; String newName = serverModpackContent.modpackName; diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/loader/LoaderManager.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/loader/LoaderManager.java index fb8a9337c..096e537fa 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/loader/LoaderManager.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/loader/LoaderManager.java @@ -1,9 +1,6 @@ package pl.skidam.automodpack_loader_core.loader; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.util.Collection; public class LoaderManager implements LoaderManagerService { @@ -12,16 +9,6 @@ public ModPlatform getPlatformType() { throw new AssertionError("Loader class not found"); } - @Override - public boolean isModLoaded(String modId) { - throw new AssertionError("Loader class not found"); - } - - @Override - public Collection getModList() { - throw new AssertionError("Loader class not found"); - } - @Override public String getLoaderVersion() { throw new AssertionError("Loader class not found"); diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/mods/ModpackLoader.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/mods/ModpackLoader.java index e01e2b14e..8a40ac1ad 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/mods/ModpackLoader.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/mods/ModpackLoader.java @@ -1,7 +1,6 @@ package pl.skidam.automodpack_loader_core.mods; import pl.skidam.automodpack_core.loader.ModpackLoaderService; -import pl.skidam.automodpack_core.utils.FileInspection; import java.nio.file.Path; import java.util.List; @@ -11,9 +10,4 @@ public class ModpackLoader implements ModpackLoaderService { public void loadModpack(List modpackMods) { throw new AssertionError("Loader class not found"); } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - throw new AssertionError("Loader class not found"); - } } diff --git a/loader/fabric/15/src/main/java/pl/skidam/automodpack_loader_core_fabric_15/mods/ModpackLoader15.java b/loader/fabric/15/src/main/java/pl/skidam/automodpack_loader_core_fabric_15/mods/ModpackLoader15.java index b8973977f..307aeedd6 100644 --- a/loader/fabric/15/src/main/java/pl/skidam/automodpack_loader_core_fabric_15/mods/ModpackLoader15.java +++ b/loader/fabric/15/src/main/java/pl/skidam/automodpack_loader_core_fabric_15/mods/ModpackLoader15.java @@ -2,7 +2,6 @@ import net.fabricmc.loader.api.LanguageAdapter; import net.fabricmc.loader.api.ModContainer; -import net.fabricmc.loader.api.metadata.ModDependency; import net.fabricmc.loader.impl.FabricLoaderImpl; import net.fabricmc.loader.impl.ModContainerImpl; import net.fabricmc.loader.impl.discovery.*; @@ -11,13 +10,9 @@ import net.fabricmc.loader.impl.metadata.DependencyOverrides; import net.fabricmc.loader.impl.metadata.VersionOverrides; import net.fabricmc.loader.impl.util.SystemProperties; -import pl.skidam.automodpack_core.loader.LoaderManagerService; import pl.skidam.automodpack_core.loader.ModpackLoaderService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; import pl.skidam.automodpack_loader_core_fabric.FabricLanguageAdapter; -import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -58,173 +53,6 @@ public void loadModpack(List modpackMods) { } } - @Override - public List getModpackNestedConflicts(Path modpackDir) { - Path modpackModsDir = modpackDir.resolve("mods"); - Path standardModsDir = MODS_DIR; - - List modpackNestedMods = new ArrayList<>(); - List standardNestedMods = new ArrayList<>(); - - try { - List candidates = (List) discoverMods(modpackModsDir); - candidates.forEach(it -> applyPaths(it, false)); - - for (ModCandidate candidate : candidates) { - if (!candidate.isRoot()) { - continue; - } - - List nestedMods = getNestedMods(candidate); - nestedMods = getOnlyNewestMods(nestedMods); - - boolean isStandard = !candidate.getPaths().get(0).toAbsolutePath().toString().contains(modpackModsDir.toAbsolutePath().toString()); - if (isStandard) { - standardNestedMods.addAll(nestedMods); - } else { - modpackNestedMods.addAll(nestedMods); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - // Remove older versions of the same mods - modpackNestedMods = getOnlyNewestMods(modpackNestedMods); - standardNestedMods = getOnlyNewestMods(standardNestedMods); - - List conflictingNestedModsImpl = new ArrayList<>(); - - for (ModCandidate standardNestedMod : standardNestedMods) { - for (ModCandidate modpackNestedMod : modpackNestedMods) { - if (!standardNestedMod.getId().equals(modpackNestedMod.getId())) - continue; - - if (modpackNestedMod.getVersion().compareTo(standardNestedMod.getVersion()) > 0) { - conflictingNestedModsImpl.add(modpackNestedMod); - } - } - } - - // Remove older versions of the same mods - conflictingNestedModsImpl = getOnlyNewestMods(conflictingNestedModsImpl); - - List modsNestedDeps = new ArrayList<>(); - - // Add nested dependencies - for (ModCandidate modCandidate : conflictingNestedModsImpl) { - List nestedDeps = getNestedDeps(modCandidate); - for (ModCandidate nestedDep : nestedDeps) { - if (conflictingNestedModsImpl.stream().anyMatch(it -> it.getId().equals(nestedDep.getId()))) - continue; - - if (modsNestedDeps.stream().anyMatch(it -> it.getId().equals(nestedDep.getId()))) - continue; - - modsNestedDeps.add(nestedDep); - } - } - - conflictingNestedModsImpl.addAll(modsNestedDeps); - - List originModIds = new ArrayList<>(); - - for (ModCandidate mod : conflictingNestedModsImpl) { - mod.getParentMods().stream().filter(ModCandidate::isRoot).findFirst().map(ModCandidate::getId).ifPresent(originModIds::add); - } - - // These are nested mods which we need to force load from standard mods dir - List conflictingNestedMods = new ArrayList<>(); - - for (ModCandidate mod : conflictingNestedModsImpl) { - // Check mods provides, if theres some mod which is named with the same id as some other mod 'provides' remove the mod which provides that id as well, otherwise loader will crash - if (originModIds.stream().anyMatch(mod.getProvides()::contains)) - continue; - - Path path = mod.getPaths().get(0); - if (path == null || path.toString().isEmpty()) - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - FileInspection.Mod conflictingMod = new FileInspection.Mod( - mod.getId(), - hash, - mod.getProvides(), - mod.getVersion().getFriendlyString(), - path, - LoaderManagerService.EnvironmentType.UNIVERSAL, - mod.getDependencies().stream().map(ModDependency::getModId).toList()); - - conflictingNestedMods.add(conflictingMod); - } - - return conflictingNestedMods; - } - - private List getNestedMods(ModCandidate originMod) { - List mods = new ArrayList<>(); - for (ModCandidate nested : originMod.getNestedMods()) { - mods.add(nested); - mods.addAll(getNestedMods(nested)); - } - - return mods; - } - - // Needed for e.g. fabric api - private List getNestedDeps(ModCandidate nestedMod) { - List deps = new ArrayList<>(); - - ModCandidate originMod; - - if (!nestedMod.isRoot()) { - originMod = nestedMod.getParentMods().stream().toList().get(0); - } else { - originMod = nestedMod; - } - - for (ModDependency dep : nestedMod.getDependencies()) { - ModCandidate candidate = originMod.getNestedMods().stream().filter(it -> it.getId().equals(dep.getModId())).findFirst().orElse(null); - if (candidate == null) { - continue; - } - - deps.add(candidate); - } - - return deps; - } - - private List getOnlyNewestMods(List allMods) { - List latestMods = new ArrayList<>(); - - for (ModCandidate standardNestedMod : allMods) { - // add mod to the standardLatestNestedMods if its id doesnt already exist or if it has a greater version then also delete the lower version - boolean alreadyExists = latestMods.stream().anyMatch(existingMod -> { - boolean hasSameId = existingMod.getId().equals(standardNestedMod.getId()); - boolean hasGreaterOrEqualVersion = existingMod.getVersion().compareTo(standardNestedMod.getVersion()) >= 0; - - return hasSameId && hasGreaterOrEqualVersion; - }); - - if (alreadyExists) { - continue; - } - - latestMods.removeIf(existingMod -> existingMod.getId().equals(standardNestedMod.getId())); - - latestMods.add(standardNestedMod); - } - - return latestMods; - } - private Collection discoverMods(Path modpackModsDir) throws ModResolutionException, IllegalAccessException { ModDiscoverer discoverer = new ModDiscoverer(new VersionOverrides(), new DependencyOverrides(FabricLoaderImpl.INSTANCE.getConfigDir())); @@ -318,9 +146,6 @@ private void setupLanguageAdapters(Collection candidates) throws I for (var entry : definitions.entrySet()) { if (!candidate.getId().equals(MOD_ID) && adapterMap.containsKey(entry.getKey())) { - - // TODO require restart or erase that package from vm and remove adapter from the map - FabricGuiEntry.displayCriticalError(new IllegalArgumentException("Duplicate language adapter ID: " + entry.getKey()), true); } diff --git a/loader/fabric/16/src/main/java/pl/skidam/automodpack_loader_core_fabric_16/mods/ModpackLoader16.java b/loader/fabric/16/src/main/java/pl/skidam/automodpack_loader_core_fabric_16/mods/ModpackLoader16.java index 14d8e85b1..7f08ed83b 100644 --- a/loader/fabric/16/src/main/java/pl/skidam/automodpack_loader_core_fabric_16/mods/ModpackLoader16.java +++ b/loader/fabric/16/src/main/java/pl/skidam/automodpack_loader_core_fabric_16/mods/ModpackLoader16.java @@ -2,7 +2,6 @@ import net.fabricmc.loader.api.LanguageAdapter; import net.fabricmc.loader.api.ModContainer; -import net.fabricmc.loader.api.metadata.ModDependency; import net.fabricmc.loader.impl.FabricLoaderImpl; import net.fabricmc.loader.impl.ModContainerImpl; import net.fabricmc.loader.impl.discovery.*; @@ -11,13 +10,9 @@ import net.fabricmc.loader.impl.metadata.DependencyOverrides; import net.fabricmc.loader.impl.metadata.VersionOverrides; import net.fabricmc.loader.impl.util.SystemProperties; -import pl.skidam.automodpack_core.loader.LoaderManagerService; import pl.skidam.automodpack_core.loader.ModpackLoaderService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; import pl.skidam.automodpack_loader_core_fabric.FabricLanguageAdapter; -import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -58,172 +53,6 @@ public void loadModpack(List modpackMods) { } } - @Override - public List getModpackNestedConflicts(Path modpackDir) { - Path modpackModsDir = modpackDir.resolve("mods"); - Path standardModsDir = MODS_DIR; - - List modpackNestedMods = new ArrayList<>(); - List standardNestedMods = new ArrayList<>(); - - try { - List candidates = (List) discoverMods(modpackModsDir); - candidates.forEach(it -> applyPaths(it, false)); - - for (ModCandidateImpl candidate : candidates) { - if (!candidate.isRoot()) { - continue; - } - - List nestedMods = getNestedMods(candidate); - nestedMods = getOnlyNewestMods(nestedMods); - - boolean isStandard = !candidate.getPaths().get(0).toAbsolutePath().toString().contains(modpackModsDir.toAbsolutePath().toString()); - if (isStandard) { - standardNestedMods.addAll(nestedMods); - } else { - modpackNestedMods.addAll(nestedMods); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - // Remove older versions of the same mods - modpackNestedMods = getOnlyNewestMods(modpackNestedMods); - standardNestedMods = getOnlyNewestMods(standardNestedMods); - - List conflictingNestedModsImpl = new ArrayList<>(); - - for (ModCandidateImpl standardNestedMod : standardNestedMods) { - for (ModCandidateImpl modpackNestedMod : modpackNestedMods) { - if (!standardNestedMod.getId().equals(modpackNestedMod.getId())) - continue; - - if (modpackNestedMod.getVersion().compareTo(standardNestedMod.getVersion()) > 0) { - conflictingNestedModsImpl.add(modpackNestedMod); - } - } - } - - // Remove older versions of the same mods - conflictingNestedModsImpl = getOnlyNewestMods(conflictingNestedModsImpl); - - List modsNestedDeps = new ArrayList<>(); - - // Add nested dependencies - for (ModCandidateImpl modCandidate : conflictingNestedModsImpl) { - List nestedDeps = getNestedDeps(modCandidate); - for (ModCandidateImpl nestedDep : nestedDeps) { - if (conflictingNestedModsImpl.stream().anyMatch(it -> it.getId().equals(nestedDep.getId()))) - continue; - - if (modsNestedDeps.stream().anyMatch(it -> it.getId().equals(nestedDep.getId()))) - continue; - - modsNestedDeps.add(nestedDep); - } - } - - conflictingNestedModsImpl.addAll(modsNestedDeps); - - List originModIds = new ArrayList<>(); - - for (ModCandidateImpl mod : conflictingNestedModsImpl) { - mod.getParentMods().stream().filter(ModCandidateImpl::isRoot).findFirst().map(ModCandidateImpl::getId).ifPresent(originModIds::add); - } - - // These are nested mods which we need to force load from standard mods dir - List conflictingNestedMods = new ArrayList<>(); - - for (ModCandidateImpl mod : conflictingNestedModsImpl) { - // Check mods provides, if theres some mod which is named with the same id as some other mod 'provides' remove the mod which provides that id as well, otherwise loader will crash - if (originModIds.stream().anyMatch(mod.getProvides()::contains)) - continue; - - Path path = mod.getPaths().get(0); - if (path == null || path.toString().isEmpty()) - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - FileInspection.Mod conflictingMod = new FileInspection.Mod( - mod.getId(), - hash, - mod.getProvides(), - mod.getVersion().getFriendlyString(), - path, - LoaderManagerService.EnvironmentType.UNIVERSAL, - mod.getDependencies().stream().map(ModDependency::getModId).toList()); - - conflictingNestedMods.add(conflictingMod); - } - - return conflictingNestedMods; - } - - private List getNestedMods(ModCandidateImpl originMod) { - List mods = new ArrayList<>(); - for (ModCandidateImpl nested : originMod.getNestedMods()) { - mods.add(nested); - mods.addAll(getNestedMods(nested)); - } - - return mods; - } - - // Needed for e.g. fabric api - private List getNestedDeps(ModCandidateImpl nestedMod) { - List deps = new ArrayList<>(); - - ModCandidateImpl originMod; - if (!nestedMod.isRoot()) { - originMod = nestedMod.getParentMods().stream().toList().get(0); - } else { - originMod = nestedMod; - } - - for (ModDependency dep : nestedMod.getDependencies()) { - ModCandidateImpl candidate = originMod.getNestedMods().stream().filter(it -> it.getId().equals(dep.getModId())).findFirst().orElse(null); - if (candidate == null) { - continue; - } - - deps.add(candidate); - } - - return deps; - } - - private List getOnlyNewestMods(List allMods) { - List latestMods = new ArrayList<>(); - - for (ModCandidateImpl standardNestedMod : allMods) { - // add mod to the standardLatestNestedMods if its id doesnt already exist or if it has a greater version then also delete the lower version - boolean alreadyExists = latestMods.stream().anyMatch(existingMod -> { - boolean hasSameId = existingMod.getId().equals(standardNestedMod.getId()); - boolean hasGreaterOrEqualVersion = existingMod.getVersion().compareTo(standardNestedMod.getVersion()) >= 0; - - return hasSameId && hasGreaterOrEqualVersion; - }); - - if (alreadyExists) { - continue; - } - - latestMods.removeIf(existingMod -> existingMod.getId().equals(standardNestedMod.getId())); - - latestMods.add(standardNestedMod); - } - - return latestMods; - } - private Collection discoverMods(Path modpackModsDir) throws ModResolutionException, IllegalAccessException { ModDiscoverer discoverer = new ModDiscoverer(new VersionOverrides(), new DependencyOverrides(FabricLoaderImpl.INSTANCE.getConfigDir())); @@ -314,9 +143,6 @@ private void setupLanguageAdapters(Collection candidates) thro for (var entry : definitions.entrySet()) { if (!candidate.getId().equals(MOD_ID) && adapterMap.containsKey(entry.getKey())) { - - // TODO require restart or erase that package from vm and remove adapter from the map - FabricGuiEntry.displayCriticalError(new IllegalArgumentException("Duplicate language adapter ID: " + entry.getKey()), true); } diff --git a/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/loader/LoaderManager.java b/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/loader/LoaderManager.java index d1a68c20e..223e22b65 100644 --- a/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/loader/LoaderManager.java +++ b/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/loader/LoaderManager.java @@ -3,19 +3,11 @@ import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; -import net.fabricmc.loader.api.metadata.ModDependency; import net.fabricmc.loader.api.metadata.ModEnvironment; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.*; -import static pl.skidam.automodpack_core.GlobalVariables.LOGGER; - @SuppressWarnings("unused") public class LoaderManager implements LoaderManagerService { @@ -24,81 +16,12 @@ public ModPlatform getPlatformType() { return ModPlatform.FABRIC; } - @Override - public boolean isModLoaded(String modId) { - return FabricLoader.getInstance().isModLoaded(modId); - } - - private Collection modList = new ArrayList<>(); - private int lastLoadingModListSize = -1; - - @Override - public Collection getModList() { - - Collection mods = FabricLoader.getInstance().getAllMods(); - - if (!modList.isEmpty() && lastLoadingModListSize == mods.size()) { - return modList; - } - - lastLoadingModListSize = mods.size(); - Collection modList = new ArrayList<>(); - - for (var info : mods) { - try { - String modID = info.getMetadata().getId(); - Path path = getModPath(modID); - if (path == null || path.toString().isEmpty()) // If we cant get the path, we skip the mod, its probably JiJed, we dont need it in the list - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - Set providesIDs = new HashSet<>(info.getMetadata().getProvides()); - List dependencies = info.getMetadata().getDependencies().stream().filter(d -> d.getKind().equals(ModDependency.Kind.DEPENDS)).map(ModDependency::getModId).toList(); - - FileInspection.Mod mod = new FileInspection.Mod( - modID, - hash, - providesIDs, - info.getMetadata().getVersion().getFriendlyString(), - path, - getModEnvironment(modID), - dependencies); - - modList.add(mod); - } catch (Exception ignored) {} - } - - return this.modList = modList; - } - @Override public String getLoaderVersion() { Optional modContainer = FabricLoader.getInstance().getModContainer("fabricloader"); return modContainer.map(container -> container.getMetadata().getVersion().getFriendlyString()).orElse(null); } - private Path getModPath(String modId) { - if (!isModLoaded(modId)) return null; - - try { - for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) { - if (modContainer.getMetadata().getId().equals(modId)) { - FileSystem fileSys = modContainer.getRootPaths().get(0).getFileSystem(); - return Path.of(fileSys.toString()); - } - } - } catch (Exception ignored) {} - - LOGGER.error("Could not find jar file for {}", modId); - return null; - } - @Override public EnvironmentType getEnvironmentType() { if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { diff --git a/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/mods/ModpackLoader.java b/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/mods/ModpackLoader.java index 0f630efc8..970410685 100644 --- a/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/mods/ModpackLoader.java +++ b/loader/fabric/core/src/main/java/pl/skidam/automodpack_loader_core_fabric/mods/ModpackLoader.java @@ -1,7 +1,6 @@ package pl.skidam.automodpack_loader_core_fabric.mods; import pl.skidam.automodpack_core.loader.ModpackLoaderService; -import pl.skidam.automodpack_core.utils.FileInspection; import pl.skidam.automodpack_core.utils.SemanticVersion; import pl.skidam.automodpack_loader_core_fabric_15.mods.ModpackLoader15; import pl.skidam.automodpack_loader_core_fabric_16.mods.ModpackLoader16; @@ -24,9 +23,4 @@ public class ModpackLoader implements ModpackLoaderService { public void loadModpack(List modpackMods) { INSTANCE.loadModpack(modpackMods); } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - return INSTANCE.getModpackNestedConflicts(modpackDir); - } } \ No newline at end of file diff --git a/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java b/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java index 190ac1065..bb40ed782 100644 --- a/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java +++ b/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java @@ -2,18 +2,8 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.fml.loading.FMLLoader; -import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; -import net.minecraftforge.forgespi.language.IModInfo; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import static pl.skidam.automodpack_core.GlobalVariables.preload; @@ -25,84 +15,11 @@ public ModPlatform getPlatformType() { return ModPlatform.FORGE; } - @Override - public boolean isModLoaded(String modId) { - return FMLLoader.getLoadingModList().getModFileById(modId) != null; - } - - private Collection modList = new ArrayList<>(); - private int lastLoadingModListSize = -1; - - @Override - public Collection getModList() { - - if (preload) { - return modList; - } - - List modInfo = FMLLoader.getLoadingModList().getMods(); - - if (!modList.isEmpty() && lastLoadingModListSize == modInfo.size()) { - // return cached - return modList; - } - - lastLoadingModListSize = modInfo.size(); - Collection modList = new ArrayList<>(); - - for (ModInfo info: modInfo) { - try { - String modID = info.getModId(); - Path path = getModPath(modID); - if (path == null || path.toString().isEmpty()) // If we cant get the path, we skip the mod, its probably JiJed, we dont need it in the list - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - List dependencies = info.getDependencies().stream().filter(IModInfo.ModVersion::isMandatory).map(IModInfo.ModVersion::getModId).toList(); - FileInspection.Mod mod = new FileInspection.Mod( - modID, - hash, - List.of(), - info.getOwningFile().versionString(), - path, - EnvironmentType.UNIVERSAL, - dependencies); - - modList.add(mod); - } catch (Exception ignored) {} - } - - return this.modList = modList; - } - @Override public String getLoaderVersion() { return FMLLoader.versionInfo().forgeVersion(); } - private Path getModPath(String modId) { - if (isDevelopmentEnvironment()) { - return null; - } - - if (isModLoaded(modId)) { - ModFileInfo modInfo = FMLLoader.getLoadingModList().getModFileById(modId); - - List mods = modInfo.getMods(); - if (!mods.isEmpty()) { - return mods.get(0).getOwningFile().getFile().getFilePath().toAbsolutePath(); - } - } - - return null; - } - @Override public EnvironmentType getEnvironmentType() { if (FMLLoader.getDist() == Dist.CLIENT) { diff --git a/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java b/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java index 013504a9d..809ae6386 100644 --- a/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java +++ b/loader/forge/fml40/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java @@ -31,9 +31,4 @@ public void loadModpack(List modpackMods) { LOGGER.error("Error while loading modpack", e); } } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - return new ArrayList<>(); - } -} +} \ No newline at end of file diff --git a/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java b/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java index 6a2e32610..b5f915636 100644 --- a/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java +++ b/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/loader/LoaderManager.java @@ -2,18 +2,8 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.fml.loading.FMLLoader; -import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; -import net.minecraftforge.forgespi.language.IModInfo; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import static pl.skidam.automodpack_core.GlobalVariables.*; @@ -25,84 +15,11 @@ public ModPlatform getPlatformType() { return ModPlatform.FORGE; } - @Override - public boolean isModLoaded(String modId) { - return FMLLoader.getLoadingModList().getModFileById(modId) != null; - } - - private Collection modList = new ArrayList<>(); - private int lastLoadingModListSize = -1; - - @Override - public Collection getModList() { - - if (preload) { - return modList; - } - - List modInfo = FMLLoader.getLoadingModList().getMods(); - - if (!modList.isEmpty() && lastLoadingModListSize == modInfo.size()) { - // return cached - return modList; - } - - lastLoadingModListSize = modInfo.size(); - Collection modList = new ArrayList<>(); - - for (ModInfo info : modInfo) { - try { - String modID = info.getModId(); - Path path = getModPath(modID); - if (path == null || path.toString().isEmpty()) // If we cant get the path, we skip the mod, its probably JiJed, we dont need it in the list - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - List dependencies = info.getDependencies().stream().filter(IModInfo.ModVersion::isMandatory).map(IModInfo.ModVersion::getModId).toList(); - FileInspection.Mod mod = new FileInspection.Mod( - modID, - hash, - List.of(), - info.getOwningFile().versionString(), - path, - EnvironmentType.UNIVERSAL, - dependencies); - modList.add(mod); - } catch (Exception ignored) { - } - } - - return this.modList = modList; - } - @Override public String getLoaderVersion() { return FMLLoader.versionInfo().forgeVersion(); } - private Path getModPath(String modId) { - if (isDevelopmentEnvironment()) { - return null; - } - - if (isModLoaded(modId)) { - ModFileInfo modInfo = FMLLoader.getLoadingModList().getModFileById(modId); - - List mods = modInfo.getMods(); - if (!mods.isEmpty()) { - return mods.get(0).getOwningFile().getFile().getFilePath().toAbsolutePath(); - } - } - - return null; - } - @Override public EnvironmentType getEnvironmentType() { if (FMLLoader.getDist() == Dist.CLIENT) { diff --git a/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java b/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java index a1f876af4..2a41e35bd 100644 --- a/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java +++ b/loader/forge/fml47/src/main/java/pl/skidam/automodpack_loader_core_forge/mods/ModpackLoader.java @@ -31,10 +31,5 @@ public void loadModpack(List modpackMods) { LOGGER.error("Error while loading modpack", e); } } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - return new ArrayList<>(); - } } diff --git a/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java b/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java index f21de22c7..aa02a123e 100644 --- a/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java +++ b/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java @@ -2,19 +2,8 @@ import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.loading.FMLLoader; -import net.neoforged.fml.loading.LoadingModList; -import net.neoforged.fml.loading.moddiscovery.ModFileInfo; import net.neoforged.fml.loading.moddiscovery.ModInfo; -import net.neoforged.neoforgespi.language.IModInfo; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import static pl.skidam.automodpack_core.GlobalVariables.*; @@ -26,91 +15,11 @@ public ModPlatform getPlatformType() { return ModPlatform.NEOFORGE; } - @Override - public boolean isModLoaded(String modId) { - LoadingModList loadingModList; - try { - loadingModList= FMLLoader.getCurrent().getLoadingModList(); - } catch (IllegalStateException e) { - return false; - } - return loadingModList.getModFileById(modId) != null; - } - - private Collection modList = new ArrayList<>(); - private int lastLoadingModListSize = -1; - - // Does it even still make sense to have this? FileInspection class should do everything anyway - @Override - public Collection getModList() { - - if (preload) { // always empty on preload - return modList; - } - - List modInfo = FMLLoader.getCurrent().getLoadingModList().getMods(); - - if (!modList.isEmpty() && lastLoadingModListSize == modInfo.size()) { - // return cached - return modList; - } - - lastLoadingModListSize = modInfo.size(); - Collection modList = new ArrayList<>(); - - for (ModInfo info: modInfo) { - try { - String modID = info.getModId(); - Path path = getModPath(modID); - if (path == null || path.toString().isEmpty()) // If we cant get the path, we skip the mod, its probably JiJed, we dont need it in the list - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - List dependencies = info.getDependencies().stream().filter(d -> d.getType() == IModInfo.DependencyType.REQUIRED).map(IModInfo.ModVersion::getModId).toList(); - FileInspection.Mod mod = new FileInspection.Mod( - modID, - hash, - List.of(), - info.getOwningFile().versionString(), - path, - EnvironmentType.UNIVERSAL, - dependencies); - - modList.add(mod); - } catch (Exception ignored) {} - } - - return this.modList = modList; - } - @Override public String getLoaderVersion() { return FMLLoader.getCurrent().getVersionInfo().neoForgeVersion(); } - private Path getModPath(String modId) { - if (isDevelopmentEnvironment()) { - return null; - } - - if (isModLoaded(modId)) { - ModFileInfo modInfo = FMLLoader.getCurrent().getLoadingModList().getModFileById(modId); - - List mods = modInfo.getMods(); - if (!mods.isEmpty()) { - return mods.getFirst().getOwningFile().getFile().getFilePath().toAbsolutePath(); - } - } - - return null; - } - @Override public EnvironmentType getEnvironmentType() { if (FMLLoader.getCurrent().getDist() == Dist.CLIENT) { diff --git a/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java b/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java index d95cf15c9..a6ca5dea2 100644 --- a/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java +++ b/loader/neoforge/fml10/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java @@ -31,9 +31,4 @@ public void loadModpack(List modpackMods) { LOGGER.error("Error while loading modpack", e); } } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - return new ArrayList<>(); - } } diff --git a/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java b/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java index 03b3c0324..44ebb8783 100644 --- a/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java +++ b/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java @@ -2,19 +2,8 @@ import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.loading.FMLLoader; -import net.neoforged.fml.loading.LoadingModList; -import net.neoforged.fml.loading.moddiscovery.ModFileInfo; import net.neoforged.fml.loading.moddiscovery.ModInfo; -import net.neoforged.neoforgespi.language.IModInfo; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import static pl.skidam.automodpack_core.GlobalVariables.*; @@ -26,91 +15,11 @@ public ModPlatform getPlatformType() { return ModPlatform.NEOFORGE; } - @Override - public boolean isModLoaded(String modId) { - LoadingModList loadingModList; - try { - loadingModList= FMLLoader.getLoadingModList(); - } catch (IllegalStateException e) { - return false; - } - return loadingModList.getModFileById(modId) != null; - } - - private Collection modList = new ArrayList<>(); - private int lastLoadingModListSize = -1; - - // Does it even still make sense to have this? FileInspection class should do everything anyway - @Override - public Collection getModList() { - - if (preload) { // always empty on preload - return modList; - } - - List modInfo = FMLLoader.getLoadingModList().getMods(); - - if (!modList.isEmpty() && lastLoadingModListSize == modInfo.size()) { - // return cached - return modList; - } - - lastLoadingModListSize = modInfo.size(); - Collection modList = new ArrayList<>(); - - for (ModInfo info: modInfo) { - try { - String modID = info.getModId(); - Path path = getModPath(modID); - if (path == null || path.toString().isEmpty()) // If we cant get the path, we skip the mod, its probably JiJed, we dont need it in the list - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - List dependencies = info.getDependencies().stream().filter(d -> d.getType() == IModInfo.DependencyType.REQUIRED).map(IModInfo.ModVersion::getModId).toList(); - FileInspection.Mod mod = new FileInspection.Mod( - modID, - hash, - List.of(), - info.getOwningFile().versionString(), - path, - EnvironmentType.UNIVERSAL, - dependencies); - - modList.add(mod); - } catch (Exception ignored) {} - } - - return this.modList = modList; - } - @Override public String getLoaderVersion() { return FMLLoader.versionInfo().neoForgeVersion(); } - private Path getModPath(String modId) { - if (isDevelopmentEnvironment()) { - return null; - } - - if (isModLoaded(modId)) { - ModFileInfo modInfo = FMLLoader.getLoadingModList().getModFileById(modId); - - List mods = modInfo.getMods(); - if (!mods.isEmpty()) { - return mods.getFirst().getOwningFile().getFile().getFilePath().toAbsolutePath(); - } - } - - return null; - } - @Override public EnvironmentType getEnvironmentType() { if (FMLLoader.getDist() == Dist.CLIENT) { diff --git a/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java b/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java index d95cf15c9..a6ca5dea2 100644 --- a/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java +++ b/loader/neoforge/fml2/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java @@ -31,9 +31,4 @@ public void loadModpack(List modpackMods) { LOGGER.error("Error while loading modpack", e); } } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - return new ArrayList<>(); - } } diff --git a/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java b/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java index 03b3c0324..44ebb8783 100644 --- a/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java +++ b/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/loader/LoaderManager.java @@ -2,19 +2,8 @@ import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.loading.FMLLoader; -import net.neoforged.fml.loading.LoadingModList; -import net.neoforged.fml.loading.moddiscovery.ModFileInfo; import net.neoforged.fml.loading.moddiscovery.ModInfo; -import net.neoforged.neoforgespi.language.IModInfo; import pl.skidam.automodpack_core.loader.LoaderManagerService; -import pl.skidam.automodpack_core.utils.ClientCacheUtils; -import pl.skidam.automodpack_core.utils.FileInspection; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import static pl.skidam.automodpack_core.GlobalVariables.*; @@ -26,91 +15,11 @@ public ModPlatform getPlatformType() { return ModPlatform.NEOFORGE; } - @Override - public boolean isModLoaded(String modId) { - LoadingModList loadingModList; - try { - loadingModList= FMLLoader.getLoadingModList(); - } catch (IllegalStateException e) { - return false; - } - return loadingModList.getModFileById(modId) != null; - } - - private Collection modList = new ArrayList<>(); - private int lastLoadingModListSize = -1; - - // Does it even still make sense to have this? FileInspection class should do everything anyway - @Override - public Collection getModList() { - - if (preload) { // always empty on preload - return modList; - } - - List modInfo = FMLLoader.getLoadingModList().getMods(); - - if (!modList.isEmpty() && lastLoadingModListSize == modInfo.size()) { - // return cached - return modList; - } - - lastLoadingModListSize = modInfo.size(); - Collection modList = new ArrayList<>(); - - for (ModInfo info: modInfo) { - try { - String modID = info.getModId(); - Path path = getModPath(modID); - if (path == null || path.toString().isEmpty()) // If we cant get the path, we skip the mod, its probably JiJed, we dont need it in the list - continue; - - if (!Files.exists(path)) - continue; - - String hash = ClientCacheUtils.computeHashIfNeeded(path); - if (hash == null) - continue; - - List dependencies = info.getDependencies().stream().filter(d -> d.getType() == IModInfo.DependencyType.REQUIRED).map(IModInfo.ModVersion::getModId).toList(); - FileInspection.Mod mod = new FileInspection.Mod( - modID, - hash, - List.of(), - info.getOwningFile().versionString(), - path, - EnvironmentType.UNIVERSAL, - dependencies); - - modList.add(mod); - } catch (Exception ignored) {} - } - - return this.modList = modList; - } - @Override public String getLoaderVersion() { return FMLLoader.versionInfo().neoForgeVersion(); } - private Path getModPath(String modId) { - if (isDevelopmentEnvironment()) { - return null; - } - - if (isModLoaded(modId)) { - ModFileInfo modInfo = FMLLoader.getLoadingModList().getModFileById(modId); - - List mods = modInfo.getMods(); - if (!mods.isEmpty()) { - return mods.getFirst().getOwningFile().getFile().getFilePath().toAbsolutePath(); - } - } - - return null; - } - @Override public EnvironmentType getEnvironmentType() { if (FMLLoader.getDist() == Dist.CLIENT) { diff --git a/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java b/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java index d95cf15c9..a6ca5dea2 100644 --- a/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java +++ b/loader/neoforge/fml4/src/main/java/pl/skidam/automodpack_loader_core_neoforge/mods/ModpackLoader.java @@ -31,9 +31,4 @@ public void loadModpack(List modpackMods) { LOGGER.error("Error while loading modpack", e); } } - - @Override - public List getModpackNestedConflicts(Path modpackDir) { - return new ArrayList<>(); - } } From 0c0501d3cd32baff3173be3e205e7bafe6c69ba2 Mon Sep 17 00:00:00 2001 From: skidam Date: Fri, 16 Jan 2026 17:19:06 +0100 Subject: [PATCH 2/2] add h2 mvstore database --- core/build.gradle.kts | 3 ++- loader/loader-fabric-core.gradle.kts | 1 + loader/loader-forge.gradle.kts | 1 + loader/loader-neoforge.gradle.kts | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 78d4d4a73..a1ee153f8 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -21,7 +21,8 @@ val deps = listOf( "com.google.code.gson:gson:2.13.2", "org.bouncycastle:bcpkix-jdk18on:1.82", "org.apache.httpcomponents.client5:httpclient5:5.5.1", - "org.tomlj:tomlj:1.1.1" + "org.tomlj:tomlj:1.1.1", + "com.h2database:h2-mvstore:2.4.240" ) dependencies { diff --git a/loader/loader-fabric-core.gradle.kts b/loader/loader-fabric-core.gradle.kts index 082ff46e1..d628c684f 100644 --- a/loader/loader-fabric-core.gradle.kts +++ b/loader/loader-fabric-core.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation("io.netty:netty-codec-haproxy:4.2.9.Final") { isTransitive = false } + implementation("com.h2database:h2-mvstore:2.4.240") } configurations { diff --git a/loader/loader-forge.gradle.kts b/loader/loader-forge.gradle.kts index d356185c7..674ee0cee 100644 --- a/loader/loader-forge.gradle.kts +++ b/loader/loader-forge.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation("io.netty:netty-codec-haproxy:4.2.9.Final") { isTransitive = false } + implementation("com.h2database:h2-mvstore:2.4.240") } configurations { diff --git a/loader/loader-neoforge.gradle.kts b/loader/loader-neoforge.gradle.kts index de19c56c9..9689beccc 100644 --- a/loader/loader-neoforge.gradle.kts +++ b/loader/loader-neoforge.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation("io.netty:netty-codec-haproxy:4.2.9.Final") { isTransitive = false } + implementation("com.h2database:h2-mvstore:2.4.240") } configurations {