package net.minecraft.launchwrapper;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
/**
* This class is specified by Mojang's launchwrapper.
*
* Note: This ClassLoader <b>must</b> define packages and code sources, or some mods
* will crash.
* Also note: packages must be defined before transformers are called.
* The CodeSource URL is the URL to the actual class file, not the JAR or directory.
* The URL for packages is the JAR or directory.
*/
public class LaunchClassLoader extends URLClassLoader {
private ArrayList<URL> sources;
private List<URL> sources_unmod;
private List<String> classloaderExclusions = new ArrayList<String>(20);
private List<String> transformerExclusions = new ArrayList<String>(20);
private ArrayList<IClassTransformer> transformers = new ArrayList<IClassTransformer>(0);
private ClassLoader parent;
private File debugDir = Boolean.getBoolean("mcforkage.classloaderDebug") ? new File("CLASSLOADER_DEBUG") : null;
{
if(debugDir != null)
debugDir.mkdirs();
}
// XXX BACKCOMPAT with logisticspipes.asm.LogisticsPipesClassInjector
private Map<String, byte[]> resourceCache = new HashMap<String, byte[]>();
// XXX BACKCOMPAT with codechicken.core.asm.DelegatedTransformer
@SuppressWarnings("unused")
private Map<String, Class<?>> cachedClasses = new AbstractMap<String, Class<?>>() {
public Class<?> put(String key, Class<?> value) {
return null;
}
@Override
public Set<Map.Entry<String, Class<?>>> entrySet() {
return Collections.emptySet();
}
};
/**
* Constructs a LaunchClassLoader which will initially load from the specified URLs.
* (See {@link URLClassLoader})
*
* @param urls An array of the URLs to load classes from.
*/
public LaunchClassLoader(URL[] urls) {
super(urls, null);
this.parent = LaunchClassLoader.class.getClassLoader();
sources = new ArrayList<URL>(urls.length);
for(URL url : urls)
sources.add(url);
sources.trimToSize();
sources_unmod = Collections.unmodifiableList(sources);
addClassLoaderExclusion("net.minecraft.launchwrapper.");
addClassLoaderExclusion("java.");
addTransformerExclusion("org.objectweb.asm.");
}
/**
* Creates a new instance of the class with the given name,
* transformer with the given class name.
* This method is specified by Mojang's launchwrapper.
*/
public void registerTransformer(String className) {
addTransformerExclusion(className);
IClassTransformer transformer;
try {
transformer = loadClass(className).asSubclass(IClassTransformer.class).newInstance();
} catch(Exception e) {
throw new RuntimeException("Failed to register transformer "+className, e);
}
transformers.add(transformer);
transformers.trimToSize();
}
/**
* Adds a URL to load from. (See {@link URLClassLoader})
*
* This method is specified by Mojang's launchwrapper.
*
* @param url The URL to add.
*/
public void addURL(URL url) {
super.addURL(url);
sources.add(url);
sources.trimToSize();
}
/**
* Returns an unmodifiable list of the URLs being loaded from.
*
* This method is specified by Mojang's launchwrapper.
*
* @return A list of the URLs classes are being loaded from.
*/
public List<URL> getSources() {
return sources_unmod;
}
/**
* This method is specified by Mojang's launchwrapper.
*/
public List<IClassTransformer> getTransformers() {
throw new UnsupportedOperationException();
}
/**
* Adds the given prefix to the classloader exclusion list.
* Any class that begins with a classloader exclusion prefix
* will not be loaded through this classloader - they will
* always be delegated to the parent.
*
* This method is specified by Mojang's launchwrapper.
*/
public void addClassLoaderExclusion(String prefix) {
classloaderExclusions.add(prefix);
}
/**
* Adds the given prefix to the transformer exclusion list.
* Any class that begins with a transformer exclusion prefix
* will not be transformed.
*
* This method is specified by Mojang's launchwrapper.
*/
public void addTransformerExclusion(String prefix) {
transformerExclusions.add(prefix);
}
private static String getClassFileName(String className) {
return className.replace('.','/').concat(".class");
}
/**
* This method is specified by Mojang's launchwrapper.
* @throws IOException
*/
public byte[] getClassBytes(String className) throws IOException {
String resname = getClassFileName(className);
InputStream stream = getResourceAsStream(resname);
if(stream == null)
throw new IOException("Not found: "+resname);
try {
ByteArrayOutputStream temp = new ByteArrayOutputStream(stream.available());
byte[] buffer = new byte[4096];
while(true) {
int nread = stream.read(buffer);
if(nread <= 0)
break;
temp.write(buffer, 0, nread);
}
return temp.toByteArray();
} finally {
stream.close();
}
}
/**
* This method is specified by Mojang's launchwrapper.
*/
public void clearNegativeEntries(Set<String> entries) {
//throw new UnsupportedOperationException();
}
/**
* This method is specified by Mojang's launchwrapper.
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
if(isClassLoaderExcluded(name))
return parent.loadClass(name);
try {
byte[] bytes;
bytes = getClassBytes(name);
String packageName;
if(name.contains("."))
packageName = name.substring(0, name.lastIndexOf('.'));
else
packageName = "";
URL classURL = getResource(getClassFileName(name));
String classURLString = classURL.toString();
URL jarURL;
if(classURLString.startsWith("jar:"))
jarURL = new URL(classURLString.substring(4, classURLString.lastIndexOf('!')));
else
jarURL = classURL;
if(getPackage(packageName) == null) {
definePackage(packageName, new Manifest(), jarURL);
}
if(!isTransformerExcluded(name))
bytes = getTransformedBytes(name, bytes);
CodeSource codeSource = new CodeSource(jarURL, new CodeSigner[0]);
return defineClass(name, bytes, 0, bytes.length, codeSource);
} catch(Exception e) {
throw new ClassNotFoundException(name, e);
} catch(AssertionError e) {
throw new ClassNotFoundException(name, e);
}
} catch(ClassNotFoundException e) {
//e.printStackTrace();
throw e;
}
}
private byte[] getTransformedBytes(String name, byte[] bytes) {
if(debugDir != null) {
int count = 0;
debugDumpClass(count++, name, bytes, null);
byte[] old = Arrays.copyOf(bytes, bytes.length);
for(IClassTransformer ct : transformers) {
bytes = ct.transform(name, name, bytes);
if(!Arrays.equals(bytes, old)) {
debugDumpClass(count, name, bytes, ct);
old = Arrays.copyOf(bytes, bytes.length);
}
count++;
}
} else {
for(IClassTransformer ct : transformers)
bytes = ct.transform(name, name, bytes);
}
// return bytes;
// XXX BACKCOMPAT: e.g. CoFH transforms net.minecraft.enchantment.Enchantment.func_92089_a
// and exceeds the max stack size but doesn't update it.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
new ClassReader(bytes).accept(cw, 0);
return cw.toByteArray();
}
// XXX BACKCOMPAT with codechicken.multipart.asm.ASMMixinCompiler$
@SuppressWarnings("unused")
private byte[] runTransformers(String a, String b, byte[] bytes) {
for(IClassTransformer ct : transformers)
bytes = ct.transform(a, b, bytes);
return bytes;
}
// XXX BACKCOMPAT with codechicken.multipart.asm.ASMMixinCompiler$
@SuppressWarnings("unused")
private Set<String> transformerExceptions = new AbstractSet<String>() {
@Override
public boolean add(String e) {
return !contains(e) && transformerExclusions.add(e);
}
@Override
public boolean remove(Object o) {
if(!contains(o))
return false;
while(transformerExclusions.remove(o));
return true;
}
@Override
public Iterator<String> iterator() {
return transformerExclusions.iterator();
}
@Override
public int size() {
return transformerExclusions.size();
}
};
private void debugDumpClass(int stageIndex, String className, byte[] bytes, IClassTransformer lastTransformer) {
File dumpDir = new File(debugDir, className);
if(!dumpDir.exists())
if(!dumpDir.mkdirs()) {
new Exception("Failed to create "+dumpDir).printStackTrace();
return;
}
File dumpFile = new File(dumpDir, stageIndex+"-"+(lastTransformer == null ? "null" : lastTransformer.getClass().getName()));
try {
FileOutputStream out = new FileOutputStream(dumpFile);
try {
out.write(bytes);
} finally {
out.close();
}
} catch(IOException e) {
e.printStackTrace();
}
}
private boolean isClassLoaderExcluded(String name) {
for(String s : classloaderExclusions)
if(name.startsWith(s))
return true;
return false;
}
private boolean isTransformerExcluded(String name) {
for(String s : transformerExclusions)
if(name.startsWith(s))
return true;
return false;
}
}