From fc47aa21715d7c8907cb3dd74b0c4377fe1ab9d0 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:10:48 +0300 Subject: [PATCH] Remap a bunch of new class names in FancyModLoader Fixes #299. --- .../loom/neoforge/LaunchHandlerPatcher.java | 40 ------ .../loom/neoforge/StringConstantPatcher.java | 127 ++++++++++++++++++ .../loom/util/ClassVisitorUtil.java | 15 ++- .../forge/ForgeLibrariesProvider.java | 46 +++++-- 4 files changed, 174 insertions(+), 54 deletions(-) delete mode 100644 src/main/java/dev/architectury/loom/neoforge/LaunchHandlerPatcher.java create mode 100644 src/main/java/dev/architectury/loom/neoforge/StringConstantPatcher.java diff --git a/src/main/java/dev/architectury/loom/neoforge/LaunchHandlerPatcher.java b/src/main/java/dev/architectury/loom/neoforge/LaunchHandlerPatcher.java deleted file mode 100644 index 1429499a..00000000 --- a/src/main/java/dev/architectury/loom/neoforge/LaunchHandlerPatcher.java +++ /dev/null @@ -1,40 +0,0 @@ -package dev.architectury.loom.neoforge; - -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -/* - * Patches the Minecraft.class check in FML's CommonUserdevLaunchHandler - * to refer to a class that is found in any mapping set (Main.class). - * - * See https://github.com/architectury/architectury-loom/issues/212 - */ -public final class LaunchHandlerPatcher extends ClassVisitor { - private static final String INPUT_CLASS_FILE = "net/minecraft/client/Minecraft.class"; - private static final String OUTPUT_CLASS_FILE = "net/minecraft/client/main/Main.class"; - - public LaunchHandlerPatcher(ClassVisitor next) { - super(Opcodes.ASM9, next); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - return new MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions)); - } - - private static final class MethodPatcher extends MethodVisitor { - MethodPatcher(MethodVisitor next) { - super(Opcodes.ASM9, next); - } - - @Override - public void visitLdcInsn(Object value) { - if (INPUT_CLASS_FILE.equals(value)) { - value = OUTPUT_CLASS_FILE; - } - - super.visitLdcInsn(value); - } - } -} diff --git a/src/main/java/dev/architectury/loom/neoforge/StringConstantPatcher.java b/src/main/java/dev/architectury/loom/neoforge/StringConstantPatcher.java new file mode 100644 index 00000000..eb279524 --- /dev/null +++ b/src/main/java/dev/architectury/loom/neoforge/StringConstantPatcher.java @@ -0,0 +1,127 @@ +package dev.architectury.loom.neoforge; + +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.mappingio.tree.MappingTreeView; + +/** + * A class visitor that replaces a set of string constants in the processed class file(s). + */ +public final class StringConstantPatcher extends ClassVisitor { + private final Map constantChanges; + + private static final String LAUNCH_HANDLER_INPUT_CLASS_FILE = "net/minecraft/client/Minecraft.class"; + private static final String LAUNCH_HANDLER_OUTPUT_CLASS_FILE = "net/minecraft/client/main/Main.class"; + + private static final RemapKey DETECTED_VERSION_KEY = new RemapKey("net/minecraft/DetectedVersion.class", "net/minecraft/class_3797"); + private static final RemapKey MINECRAFT_KEY = new RemapKey("net/minecraft/client/Minecraft.class", "net/minecraft/class_310"); + + private StringConstantPatcher(ClassVisitor next, Map constantChanges) { + super(Opcodes.ASM9, next); + this.constantChanges = constantChanges; + } + + private StringConstantPatcher(ClassVisitor next, String from, String to) { + this(next, Map.of(from, to)); + } + + /** + * Patches the Minecraft.class check in FML's CommonUserdevLaunchHandler + * to refer to a class that is found in any mapping set (Main.class). + * + *

See issue #212 + */ + public static ClassVisitor forUserdevLaunchHandler(ClassVisitor next) { + return new StringConstantPatcher(next, LAUNCH_HANDLER_INPUT_CLASS_FILE, LAUNCH_HANDLER_OUTPUT_CLASS_FILE); + } + + private static ClassVisitor forRemapping(ClassVisitor next, MappingTreeView mappings, RemapKey... keys) { + final Map constantChanges = new HashMap<>(); + + for (RemapKey key : keys) { + final @Nullable String target = getNamedClassName(mappings, key.intermediary); + + if (target == null || key.constantWithClassSuffix.equals(target + ".class")) { + continue; + } + + constantChanges.put(key.constantWithClassSuffix, target + ".class"); + } + + if (!constantChanges.isEmpty()) { + return new StringConstantPatcher(next, constantChanges); + } else { + return next; + } + } + + private static @Nullable String getNamedClassName(MappingTreeView mappings, String intermediary) { + final int intermediaryNsId = mappings.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString()); + final @Nullable MappingTreeView.ClassMappingView c = mappings.getClass(intermediary, intermediaryNsId); + return c != null ? c.getName(MappingsNamespace.NAMED.toString()) : null; + } + + /** + * Patches the DetectedVersion.class check in FML's FMLLoader + * to remap that reference to the current deobfuscated ns. + * + *

See issue #299 + */ + public static ClassVisitor forFmlLoader(ClassVisitor next, MappingTreeView mappings) { + return forRemapping(next, mappings, DETECTED_VERSION_KEY); + } + + /** + * Patches the Minecraft.class check in FML's GameLocator + * to remap that reference to the current deobfuscated ns. + * + *

See issue #299 + */ + public static ClassVisitor forGameLocator(ClassVisitor next, MappingTreeView mappings) { + return forRemapping(next, mappings, MINECRAFT_KEY); + } + + /** + * Patches the class checks in FML's RequiredSystemFiles + * to remap that reference to the current deobfuscated ns. + * + *

See issue #299 + */ + public static ClassVisitor forRequiredSystemFiles(ClassVisitor next, MappingTreeView mappings) { + return forRemapping(next, mappings, DETECTED_VERSION_KEY, MINECRAFT_KEY); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions)); + } + + private final class MethodPatcher extends MethodVisitor { + MethodPatcher(MethodVisitor next) { + super(Opcodes.ASM9, next); + } + + @Override + public void visitLdcInsn(Object value) { + if (value instanceof String key) { + final @Nullable String target = constantChanges.get(key); + + if (target != null) { + value = target; + } + } + + super.visitLdcInsn(value); + } + } + + private record RemapKey(String constantWithClassSuffix, String intermediary) { + } +} diff --git a/src/main/java/dev/architectury/loom/util/ClassVisitorUtil.java b/src/main/java/dev/architectury/loom/util/ClassVisitorUtil.java index a6bec6c0..99b6ea77 100644 --- a/src/main/java/dev/architectury/loom/util/ClassVisitorUtil.java +++ b/src/main/java/dev/architectury/loom/util/ClassVisitorUtil.java @@ -14,11 +14,22 @@ import net.fabricmc.loom.util.ExceptionUtil; public final class ClassVisitorUtil { public static void rewriteClassFile(Path path, UnaryOperator visitorFactory) throws IOException { + rewriteClassFile(path, false, visitorFactory); + } + + public static void rewriteClassFile(Path path, boolean recomputeFrames, UnaryOperator visitorFactory) throws IOException { try { final byte[] inputBytes = Files.readAllBytes(path); final var reader = new ClassReader(inputBytes); - final var writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - reader.accept(visitorFactory.apply(writer), 0); + final var writer = new ClassWriter(recomputeFrames ? ClassWriter.COMPUTE_FRAMES : 0); + final ClassVisitor visitor = visitorFactory.apply(writer); + + // If we're not doing any changes to the file, no need to process it. + if (visitor == writer) { + return; + } + + reader.accept(visitor, 0); final byte[] outputBytes = writer.toByteArray(); if (!Arrays.equals(inputBytes, outputBytes)) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java index 66d5b093..d43c0a27 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java @@ -25,16 +25,15 @@ package net.fabricmc.loom.configuration.providers.forge; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import com.google.common.hash.Hashing; import dev.architectury.loom.forge.ModDirTransformerDiscovererPatch; -import dev.architectury.loom.neoforge.LaunchHandlerPatcher; +import dev.architectury.loom.neoforge.StringConstantPatcher; import dev.architectury.loom.util.ClassVisitorUtil; +import dev.architectury.loom.util.MappingOption; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleDependency; @@ -49,15 +48,22 @@ import net.fabricmc.loom.configuration.mods.ModConfigurationRemapper; import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper; import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext; import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; +import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService; +import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.ExceptionUtil; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.PropertyUtil; -import net.fabricmc.loom.util.srg.RemapObjectHolderVisitor; +import net.fabricmc.loom.util.service.ScopedServiceFactory; +import net.fabricmc.loom.util.service.ServiceFactory; import net.fabricmc.loom.util.srg.ForgeMappingsMerger; +import net.fabricmc.loom.util.srg.RemapObjectHolderVisitor; +import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; public class ForgeLibrariesProvider { + private static final String FML_PATCH_VERSION = "-loom-patch-v1"; + private static final String FML_LOADER_GROUP = "net.minecraftforge"; private static final String FML_LOADER_NAME = "fmlloader"; private static final String FANCYML_LOADER_GROUP = "net.neoforged.fancymodloader"; @@ -67,6 +73,9 @@ public class ForgeLibrariesProvider { private static final String FORGE_MOD_DIR_TRANSFORMER_DISCOVERER_FILE = "net/minecraftforge/fml/loading/ModDirTransformerDiscoverer.class"; private static final String NEOFORGE_OBJECT_HOLDER_FILE = "net/neoforged/fml/common/asm/ObjectHolderDefinalize.class"; private static final String NEOFORGE_LAUNCH_HANDLER_FILE = "net/neoforged/fml/loading/targets/CommonUserdevLaunchHandler.class"; + private static final String NEOFORGE_LOADER_FILE = "net/neoforged/fml/loading/FMLLoader.class"; + private static final String NEOFORGE_GAME_LOCATOR_FILE = "net/neoforged/fml/loading/moddiscovery/locators/GameLocator.class"; + private static final String NEOFORGE_REQUIRED_SYSTEM_FILES_FILE = "net/neoforged/fml/loading/moddiscovery/locators/RequiredSystemFiles.class"; public static void provide(MappingConfiguration mappingConfiguration, Project project) throws Exception { LoomGradleExtension extension = LoomGradleExtension.get(project); @@ -125,14 +134,14 @@ public class ForgeLibrariesProvider { if (isFML || isFancyML) { // If FML, remap it. - try { + try (var serviceFactory = new ScopedServiceFactory()) { if (isFML) { project.getLogger().info(":remapping FML loader"); } else if (isFancyML) { project.getLogger().info(":remapping FancyML loader"); } - dep = remapFmlLoader(project, artifact, mappingConfiguration); + dep = remapFmlLoader(project, serviceFactory, artifact, mappingConfiguration); } catch (IOException e) { throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Could not remap FML", e); } @@ -151,15 +160,13 @@ public class ForgeLibrariesProvider { } // Returns a Gradle dependency notation. - private static Object remapFmlLoader(Project project, ResolvedArtifact artifact, MappingConfiguration mappingConfiguration) throws IOException { + private static Object remapFmlLoader(Project project, ServiceFactory serviceFactory, ResolvedArtifact artifact, MappingConfiguration mappingConfiguration) throws IOException { final LoomGradleExtension extension = LoomGradleExtension.get(project); // A hash of the current mapping configuration. The transformations only need to be done once per mapping set. // While the mappings ID is definitely valid in file names, splitting MC versions parts into nested directories // isn't good. - final String mappingHash = Hashing.sha256() - .hashString(mappingConfiguration.mappingsIdentifier(), StandardCharsets.UTF_8) - .toString(); + final String mappingHash = Checksum.of(mappingConfiguration.mappingsIdentifier() + FML_PATCH_VERSION).sha256().hex(); // Resolve the inputs and outputs. final ModuleVersionIdentifier id = artifact.getModuleVersion().getId(); @@ -173,6 +180,9 @@ public class ForgeLibrariesProvider { final Path inputJar = artifact.getFile().toPath(); final Path outputJar = mavenHelper.getOutputFile(null); + final TinyMappingsService mappingsService = mappingConfiguration.getMappingsService(project, serviceFactory, MappingOption.DEFAULT); + final MappingTree mappings = mappingsService.getMappingTree(); + // Modify jar. if (!Files.exists(outputJar) || extension.refreshDeps()) { mavenHelper.copyToMaven(inputJar, null); @@ -186,7 +196,7 @@ public class ForgeLibrariesProvider { } if (Files.exists(fs.getPath(FORGE_MOD_DIR_TRANSFORMER_DISCOVERER_FILE))) { - ClassVisitorUtil.rewriteClassFile(fs.getPath(FORGE_MOD_DIR_TRANSFORMER_DISCOVERER_FILE), ModDirTransformerDiscovererPatch::new); + ClassVisitorUtil.rewriteClassFile(fs.getPath(FORGE_MOD_DIR_TRANSFORMER_DISCOVERER_FILE), true, ModDirTransformerDiscovererPatch::new); } if (Files.exists(fs.getPath(NEOFORGE_OBJECT_HOLDER_FILE))) { @@ -194,7 +204,19 @@ public class ForgeLibrariesProvider { } if (Files.exists(fs.getPath(NEOFORGE_LAUNCH_HANDLER_FILE))) { - ClassVisitorUtil.rewriteClassFile(fs.getPath(NEOFORGE_LAUNCH_HANDLER_FILE), LaunchHandlerPatcher::new); + ClassVisitorUtil.rewriteClassFile(fs.getPath(NEOFORGE_LAUNCH_HANDLER_FILE), StringConstantPatcher::forUserdevLaunchHandler); + } + + if (Files.exists(fs.getPath(NEOFORGE_LOADER_FILE))) { + ClassVisitorUtil.rewriteClassFile(fs.getPath(NEOFORGE_LOADER_FILE), next -> StringConstantPatcher.forFmlLoader(next, mappings)); + } + + if (Files.exists(fs.getPath(NEOFORGE_GAME_LOCATOR_FILE))) { + ClassVisitorUtil.rewriteClassFile(fs.getPath(NEOFORGE_GAME_LOCATOR_FILE), next -> StringConstantPatcher.forGameLocator(next, mappings)); + } + + if (Files.exists(fs.getPath(NEOFORGE_REQUIRED_SYSTEM_FILES_FILE))) { + ClassVisitorUtil.rewriteClassFile(fs.getPath(NEOFORGE_REQUIRED_SYSTEM_FILES_FILE), next -> StringConstantPatcher.forRequiredSystemFiles(next, mappings)); } }