package net.fybertech.dynamicmappings;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.commons.RemappingClassAdapter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import net.fybertech.dynamicmappings.InheritanceMap;
import net.fybertech.dynamicmappings.InheritanceMap.FieldHolder;
import net.fybertech.dynamicmappings.InheritanceMap.MethodHolder;
import net.fybertech.dynamicmappings.ParmParser.Parm;
import net.fybertech.meddle.MeddleUtil;
public class DynamicRemap
{
Map<String, String> classMappings;
Map<String, String> fieldMappings;
Map<String, String> methodMappings;
public static String unpackagedPrefix = "net/minecraft/class_";
public static String unpackagedInnerPrefix = "innerclass_";
public InheritanceMap inheritanceMapper = new InheritanceMap();
public DynamicRemap(Map<String, String> cm, Map<String, String> fm, Map<String, String> mm)
{
classMappings = cm;
fieldMappings = fm;
methodMappings = mm;
}
public ClassNode getClassNode(String className)
{
return DynamicMappings.getClassNode(className);
}
private boolean isObfInner(String s)
{
//if (s.length() > 1) return false;
try {
Integer.parseInt(s);
return false;
}
catch (NumberFormatException e)
{
return true;
}
}
private class MyRemapper extends Remapper
{
boolean showMapMethod = false;
final ClassNode classNode;
public MyRemapper(ClassNode cn, boolean debug) {
classNode = cn;
showMapMethod = debug;
}
@Override
public String map(String typeName)
{
boolean originallyUnpackaged = !typeName.contains("/");
if (classMappings.containsKey(typeName)) return classMappings.get(typeName);
// Remap the parent class in the case of an inner class
String[] split = typeName.split("\\$");
if (classMappings.containsKey(split[0])) split[0] = classMappings.get(split[0]);
typeName = split[0];
for (int n = 1; n < split.length; n++) {
String inner = split[n];
if (originallyUnpackaged && isObfInner(inner) && unpackagedInnerPrefix != null) inner = unpackagedInnerPrefix + inner + n;
typeName += "$" + inner;
}
if (!typeName.contains("/") && unpackagedPrefix != null) typeName = unpackagedPrefix + typeName;
return super.map(typeName);
}
@Override
public String mapFieldName(String owner, String name, String desc)
{
ClassNode cn = getClassNode(owner);
if (cn == null) return super.mapFieldName(owner, name, desc);
InheritanceMap map = null;
try {
map = inheritanceMapper.buildMap(cn);
} catch (IOException e) {
e.printStackTrace();
}
Set<FieldHolder> fields = map.fields.get(name + " " + desc);
if (fields == null) return super.mapFieldName(owner, name, desc);
for (FieldHolder holder : fields) {
String key = holder.cn.name + " " + holder.fn.name + " " + holder.fn.desc;
if (fieldMappings.containsKey(key)) {
String mapping = fieldMappings.get(key);
String[] split = mapping.split(" ");
return super.mapFieldName(owner, split[1], desc);
}
}
return super.mapFieldName(owner, name, desc);
}
@Override
public String mapMethodName(String owner, String name, String desc)
{
if (owner.startsWith("[") || name.startsWith("<")) return super.mapMethodName(owner, name, desc);
if (showMapMethod) System.out.println("mapMethod: " + owner + " " + name + " " + desc);
ClassNode cn = getClassNode(owner);
if (cn == null) return super.mapMethodName(owner, name, desc);
InheritanceMap map = null;
try {
map = inheritanceMapper.buildMap(cn);
} catch (IOException e) {
e.printStackTrace();
}
Set<MethodHolder> methods = map.methods.get(name + " " + desc);
if (methods == null) return super.mapMethodName(owner, name, desc);
for (MethodHolder holder : methods) {
String key = holder.cn.name + " " + holder.mn.name + " " + holder.mn.desc;
if (showMapMethod) System.out.println("Key: " + key);
if (methodMappings.containsKey(key)) {
if (showMapMethod) System.out.println(" HAS KEY");
String mapping = methodMappings.get(key);
//System.out.println(mapping);
String[] split = mapping.split(" ");
return super.mapMethodName(owner, split[1], desc);
}
}
return super.mapMethodName(owner, name, desc);
}
}
public ClassNode remapClass(String className)
{
if (className == null) return null;
InputStream stream = getClass().getClassLoader().getResourceAsStream(className + ".class");
return remapClass(stream);
}
public ClassNode remapClass(InputStream stream)
{
if (stream == null) return null;
ClassReader reader = null;
try {
reader = new ClassReader(stream);
} catch (IOException e) {
e.printStackTrace();
}
return remapClass(reader);
}
public byte[] remapClass(byte[] basicClass)
{
if (basicClass == null) return null;
ClassReader reader = new ClassReader(basicClass);
ClassNode cn = remapClass(reader);
if (cn == null) return null;
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
return cw.toByteArray();
}
private class CustomRemappingClassAdapter extends RemappingClassAdapter
{
public CustomRemappingClassAdapter(ClassVisitor cv, Remapper remapper) {
super(cv, remapper);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access)
{
// TODO - Might need to handle for nested inner classes
if (classMappings.containsKey(name)) {
String outer = classMappings.get(outerName);
if (outer == null) outer = outerName;
outerName = outer;
name = classMappings.get(name);
innerName = name.substring(name.lastIndexOf("$") + 1);
//System.out.println("INNER: " + outerName + " " + innerName);
super.visitInnerClass(name, outerName, innerName, access);
return;
}
if (!name.contains("/") && innerName != null && unpackagedInnerPrefix != null && isObfInner(innerName))
{
innerName = unpackagedInnerPrefix + innerName;
}
super.visitInnerClass(name, outerName, innerName, access);
}
}
public ClassNode remapClass(ClassReader reader)
{
boolean showDebug = false;
ClassNode cn = new ClassNode();
reader.accept(new CustomRemappingClassAdapter(cn, new MyRemapper(cn, showDebug)), ClassReader.EXPAND_FRAMES);
// Fix obfuscation of local variable names
for (MethodNode method : cn.methods)
{
int paramCount = 0;
int varCount = 0;
if (method.localVariables != null)
for (LocalVariableNode lvn : method.localVariables)
{
if (!lvn.name.equals("\u2603")) continue;
if (lvn.start == method.instructions.getFirst()) lvn.name = "param" + paramCount++;
else lvn.name = "var" + varCount++;
}
}
if (showDebug) {
System.out.println(cn.name);
for (MethodNode method : cn.methods) {
System.out.println(" " + method.name + " " + method.desc);
}
}
return cn;
}
public static byte[] getFileFromZip(ZipEntry entry, ZipFile zipFile)
{
byte[] buffer = null;
if (entry != null)
{
try {
InputStream stream = zipFile.getInputStream(entry);
int pos = 0;
buffer = new byte[(int)entry.getSize()];
while (true)
{
int read = stream.read(buffer, pos, Math.min(1024, (int)entry.getSize() - pos));
pos += read;
if (read < 1) break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer;
}
/**
* Parses the jar to find any unmapped child classes of any of the specified
* classes, then gives them generic names and repackages them.
*
* @param mcJar - The obfuscated Minecraft jar
* @param baseClasses - List of deobfuscated class names to discover children for
*/
public static void remapUnknownChildren(JarFile mcJar, String ... baseClasses )
{
for (Enumeration<JarEntry> enumerator = mcJar.entries(); enumerator.hasMoreElements();)
{
JarEntry entry = enumerator.nextElement();
String filename = entry.getName();
if (!filename.endsWith(".class")) continue;
String className = filename.substring(0, filename.length() - 6);
if (className.contains("$")) continue;
for (String mappedClass : baseClasses) {
String baseClass = DynamicMappings.getClassMapping(mappedClass);
String classPrefix = mappedClass + "Unknown_";
if (baseClass != null && DynamicMappings.doesInheritFrom(className, baseClass) && !DynamicMappings.reverseClassMappings.containsKey(className)) {
DynamicMappings.addClassMapping(classPrefix + className, className);
break;
}
}
}
}
public static void main(String[] args)
{
ParmParser pp = new ParmParser();
Parm outputParm = pp.addParm("-o", 1); // Output location
Parm clearMappersParm = pp.addParm("-clearmappers", 0);
Parm addMapperParm = pp.addParm("-addmappers", 1);
pp.processArgs(args);
File outputFile = new File("mcremapped.jar");
if (outputParm.found) outputFile = new File(outputParm.getFirstResult());
if (clearMappersParm.found) DynamicMappings.MAPPINGS_CLASSES.clear();
if (addMapperParm.found) {
String split[] = addMapperParm.getFirstResult().split(":;,");
for (String mapper : split) DynamicMappings.MAPPINGS_CLASSES.add(mapper);
}
DynamicMappings.generateClassMappings();
AccessUtil accessUtil = new AccessUtil();
accessUtil.readAllTransformerConfigs();
JarFile mcJar = DynamicMappings.getMinecraftJar();
// Moves unmapped blocks, items, etc to the appropriate packages
if (mcJar != null) {
remapUnknownChildren(mcJar, "net/minecraft/block/Block", "net/minecraft/item/Item", "net/minecraft/entity/monster/EntityMob",
"net/minecraft/entity/Entity", "net/minecraft/tileentity/TileEntity", "net/minecraft/inventory/Container",
"net/minecraft/client/gui/inventory/GuiContainer", "net/minecraft/client/gui/Gui", "net/minecraft/stats/StatBase");
/*try {
mcJar.close();
} catch (IOException e) {}*/
}
DynamicRemap remapper = new DynamicRemap(
DynamicMappings.reverseClassMappings,
DynamicMappings.reverseFieldMappings,
DynamicMappings.reverseMethodMappings);
JarFile jar = mcJar;
/*URL url = DynamicRemap.class.getClassLoader().getResource("net/minecraft/server/MinecraftServer.class");
if (url == null) { System.out.println("Couldn't locate server class!"); return; }
JarFile jar = null;
if ("jar".equals(url.getProtocol())) {
JarURLConnection connection = null;
try {
connection = (JarURLConnection) url.openConnection();
jar = connection.getJarFile();
} catch (IOException e) {
e.printStackTrace();
}
}*/
if (jar == null) { System.out.println("Couldn't locate Minecraft jar!"); return; }
JarOutputStream outJar = null;
try {
outJar = new JarOutputStream(new FileOutputStream(outputFile));
} catch (Exception e) {
e.printStackTrace();
}
for (Enumeration<JarEntry> enumerator = jar.entries(); enumerator.hasMoreElements();)
{
JarEntry entry = enumerator.nextElement();
String name = entry.getName();
byte[] bytes = null;
if (name.startsWith("META-INF/")) {
if (name.endsWith(".RSA") || name.endsWith(".SF")) continue;
}
if (name.endsWith(".class")) {
name = name.substring(0, name.length() - 6);
ClassNode mapped = remapper.remapClass(name);
// Correct the source filename
if (mapped.sourceFile != null && mapped.sourceFile.equals("SourceFile")) {
String sourceName = mapped.name;
if (sourceName.indexOf('$') >= 0)
sourceName = sourceName.substring(0, sourceName.indexOf('$'));
mapped.sourceFile = sourceName + ".java";
}
accessUtil.transformDeobfuscatedClass(mapped);
ClassWriter writer = new ClassWriter(0);
mapped.accept(writer);
name = mapped.name + ".class";
bytes = writer.toByteArray();
}
else bytes = getFileFromZip(entry, jar);
ZipEntry ze = new ZipEntry(name);
try {
outJar.putNextEntry(ze);
outJar.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
outJar.close();
jar.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}