diff --git a/src/main/java/dev/architectury/loom/neoforge/LaunchHandlerPatcher.java b/src/main/java/dev/architectury/loom/neoforge/LaunchHandlerPatcher.java new file mode 100644 index 000000000..1429499a3 --- /dev/null +++ b/src/main/java/dev/architectury/loom/neoforge/LaunchHandlerPatcher.java @@ -0,0 +1,40 @@ +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/util/ClassVisitorUtil.java b/src/main/java/dev/architectury/loom/util/ClassVisitorUtil.java new file mode 100644 index 000000000..f0e0003ac --- /dev/null +++ b/src/main/java/dev/architectury/loom/util/ClassVisitorUtil.java @@ -0,0 +1,31 @@ +package dev.architectury.loom.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.function.UnaryOperator; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import net.fabricmc.loom.util.ExceptionUtil; + +public final class ClassVisitorUtil { + public static void rewriteClassFile(Path path, UnaryOperator visitorFactory) throws IOException { + try { + final byte[] inputBytes = Files.readAllBytes(path); + final var reader = new ClassReader(inputBytes); + final var writer = new ClassWriter(0); + reader.accept(visitorFactory.apply(writer), 0); + final byte[] outputBytes = writer.toByteArray(); + + if (!Arrays.equals(inputBytes, outputBytes)) { + Files.write(path, outputBytes); + } + } catch (IOException e) { + throw ExceptionUtil.createDescriptiveWrapper(IOException::new, "Failed to patch " + path, e); + } + } +} 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 6b56d81e7..31be3cfa9 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 @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2022-2023 FabricMC + * Copyright (c) 2022-2024 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,6 +32,8 @@ import java.util.List; import com.google.common.hash.Hashing; +import dev.architectury.loom.neoforge.LaunchHandlerPatcher; +import dev.architectury.loom.util.ClassVisitorUtil; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleDependency; @@ -60,6 +62,10 @@ public class ForgeLibrariesProvider { private static final String FANCYML_LOADER_GROUP = "net.neoforged.fancymodloader"; private static final String FANCYML_LOADER_NAME = "loader"; + private static final String FORGE_OBJECT_HOLDER_FILE = "net/minecraftforge/fml/common/asm/ObjectHolderDefinalize.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"; + public static void provide(MappingConfiguration mappingConfiguration, Project project) throws Exception { LoomGradleExtension extension = LoomGradleExtension.get(project); final List dependencies = new ArrayList<>(); @@ -166,13 +172,17 @@ private static Object remapFmlLoader(Project project, ResolvedArtifact artifact, Path path = fs.get().getPath("META-INF/services/cpw.mods.modlauncher.api.INameMappingService"); Files.deleteIfExists(path); - if (Files.exists(fs.get().getPath("net/minecraftforge/fml/common/asm/ObjectHolderDefinalize.class"))) { + if (Files.exists(fs.get().getPath(FORGE_OBJECT_HOLDER_FILE))) { remapObjectHolder(project, outputJar, mappingConfiguration); } - if (Files.exists(fs.getPath("net/neoforged/fml/common/asm/ObjectHolderDefinalize.class"))) { + if (Files.exists(fs.getPath(NEOFORGE_OBJECT_HOLDER_FILE))) { remapNeoForgeObjectHolder(project, outputJar, mappingConfiguration); } + + if (Files.exists(fs.getPath(NEOFORGE_LAUNCH_HANDLER_FILE))) { + ClassVisitorUtil.rewriteClassFile(fs.getPath(NEOFORGE_LAUNCH_HANDLER_FILE), LaunchHandlerPatcher::new); + } } // Copy sources when not running under CI.