Add in support for Optional interfaces and methods. Be gone coremods!
This commit is contained in:
parent
ea25a3ffd0
commit
19173a7b97
10 changed files with 296 additions and 61 deletions
|
@ -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");
|
||||
|
|
22
fml/common/cpw/mods/fml/common/ModAPIManager.java
Normal file
22
fml/common/cpw/mods/fml/common/ModAPIManager.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
50
fml/common/cpw/mods/fml/common/Optional.java
Normal file
50
fml/common/cpw/mods/fml/common/Optional.java
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue