package codechicken.lib.asm; import codechicken.lib.config.ConfigTag; import com.google.common.base.Charsets; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.io.LineProcessor; import com.google.common.io.Resources; import net.minecraft.launchwrapper.Launch; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; import net.minecraftforge.fml.relauncher.FMLInjectionData; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.tree.*; import javax.swing.*; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; public class ObfMapping { public static class ObfRemapper extends Remapper { private HashMap<String, String> fields = new HashMap<String, String>(); private HashMap<String, String> funcs = new HashMap<String, String>(); public ObfRemapper() { try { Field rawFieldMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawFieldMaps"); Field rawMethodMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawMethodMaps"); rawFieldMapsField.setAccessible(true); rawMethodMapsField.setAccessible(true); Map<String, Map<String, String>> rawFieldMaps = (Map<String, Map<String, String>>) rawFieldMapsField.get(FMLDeobfuscatingRemapper.INSTANCE); Map<String, Map<String, String>> rawMethodMaps = (Map<String, Map<String, String>>) rawMethodMapsField.get(FMLDeobfuscatingRemapper.INSTANCE); if (rawFieldMaps == null) { throw new IllegalStateException("codechicken.lib.asm.ObfMapping loaded too early. Make sure all references are in or after the asm transformer load stage"); } for (Map<String, String> map : rawFieldMaps.values()) { for (Entry<String, String> entry : map.entrySet()) { if (entry.getValue().startsWith("field")) { fields.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf(':'))); } } } for (Map<String, String> map : rawMethodMaps.values()) { for (Entry<String, String> entry : map.entrySet()) { if (entry.getValue().startsWith("func")) { funcs.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf('('))); } } } } catch (Exception e) { throw new RuntimeException(e); } } @Override public String mapMethodName(String owner, String name, String desc) { String s = funcs.get(name); return s == null ? name : s; } @Override public String mapFieldName(String owner, String name, String desc) { String s = fields.get(name); return s == null ? name : s; } @Override public String map(String typeName) { return FMLDeobfuscatingRemapper.INSTANCE.unmap(typeName); } public String unmap(String typeName) { return FMLDeobfuscatingRemapper.INSTANCE.map(typeName); } public boolean isObf(String typeName) { return !map(typeName).equals(typeName) || !unmap(typeName).equals(typeName); } } public static class MCPRemapper extends Remapper implements LineProcessor<Void> { public static File[] getConfFiles() { // check for GradleStart system vars if (!Strings.isNullOrEmpty(System.getProperty("net.minecraftforge.gradle.GradleStart.srgDir"))) { File srgDir = new File(System.getProperty("net.minecraftforge.gradle.GradleStart.srgDir")); File csvDir = new File(System.getProperty("net.minecraftforge.gradle.GradleStart.csvDir")); if (srgDir.exists() && csvDir.exists()) { File srg = new File(srgDir, "notch-srg.srg"); File fieldCsv = new File(csvDir, "fields.csv"); File methodCsv = new File(csvDir, "methods.csv"); if (srg.exists() && fieldCsv.exists() && methodCsv.exists()) { return new File[] { srg, fieldCsv, methodCsv }; } } } ConfigTag tag = ASMHelper.config.getTag("mappingDir").setComment("Path to directory holding packaged.srg, fields.csv and methods.csv for mcp remapping"); for (int i = 0; i < DIR_GUESSES + DIR_ASKS; i++) { File dir = confDirectoryGuess(i, tag); if (dir == null || dir.isFile()) { continue; } File[] mappings; try { mappings = parseConfDir(dir); } catch (Exception e) { if (i >= DIR_GUESSES) { e.printStackTrace(); } continue; } tag.setValue(dir.getPath()); return mappings; } throw new RuntimeException("Failed to select mappings directory, set it manually in the config"); } private static final int DIR_GUESSES = 4; private static final int DIR_ASKS = 3; public static File confDirectoryGuess(int i, ConfigTag tag) { File mcDir = (File) FMLInjectionData.data()[6]; switch (i) { case 0: return tag.value != null ? new File(tag.getValue()) : null; case 1: return new File(mcDir, "../conf"); case 2: return new File(mcDir, "../build/unpacked/conf"); case 3: return new File(System.getProperty("user.home"), ".gradle/caches/minecraft/net/minecraftforge/forge/" + FMLInjectionData.data()[4] + "-" + ForgeVersion.getVersion() + "/unpacked/conf"); default: JFileChooser fc = new JFileChooser(mcDir); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fc.setDialogTitle("Select an mcp conf dir for the deobfuscator."); int ret = fc.showDialog(null, "Select"); return ret == JFileChooser.APPROVE_OPTION ? fc.getSelectedFile() : null; } } public static File[] parseConfDir(File confDir) { File srgDir = new File(confDir, "conf"); if (!srgDir.exists()) { srgDir = confDir; } File srgs = new File(srgDir, "packaged.srg"); if (!srgs.exists()) { srgs = new File(srgDir, "joined.srg"); } if (!srgs.exists()) { throw new RuntimeException("Could not find packaged.srg or joined.srg"); } File mapDir = new File(confDir, "mappings"); if (!mapDir.exists()) { mapDir = confDir; } File methods = new File(mapDir, "methods.csv"); if (!methods.exists()) { throw new RuntimeException("Could not find methods.csv"); } File fields = new File(mapDir, "fields.csv"); if (!fields.exists()) { throw new RuntimeException("Could not find fields.csv"); } return new File[] { srgs, methods, fields }; } private HashMap<String, String> fields = new HashMap<String, String>(); private HashMap<String, String> funcs = new HashMap<String, String>(); public MCPRemapper() { File[] mappings = getConfFiles(); try { Resources.readLines(mappings[1].toURI().toURL(), Charsets.UTF_8, this); Resources.readLines(mappings[2].toURI().toURL(), Charsets.UTF_8, this); } catch (IOException e) { e.printStackTrace(); } } @Override public String mapMethodName(String owner, String name, String desc) { String s = funcs.get(name); return s == null ? name : s; } @Override public String mapFieldName(String owner, String name, String desc) { String s = fields.get(name); return s == null ? name : s; } @Override public boolean processLine(String line) throws IOException { int i = line.indexOf(','); String srg = line.substring(0, i); int i2 = i + 1; i = line.indexOf(',', i2); String mcp = line.substring(i2, i); (srg.startsWith("func") ? funcs : fields).put(srg, mcp); return true; } @Override public Void getResult() { return null; } } public static ObfRemapper obfMapper = new ObfRemapper(); public static Remapper mcpMapper = null; public static void loadMCPRemapper() { if (mcpMapper == null) { mcpMapper = new MCPRemapper(); } } public static final boolean obfuscated; static { boolean obf = true; try { obf = Launch.classLoader.getClassBytes("net.minecraft.world.World") == null; } catch (IOException ignored) { } obfuscated = obf; if (!obf) { loadMCPRemapper(); } } public String s_owner; public String s_name; public String s_desc; public ObfMapping(String owner) { this(owner, "", ""); } public ObfMapping(String owner, String name, String desc) { this.s_owner = owner; this.s_name = name; this.s_desc = desc; if (s_owner.contains(".")) { throw new IllegalArgumentException(s_owner); } } public ObfMapping(ObfMapping descmap, String subclass) { this(subclass, descmap.s_name, descmap.s_desc); } public static ObfMapping fromDesc(String s) { int lastDot = s.lastIndexOf('.'); if (lastDot < 0) { return new ObfMapping(s, "", ""); } int sep = s.indexOf('(');//methods int sep_end = sep; if (sep < 0) { sep = s.indexOf(' ');//some stuffs sep_end = sep + 1; } if (sep < 0) { sep = s.indexOf(':');//fields sep_end = sep + 1; } if (sep < 0) { return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1), ""); } return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1, sep), s.substring(sep_end)); } public ObfMapping subclass(String subclass) { return new ObfMapping(this, subclass); } public boolean matches(MethodNode node) { return s_name.equals(node.name) && s_desc.equals(node.desc); } public boolean matches(MethodInsnNode node) { return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc); } public AbstractInsnNode toInsn(int opcode) { if (isClass()) { return new TypeInsnNode(opcode, s_owner); } else if (isMethod()) { return new MethodInsnNode(opcode, s_owner, s_name, s_desc); } else { return new FieldInsnNode(opcode, s_owner, s_name, s_desc); } } public void visitTypeInsn(MethodVisitor mv, int opcode) { mv.visitTypeInsn(opcode, s_owner); } public void visitMethodInsn(MethodVisitor mv, int opcode) { mv.visitMethodInsn(opcode, s_owner, s_name, s_desc); } public void visitFieldInsn(MethodVisitor mv, int opcode) { mv.visitFieldInsn(opcode, s_owner, s_name, s_desc); } public MethodVisitor visitMethod(ClassVisitor visitor, int access, String[] exceptions) { return visitor.visitMethod(access, s_name, s_desc, null, exceptions); } public FieldVisitor visitField(ClassVisitor visitor, int access, Object value) { return visitor.visitField(access, s_name, s_desc, null, value); } public boolean isClass(String name) { return name.replace('.', '/').equals(s_owner); } public boolean matches(String name, String desc) { return s_name.equals(name) && s_desc.equals(desc); } public boolean matches(FieldNode node) { return s_name.equals(node.name) && s_desc.equals(node.desc); } public boolean matches(FieldInsnNode node) { return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc); } public String javaClass() { return s_owner.replace('/', '.'); } @Override public boolean equals(Object obj) { if (!(obj instanceof ObfMapping)) { return false; } ObfMapping desc = (ObfMapping) obj; return s_owner.equals(desc.s_owner) && s_name.equals(desc.s_name) && s_desc.equals(desc.s_desc); } @Override public int hashCode() { return Objects.hashCode(s_desc, s_name, s_owner); } @Override public String toString() { if (s_name.length() == 0) { return "[" + s_owner + "]"; } if (s_desc.length() == 0) { return "[" + s_owner + "." + s_name + "]"; } return "[" + (isMethod() ? methodDesc() : fieldDesc()) + "]"; } public String methodDesc() { return s_owner + "." + s_name + s_desc; } public String fieldDesc() { return s_owner + "." + s_name + ":" + s_desc; } public boolean isClass() { return s_name.length() == 0; } public boolean isMethod() { return s_desc.contains("("); } public boolean isField() { return !isClass() && !isMethod(); } public ObfMapping map(Remapper mapper) { if (mapper == null) { return this; } if (isMethod()) { s_name = mapper.mapMethodName(s_owner, s_name, s_desc); } else if (isField()) { s_name = mapper.mapFieldName(s_owner, s_name, s_desc); } s_owner = mapper.mapType(s_owner); if (isMethod()) { s_desc = mapper.mapMethodDesc(s_desc); } else if (s_desc.length() > 0) { s_desc = mapper.mapDesc(s_desc); } return this; } public ObfMapping toRuntime() { map(mcpMapper); return this; } public ObfMapping toClassloading() { if (!obfuscated) { map(mcpMapper); } else if (obfMapper.isObf(s_owner)) { map(obfMapper); } return this; } public ObfMapping copy() { return new ObfMapping(s_owner, s_name, s_desc); } }