Add in support for Optional interfaces and methods. Be gone coremods!

This commit is contained in:
Christian 2013-10-04 17:20:05 -04:00
parent ea25a3ffd0
commit 19173a7b97
10 changed files with 296 additions and 61 deletions

View file

@ -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");

View file

@ -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);
}
}

View file

@ -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<IClassTransformer> transformers = mainClassLoader.getTransformers();
ModAPITransformer modAPI = (ModAPITransformer) transformers.get(transformers.size()-1);
modAPI.initTable(dataTable);
return modAPI;
}
}

View file

@ -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();
}
}

View file

@ -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<String, ASMData> 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<MethodNode> 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<ASMData> interfaces = dataTable.getAll("cpw.mods.fml.common.Optional$Interface");
addData(interfaces);
Set<ASMData> methods = dataTable.getAll("cpw.mods.fml.common.Optional$Method");
addData(methods);
}
private void addData(Set<ASMData> interfaces)
{
for (ASMData data : interfaces)
{
optionals.put(data.getClassName(),data);
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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.<String>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;
}
}

View file

@ -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<Label> labels = Lists.newLinkedList();
private String foundProperties;
private boolean validProperties;
public ModLoaderPropertiesMethodVisitor(String name, ASMModParser discoverer)
{
super(Opcodes.ASM4);
this.discoverer = discoverer;
}
@Override
public void visitCode()
{
labels.clear();
}
@Override
public void visitLdcInsn(Object cst)
{
if (cst instanceof String && labels.size() == 1)
{
foundProperties = (String) cst;
}
}
@Override
public void visitInsn(int opcode)
{
if (Opcodes.ARETURN == opcode && labels.size() == 1 && foundProperties != null)
{
validProperties = true;
}
}
@Override
public void visitLabel(Label label)
{
labels.push(label);
}
@Override
public void visitEnd()
{
if (validProperties)
{
discoverer.setBaseModProperties(foundProperties);
}
}
}

View file

@ -1,73 +1,28 @@
/*
* 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.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import com.google.common.collect.Lists;
public class ModMethodVisitor extends MethodVisitor
{
public class ModMethodVisitor extends MethodVisitor {
private String methodName;
private String methodDescriptor;
private ASMModParser discoverer;
private boolean inCode;
private LinkedList<Label> labels = Lists.newLinkedList();
private String foundProperties;
private boolean validProperties;
public ModMethodVisitor(String name, ASMModParser discoverer)
public ModMethodVisitor(String name, String desc, ASMModParser discoverer)
{
super(Opcodes.ASM4);
this.methodName = name;
this.methodDescriptor = desc;
this.discoverer = discoverer;
}
@Override
public void visitCode()
public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible)
{
labels.clear();
}
@Override
public void visitLdcInsn(Object cst)
{
if (cst instanceof String && labels.size() == 1)
{
foundProperties = (String) cst;
}
}
@Override
public void visitInsn(int opcode)
{
if (Opcodes.ARETURN == opcode && labels.size() == 1 && foundProperties != null)
{
validProperties = true;
}
}
@Override
public void visitLabel(Label label)
{
labels.push(label);
}
@Override
public void visitEnd()
{
if (validProperties)
{
discoverer.setBaseModProperties(foundProperties);
}
discoverer.startMethodAnnotation(methodName, methodDescriptor, annotationName);
return new ModAnnotationVisitor(discoverer);
}
}