From 19173a7b97e15318a068c9758051fd5da7a079fc Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 4 Oct 2013 17:20:05 -0400 Subject: [PATCH] Add in support for Optional interfaces and methods. Be gone coremods! --- fml/common/cpw/mods/fml/common/Loader.java | 1 + .../cpw/mods/fml/common/ModAPIManager.java | 22 ++++ .../cpw/mods/fml/common/ModClassLoader.java | 11 ++ fml/common/cpw/mods/fml/common/Optional.java | 50 ++++++++ .../asm/transformers/ModAPITransformer.java | 112 ++++++++++++++++++ .../fml/common/discovery/ModDiscoverer.java | 4 +- .../common/discovery/asm/ASMModParser.java | 6 + .../common/discovery/asm/ModClassVisitor.java | 9 +- .../asm/ModLoaderPropertiesMethodVisitor.java | 73 ++++++++++++ .../discovery/asm/ModMethodVisitor.java | 69 ++--------- 10 files changed, 296 insertions(+), 61 deletions(-) create mode 100644 fml/common/cpw/mods/fml/common/ModAPIManager.java create mode 100644 fml/common/cpw/mods/fml/common/Optional.java create mode 100644 fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java create mode 100644 fml/common/cpw/mods/fml/common/discovery/asm/ModLoaderPropertiesMethodVisitor.java diff --git a/fml/common/cpw/mods/fml/common/Loader.java b/fml/common/cpw/mods/fml/common/Loader.java index 9b6dd2fe2..6d063be29 100644 --- a/fml/common/cpw/mods/fml/common/Loader.java +++ b/fml/common/cpw/mods/fml/common/Loader.java @@ -505,6 +505,7 @@ public class Loader } } } + ModAPIManager.INSTANCE.buildAPITransformer(modClassLoader, disc); modController.transition(LoaderState.CONSTRUCTING, false); modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, disc.getASMTable()); FMLLog.fine("Mod signature data"); diff --git a/fml/common/cpw/mods/fml/common/ModAPIManager.java b/fml/common/cpw/mods/fml/common/ModAPIManager.java new file mode 100644 index 000000000..f8c04ddf0 --- /dev/null +++ b/fml/common/cpw/mods/fml/common/ModAPIManager.java @@ -0,0 +1,22 @@ +package cpw.mods.fml.common; + +import cpw.mods.fml.common.asm.transformers.ModAPITransformer; +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.discovery.ModDiscoverer; + +public class ModAPIManager { + public static final ModAPIManager INSTANCE = new ModAPIManager(); + private ModAPITransformer transformer; + private ASMDataTable dataTable; + + public void registerDataTable(ASMDataTable dataTable) + { + this.dataTable = dataTable; + } + + public void buildAPITransformer(ModClassLoader modClassLoader, ModDiscoverer discoverer) + { + registerDataTable(discoverer.getASMTable()); + transformer = modClassLoader.addModAPITransformer(dataTable); + } +} diff --git a/fml/common/cpw/mods/fml/common/ModClassLoader.java b/fml/common/cpw/mods/fml/common/ModClassLoader.java index d63c80469..52256cb7e 100644 --- a/fml/common/cpw/mods/fml/common/ModClassLoader.java +++ b/fml/common/cpw/mods/fml/common/ModClassLoader.java @@ -28,6 +28,8 @@ import com.google.common.collect.ImmutableList; import cpw.mods.fml.common.asm.ASMTransformer; import cpw.mods.fml.common.asm.transformers.AccessTransformer; +import cpw.mods.fml.common.asm.transformers.ModAPITransformer; +import cpw.mods.fml.common.discovery.ASMDataTable; import cpw.mods.fml.common.modloader.BaseModProxy; /** @@ -106,4 +108,13 @@ public class ModClassLoader extends URLClassLoader { mainClassLoader.clearNegativeEntries(classList); } + + public ModAPITransformer addModAPITransformer(ASMDataTable dataTable) + { + mainClassLoader.registerTransformer("cpw.mods.fml.common.asm.transformers.ModAPITransformer"); + List transformers = mainClassLoader.getTransformers(); + ModAPITransformer modAPI = (ModAPITransformer) transformers.get(transformers.size()-1); + modAPI.initTable(dataTable); + return modAPI; + } } diff --git a/fml/common/cpw/mods/fml/common/Optional.java b/fml/common/cpw/mods/fml/common/Optional.java new file mode 100644 index 000000000..00cca2021 --- /dev/null +++ b/fml/common/cpw/mods/fml/common/Optional.java @@ -0,0 +1,50 @@ +package cpw.mods.fml.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Classes annotated with this will have the named interface or method removed from the runtime definition of the class + * if the modid specified is missing. + * + * @author cpw + * + */ +public interface Optional { + /** + * Used to remove optional interfaces + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Interface { + /** + * The fully qualified name of the interface to be stripped + * @return the interface name + */ + public String iface(); + + /** + * The modid that is required to be present for stripping NOT to occur + * @return the modid + */ + public String modid(); + } + /** + * Used to remove optional methods + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Method { + /** + * The modid that is required to be present for stripping NOT to occur + * @return the modid + */ + public String modid(); + } +} diff --git a/fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java b/fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java new file mode 100644 index 000000000..ed583111d --- /dev/null +++ b/fml/common/cpw/mods/fml/common/asm/transformers/ModAPITransformer.java @@ -0,0 +1,112 @@ +package cpw.mods.fml.common.asm.transformers; + +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.Optional; +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; +import cpw.mods.fml.relauncher.FMLRelaunchLog; + +import net.minecraft.launchwrapper.IClassTransformer; + +public class ModAPITransformer implements IClassTransformer { + + private static final boolean logDebugInfo = Boolean.valueOf(System.getProperty("fml.debugAPITransformer", "false")); + private ListMultimap optionals; + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) + { + if (optionals == null || !optionals.containsKey(name)) + { + return basicClass; + } + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(basicClass); + classReader.accept(classNode, 0); + + if (logDebugInfo) FMLRelaunchLog.finest("Optional removal - found optionals for class %s - processing", name); + for (ASMData optional : optionals.get(name)) + { + String modId = (String) optional.getAnnotationInfo().get("modid"); + + if (Loader.isModLoaded(modId)) + { + if (logDebugInfo) FMLRelaunchLog.finest("Optional removal skipped - mod present %s", modId); + continue; + } + if (logDebugInfo) FMLRelaunchLog.finest("Optional on %s triggered - mod missing %s", name, modId); + + if ("cpw.mods.fml.common.Optional$Interface".equals(optional.getAnnotationName())) + { + stripInterface(classNode,(String)optional.getAnnotationInfo().get("iface")); + } + else + { + stripMethod(classNode, (String)optional.getObjectName()); + } + + } + if (logDebugInfo) FMLRelaunchLog.finest("Optional removal - class %s processed", name); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + + private void stripMethod(ClassNode classNode, String methodDescriptor) + { + for (ListIterator iterator = classNode.methods.listIterator(); iterator.hasNext();) + { + MethodNode method = iterator.next(); + if (methodDescriptor.equals(method.name+method.desc)) + { + iterator.remove(); + if (logDebugInfo) FMLRelaunchLog.finest("Optional removal - method %s removed", methodDescriptor); + return; + } + } + if (logDebugInfo) FMLRelaunchLog.finest("Optional removal - method %s NOT removed - not found", methodDescriptor); + } + + private void stripInterface(ClassNode classNode, String interfaceName) + { + String ifaceName = interfaceName.replace('.', '/'); + boolean found = classNode.interfaces.remove(ifaceName); + if (found && logDebugInfo) FMLRelaunchLog.finest("Optional removal - interface %s removed", interfaceName); + if (!found && logDebugInfo) FMLRelaunchLog.finest("Optional removal - interface %s NOT removed - not found", interfaceName); + } + + public void initTable(ASMDataTable dataTable) + { + optionals = ArrayListMultimap.create(); + Set interfaces = dataTable.getAll("cpw.mods.fml.common.Optional$Interface"); + addData(interfaces); + Set methods = dataTable.getAll("cpw.mods.fml.common.Optional$Method"); + addData(methods); + } + + private void addData(Set interfaces) + { + for (ASMData data : interfaces) + { + optionals.put(data.getClassName(),data); + } + } + +} diff --git a/fml/common/cpw/mods/fml/common/discovery/ModDiscoverer.java b/fml/common/cpw/mods/fml/common/discovery/ModDiscoverer.java index 50b669bb7..3dff09bed 100644 --- a/fml/common/cpw/mods/fml/common/discovery/ModDiscoverer.java +++ b/fml/common/cpw/mods/fml/common/discovery/ModDiscoverer.java @@ -19,6 +19,8 @@ import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; +import net.minecraft.launchwrapper.LaunchClassLoader; + import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -27,6 +29,7 @@ import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.LoaderException; import cpw.mods.fml.common.ModClassLoader; import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.asm.transformers.ModAPITransformer; import cpw.mods.fml.relauncher.CoreModManager; public class ModDiscoverer @@ -156,5 +159,4 @@ public class ModDiscoverer { return nonModLibs; } - } diff --git a/fml/common/cpw/mods/fml/common/discovery/asm/ASMModParser.java b/fml/common/cpw/mods/fml/common/discovery/asm/ASMModParser.java index c95f161f9..005e79d66 100644 --- a/fml/common/cpw/mods/fml/common/discovery/asm/ASMModParser.java +++ b/fml/common/cpw/mods/fml/common/discovery/asm/ASMModParser.java @@ -179,4 +179,10 @@ public class ASMModParser ModAnnotation child = annotations.removeFirst(); annotations.addLast(child); } + + public void startMethodAnnotation(String methodName, String methodDescriptor, String annotationName) + { + ModAnnotation ann = new ModAnnotation(AnnotationType.METHOD, Type.getType(annotationName), methodName+methodDescriptor); + annotations.addFirst(ann); + } } diff --git a/fml/common/cpw/mods/fml/common/discovery/asm/ModClassVisitor.java b/fml/common/cpw/mods/fml/common/discovery/asm/ModClassVisitor.java index 0ee315e63..76d90a39b 100644 --- a/fml/common/cpw/mods/fml/common/discovery/asm/ModClassVisitor.java +++ b/fml/common/cpw/mods/fml/common/discovery/asm/ModClassVisitor.java @@ -5,7 +5,7 @@ * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * + * * Contributors: * cpw - implementation */ @@ -57,8 +57,11 @@ public class ModClassVisitor extends ClassVisitor { if (discoverer.isBaseMod(Collections.emptyList()) && name.equals("getPriorities") && desc.equals(Type.getMethodDescriptor(Type.getType(String.class)))) { - return new ModMethodVisitor(name, discoverer); + return new ModLoaderPropertiesMethodVisitor(name, discoverer); + } + else + { + return new ModMethodVisitor(name, desc, discoverer); } - return null; } } diff --git a/fml/common/cpw/mods/fml/common/discovery/asm/ModLoaderPropertiesMethodVisitor.java b/fml/common/cpw/mods/fml/common/discovery/asm/ModLoaderPropertiesMethodVisitor.java new file mode 100644 index 000000000..4f523a228 --- /dev/null +++ b/fml/common/cpw/mods/fml/common/discovery/asm/ModLoaderPropertiesMethodVisitor.java @@ -0,0 +1,73 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2013 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors: + * cpw - implementation + */ + +package cpw.mods.fml.common.discovery.asm; + +import java.util.LinkedList; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import com.google.common.collect.Lists; + +public class ModLoaderPropertiesMethodVisitor extends MethodVisitor +{ + + private ASMModParser discoverer; + private boolean inCode; + private LinkedList