Created
August 13, 2016 20:32
-
-
Save FormallyMyles/e9d23ebaebcbfc3ebebd1d88fdf15ec4 to your computer and use it in GitHub Desktop.
DatawatcherCollector - Compile then run passing in jar file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package us.myles.dwc; | |
import org.objectweb.asm.*; | |
import org.objectweb.asm.tree.*; | |
import java.lang.reflect.Modifier; | |
import java.util.*; | |
import java.util.jar.JarEntry; | |
import java.util.jar.JarFile; | |
public class DatawatcherCollector { | |
private static String entity; | |
private static String dataWatcher; | |
private static String entityTypes; | |
private static Map<String, ClassNode> classes = new HashMap<>(); | |
public static void main(String[] args) throws Exception { | |
JarFile jarFile = new JarFile(args[0]); | |
// Entity - entityBaseTick | |
// EntityTypes - Skipping Entity with id {} | |
// DataWatcher - Data value id is too big with | |
System.out.println("Collecting useful information..."); | |
Enumeration<JarEntry> iter = jarFile.entries(); | |
while (iter.hasMoreElements()) { | |
JarEntry entry = iter.nextElement(); | |
if (entry.getName().endsWith(".class") && (entry.getName().startsWith("net/minecraft") || !entry.getName().contains("/"))) { | |
ClassReader reader = new ClassReader(jarFile.getInputStream(entry)); | |
ClassNode node = new ClassNode(); | |
reader.accept(node, ClassReader.EXPAND_FRAMES); | |
classes.put(entry.getName().replace('/', '.').replace(".class", ""), node); | |
} | |
} | |
System.out.println("Finding required classes..."); | |
System.out.println(); | |
// Using magic technology find classes :D | |
entity = findClassFromConstant("entityBaseTick"); | |
entityTypes = findClassFromConstant("Skipping Entity with id {}"); | |
dataWatcher = findClassFromConstant("Data value id is too big with "); | |
if (entity == null || entityTypes == null || dataWatcher == null) { | |
throw new RuntimeException("Constants have changed!! "); | |
} | |
System.out.println("Entity Class: " + entity); | |
System.out.println("EntityTypes Class: " + entityTypes); | |
System.out.println("DataWatcher Class: " + dataWatcher); | |
System.out.println(); | |
System.out.println("Attempting to Map Parents..."); | |
List<String> queue = new ArrayList<>(classes.keySet()); | |
ClassTree object = new ClassTree("java.lang.Object"); | |
String current = null; | |
int total = queue.size(); | |
while (queue.size() > 0) { | |
if (queue.size() % 100 == 0) { | |
System.out.println((total - queue.size()) + "/" + total + " done..."); | |
} | |
if (object.contains(current) || current == null) { | |
current = queue.get(0); | |
} | |
ClassNode clazz = classes.get(current); | |
if (clazz != null) { | |
// Check super | |
String superC = clazz.superName.replace('/', '.'); | |
if (object.getName().equals(superC) || object.contains(superC)) { | |
object.insert(superC, current); | |
queue.remove(current); | |
current = null; | |
} else { | |
if (queue.contains(superC)) { | |
current = superC; | |
} else { | |
queue.remove(current); | |
current = null; | |
} | |
} | |
} else { | |
queue.remove(current); | |
current = null; | |
} | |
} | |
System.out.println("Fetching entity tree..."); | |
System.out.println(); | |
ClassTree tree = object.find(entity); // Name resolver? | |
print(tree, 0); | |
System.out.println(); | |
System.out.println("Printing Metadata:"); | |
metadataTree(null, tree, 0); | |
} | |
public static String resolveName(String clazz) { | |
if (clazz.equals(entity)) return "Entity"; | |
ClassNode entityTypesNode = classes.get(entityTypes); | |
InvokeClassStringExtractor extractor = new InvokeClassStringExtractor(clazz, entityTypes); | |
entityTypesNode.accept(extractor); | |
if (extractor.getFoundName() == null) | |
return "Unknown(" + clazz + ")"; | |
return extractor.getFoundName(); | |
} | |
public static void metadataTree(ClassTree parent, ClassTree tree, int i) { | |
List<String> mt = metadata(tree.getName()); | |
System.out.println(); | |
System.out.println(tree.getName() + "(" + resolveName(tree.getName()) + ")" + " Parent: " + (parent == null ? "None" : resolveName(parent.getName()))); | |
// print each metadata for this class | |
for (String meta : mt) { | |
System.out.println(i++ + ". " + meta); | |
} | |
// print children next | |
for (ClassTree item : tree.getChildren()) { | |
metadataTree(tree, item, i); | |
} | |
} | |
private static List<String> metadata(String name) { | |
List<String> results = new ArrayList<>(); | |
ClassNode node = classes.get(name); | |
List<MethodNode> methods = node.methods; | |
for (MethodNode method : methods) { | |
if (method.name.equals("<clinit>")) { | |
// Static init | |
MethodInsnNode lastMethod = null; | |
for (AbstractInsnNode insn : method.instructions.toArray()) { | |
if (insn instanceof MethodInsnNode) { | |
if (((MethodInsnNode) insn).owner.equals(dataWatcher)) { | |
lastMethod = (MethodInsnNode) insn; | |
} | |
} | |
if (insn instanceof FieldInsnNode) { | |
FieldInsnNode fieldInsn = (FieldInsnNode) insn; | |
if (insn.getOpcode() == Opcodes.PUTSTATIC) { | |
if (lastMethod != null) { | |
if (!fieldInsn.owner.equals(name)) { | |
continue; | |
} | |
// Find field | |
for (FieldNode fieldNode : (List<FieldNode>) node.fields) { | |
if (fieldNode.name.equals(fieldInsn.name)) { | |
// Got signature | |
results.add("DataWatcher." + lastMethod.name + "() - " + fieldNode.name + " - " + fieldNode.signature); | |
break; | |
} | |
} | |
} | |
} | |
lastMethod = null; | |
} | |
} | |
} | |
} | |
return results; | |
} | |
public static void print(ClassTree tree, int depth) { | |
System.out.println(new String(new char[depth]).replace("\0", "-") + "> " + tree.getName() + "(" + resolveName(tree.getName()) + ")"); | |
for (ClassTree item : tree.getChildren()) { | |
print(item, depth + 1); | |
} | |
} | |
public static String findClassFromConstant(String str) { | |
for (Map.Entry<String, ClassNode> s : classes.entrySet()) { | |
ClassNode clazz = s.getValue(); | |
List<MethodNode> methods = clazz.methods; | |
for (MethodNode method : methods) { | |
for (AbstractInsnNode insnNode : method.instructions.toArray()) { | |
if (insnNode instanceof LdcInsnNode) { | |
LdcInsnNode ldc = (LdcInsnNode) insnNode; | |
if (str.equals(ldc.cst)) { | |
return s.getKey(); | |
} | |
} | |
} | |
} | |
} | |
return null; | |
} | |
} | |
class InvokeClassStringExtractor extends ClassVisitor { | |
private final String entityTypes; | |
private final String classToFind; | |
public String foundName; | |
public InvokeClassStringExtractor(String classToFind, String entityTypes) { | |
super(Opcodes.ASM5); | |
this.entityTypes = entityTypes; | |
this.classToFind = classToFind; | |
} | |
public String getFoundName() { | |
return foundName; | |
} | |
@Override | |
public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) { | |
if (Modifier.isStatic(access) && foundName == null) { | |
return new MethodVisitor(Opcodes.ASM4, super.visitMethod(access, methodName, desc, signature, exceptions)) { | |
private Stack args = new Stack(); | |
@Override | |
public void visitLdcInsn(Object cst) { | |
if (foundName != null) return; | |
args.push(cst); | |
super.visitLdcInsn(cst); | |
} | |
@Override | |
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { | |
if (foundName != null) return; | |
if (owner.equals(entityTypes)) { | |
// Take the last variables on the stack | |
Type[] argTypes = Type.getArgumentTypes(desc); | |
boolean clazz = false; | |
boolean string = false; | |
for (Type t : argTypes) { | |
if (t.getClassName().equals("java.lang.Class")) { | |
clazz = true; | |
} | |
if (t.getClassName().equals("java.lang.String")) { | |
string = true; | |
} | |
} | |
if (clazz && string) { | |
// Get last string | |
String className = null; | |
String entityName = null; | |
for (int i = 0; i < argTypes.length; i++) { | |
if (args.size() > 0) { | |
Object pop = args.pop(); | |
if (pop instanceof Type) { | |
if (!((Type) pop).getClassName().equals(classToFind)) { | |
break; | |
} else { | |
if (entityName != null) { | |
foundName = entityName; | |
return; | |
} else { | |
className = ((Type) pop).getClassName(); | |
} | |
} | |
} | |
if (pop instanceof String) { | |
if (className != null) { | |
foundName = (String) pop; | |
return; | |
} else { | |
entityName = (String) pop; | |
} | |
} | |
} | |
} | |
} | |
} | |
// Clear otherwise | |
args.clear(); | |
super.visitMethodInsn(opcode, owner, name, desc, itf); | |
} | |
}; | |
} else { | |
return super.visitMethod(access, methodName, desc, signature, exceptions); | |
} | |
} | |
} | |
class ClassTree { | |
private String name; | |
private ArrayList<ClassTree> children = new ArrayList<>(); | |
public ClassTree(String name) { | |
this.name = name; | |
} | |
public String getName() { | |
return name; | |
} | |
public ArrayList<ClassTree> getChildren() { | |
return children; | |
} | |
public boolean contains(String name) { | |
for (ClassTree item : children) { | |
if (item.getName().equals(name)) return true; | |
if (item.contains(name)) return true; | |
} | |
return false; | |
} | |
public ClassTree find(String name) { | |
for (ClassTree item : children) { | |
if (item.getName().equals(name)) return item; | |
if (item.contains(name)) return item.find(name); | |
} | |
return null; | |
} | |
public boolean insert(String superclass, String name) { | |
if (getName().equals(superclass)) { | |
children.add(new ClassTree(name)); | |
return true; | |
} else { | |
for (ClassTree item : children) { | |
if (item.insert(superclass, name)) | |
return true; | |
} | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment