package openmods.world;
import openmods.Log;
import openmods.api.IResultListener;
import openmods.asm.MappedType;
import openmods.asm.MethodMatcher;
import openmods.asm.VisitorHelper;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public class MapGenStructureVisitor extends ClassVisitor {
private final MethodMatcher modifiedMethod;
private final MethodMatcher markerMethod;
private final MappedType structureStartCls;
private final IResultListener listener;
private class FixerMethodVisitor extends MethodVisitor {
public FixerMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
private boolean checkcastFound;
private Integer localVarId;
private boolean markerMethodFound;
/*
* Default compilator usually creates:
* checkcast class net/minecraft/world/gen/structure/StructureStart
* astore X
* aload X
*
* We use that to get id of local variable that stores 'structurestart'
*/
@Override
public void visitTypeInsn(int opcode, String type) {
super.visitTypeInsn(opcode, type);
if (opcode == Opcodes.CHECKCAST && type.equals(structureStartCls.name())) {
checkcastFound = true;
Log.debug("Found checkcast to '%s'", type);
}
}
@Override
public void visitVarInsn(int opcode, int var) {
super.visitVarInsn(opcode, var);
if (checkcastFound && opcode == Opcodes.ASTORE) {
localVarId = var;
checkcastFound = false;
Log.debug("Found var: %d", localVarId);
}
}
/*
* Here we are transforming condition
* if (structurestart.isSizeableStructure())
* to
* if (structurestart.isSizeableStructure() &&
* !structurestart.getComponents().isEmpty())
*
* Again, we assume that compilator places IFEQ jump just after calling
* isSizeableStructure from first expression. We can then reuse label
* for second part
*/
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean intf) {
super.visitMethodInsn(opcode, owner, name, desc, intf);
if (opcode == Opcodes.INVOKEVIRTUAL && owner.equals(structureStartCls.name()) && markerMethod.match(name, desc)) {
markerMethodFound = true;
Log.debug("Found 'StructureStart.isSizeableStructure' (%s.%s) call", owner, name);
}
}
@Override
public void visitJumpInsn(int opcode, Label label) {
super.visitJumpInsn(opcode, label);
if (markerMethodFound && localVarId != null && opcode == Opcodes.IFEQ) {
Log.debug("All conditions matched, inserting extra condition");
super.visitVarInsn(Opcodes.ALOAD, localVarId); // hopefully 'structurestart'
String getComponentsMethodName = VisitorHelper.useSrgNames()? "func_75073_b" : "getComponents";
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, structureStartCls.name(), getComponentsMethodName, "()Ljava/util/LinkedList;", false);
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/LinkedList", "isEmpty", "()Z", false);
super.visitJumpInsn(Opcodes.IFNE, label);
listener.onSuccess();
markerMethodFound = false;
}
}
}
public MapGenStructureVisitor(String obfClassName, ClassVisitor cv, IResultListener listener) {
super(Opcodes.ASM5, cv);
this.listener = listener;
structureStartCls = MappedType.of("net/minecraft/world/gen/structure/StructureStart");
MappedType chunkPositionCls = MappedType.of("net/minecraft/world/ChunkPosition");
MappedType worldCls = MappedType.of("net/minecraft/world/World");
String descriptor = Type.getMethodDescriptor(
chunkPositionCls.type(),
worldCls.type(),
Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE);
modifiedMethod = new MethodMatcher(obfClassName, descriptor, "func_151545_a", "func_151545_a");
markerMethod = new MethodMatcher(structureStartCls, "()Z", "isSizeableStructure", "func_75069_d");
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor parent = super.visitMethod(access, name, desc, signature, exceptions);
return modifiedMethod.match(name, desc)? new FixerMethodVisitor(parent) : parent;
}
}