package codechicken.core.asm;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Map;
import java.util.Stack;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import static codechicken.core.launch.CodeChickenCorePlugin.logger;
public class DelegatedTransformer implements IClassTransformer {
private static ArrayList<IClassTransformer> delegatedTransformers;
private static Method m_defineClass;
private static Field f_cachedClasses;
public DelegatedTransformer() {
delegatedTransformers = new ArrayList<IClassTransformer>();
try {
m_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
m_defineClass.setAccessible(true);
f_cachedClasses = LaunchClassLoader.class.getDeclaredField("cachedClasses");
f_cachedClasses.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public byte[] transform(String name, String tname, byte[] bytes) {
if (bytes == null) {
return null;
}
for (IClassTransformer trans : delegatedTransformers) {
bytes = trans.transform(name, tname, bytes);
}
return bytes;
}
public static void addTransformer(String transformer, JarFile jar, File jarFile) {
logger.debug("Adding CCTransformer: " + transformer);
try {
byte[] bytes;
bytes = Launch.classLoader.getClassBytes(transformer);
if (bytes == null) {
String resourceName = transformer.replace('.', '/') + ".class";
ZipEntry entry = jar.getEntry(resourceName);
if (entry == null) {
throw new Exception("Failed to add transformer: " + transformer + ". Entry not found in jar file " + jarFile.getName());
}
bytes = readFully(jar.getInputStream(entry));
}
defineDependancies(bytes, jar, jarFile);
Class<?> clazz = defineClass(transformer, bytes);
if (!IClassTransformer.class.isAssignableFrom(clazz)) {
throw new Exception("Failed to add transformer: " + transformer + " is not an instance of IClassTransformer");
}
IClassTransformer classTransformer;
try {
classTransformer = (IClassTransformer) clazz.getDeclaredConstructor(File.class).newInstance(jarFile);
} catch (NoSuchMethodException nsme) {
classTransformer = (IClassTransformer) clazz.newInstance();
}
delegatedTransformers.add(classTransformer);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile) throws Exception {
defineDependancies(bytes, jar, jarFile, new Stack<String>());
}
private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile, Stack<String> depStack) throws Exception {
ClassReader reader = new ClassReader(bytes);
DependancyLister lister = new DependancyLister(Opcodes.ASM4);
reader.accept(lister, 0);
depStack.push(reader.getClassName());
for (String dependancy : lister.getDependancies()) {
if (depStack.contains(dependancy)) {
continue;
}
try {
Launch.classLoader.loadClass(dependancy.replace('/', '.'));
} catch (ClassNotFoundException cnfe) {
ZipEntry entry = jar.getEntry(dependancy + ".class");
if (entry == null) {
throw new Exception("Dependency " + dependancy + " not found in jar file " + jarFile.getName());
}
byte[] depbytes = readFully(jar.getInputStream(entry));
defineDependancies(depbytes, jar, jarFile, depStack);
logger.debug("Defining dependancy: " + dependancy);
defineClass(dependancy.replace('/', '.'), depbytes);
}
}
depStack.pop();
}
private static Class<?> defineClass(String classname, byte[] bytes) throws Exception {
Class<?> clazz = (Class<?>) m_defineClass.invoke(Launch.classLoader, classname, bytes, 0, bytes.length);
((Map<String, Class<?>>) f_cachedClasses.get(Launch.classLoader)).put(classname, clazz);
return clazz;
}
public static byte[] readFully(InputStream stream) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available());
int r;
while ((r = stream.read()) != -1) {
bos.write(r);
}
return bos.toByteArray();
}
}