forked from openrs2/openrs2
parent
1c2d2f1264
commit
f56b288663
@ -1,125 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,113 @@ |
|||||||
|
package dev.openrs2.deob.transform |
||||||
|
|
||||||
|
import com.github.michaelbull.logging.InlineLogger |
||||||
|
import dev.openrs2.asm.InsnMatcher |
||||||
|
import dev.openrs2.asm.MemberRef |
||||||
|
import dev.openrs2.asm.classpath.ClassPath |
||||||
|
import dev.openrs2.asm.classpath.Library |
||||||
|
import dev.openrs2.asm.hasCode |
||||||
|
import dev.openrs2.asm.transform.Transformer |
||||||
|
import org.objectweb.asm.Opcodes |
||||||
|
import org.objectweb.asm.tree.* |
||||||
|
|
||||||
|
class OpaquePredicateTransformer : Transformer() { |
||||||
|
private val flowObstructors = mutableSetOf<MemberRef>() |
||||||
|
private var opaquePredicates = 0 |
||||||
|
private var stores = 0 |
||||||
|
|
||||||
|
override fun preTransform(classPath: ClassPath) { |
||||||
|
flowObstructors.clear() |
||||||
|
opaquePredicates = 0 |
||||||
|
stores = 0 |
||||||
|
|
||||||
|
for (library in classPath.libraries) { |
||||||
|
for (clazz in library) { |
||||||
|
for (method in clazz.methods) { |
||||||
|
if (method.hasCode()) { |
||||||
|
findFlowObstructors(library, method) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
logger.info { "Identified flow obstructors $flowObstructors" } |
||||||
|
} |
||||||
|
|
||||||
|
private fun findFlowObstructors(library: Library, method: MethodNode) { |
||||||
|
FLOW_OBSTRUCTOR_INITIALIZER_MATCHER.match(method).forEach { |
||||||
|
// add flow obstructor to set |
||||||
|
val putstatic = it.last() as FieldInsnNode |
||||||
|
flowObstructors.add(MemberRef(putstatic)) |
||||||
|
|
||||||
|
// remove initializer |
||||||
|
it.forEach(method.instructions::remove) |
||||||
|
|
||||||
|
// remove field |
||||||
|
val owner = library[putstatic.owner]!! |
||||||
|
owner.fields.removeIf { it.name == putstatic.name && it.desc == putstatic.desc } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun isFlowObstructor(insn: FieldInsnNode): Boolean { |
||||||
|
return flowObstructors.contains(MemberRef(insn)) |
||||||
|
} |
||||||
|
|
||||||
|
private fun isOpaquePredicate(method: MethodNode, match: List<AbstractInsnNode>): Boolean { |
||||||
|
val load = match[0] |
||||||
|
|
||||||
|
// flow obstructor loaded directly? |
||||||
|
if (load is FieldInsnNode && load.opcode == Opcodes.GETSTATIC) { |
||||||
|
return isFlowObstructor(load) |
||||||
|
} |
||||||
|
|
||||||
|
// flow obstructor loaded via local variable |
||||||
|
val iload = load as VarInsnNode |
||||||
|
return STORE_MATCHER.match(method).anyMatch { |
||||||
|
val getstatic = it[0] as FieldInsnNode |
||||||
|
val istore = it[1] as VarInsnNode |
||||||
|
return@anyMatch isFlowObstructor(getstatic) && iload.`var` == istore.`var` |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun isRedundantStore(match: List<AbstractInsnNode>): Boolean { |
||||||
|
val getstatic = match[0] as FieldInsnNode |
||||||
|
return isFlowObstructor(getstatic) |
||||||
|
} |
||||||
|
|
||||||
|
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { |
||||||
|
// find and fix opaque predicates |
||||||
|
OPAQUE_PREDICATE_MATCHER.match(method).filter { isOpaquePredicate(method, it) }.forEach { |
||||||
|
val branch = it[1] as JumpInsnNode |
||||||
|
if (branch.opcode == Opcodes.IFEQ) { |
||||||
|
// branch is always taken |
||||||
|
method.instructions.remove(it[0]) |
||||||
|
branch.opcode = Opcodes.GOTO |
||||||
|
} else { // IFNE |
||||||
|
// branch is never taken |
||||||
|
it.forEach(method.instructions::remove) |
||||||
|
} |
||||||
|
|
||||||
|
opaquePredicates++ |
||||||
|
} |
||||||
|
|
||||||
|
// remove redundant stores |
||||||
|
STORE_MATCHER.match(method).filter(this::isRedundantStore).forEach { |
||||||
|
it.forEach(method.instructions::remove) |
||||||
|
stores++ |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
override fun postTransform(classPath: ClassPath) { |
||||||
|
logger.info { "Removed $opaquePredicates opaque predicates and $stores redundant stores" } |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
private val logger = InlineLogger() |
||||||
|
private val FLOW_OBSTRUCTOR_INITIALIZER_MATCHER = |
||||||
|
InsnMatcher.compile("(GETSTATIC | ILOAD) IFEQ (((GETSTATIC ISTORE)? IINC ILOAD) | ((GETSTATIC | ILOAD) IFEQ ICONST GOTO ICONST)) PUTSTATIC") |
||||||
|
private val OPAQUE_PREDICATE_MATCHER = InsnMatcher.compile("(GETSTATIC | ILOAD) (IFEQ | IFNE)") |
||||||
|
private val STORE_MATCHER = InsnMatcher.compile("GETSTATIC ISTORE") |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue