Open-source multiplayer game server compatible with the RuneScape client https://www.openrs2.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
openrs2/deob/src/main/java/dev/openrs2/deob/transform/OpaquePredicateTransformer....

125 lines
4.1 KiB

package dev.openrs2.deob.transform;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import dev.openrs2.asm.InsnMatcher;
import dev.openrs2.asm.MemberRef;
import dev.openrs2.asm.MethodNodeUtilsKt;
import dev.openrs2.asm.classpath.ClassPath;
import dev.openrs2.asm.classpath.Library;
import dev.openrs2.asm.transform.Transformer;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class OpaquePredicateTransformer extends Transformer {
private static final Logger logger = LoggerFactory.getLogger(OpaquePredicateTransformer.class);
private static final InsnMatcher FLOW_OBSTRUCTOR_INITIALIZER_MATCHER = InsnMatcher.compile("(GETSTATIC | ILOAD) IFEQ (((GETSTATIC ISTORE)? IINC ILOAD) | ((GETSTATIC | ILOAD) IFEQ ICONST GOTO ICONST)) PUTSTATIC");
private static final InsnMatcher OPAQUE_PREDICATE_MATCHER = InsnMatcher.compile("(GETSTATIC | ILOAD) (IFEQ | IFNE)");
private static final InsnMatcher STORE_MATCHER = InsnMatcher.compile("GETSTATIC ISTORE");
private final Set<MemberRef> flowObstructors = new HashSet<>();
private int opaquePredicates, stores;
@Override
public void preTransform(ClassPath classPath) {
flowObstructors.clear();
opaquePredicates = 0;
stores = 0;
for (var library : classPath.getLibraries()) {
for (var clazz : library) {
for (var method : clazz.methods) {
if (MethodNodeUtilsKt.hasCode(method)) {
findFlowObstructors(library, method);
}
}
}
}
logger.info("Identified flow obstructors {}", flowObstructors);
}
private void findFlowObstructors(Library library, MethodNode method) {
FLOW_OBSTRUCTOR_INITIALIZER_MATCHER.match(method).forEach(match -> {
/* add flow obstructor to set */
var putstatic = (FieldInsnNode) match.get(match.size() - 1);
flowObstructors.add(new MemberRef(putstatic));
/* remove initializer */
match.forEach(method.instructions::remove);
/* remove field */
var owner = library.get(putstatic.owner);
owner.fields.removeIf(field -> field.name.equals(putstatic.name) && field.desc.equals(putstatic.desc));
});
}
private boolean isFlowObstructor(FieldInsnNode insn) {
return flowObstructors.contains(new MemberRef(insn));
}
private boolean isOpaquePredicate(MethodNode method, List<AbstractInsnNode> match) {
var load = match.get(0);
/* flow obstructor loaded directly? */
if (load.getOpcode() == Opcodes.GETSTATIC) {
var getstatic = (FieldInsnNode) load;
return isFlowObstructor(getstatic);
}
/* flow obstructor loaded via local variable? */
var iload = (VarInsnNode) load;
return STORE_MATCHER.match(method).anyMatch(storeMatch -> {
var getstatic = (FieldInsnNode) storeMatch.get(0);
var istore = (VarInsnNode) storeMatch.get(1);
return isFlowObstructor(getstatic) && iload.var == istore.var;
});
}
private boolean isRedundantStore(List<AbstractInsnNode> match) {
var getstatic = (FieldInsnNode) match.get(0);
return isFlowObstructor(getstatic);
}
@Override
public boolean transformCode(ClassPath classPath, Library library, ClassNode clazz, MethodNode method) {
/* find and fix opaque predicates */
OPAQUE_PREDICATE_MATCHER.match(method).filter(match -> isOpaquePredicate(method, match)).forEach(match -> {
var branch = (JumpInsnNode) match.get(1);
if (branch.getOpcode() == Opcodes.IFEQ) {
/* branch is always taken */
method.instructions.remove(match.get(0));
branch.setOpcode(Opcodes.GOTO);
} else { /* IFNE */
/* branch is never taken */
match.forEach(method.instructions::remove);
}
opaquePredicates++;
});
/* remove redundant stores */
STORE_MATCHER.match(method).filter(this::isRedundantStore).forEach(match -> {
match.forEach(method.instructions::remove);
stores++;
});
return false;
}
@Override
public void postTransform(ClassPath classPath) {
logger.info("Removed {} opaque predicates and {} redundant stores", opaquePredicates, stores);
}
}