Merge branch 'dev/1.11' into exp/1.12

This commit is contained in:
Juuz
2025-10-01 11:13:19 +03:00
4 changed files with 173 additions and 50 deletions

View File

@@ -35,7 +35,8 @@ import java.util.Set;
import dev.architectury.loom.forge.ModDirTransformerDiscovererPatch;
import dev.architectury.loom.forge.RemapObjectHolderVisitor;
import dev.architectury.loom.mappings.ForgeMappingsMerger;
import dev.architectury.loom.neoforge.LaunchHandlerPatcher;
import dev.architectury.loom.mappings.MappingOption;
import dev.architectury.loom.neoforge.StringConstantPatcher;
import dev.architectury.loom.util.ClassVisitorUtil;
import dev.architectury.loom.util.PropertyUtil;
import org.gradle.api.Project;
@@ -52,13 +53,19 @@ 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.service.ScopedServiceFactory;
import net.fabricmc.loom.util.service.ServiceFactory;
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";
@@ -68,6 +75,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);
@@ -126,14 +136,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);
}
@@ -152,13 +162,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 = Checksum.of(mappingConfiguration.mappingsIdentifier()).sha256().hex();
final String mappingHash = Checksum.of(mappingConfiguration.mappingsIdentifier() + FML_PATCH_VERSION).sha256().hex();
// Resolve the inputs and outputs.
final ModuleVersionIdentifier id = artifact.getModuleVersion().getId();
@@ -172,6 +182,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);
@@ -185,7 +198,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))) {
@@ -193,7 +206,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));
}
}

View File

@@ -1,41 +0,0 @@
package dev.architectury.loom.neoforge;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import net.fabricmc.loom.util.Constants;
/*
* 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(Constants.ASM_VERSION, 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(Constants.ASM_VERSION, next);
}
@Override
public void visitLdcInsn(Object value) {
if (INPUT_CLASS_FILE.equals(value)) {
value = OUTPUT_CLASS_FILE;
}
super.visitLdcInsn(value);
}
}
}

View File

@@ -0,0 +1,128 @@
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.loom.util.Constants;
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<String, String> 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<String, String> constantChanges) {
super(Constants.ASM_VERSION, 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).
*
* <p>See <a href="https://github.com/architectury/architectury-loom/issues/212">issue #212</a>
*/
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<String, String> 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.
*
* <p>See <a href="https://github.com/architectury/architectury-loom/issues/299">issue #299</a>
*/
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.
*
* <p>See <a href="https://github.com/architectury/architectury-loom/issues/299">issue #299</a>
*/
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.
*
* <p>See <a href="https://github.com/architectury/architectury-loom/issues/299">issue #299</a>
*/
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) {
}
}

View File

@@ -14,11 +14,22 @@ import net.fabricmc.loom.util.ExceptionUtil;
public final class ClassVisitorUtil {
public static void rewriteClassFile(Path path, UnaryOperator<ClassVisitor> visitorFactory) throws IOException {
rewriteClassFile(path, false, visitorFactory);
}
public static void rewriteClassFile(Path path, boolean recomputeFrames, UnaryOperator<ClassVisitor> 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)) {