285 lines
8.7 KiB
Java
285 lines
8.7 KiB
Java
/*
|
|
* Minecraft Forge
|
|
* Copyright (c) 2016-2018.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package net.minecraftforge.fml.common.asm;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLConnection;
|
|
import java.net.URLStreamHandler;
|
|
import java.security.Permission;
|
|
import java.util.Map;
|
|
|
|
import net.minecraft.launchwrapper.IClassTransformer;
|
|
import net.minecraft.launchwrapper.LaunchClassLoader;
|
|
|
|
import org.objectweb.asm.ClassVisitor;
|
|
import org.objectweb.asm.ClassWriter;
|
|
import org.objectweb.asm.FieldVisitor;
|
|
import org.objectweb.asm.Opcodes;
|
|
import org.objectweb.asm.Type;
|
|
import org.objectweb.asm.commons.GeneratorAdapter;
|
|
import org.objectweb.asm.commons.Method;
|
|
|
|
import com.google.common.cache.CacheBuilder;
|
|
import com.google.common.cache.CacheLoader;
|
|
import com.google.common.cache.LoadingCache;
|
|
import com.google.common.collect.Maps;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
public class ASMTransformerWrapper
|
|
{
|
|
private static final Map<String, String> wrapperModMap = Maps.newHashMap();
|
|
private static final Map<String, String> wrapperParentMap = Maps.newHashMap();
|
|
|
|
private static final LoadingCache<String, byte[]> wrapperCache = CacheBuilder.newBuilder()
|
|
.maximumSize(30)
|
|
.weakValues()
|
|
.build(new CacheLoader<String, byte[]>()
|
|
{
|
|
@Override
|
|
public byte[] load(String file) throws Exception
|
|
{
|
|
return makeWrapper(file);
|
|
}
|
|
});
|
|
|
|
private static final URL asmGenRoot;
|
|
private static boolean injected = false;
|
|
|
|
static
|
|
{
|
|
try
|
|
{
|
|
asmGenRoot = new URL("asmgen", null, -1, "/", new ASMGenHandler());
|
|
}
|
|
catch(MalformedURLException e)
|
|
{
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private static class ASMGenHandler extends URLStreamHandler
|
|
{
|
|
@Override
|
|
@Nullable
|
|
protected URLConnection openConnection(URL url) throws IOException
|
|
{
|
|
String file = url.getFile();
|
|
if(file.equals("/"))
|
|
{
|
|
return new URLConnection(url)
|
|
{
|
|
@Override
|
|
public void connect() throws IOException
|
|
{
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
};
|
|
}
|
|
if(!file.startsWith("/")) throw new RuntimeException("Malformed URL: " + url);
|
|
file = file.substring(1);
|
|
if(wrapperModMap.containsKey(file))
|
|
{
|
|
return new ASMGenConnection(url, file);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static class ASMGenConnection extends URLConnection
|
|
{
|
|
private final String file;
|
|
|
|
protected ASMGenConnection(URL url, String file)
|
|
{
|
|
super(url);
|
|
this.file = file;
|
|
}
|
|
|
|
@Override
|
|
public void connect() throws IOException
|
|
{
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public InputStream getInputStream()
|
|
{
|
|
return new ByteArrayInputStream(wrapperCache.getUnchecked(file));
|
|
}
|
|
|
|
@Override
|
|
@Nullable
|
|
public Permission getPermission()
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static String getTransformerWrapper(LaunchClassLoader launchLoader, String parentClass, String coreMod)
|
|
{
|
|
if(!injected)
|
|
{
|
|
injected = true;
|
|
launchLoader.addURL(asmGenRoot);
|
|
}
|
|
|
|
String name = getWrapperName(parentClass);
|
|
String fileName = name.replace('.', '/') + ".class";
|
|
wrapperModMap.put(fileName, coreMod);
|
|
wrapperParentMap.put(fileName, parentClass);
|
|
return name;
|
|
}
|
|
|
|
private static byte[] makeWrapper(String fileName)
|
|
{
|
|
if(!wrapperModMap.containsKey(fileName) || !wrapperParentMap.containsKey(fileName) || !fileName.endsWith(".class"))
|
|
{
|
|
throw new IllegalArgumentException("makeWrapper called with strange argument: " + fileName);
|
|
}
|
|
String name = fileName.substring(0, fileName.length() - ".class".length());
|
|
|
|
try
|
|
{
|
|
Type wrapper = Type.getType(TransformerWrapper.class);
|
|
|
|
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
|
|
|
writer.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, name, null, wrapper.getInternalName(), null);
|
|
|
|
Method m = Method.getMethod("void <init> ()");
|
|
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, m, null, null, writer);
|
|
mg.loadThis();
|
|
mg.invokeConstructor(wrapper, m);
|
|
mg.returnValue();
|
|
mg.endMethod();
|
|
|
|
m = Method.getMethod("java.lang.String getParentClass ()");
|
|
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
|
|
mg.push(wrapperParentMap.get(fileName));
|
|
mg.returnValue();
|
|
mg.endMethod();
|
|
|
|
m = Method.getMethod("java.lang.String getCoreMod ()");
|
|
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
|
|
mg.push(wrapperModMap.get(fileName));
|
|
mg.returnValue();
|
|
mg.endMethod();
|
|
|
|
writer.visitEnd();
|
|
|
|
return writer.toByteArray();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private static String getWrapperName(String parentClass)
|
|
{
|
|
return "$wrapper." + parentClass;
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
private static class WrapperVisitor extends ClassVisitor
|
|
{
|
|
private final String name;
|
|
private final String parentClass;
|
|
|
|
public WrapperVisitor(ClassVisitor cv, String name, String parentClass)
|
|
{
|
|
super(Opcodes.ASM5, cv);
|
|
this.name = name.replace('.', '/');
|
|
this.parentClass = parentClass;
|
|
}
|
|
|
|
@Override
|
|
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
|
|
{
|
|
super.visit(version, access, this.name, signature, superName, interfaces);
|
|
}
|
|
|
|
@Override
|
|
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
|
|
{
|
|
if(name.equals("parentClass"))
|
|
{
|
|
return super.visitField(access, name, desc, signature, parentClass);
|
|
}
|
|
return super.visitField(access, name, desc, signature, value);
|
|
}
|
|
}
|
|
|
|
public static abstract class TransformerWrapper implements IClassTransformer
|
|
{
|
|
private final IClassTransformer parent;
|
|
|
|
public TransformerWrapper()
|
|
{
|
|
try
|
|
{
|
|
this.parent = (IClassTransformer)this.getClass().getClassLoader().loadClass(getParentClass()).newInstance();
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public byte[] transform(String name, String transformedName, byte[] basicClass)
|
|
{
|
|
try
|
|
{
|
|
return parent.transform(name, transformedName, basicClass);
|
|
}
|
|
catch(Throwable e)
|
|
{
|
|
throw new TransformerException("Exception in class transformer " + parent + " from coremod " + getCoreMod(), e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
return "TransformerWrapper(" + getParentClass() + ", " + getCoreMod() + ")";
|
|
}
|
|
|
|
protected abstract String getParentClass();
|
|
|
|
protected abstract String getCoreMod();
|
|
}
|
|
|
|
static class TransformerException extends RuntimeException
|
|
{
|
|
private static final long serialVersionUID = -6616232415696157218L;
|
|
|
|
public TransformerException(String message, Throwable cause)
|
|
{
|
|
super(message, cause);
|
|
}
|
|
}
|
|
}
|