From 026af679a4c2ed1f5fe294aec5ba05b2ffe86f7d Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Tue, 4 Nov 2025 14:27:57 +0100 Subject: [PATCH 1/9] Update `Relocator` to handle JSON mixin path conflicts, enhance readability, and align version string format. --- .../io/github/pacifistmc/forgix/Forgix.java | 2 +- .../pacifistmc/forgix/core/Relocator.java | 482 ++++++++++-------- 2 files changed, 268 insertions(+), 216 deletions(-) diff --git a/src/main/java/io/github/pacifistmc/forgix/Forgix.java b/src/main/java/io/github/pacifistmc/forgix/Forgix.java index 1cbec4b..f53209a 100644 --- a/src/main/java/io/github/pacifistmc/forgix/Forgix.java +++ b/src/main/java/io/github/pacifistmc/forgix/Forgix.java @@ -13,7 +13,7 @@ import java.util.stream.Collectors; public class Forgix { - public static final String VERSION = "2.0.0-SNAPSHOT.5.1"; + public static final String VERSION = "2.0.0-SNAPSHOT.5.1-FORK.3"; private static final String MANIFEST_VERSION_KEY = "Forgix-Version"; private static final String MANIFEST_MAPPINGS_KEY = "Forgix-Mappings"; diff --git a/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java b/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java index 708c718..fa720d7 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java @@ -1,5 +1,6 @@ package io.github.pacifistmc.forgix.core; +import com.google.gson.*; import io.github.pacifistmc.forgix.utils.JAR; import io.github.pacifistmc.forgix.utils.TinyClassWriter; import net.fabricmc.tinyremapper.*; @@ -22,220 +23,271 @@ * Relocates conflicting files in JARs. */ public class Relocator { - private static final File tempDir = Files.createTempDirectory("forgix-tiny").toFile(); - static { - tempDir.mustDeleteOnExit(); - } - - /** - * Relocates conflicting files in JARs. - * @param relocationConfigs The relocationConfigs to process - */ - public static void relocate(List relocationConfigs) { - relocateClasses(relocationConfigs); - relocateResources(relocationConfigs); - } - - /** - * Relocates conflicting classes in JARs. - * @param relocationConfigs The relocationConfigs to process - */ - private static final Map> mappingsSnapshot = new ConcurrentHashMap<>(); // Store a snapshot of the mappings so we can restore it at the end of all passes - public static void relocateClasses(List relocationConfigs, boolean doAnotherPass = false) { - if (doAnotherPass || relocationConfigs.getFirst().tinyFile == null) generateMappings(relocationConfigs, !doAnotherPass); // Generate mappings if they don't exist - - // Return if there are no new conflicts (no new class conflicts) - // This will return if there are no conflicts at all (empty mappings) or there are only resource conflicts which we're ignoring - if (doAnotherPass && relocationConfigs.parallelStream() - .flatMap(config -> config.getMappings().keySet().parallelStream()) - .noneMatch(mapping -> mapping.endsWith(".class")) - ) { - relocationConfigs.parallelStream().forEach(config -> config.setMappings(mappingsSnapshot.get(config.getJarFile()))); - return; - } - - // Process each JAR file in parallel - relocationConfigs.parallelStream().forEach(relocationConfig -> { - // Update mapping snapshot, merge with existing ones - // TODO: Make mappingsSnapshot faster, relocateResources needs access to class mappings and - // doing multiple passes of class relocation resets the mappings so we need to store a snapshot of the mappings - // in order to restore them at the end of all passes - mappingsSnapshot.computeIfAbsent(relocationConfig.getJarFile(), _ -> new ConcurrentHashMap<>()); - mappingsSnapshot.computeIfPresent(relocationConfig.getJarFile(), (_, existingMap) -> { - existingMap.putAll(relocationConfig.mappings); - return existingMap; - }); - - // Create a tiny remapper with the mappings - IMappingProvider tinyMappings = TinyUtils.createTinyMappingProvider(relocationConfig.tinyFile.toPath(), "original", "relocated"); - var logger = new ConsoleLogger(); logger.setLevel(TrLogger.Level.ERROR); - TinyRemapper tinyRemapper = TinyRemapper.newRemapper(logger).withMappings(tinyMappings).ignoreConflicts(true).fixPackageAccess(true).renameInvalidLocals(true).rebuildSourceFilenames(true).resolveMissing(true).build(); - - // Get the paths - Path jarFilePath = Paths.get(relocationConfig.jarFile.getName()); - Path tempJarFilePath = Paths.get(relocationConfig.jarFile.getName().setExtension("tmp")); - - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(tempJarFilePath).assumeArchive(true).build()) { - // Add the jar file as a target - outputConsumer.addNonClassFiles(jarFilePath); - tinyRemapper.readInputs(jarFilePath); - - // Apply the mappings - tinyRemapper.apply(outputConsumer); - } finally { - tinyRemapper.finish(); // Close the remapper - } - - // Close the original JAR file - relocationConfig.jarFile.close(); - - // Replace the original JAR file with the relocated one - Files.move(tempJarFilePath, jarFilePath, StandardCopyOption.REPLACE_EXISTING); // TODO: Probably don't overwrite, update the jar location in relocation config instead. FIXME: Trying to update the jar location gives so many lambda bootstrap errors due to how cursed manifold is 😭 - }); - - // Do multiple passes to handle possible conflicts created by the previous pass - // Example: - // ``` - // class A { - // public static Object get() { - // return Minecraft.getInstance(); - // } - // } - // class B { - // public static void use() { - // System.out.println(A.get()); - // } - // } - // ``` - // A will get remapped to `fabric.A` and `forge.A`, which will B call? - relocateClasses(relocationConfigs, true); - } - - /** - * Relocates conflicting resources in JARs. - * @param relocationConfigs The relocationConfigs to process - */ - public static void relocateResources(List relocationConfigs, boolean anotherPass = false) { - // Generate mappings if they don't exist or this is another pass - if (anotherPass || relocationConfigs.getFirst().tinyFile == null) generateMappings(relocationConfigs, !anotherPass); - - // Return if there are no new conflicts + private static final File tempDir = Files.createTempDirectory("forgix-tiny").toFile(); + + static { + tempDir.mustDeleteOnExit(); + } + + /** + * Relocates conflicting files in JARs. + * + * @param relocationConfigs The relocationConfigs to process + */ + public static void relocate(List relocationConfigs) { + relocateClasses(relocationConfigs); + relocateResources(relocationConfigs); + } + + /** + * Relocates conflicting classes in JARs. + * + * @param relocationConfigs The relocationConfigs to process + */ + private static final Map> mappingsSnapshot = new ConcurrentHashMap<>(); // Store a snapshot of the mappings so we can restore it at the end of all passes + + public static void relocateClasses(List relocationConfigs, boolean doAnotherPass=false) { + if (doAnotherPass || relocationConfigs.getFirst().tinyFile == null) + generateMappings(relocationConfigs, !doAnotherPass); // Generate mappings if they don't exist + + // Return if there are no new conflicts (no new class conflicts) + // This will return if there are no conflicts at all (empty mappings) or there are only resource conflicts which we're ignoring + if (doAnotherPass && relocationConfigs.parallelStream() + .flatMap(config -> config.getMappings().keySet().parallelStream()) + .noneMatch(mapping -> mapping.endsWith(".class")) + ) { + relocationConfigs.parallelStream().forEach(config -> config.setMappings(mappingsSnapshot.get(config.getJarFile()))); + return; + } + + // Process each JAR file in parallel + relocationConfigs.parallelStream().forEach(relocationConfig -> { + // Update mapping snapshot, merge with existing ones + // TODO: Make mappingsSnapshot faster, relocateResources needs access to class mappings and + // doing multiple passes of class relocation resets the mappings so we need to store a snapshot of the mappings + // in order to restore them at the end of all passes + mappingsSnapshot.computeIfAbsent(relocationConfig.getJarFile(), _ -> new ConcurrentHashMap<>()); + mappingsSnapshot.computeIfPresent(relocationConfig.getJarFile(), (_, existingMap) -> { + existingMap.putAll(relocationConfig.mappings); + return existingMap; + }); + + // Create a tiny remapper with the mappings + IMappingProvider tinyMappings = TinyUtils.createTinyMappingProvider(relocationConfig.tinyFile.toPath(), "original", "relocated"); + var logger = new ConsoleLogger(); + logger.setLevel(TrLogger.Level.ERROR); + TinyRemapper tinyRemapper = TinyRemapper.newRemapper(logger).withMappings(tinyMappings).ignoreConflicts(true).fixPackageAccess(true).renameInvalidLocals(true).rebuildSourceFilenames(true).resolveMissing(true).build(); + + // Get the paths + Path jarFilePath = Paths.get(relocationConfig.jarFile.getName()); + Path tempJarFilePath = Paths.get(relocationConfig.jarFile.getName().setExtension("tmp")); + + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(tempJarFilePath).assumeArchive(true).build()) { + // Add the jar file as a target + outputConsumer.addNonClassFiles(jarFilePath); + tinyRemapper.readInputs(jarFilePath); + + // Apply the mappings + tinyRemapper.apply(outputConsumer); + } finally { + tinyRemapper.finish(); // Close the remapper + } + + // Close the original JAR file + relocationConfig.jarFile.close(); + + // Replace the original JAR file with the relocated one + Files.move(tempJarFilePath, jarFilePath, StandardCopyOption.REPLACE_EXISTING); // TODO: Probably don't overwrite, update the jar location in relocation config instead. FIXME: Trying to update the jar location gives so many lambda bootstrap errors due to how cursed manifold is 😭 + }); + + // Do multiple passes to handle possible conflicts created by the previous pass + // Example: + // ``` + // class A { + // public static Object get() { + // return Minecraft.getInstance(); + // } + // } + // class B { + // public static void use() { + // System.out.println(A.get()); + // } + // } + // ``` + // A will get remapped to `fabric.A` and `forge.A`, which will B call? + relocateClasses(relocationConfigs, true); + } + + /** + * Relocates conflicting resources in JARs. + * + * @param relocationConfigs The relocationConfigs to process + */ + public static void relocateResources(List relocationConfigs, boolean anotherPass=false) { + // Generate mappings if they don't exist or this is another pass + if (anotherPass || relocationConfigs.getFirst().tinyFile == null) generateMappings(relocationConfigs, !anotherPass); + + // Return if there are no new conflicts // if (anotherPass && relocationConfigs.stream().allMatch(config -> config.mappings.isEmpty())) return; - // This will return if there are no conflicts at all (empty mappings) or there are only class and/or META-INF conflicts which we're ignoring - if (anotherPass && relocationConfigs.stream() - .flatMap(config -> config.mappings.keySet().stream()) - .noneMatch(mapping -> !mapping.endsWith(".class") && !mapping.startsWith("META-INF/")) // Checking to see if there's any resource that isn't a class or META-INF, if there isn't then return - ) return; - - AtomicBoolean doAnotherPass = new AtomicBoolean(false); - - // Process each JAR file in parallel - relocationConfigs.parallelStream().forEach(relocationConfig -> { - JarFile jarFile = JAR.isClosed(relocationConfig.jarFile) ? new JarFile(relocationConfig.jarFile.getName()) : relocationConfig.jarFile; // Reopen the JAR file if it's closed (it can be closed by the `relocateClasses` method) - - Set resources = JAR.getResources(jarFile); - Map contentMapping = new HashMap<>(); - - // Create a map of conflicts with path alterations. - Map, Map> conflicts = new HashMap<>(); - Map fileConflicts = new HashMap<>(); // Keep track of mixins to handle them specially - relocationConfig.mappings.forEach((originalPath, relocatedPath) -> { - if (originalPath.endsWith("META-INF/MANIFEST.MF")) return; // Skip manifest - if (!originalPath.endsWith(".class") && !originalPath.startsWith("META-INF/services/")) { // Is a regular file conflict - fileConflicts.put(originalPath, relocatedPath); - } - // replacing with `removeExtension()` would make the ones with extensions be replaced which is what we want - conflicts.put(content -> content.contains(originalPath.removeExtension()), Map.of(originalPath.removeExtension(), relocatedPath.removeExtension())); // Add the original path without the .class extension // the predicate should be _ -> true but for some reason that doesn't work - conflicts.put(_ -> originalPath.contains("/"), Map.of(originalPath.removeExtension().replace('/', '.'), relocatedPath.removeExtension().replace('/', '.'))); // If it's in a directory, add the original path without the .class extension and with dots instead of slashes - conflicts.put(_ -> originalPath.contains("/"), Map.of(originalPath.removeExtension().replace('/', '\\'), relocatedPath.removeExtension().replace('/', '\\'))); // If it's in a directory, add the original path without the .class extension and with backslashes instead of slashes - if (originalPath.endsWith(".class")) { // Is a class conflict - conflicts.put(content -> content.contains("\"${originalPath.getPath().replace('/', '.')}\""), // look for the path with dots and in quotes (for mixins), example: "com.example.mod.mixins" - Map.of("\"${originalPath.getBaseName().removeExtension()}\"", "\"${relocatedPath.getBaseName().removeExtension()}\"")); // Just the filename without the path & extension and in quotes (for mixins) - } - }); - - resources.parallelStream().forEach(entry -> { - String content = JAR.getResource(jarFile, entry); - for (var conflict : conflicts.entrySet()) { - // TODO: Smart replace - // If a file has "com.example.Meow" and "com.example.Meow2" and we're only replacing "com.example.Meow" then only replace all instances of "com.example.Meow" but not "com.example.Meow2" - if (conflict.getKey().test(content)) { - for (var replacement : conflict.getValue().entrySet()) { - content = content.replace(replacement.getKey(), replacement.getValue()); - } - } - } - contentMapping.put(entry, content); - }); - - doAnotherPass.set(JAR.writeResources(jarFile, contentMapping)); - jarFile.close(); - JAR.renameResources(jarFile.getName(), fileConflicts); - }); - - // Do a multiple passes to handle conflicts that were created by the previous pass - if (doAnotherPass.get()) relocateResources(relocationConfigs, true); - } - - /** - * Sets up the mappings for conflicting files in JARs. - * @param relocationConfigs The relocationConfigs to process - * @param append Isn't a good name, but we set it to false to check if we have any new conflicts, - * setting it to false will ignore previous mappings and overwrite them, - * but it will still append the mappings to the tiny file - */ - public static void generateMappings(List relocationConfigs, boolean append = true) { - mapConflicts(relocationConfigs, append); - TinyClassWriter.write(relocationConfigs, tempDir); - } - - /** - * Maps conflicting entries to their relocated paths. - * @param relocationConfigs The list of relocationConfigs to process - * @param append Whether to append to the existing mappings - */ - private static void mapConflicts(List relocationConfigs, boolean append = true) { - record FileInfo(String path, byte[] hash, RelocationConfig source) { } - - // Map to store all relocationConfigs and their hashes grouped by path - Map> filesByPath = new ConcurrentHashMap<>(); - - // Process each JAR file in parallel - relocationConfigs.parallelStream().forEach(file -> { - var jarFile = append ? file.jarFile : new JarFile(file.jarFile.getName()); // If we're not appending, we need to reopen the JAR file - var entries = jarFile.entries(); - while (entries.hasMoreElements()) { - var entry = entries.nextElement(); - if (!entry.isDirectory()) { - // Normalize the path to use forward slashes (JAR standard) - String path = FilenameUtils.normalize(entry.getName(), true); - byte[] hash = JAR.computeHash(jarFile, entry); - - filesByPath.compute(path, (_, existing) -> { - var list = existing == null ? new ArrayList() : existing; - // Only add if the hash is different - if (list.stream().noneMatch(info -> Arrays.equals(info.hash, hash))) - list.add(new FileInfo(path, hash, file)); - return list; - }); - } - } - if (!append) jarFile.close(); // Close the jar if we opened it - }); - - if (!append) { // remove all mappings from the relocation configs as we're not appending - relocationConfigs.forEach(config -> config.setMappings(new HashMap<>())); - } - - // Create mappings for conflicts - filesByPath.forEach((_, fileInfos) -> { - // Return if there are no conflicts - if (fileInfos.size() <= 1) return; - - // Create mappings for all relocationConfigs - fileInfos.forEach(fileInfo -> - fileInfo.source.mappings.putIfAbsent(fileInfo.path, fileInfo.path.addPrefixExtension(fileInfo.source.conflictPrefix)) - ); - }); - } + // This will return if there are no conflicts at all (empty mappings) or there are only class and/or META-INF conflicts which we're ignoring + if (anotherPass && relocationConfigs.stream() + .flatMap(config -> config.mappings.keySet().stream()) + .noneMatch(mapping -> !mapping.endsWith(".class") && !mapping.startsWith("META-INF/")) // Checking to see if there's any resource that isn't a class or META-INF, if there isn't then return + ) return; + + AtomicBoolean doAnotherPass = new AtomicBoolean(false); + + // Process each JAR file in parallel + relocationConfigs.parallelStream().forEach(relocationConfig -> { + JarFile jarFile = JAR.isClosed(relocationConfig.jarFile) ? new JarFile(relocationConfig.jarFile.getName()) : relocationConfig.jarFile; // Reopen the JAR file if it's closed (it can be closed by the `relocateClasses` method) + + Set resources = JAR.getResources(jarFile); + Map contentMapping = new HashMap<>(); + + // Create a map of conflicts with path alterations. + Map, Map> conflicts = new HashMap<>(); + Map fileConflicts = new HashMap<>(); // Keep track of mixins to handle them specially + relocationConfig.mappings.forEach((originalPath, relocatedPath) -> { + if (originalPath.endsWith("META-INF/MANIFEST.MF")) return; // Skip manifest + if (!originalPath.endsWith(".class") && !originalPath.startsWith("META-INF/services/")) { // Is a regular file conflict + fileConflicts.put(originalPath, relocatedPath); + } + // replacing with `removeExtension()` would make the ones with extensions be replaced which is what we want + conflicts.put(content -> content.contains(originalPath.removeExtension()), Map.of(originalPath.removeExtension(), relocatedPath.removeExtension())); // Add the original path without the .class extension // the predicate should be _ -> true but for some reason that doesn't work + conflicts.put(_ -> originalPath.contains("/"), Map.of(originalPath.removeExtension().replace('/', '.'), relocatedPath.removeExtension().replace('/', '.'))); // If it's in a directory, add the original path without the .class extension and with dots instead of slashes + conflicts.put(_ -> originalPath.contains("/"), Map.of(originalPath.removeExtension().replace('/', '\\'), relocatedPath.removeExtension().replace('/', '\\'))); // If it's in a directory, add the original path without the .class extension and with backslashes instead of slashes + if (originalPath.endsWith(".class")) { // Is a class conflict + conflicts.put(content -> content.contains("\"${originalPath.getPath().replace('/', '.')}\""), // look for the path with dots and in quotes (for mixins), example: "com.example.mod.mixins" + Map.of("\"${originalPath.getBaseName().removeExtension()}\"", "\"${relocatedPath.getBaseName().removeExtension()}\"")); // Just the filename without the path & extension and in quotes (for mixins) + } + }); + + resources.parallelStream().forEach(entry -> { + String content = JAR.getResource(jarFile, entry); + + for (var conflict : conflicts.entrySet()) { + if (entry.getName().endsWith(".mixins.json") || entry.getName().endsWith(".mixin.json")) { // Handle mixin paths + for (var replacement : conflict.getValue().entrySet()) { + var gson = new GsonBuilder().setPrettyPrinting().create(); + var mixinJson = gson.fromJson(content, JsonObject.class); + var packagePath = mixinJson.get("package").getAsString(); + + var commonMixinPaths = mixinJson.getAsJsonArray("mixins"); + var newCommonMixinPaths = new JsonArray(); + + var clientMixinPaths = mixinJson.getAsJsonArray("client"); + var newClientMixinPaths = new JsonArray(); + + for (JsonElement mixinPath : commonMixinPaths) { + var fullPath = packagePath + "." + mixinPath.getAsString(); + if (replacement.getKey().equals(fullPath)) + newCommonMixinPaths.add(replacement.getValue().substring(packagePath.length() + 1)); + else + newCommonMixinPaths.add(mixinPath); + } + + for (JsonElement mixinPath : clientMixinPaths) { + var fullPath = packagePath + "." + mixinPath.getAsString(); + if (replacement.getKey().equals(fullPath)) + newClientMixinPaths.add(replacement.getValue().substring(packagePath.length() + 1)); + else + newClientMixinPaths.add(mixinPath); + } + + mixinJson.add("mixins", newCommonMixinPaths); + mixinJson.add("client", newClientMixinPaths); + + content = gson.toJson(mixinJson); + } + } else { + // TODO: Smart replace + // If a file has "com.example.Meow" and "com.example.Meow2" and we're only replacing "com.example.Meow" then only replace all instances of "com.example.Meow" but not "com.example.Meow2" + if (conflict.getKey().test(content)) { + for (var replacement : conflict.getValue().entrySet()) { + content = content.replace(replacement.getKey(), replacement.getValue()); + } + } + } + + if (entry.getName().endsWith(".mixins.json") || entry.getName().endsWith(".mixin.json")) { + System.out.println(content); + } + + contentMapping.put(entry, content); + } + }); + + doAnotherPass.set(JAR.writeResources(jarFile, contentMapping)); + jarFile.close(); + JAR.renameResources(jarFile.getName(), fileConflicts); + }); + + // Do a multiple passes to handle conflicts that were created by the previous pass + if (doAnotherPass.get()) relocateResources(relocationConfigs, true); + } + + /** + * Sets up the mappings for conflicting files in JARs. + * + * @param relocationConfigs The relocationConfigs to process + * @param append Isn't a good name, but we set it to false to check if we have any new conflicts, + * setting it to false will ignore previous mappings and overwrite them, + * but it will still append the mappings to the tiny file + */ + public static void generateMappings(List relocationConfigs, boolean append=true) { + mapConflicts(relocationConfigs, append); + TinyClassWriter.write(relocationConfigs, tempDir); + } + + /** + * Maps conflicting entries to their relocated paths. + * + * @param relocationConfigs The list of relocationConfigs to process + * @param append Whether to append to the existing mappings + */ + private static void mapConflicts(List relocationConfigs, boolean append=true) { + record FileInfo(String path, byte[] hash, RelocationConfig source) { + } + + // Map to store all relocationConfigs and their hashes grouped by path + Map> filesByPath = new ConcurrentHashMap<>(); + + // Process each JAR file in parallel + relocationConfigs.parallelStream().forEach(file -> { + var jarFile = append ? file.jarFile : new JarFile(file.jarFile.getName()); // If we're not appending, we need to reopen the JAR file + var entries = jarFile.entries(); + while (entries.hasMoreElements()) { + var entry = entries.nextElement(); + if (!entry.isDirectory()) { + // Normalize the path to use forward slashes (JAR standard) + String path = FilenameUtils.normalize(entry.getName(), true); + byte[] hash = JAR.computeHash(jarFile, entry); + + filesByPath.compute(path, (_, existing) -> { + var list = existing == null ? new ArrayList() : existing; + // Only add if the hash is different + if (list.stream().noneMatch(info -> Arrays.equals(info.hash, hash))) + list.add(new FileInfo(path, hash, file)); + return list; + }); + } + } + if (!append) jarFile.close(); // Close the jar if we opened it + }); + + if (!append) { // remove all mappings from the relocation configs as we're not appending + relocationConfigs.forEach(config -> config.setMappings(new HashMap<>())); + } + + // Create mappings for conflicts + filesByPath.forEach((_, fileInfos) -> { + // Return if there are no conflicts + if (fileInfos.size() <= 1) return; + + // Create mappings for all relocationConfigs + fileInfos.forEach(fileInfo -> + fileInfo.source.mappings.putIfAbsent(fileInfo.path, fileInfo.path.addPrefixExtension(fileInfo.source.conflictPrefix)) + ); + }); + } } From 2f29666d89aeb6d21efdb81dd4d49835f0060e2c Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Tue, 4 Nov 2025 14:28:14 +0100 Subject: [PATCH 2/9] Bump `VERSION` to `2.0.0-SNAPSHOT.5.1-FORK.4` --- src/main/java/io/github/pacifistmc/forgix/Forgix.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/pacifistmc/forgix/Forgix.java b/src/main/java/io/github/pacifistmc/forgix/Forgix.java index f53209a..b0539c4 100644 --- a/src/main/java/io/github/pacifistmc/forgix/Forgix.java +++ b/src/main/java/io/github/pacifistmc/forgix/Forgix.java @@ -13,7 +13,7 @@ import java.util.stream.Collectors; public class Forgix { - public static final String VERSION = "2.0.0-SNAPSHOT.5.1-FORK.3"; + public static final String VERSION = "2.0.0-SNAPSHOT.5.1-FORK.4"; private static final String MANIFEST_VERSION_KEY = "Forgix-Version"; private static final String MANIFEST_MAPPINGS_KEY = "Forgix-Mappings"; From 3dfb49cef2f636c2f824f7b6fe6b5658d2247c82 Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Tue, 4 Nov 2025 23:49:04 +0100 Subject: [PATCH 3/9] Bump `VERSION` to `2.0.0-fork.9` and add GitHub Maven repository configuration for publishing --- build.gradle | 17 +++++++++++++++++ .../io/github/pacifistmc/forgix/Forgix.java | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7fc714d..b384cf3 100644 --- a/build.gradle +++ b/build.gradle @@ -108,12 +108,29 @@ pluginBundle { } publishing { + publications { + pluginMaven(MavenPublication) { + artifactId = "forgix" + } + } + repositories { maven { name = 'localPluginRepository' url = '../local-plugin-repository' } } + + repositories { + maven { + name = "Github" + url = "https://maven.pkg.github.com/ThePandaOliver/Forgix" + credentials { + username = System.getenv("GITHUB_USER") + password = System.getenv("GITHUB_API_TOKEN") + } + } + } } tasks.withType(GenerateModuleMetadata).configureEach { diff --git a/src/main/java/io/github/pacifistmc/forgix/Forgix.java b/src/main/java/io/github/pacifistmc/forgix/Forgix.java index b0539c4..146b597 100644 --- a/src/main/java/io/github/pacifistmc/forgix/Forgix.java +++ b/src/main/java/io/github/pacifistmc/forgix/Forgix.java @@ -13,7 +13,7 @@ import java.util.stream.Collectors; public class Forgix { - public static final String VERSION = "2.0.0-SNAPSHOT.5.1-FORK.4"; + public static final String VERSION = "2.0.0-fork.9"; private static final String MANIFEST_VERSION_KEY = "Forgix-Version"; private static final String MANIFEST_MAPPINGS_KEY = "Forgix-Mappings"; From d32cf7a175f702f00c072198e38e91497ae248a7 Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Mon, 17 Nov 2025 13:37:21 +0100 Subject: [PATCH 4/9] Implemented a custom file handling system. It can be useful for making handlers for files that need special handling like Mixin which doesn't store full paths. --- .../io/github/pacifistmc/forgix/Forgix.java | 9 +- .../pacifistmc/forgix/core/Multiversion.java | 2 +- .../forgix/core/RelocationConfig.java | 2 + .../pacifistmc/forgix/core/Relocator.java | 81 +++++-------- .../core/filehandlers/MixinFileHandler.java | 42 +++++++ .../configurations/ForgixConfiguration.java | 22 ++++ .../forgix/plugin/tasks/MergeJarsTask.java | 2 +- .../pacifistmc/forgix/tests/CoreTest.java | 110 +++++++++++++++++- 8 files changed, 208 insertions(+), 62 deletions(-) create mode 100644 src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java diff --git a/src/main/java/io/github/pacifistmc/forgix/Forgix.java b/src/main/java/io/github/pacifistmc/forgix/Forgix.java index 146b597..8ea931c 100644 --- a/src/main/java/io/github/pacifistmc/forgix/Forgix.java +++ b/src/main/java/io/github/pacifistmc/forgix/Forgix.java @@ -3,6 +3,7 @@ import io.github.pacifistmc.forgix.core.Multiversion; import io.github.pacifistmc.forgix.core.RelocationConfig; import io.github.pacifistmc.forgix.core.Relocator; +import io.github.pacifistmc.forgix.plugin.configurations.ForgixConfiguration; import io.github.pacifistmc.forgix.utils.JAR; import java.io.File; @@ -13,11 +14,13 @@ import java.util.stream.Collectors; public class Forgix { - public static final String VERSION = "2.0.0-fork.9"; + public static final String VERSION = "2.0.0-fork.11"; private static final String MANIFEST_VERSION_KEY = "Forgix-Version"; private static final String MANIFEST_MAPPINGS_KEY = "Forgix-Mappings"; - public static void mergeLoaders(Map jarsAndLoadersMap, File outputFile, boolean silence = false) { + public static void mergeLoaders(Map jarsAndLoadersMap, File outputFile, + Map customFileHandlers = new HashMap(), + boolean silence = false) { if (!silence) { """ Thank you for using Forgix! @@ -27,7 +30,7 @@ public static void mergeLoaders(Map jarsAndLoadersMap, File output List configs = new ArrayList<>(); jarsAndLoadersMap.forEach((jar, loader) -> configs.add(new RelocationConfig(new JarFile(jar), loader))); - Relocator.relocate(configs); + Relocator.relocate(configs, customFileHandlers); Map tinyFiles = configs.stream() .map(RelocationConfig::getTinyFile) diff --git a/src/main/java/io/github/pacifistmc/forgix/core/Multiversion.java b/src/main/java/io/github/pacifistmc/forgix/core/Multiversion.java index d864631..49b94f7 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/Multiversion.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/Multiversion.java @@ -48,7 +48,7 @@ private static String generateMultiversionUUID() { var relocationConfig = new RelocationConfig(jarFile, uuid); relocationConfig.setMappings(renameMap); - Relocator.relocate(List.of(relocationConfig)); + Relocator.relocate(List.of(relocationConfig), Map.of()); } JAR.removeFiles(multiversionJar, List.of( diff --git a/src/main/java/io/github/pacifistmc/forgix/core/RelocationConfig.java b/src/main/java/io/github/pacifistmc/forgix/core/RelocationConfig.java index e67c4ae..db9378f 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/RelocationConfig.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/RelocationConfig.java @@ -1,5 +1,7 @@ package io.github.pacifistmc.forgix.core; +import io.github.pacifistmc.forgix.plugin.configurations.ForgixConfiguration; + import java.io.File; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java b/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java index fa720d7..1cf88a0 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java @@ -1,6 +1,6 @@ package io.github.pacifistmc.forgix.core; -import com.google.gson.*; +import io.github.pacifistmc.forgix.plugin.configurations.ForgixConfiguration; import io.github.pacifistmc.forgix.utils.JAR; import io.github.pacifistmc.forgix.utils.TinyClassWriter; import net.fabricmc.tinyremapper.*; @@ -8,10 +8,7 @@ import org.apache.commons.io.FilenameUtils; import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.nio.file.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -30,13 +27,14 @@ public class Relocator { } /** - * Relocates conflicting files in JARs. - * - * @param relocationConfigs The relocationConfigs to process - */ - public static void relocate(List relocationConfigs) { + * Relocates conflicting files in JARs. + * + * @param relocationConfigs The relocationConfigs to process + * @param customFileHandlers + */ + public static void relocate(List relocationConfigs, Map customFileHandlers) { relocateClasses(relocationConfigs); - relocateResources(relocationConfigs); + relocateResources(relocationConfigs, customFileHandlers); } /** @@ -123,7 +121,7 @@ public static void relocateClasses(List relocationConfigs, boo * * @param relocationConfigs The relocationConfigs to process */ - public static void relocateResources(List relocationConfigs, boolean anotherPass=false) { + public static void relocateResources(List relocationConfigs, Map customFileHandlers, boolean anotherPass=false) { // Generate mappings if they don't exist or this is another pass if (anotherPass || relocationConfigs.getFirst().tinyFile == null) generateMappings(relocationConfigs, !anotherPass); @@ -164,55 +162,28 @@ public static void relocateResources(List relocationConfigs, b resources.parallelStream().forEach(entry -> { String content = JAR.getResource(jarFile, entry); + // Get the custom handler for this file if the name fits a pattern + ForgixConfiguration.CustomFileHandler handler = customFileHandlers.entrySet() + .stream() + .filter(entry1 -> { + var matcher = FileSystems.getDefault().getPathMatcher("glob:${entry1.key}"); + return matcher.matches(Paths.get(entry.getName())); + }) + .findFirst() + .map(Map.Entry::getValue) + .orElse(null); for (var conflict : conflicts.entrySet()) { - if (entry.getName().endsWith(".mixins.json") || entry.getName().endsWith(".mixin.json")) { // Handle mixin paths - for (var replacement : conflict.getValue().entrySet()) { - var gson = new GsonBuilder().setPrettyPrinting().create(); - var mixinJson = gson.fromJson(content, JsonObject.class); - var packagePath = mixinJson.get("package").getAsString(); - - var commonMixinPaths = mixinJson.getAsJsonArray("mixins"); - var newCommonMixinPaths = new JsonArray(); - - var clientMixinPaths = mixinJson.getAsJsonArray("client"); - var newClientMixinPaths = new JsonArray(); - - for (JsonElement mixinPath : commonMixinPaths) { - var fullPath = packagePath + "." + mixinPath.getAsString(); - if (replacement.getKey().equals(fullPath)) - newCommonMixinPaths.add(replacement.getValue().substring(packagePath.length() + 1)); - else - newCommonMixinPaths.add(mixinPath); - } - - for (JsonElement mixinPath : clientMixinPaths) { - var fullPath = packagePath + "." + mixinPath.getAsString(); - if (replacement.getKey().equals(fullPath)) - newClientMixinPaths.add(replacement.getValue().substring(packagePath.length() + 1)); - else - newClientMixinPaths.add(mixinPath); - } - - mixinJson.add("mixins", newCommonMixinPaths); - mixinJson.add("client", newClientMixinPaths); - - content = gson.toJson(mixinJson); - } - } else { + if (handler != null) { // If there's a custom handler for this file, use it to handle the conflicts + content = handler.handle(entry.getName(), content, conflict.value); + } else if (conflict.getKey().test(content)) { // TODO: Smart replace // If a file has "com.example.Meow" and "com.example.Meow2" and we're only replacing "com.example.Meow" then only replace all instances of "com.example.Meow" but not "com.example.Meow2" - if (conflict.getKey().test(content)) { - for (var replacement : conflict.getValue().entrySet()) { - content = content.replace(replacement.getKey(), replacement.getValue()); - } + for (var replacement : conflict.getValue().entrySet()) { + content = content.replace(replacement.getKey(), replacement.getValue()); } } - if (entry.getName().endsWith(".mixins.json") || entry.getName().endsWith(".mixin.json")) { - System.out.println(content); - } - contentMapping.put(entry, content); } }); @@ -223,7 +194,7 @@ public static void relocateResources(List relocationConfigs, b }); // Do a multiple passes to handle conflicts that were created by the previous pass - if (doAnotherPass.get()) relocateResources(relocationConfigs, true); + if (doAnotherPass.get()) relocateResources(relocationConfigs, customFileHandlers, true); } /** diff --git a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java new file mode 100644 index 0000000..8438f66 --- /dev/null +++ b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java @@ -0,0 +1,42 @@ +package io.github.pacifistmc.forgix.core.filehandlers; + +import com.google.gson.*; +import io.github.pacifistmc.forgix.plugin.configurations.ForgixConfiguration; + +import java.util.Map; + +public class MixinFileHandler implements ForgixConfiguration.CustomFileHandler { + @Override + public String handle(String fileName, String fileContent, Map replacementPaths) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + JsonObject mixinJson = gson.fromJson(fileContent, JsonObject.class); + String packagePath = mixinJson.get("package").getAsString(); + + JsonArray commonMixinPaths = mixinJson.getAsJsonArray("mixins"); + JsonArray newCommonMixinPaths = new JsonArray(); + for (JsonElement mixinPath : commonMixinPaths) { + String fullPath = packagePath + "." + mixinPath.getAsString(); + if (replacementPaths.containsKey(fullPath)) + newCommonMixinPaths.add(replacementPaths.get(fullPath).substring(packagePath.length() + 1)); + else + newCommonMixinPaths.add(mixinPath); + } + mixinJson.add("mixins", newCommonMixinPaths); + + JsonArray clientMixinPaths = mixinJson.getAsJsonArray("client"); + JsonArray newClientMixinPaths = new JsonArray(); + for (JsonElement mixinPath : clientMixinPaths) { + String fullPath = packagePath + "." + mixinPath.getAsString(); + if (replacementPaths.containsKey(fullPath)) + newClientMixinPaths.add(replacementPaths.get(fullPath).substring(packagePath.length() + 1)); + else + newClientMixinPaths.add(mixinPath); + } + mixinJson.add("client", newClientMixinPaths); + + JsonPrimitive mixinPlugin = mixinJson.getAsJsonPrimitive("plugin"); + JsonPrimitive mixinRef = mixinJson.getAsJsonPrimitive("refmap"); + + return gson.toJson(mixinJson); + } +} diff --git a/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java b/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java index 50bac73..b4f8426 100644 --- a/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java +++ b/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java @@ -1,5 +1,7 @@ package io.github.pacifistmc.forgix.plugin.configurations; +import com.google.gson.*; +import io.github.pacifistmc.forgix.core.filehandlers.MixinFileHandler; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.file.Directory; @@ -22,6 +24,7 @@ public class ForgixConfiguration { private final Property destinationDirectory; private final Map mergeConfigurations = new HashMap<>(); public MultiversionConfiguration multiversionConfiguration; + private final Map customFileHandlers = new HashMap<>(); private final Map> LOADER_DEFAULT_METHOD_MAP = new HashMap<>(); private final Project rootProject; @@ -327,6 +330,25 @@ public Property getInputJars() { } } + // Custom file handling stuff + + public void mixinHandler(String mixinNamePattern) { + fileHandler(mixinNamePattern, new MixinFileHandler()); + } + + public void fileHandler(String fileNamePattern, CustomFileHandler handler) { + customFileHandlers.put(fileNamePattern, handler); + } + + public Map getCustomFileHandlers() { + return customFileHandlers; + } + + @FunctionalInterface + public interface CustomFileHandler { + String handle(String fileName, String fileContent, Map replacementPaths); + } + // Internal Gradle stuff @Inject diff --git a/src/main/java/io/github/pacifistmc/forgix/plugin/tasks/MergeJarsTask.java b/src/main/java/io/github/pacifistmc/forgix/plugin/tasks/MergeJarsTask.java index eb9471a..753f72b 100644 --- a/src/main/java/io/github/pacifistmc/forgix/plugin/tasks/MergeJarsTask.java +++ b/src/main/java/io/github/pacifistmc/forgix/plugin/tasks/MergeJarsTask.java @@ -74,7 +74,7 @@ void mergeJars() { } // Perform the merge operation - Forgix.mergeLoaders(jarMap, outputFile, silence.get()); + Forgix.mergeLoaders(jarMap, outputFile, settings.customFileHandlers, silence.get()); } @Override diff --git a/src/test/java/io/github/pacifistmc/forgix/tests/CoreTest.java b/src/test/java/io/github/pacifistmc/forgix/tests/CoreTest.java index df93b49..20412bd 100644 --- a/src/test/java/io/github/pacifistmc/forgix/tests/CoreTest.java +++ b/src/test/java/io/github/pacifistmc/forgix/tests/CoreTest.java @@ -3,8 +3,11 @@ import io.github.pacifistmc.forgix.Forgix; import io.github.pacifistmc.forgix.core.Multiversion; import io.github.pacifistmc.forgix.core.Relocator; +import io.github.pacifistmc.forgix.core.filehandlers.MixinFileHandler; import io.github.pacifistmc.forgix.utils.JAR; import io.github.pacifistmc.forgix.utils.TinyClassWriter; +import org.apache.commons.io.IOUtils; +import org.gradle.internal.impldep.org.junit.Assert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,13 +16,16 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.zip.ZipEntry; import org.apache.commons.io.FileUtils; @@ -184,7 +190,7 @@ void testRelocation() throws IOException { new RelocationConfig(differentJarA, "diffA"), new RelocationConfig(differentJarB, "diffB") )); - Relocator.relocate(files); + Relocator.relocate(files, Map.of()); var differentJarA1 = new JarFile(differentJarACopy); var differentJarB1 = new JarFile(differentJarBCopy); @@ -242,7 +248,7 @@ void testMerge() throws IOException { new RelocationConfig(mergeJarA, "diffA"), new RelocationConfig(mergeJarB, "diffB") )); - Relocator.relocate(files); + Relocator.relocate(files, Map.of()); try (var baos = JAR.combineJars(List.of(mergeJarACopy, mergeJarBCopy))) { try (var fos = new FileOutputStream(mergedJar)) { baos.writeTo(fos); @@ -307,6 +313,106 @@ void testMergeCLI() throws IOException { } } + @Test + void testCustomFileHandling() throws IOException { + File differentJarACopy = tempDir.resolve("conflict-a.jar").toFile(); + File differentJarBCopy = tempDir.resolve("conflict-b.jar").toFile(); + FileUtils.copyFile(differentJarA, differentJarACopy); + FileUtils.copyFile(differentJarB, differentJarBCopy); + + // Relocate & merge + try(JarFile differentJarA = new JarFile(differentJarACopy); + JarFile differentJarB = new JarFile(differentJarBCopy)) { + + List files = new ArrayList<>(List.of( + new RelocationConfig(differentJarA, "diffA"), + new RelocationConfig(differentJarB, "diffB") + )); + Relocator.relocate(files, Map.of("**.mixins.json", new MixinFileHandler())); + } + + // Verify the merged JAR mixins + try (JarFile differentJarA = new JarFile(differentJarACopy); + JarFile differentJarB = new JarFile(differentJarBCopy)) { + ZipEntry mixinEntryA = differentJarA.getEntry("DistantHorizons.fabric.mixins_diffA.json"); + try (InputStream is = differentJarA.getInputStream(mixinEntryA)) { + String content = IOUtils.toString(is, StandardCharsets.UTF_8); + if (debug) { + content.println(); + } + Assert.assertEquals(content, """ + { + "required": true, + "minVersion": "0.8", + "package": "com.seibel.distanthorizons.fabric.mixins", + "mixins": [ + "server.MixinChunkGenerator_diffA", + "server.MixinChunkMap_diffA", + "server.MixinEntity_diffA", + "server.MixinServerPlayer_diffA", + "server.MixinTracingExecutor_diffA", + "server.MixinUtilBackgroundThread_diffA" + ], + "client": [ + "client.MixinClientLevel_diffA", + "client.MixinClientPacketListener_diffA", + "client.MixinDebugScreenOverlay_diffA", + "client.MixinFogRenderer_diffA", + "client.MixinLevelRenderer_diffA", + "client.MixinLightTexture_diffA", + "client.MixinMinecraft_diffA", + "client.MixinOptionsScreen_diffA", + "client.MixinTextureUtil_diffA" + ], + "server": [], + "injectors": { + "defaultRequire": 1 + }, + "plugin": "com.seibel.distanthorizons.fabric.mixins.FabricMixinPlugin", + "refmap": "DistantHorizons-fabric-refmap.json" + }""".stripIndent()); + } + + ZipEntry mixinEntryB = differentJarB.getEntry("DistantHorizons.fabric.mixins_diffB.json"); + try (InputStream is = differentJarB.getInputStream(mixinEntryB)) { + String content = IOUtils.toString(is, StandardCharsets.UTF_8); + if (debug) { + content.println(); + } + Assert.assertEquals(content, """ + { + "required": true, + "minVersion": "0.8", + "package": "com.seibel.distanthorizons.fabric.mixins", + "mixins": [ + "server.MixinChunkGenerator_diffB", + "server.MixinChunkMap_diffB", + "server.MixinEntity_diffB", + "server.MixinServerPlayer_diffB", + "server.MixinTracingExecutor_diffB", + "server.MixinUtilBackgroundThread_diffB" + ], + "client": [ + "client.MixinClientLevel_diffB", + "client.MixinClientPacketListener_diffB", + "client.MixinDebugScreenOverlay_diffB", + "client.MixinFogRenderer_diffB", + "client.MixinLevelRenderer_diffB", + "client.MixinLightTexture_diffB", + "client.MixinMinecraft_diffB", + "client.MixinOptionsScreen_diffB", + "client.MixinTextureUtil_diffB" + ], + "injectors": { + "defaultRequire": 1 + }, + "plugin": "com.seibel.distanthorizons.fabric.mixins.FabricMixinPlugin", + "refmap": "DistantHorizons-fabric-refmap.json" + }""".stripIndent()); + } + } + } + @Test void testMultiversion() throws IOException { // Copy version jars into the temp directory From 1c08badeb1b47f5cf81dba3c586a7cf4fa39cc62 Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Mon, 17 Nov 2025 13:45:17 +0100 Subject: [PATCH 5/9] Clean up --- build.gradle | 17 ----------------- .../io/github/pacifistmc/forgix/Forgix.java | 5 +++-- .../pacifistmc/forgix/core/Relocator.java | 7 ++++--- .../core/filehandlers/CustomFileHandler.java | 8 ++++++++ .../core/filehandlers/MixinFileHandler.java | 3 +-- .../configurations/ForgixConfiguration.java | 6 +----- 6 files changed, 17 insertions(+), 29 deletions(-) create mode 100644 src/main/java/io/github/pacifistmc/forgix/core/filehandlers/CustomFileHandler.java diff --git a/build.gradle b/build.gradle index b384cf3..7fc714d 100644 --- a/build.gradle +++ b/build.gradle @@ -108,29 +108,12 @@ pluginBundle { } publishing { - publications { - pluginMaven(MavenPublication) { - artifactId = "forgix" - } - } - repositories { maven { name = 'localPluginRepository' url = '../local-plugin-repository' } } - - repositories { - maven { - name = "Github" - url = "https://maven.pkg.github.com/ThePandaOliver/Forgix" - credentials { - username = System.getenv("GITHUB_USER") - password = System.getenv("GITHUB_API_TOKEN") - } - } - } } tasks.withType(GenerateModuleMetadata).configureEach { diff --git a/src/main/java/io/github/pacifistmc/forgix/Forgix.java b/src/main/java/io/github/pacifistmc/forgix/Forgix.java index 8ea931c..9ee3cc3 100644 --- a/src/main/java/io/github/pacifistmc/forgix/Forgix.java +++ b/src/main/java/io/github/pacifistmc/forgix/Forgix.java @@ -3,6 +3,7 @@ import io.github.pacifistmc.forgix.core.Multiversion; import io.github.pacifistmc.forgix.core.RelocationConfig; import io.github.pacifistmc.forgix.core.Relocator; +import io.github.pacifistmc.forgix.core.filehandlers.CustomFileHandler; import io.github.pacifistmc.forgix.plugin.configurations.ForgixConfiguration; import io.github.pacifistmc.forgix.utils.JAR; @@ -14,12 +15,12 @@ import java.util.stream.Collectors; public class Forgix { - public static final String VERSION = "2.0.0-fork.11"; + public static final String VERSION = "2.0.0-SNAPSHOT.5.1"; private static final String MANIFEST_VERSION_KEY = "Forgix-Version"; private static final String MANIFEST_MAPPINGS_KEY = "Forgix-Mappings"; public static void mergeLoaders(Map jarsAndLoadersMap, File outputFile, - Map customFileHandlers = new HashMap(), + Map customFileHandlers = new HashMap(), boolean silence = false) { if (!silence) { """ diff --git a/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java b/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java index 1cf88a0..72eb45d 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/Relocator.java @@ -1,5 +1,6 @@ package io.github.pacifistmc.forgix.core; +import io.github.pacifistmc.forgix.core.filehandlers.CustomFileHandler; import io.github.pacifistmc.forgix.plugin.configurations.ForgixConfiguration; import io.github.pacifistmc.forgix.utils.JAR; import io.github.pacifistmc.forgix.utils.TinyClassWriter; @@ -32,7 +33,7 @@ public class Relocator { * @param relocationConfigs The relocationConfigs to process * @param customFileHandlers */ - public static void relocate(List relocationConfigs, Map customFileHandlers) { + public static void relocate(List relocationConfigs, Map customFileHandlers) { relocateClasses(relocationConfigs); relocateResources(relocationConfigs, customFileHandlers); } @@ -121,7 +122,7 @@ public static void relocateClasses(List relocationConfigs, boo * * @param relocationConfigs The relocationConfigs to process */ - public static void relocateResources(List relocationConfigs, Map customFileHandlers, boolean anotherPass=false) { + public static void relocateResources(List relocationConfigs, Map customFileHandlers, boolean anotherPass=false) { // Generate mappings if they don't exist or this is another pass if (anotherPass || relocationConfigs.getFirst().tinyFile == null) generateMappings(relocationConfigs, !anotherPass); @@ -163,7 +164,7 @@ public static void relocateResources(List relocationConfigs, M resources.parallelStream().forEach(entry -> { String content = JAR.getResource(jarFile, entry); // Get the custom handler for this file if the name fits a pattern - ForgixConfiguration.CustomFileHandler handler = customFileHandlers.entrySet() + CustomFileHandler handler = customFileHandlers.entrySet() .stream() .filter(entry1 -> { var matcher = FileSystems.getDefault().getPathMatcher("glob:${entry1.key}"); diff --git a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/CustomFileHandler.java b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/CustomFileHandler.java new file mode 100644 index 0000000..32f2a22 --- /dev/null +++ b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/CustomFileHandler.java @@ -0,0 +1,8 @@ +package io.github.pacifistmc.forgix.core.filehandlers; + +import java.util.Map; + +@FunctionalInterface +public interface CustomFileHandler { + String handle(String fileName, String fileContent, Map replacementPaths); +} \ No newline at end of file diff --git a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java index 8438f66..9d2b9ec 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java @@ -1,11 +1,10 @@ package io.github.pacifistmc.forgix.core.filehandlers; import com.google.gson.*; -import io.github.pacifistmc.forgix.plugin.configurations.ForgixConfiguration; import java.util.Map; -public class MixinFileHandler implements ForgixConfiguration.CustomFileHandler { +public class MixinFileHandler implements CustomFileHandler { @Override public String handle(String fileName, String fileContent, Map replacementPaths) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); diff --git a/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java b/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java index b4f8426..ab9d8d8 100644 --- a/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java +++ b/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java @@ -1,6 +1,7 @@ package io.github.pacifistmc.forgix.plugin.configurations; import com.google.gson.*; +import io.github.pacifistmc.forgix.core.filehandlers.CustomFileHandler; import io.github.pacifistmc.forgix.core.filehandlers.MixinFileHandler; import org.gradle.api.Action; import org.gradle.api.Project; @@ -344,11 +345,6 @@ public Map getCustomFileHandlers() { return customFileHandlers; } - @FunctionalInterface - public interface CustomFileHandler { - String handle(String fileName, String fileContent, Map replacementPaths); - } - // Internal Gradle stuff @Inject From 637927d26099fdfab3dc6faf8cd5a19e272fdbb4 Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Mon, 17 Nov 2025 13:49:52 +0100 Subject: [PATCH 6/9] Renamed mixinHandler method to mixin --- .../forgix/plugin/configurations/ForgixConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java b/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java index ab9d8d8..a39f647 100644 --- a/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java +++ b/src/main/java/io/github/pacifistmc/forgix/plugin/configurations/ForgixConfiguration.java @@ -333,8 +333,8 @@ public Property getInputJars() { // Custom file handling stuff - public void mixinHandler(String mixinNamePattern) { - fileHandler(mixinNamePattern, new MixinFileHandler()); + public void mixin(String mixinPathPattern) { + fileHandler(mixinPathPattern, new MixinFileHandler()); } public void fileHandler(String fileNamePattern, CustomFileHandler handler) { From c1429d8591c54562f1ef8a83ad31d4f40c5bb39d Mon Sep 17 00:00:00 2001 From: The Panda Oliver <70108603+ThePandaOliver@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:38:21 +0100 Subject: [PATCH 7/9] Update src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../core/filehandlers/MixinFileHandler.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java index 9d2b9ec..96b420c 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java @@ -12,15 +12,17 @@ public String handle(String fileName, String fileContent, Map re String packagePath = mixinJson.get("package").getAsString(); JsonArray commonMixinPaths = mixinJson.getAsJsonArray("mixins"); - JsonArray newCommonMixinPaths = new JsonArray(); - for (JsonElement mixinPath : commonMixinPaths) { - String fullPath = packagePath + "." + mixinPath.getAsString(); - if (replacementPaths.containsKey(fullPath)) - newCommonMixinPaths.add(replacementPaths.get(fullPath).substring(packagePath.length() + 1)); - else - newCommonMixinPaths.add(mixinPath); + if (commonMixinPaths != null) { + JsonArray newCommonMixinPaths = new JsonArray(); + for (JsonElement mixinPath : commonMixinPaths) { + String fullPath = packagePath + "." + mixinPath.getAsString(); + if (replacementPaths.containsKey(fullPath)) + newCommonMixinPaths.add(replacementPaths.get(fullPath).substring(packagePath.length() + 1)); + else + newCommonMixinPaths.add(mixinPath); + } + mixinJson.add("mixins", newCommonMixinPaths); } - mixinJson.add("mixins", newCommonMixinPaths); JsonArray clientMixinPaths = mixinJson.getAsJsonArray("client"); JsonArray newClientMixinPaths = new JsonArray(); From 0d0befea77319b27ab908fc60395a9b33bd2921b Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Mon, 17 Nov 2025 14:41:39 +0100 Subject: [PATCH 8/9] Ensure null checks for client mixins, plugin, and refmap in MixinFileHandler --- .../core/filehandlers/MixinFileHandler.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java index 96b420c..7c16400 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java @@ -25,18 +25,27 @@ public String handle(String fileName, String fileContent, Map re } JsonArray clientMixinPaths = mixinJson.getAsJsonArray("client"); - JsonArray newClientMixinPaths = new JsonArray(); - for (JsonElement mixinPath : clientMixinPaths) { - String fullPath = packagePath + "." + mixinPath.getAsString(); - if (replacementPaths.containsKey(fullPath)) - newClientMixinPaths.add(replacementPaths.get(fullPath).substring(packagePath.length() + 1)); - else - newClientMixinPaths.add(mixinPath); + if (clientMixinPaths != null) { + JsonArray newClientMixinPaths = new JsonArray(); + for (JsonElement mixinPath : clientMixinPaths) { + String fullPath = packagePath + "." + mixinPath.getAsString(); + if (replacementPaths.containsKey(fullPath)) + newClientMixinPaths.add(replacementPaths.get(fullPath).substring(packagePath.length() + 1)); + else + newClientMixinPaths.add(mixinPath); + } + mixinJson.add("client", newClientMixinPaths); } - mixinJson.add("client", newClientMixinPaths); JsonPrimitive mixinPlugin = mixinJson.getAsJsonPrimitive("plugin"); + if (mixinPlugin != null) { + mixinJson.addProperty("plugin", replacementPaths.get(mixinPlugin.getAsString())); + } + JsonPrimitive mixinRef = mixinJson.getAsJsonPrimitive("refmap"); + if (mixinRef != null) { + mixinJson.addProperty("refmap", replacementPaths.get(mixinRef.getAsString())); + } return gson.toJson(mixinJson); } From 1e6b535949dfd59b72e098c041e45214da1aa926 Mon Sep 17 00:00:00 2001 From: The Panda Oliver Date: Mon, 17 Nov 2025 15:02:52 +0100 Subject: [PATCH 9/9] Add fallback for missing replacement paths in MixinFileHandler --- .../forgix/core/filehandlers/MixinFileHandler.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java index 7c16400..41e84d1 100644 --- a/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java +++ b/src/main/java/io/github/pacifistmc/forgix/core/filehandlers/MixinFileHandler.java @@ -39,12 +39,18 @@ public String handle(String fileName, String fileContent, Map re JsonPrimitive mixinPlugin = mixinJson.getAsJsonPrimitive("plugin"); if (mixinPlugin != null) { - mixinJson.addProperty("plugin", replacementPaths.get(mixinPlugin.getAsString())); + if (replacementPaths.containsKey(mixinPlugin.getAsString())) + mixinJson.addProperty("plugin", replacementPaths.get(mixinPlugin.getAsString())); + else + mixinJson.addProperty("plugin", mixinPlugin.getAsString()); } JsonPrimitive mixinRef = mixinJson.getAsJsonPrimitive("refmap"); if (mixinRef != null) { - mixinJson.addProperty("refmap", replacementPaths.get(mixinRef.getAsString())); + if (replacementPaths.containsKey(mixinRef.getAsString())) + mixinJson.addProperty("refmap", replacementPaths.get(mixinRef.getAsString())); + else + mixinJson.addProperty("refmap", mixinRef.getAsString()); } return gson.toJson(mixinJson);