From 673facee9077454b8401b1bb018f626e02bf3802 Mon Sep 17 00:00:00 2001 From: "Joseph C. Sible" Date: Wed, 6 Dec 2017 09:42:10 -0500 Subject: [PATCH] Remove @Optional annotations that specify our lowercase mod ID (#2661) Fixes #2649. Explanation: * Other mods can integrate with us by implementing the SimpleComponent interface. They annotate its methods with @Optional to avoid a hard dependency on us. * Our mod ID changed from "OpenComputers" in 1.10 to "opencomputers" in 1.11. * Some mods (like RFTools) can't refer to our mod by the right-case mod ID, because the same .jar works on both 1.10 and 1.11. * In most cases, using the wrong-case mod ID would just mean that integration wouldn't work, but for us, it causes a crash. * The crash is because we use ASM to inject some extra methods and make the class implement a different interface. This means that if the @Optional mod ID is wrong, our new interface will still be there but some of the original methods won't, which leads to AbstractMethodErrors like McJty/RFTools#1466. * Forge looks for the @Optional annotations before our coremod can do anything, so we can't just remove them from the class. Instead, we need to remove them from Forge's internal list. --- .../cil/oc/common/asm/ClassTransformer.scala | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.scala b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.scala index cc28d2772..a0af4b59a 100644 --- a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.scala +++ b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.scala @@ -1,10 +1,13 @@ package li.cil.oc.common.asm +import com.google.common.collect.ListMultimap import li.cil.oc.common.asm.template.SimpleComponentImpl import li.cil.oc.integration.Mods import net.minecraft.launchwrapper.IClassTransformer import net.minecraft.launchwrapper.LaunchClassLoader +import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper +import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData import org.apache.logging.log4j.LogManager import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter @@ -42,6 +45,19 @@ class ClassTransformer extends IClassTransformer { private val loader = classOf[ClassTransformer].getClassLoader.asInstanceOf[LaunchClassLoader] private val log = LogManager.getLogger("OpenComputers") + // Stuff to help us remove the @Optional annotations below. We set + // this up outside of the method so that it only has to be done once. + private val optionalsField = classOf[ModAPITransformer].getDeclaredField("optionals") + optionalsField.setAccessible(true) + private val transformers = loader.getTransformers() + private var foundModAPITransformer = false + // If these weren't lazy, they'd run before the ModAPITransformer gets added. + private lazy val modAPITransformer = { + foundModAPITransformer = true + transformers.find(_.isInstanceOf[ModAPITransformer]).get + } + private lazy val optionals = optionalsField.get(modAPITransformer).asInstanceOf[ListMultimap[String, ASMData]] + override def transform(name: String, transformedName: String, basicClass: Array[Byte]): Array[Byte] = { if (basicClass == null || name.startsWith("scala.")) return basicClass var transformedClass = basicClass @@ -146,6 +162,17 @@ class ClassTransformer extends IClassTransformer { } } } + if(foundModAPITransformer || transformers.find(_.isInstanceOf[ModAPITransformer]).isDefined) { + // Forge makes the list of @Optional annotations before we can remove + // them from the class, so we need to remove them from its list instead. + val lookupName = if(name.endsWith("$class")) name.substring(0, name.length() - 6) else name + val ourOptionals = optionals.get(lookupName) + val filteredOptionals = ourOptionals.filter(_.getAnnotationInfo().get("modid") == "opencomputers") + if(!filteredOptionals.isEmpty) { + filteredOptionals.foreach(ourOptionals.remove) + log.info(s"Successfully removed our lowercase @Optional annotations from class $name.") + } + } } // Inject some code into the EntityLiving classes recreateLeash method to allow