Add renamer to the deobfuscator

bzip2
Graham 5 years ago
parent 7e4dee02d0
commit c1358f6105
  1. 4
      asm/pom.xml
  2. 20
      asm/src/main/java/dev/openrs2/asm/Library.java
  3. 43
      asm/src/main/java/dev/openrs2/asm/MemberDesc.java
  4. 4
      asm/src/main/java/dev/openrs2/asm/MemberRef.java
  5. 36
      deob/src/main/java/dev/openrs2/deob/ClassNamePrefixer.java
  6. 108
      deob/src/main/java/dev/openrs2/deob/Deobfuscator.java
  7. 114
      deob/src/main/java/dev/openrs2/deob/SignedClassSet.java
  8. 72
      deob/src/main/java/dev/openrs2/deob/path/AsmClassMetadata.java
  9. 34
      deob/src/main/java/dev/openrs2/deob/path/ClassMetadata.java
  10. 61
      deob/src/main/java/dev/openrs2/deob/path/ClassPath.java
  11. 74
      deob/src/main/java/dev/openrs2/deob/path/ReflectionClassMetadata.java
  12. 320
      deob/src/main/java/dev/openrs2/deob/path/TypedRemapper.java
  13. 42
      deob/src/main/java/dev/openrs2/deob/transform/ClassForNameTransformer.java
  14. 29
      util/src/main/java/dev/openrs2/util/StringUtils.java

@ -23,6 +23,10 @@
<groupId>org.ow2.asm</groupId> <groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId> <artifactId>asm</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.ow2.asm</groupId> <groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId> <artifactId>asm-tree</artifactId>

@ -18,7 +18,10 @@ import java.util.zip.GZIPOutputStream;
import dev.openrs2.util.io.DeterministicJarOutputStream; import dev.openrs2.util.io.DeterministicJarOutputStream;
import dev.openrs2.util.io.SkipOutputStream; import dev.openrs2.util.io.SkipOutputStream;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.util.CheckClassAdapter; import org.objectweb.asm.util.CheckClassAdapter;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -107,26 +110,33 @@ public final class Library implements Iterable<ClassNode> {
return classes.values().iterator(); return classes.values().iterator();
} }
public void writeJar(Path path) throws IOException { public void writeJar(Path path, Remapper remapper) throws IOException {
logger.info("Writing jar {}", path); logger.info("Writing jar {}", path);
try (var out = new DeterministicJarOutputStream(Files.newOutputStream(path))) { try (var out = new DeterministicJarOutputStream(Files.newOutputStream(path))) {
for (var clazz : classes.values()) { for (var clazz : classes.values()) {
var name = clazz.name;
var writer = new ClassWriter(0); var writer = new ClassWriter(0);
clazz.accept(new CheckClassAdapter(writer, true));
out.putNextEntry(new JarEntry(clazz.name + CLASS_SUFFIX)); ClassVisitor visitor = new CheckClassAdapter(writer, true);
if (remapper != null) {
visitor = new ClassRemapper(visitor, remapper);
name = remapper.map(name);
}
clazz.accept(visitor);
out.putNextEntry(new JarEntry(name + CLASS_SUFFIX));
out.write(writer.toByteArray()); out.write(writer.toByteArray());
} }
} }
} }
public void writePack(Path path) throws IOException { public void writePack(Path path, Remapper remapper) throws IOException {
logger.info("Writing pack {}", path); logger.info("Writing pack {}", path);
var temp = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX); var temp = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX);
try { try {
writeJar(temp); writeJar(temp, remapper);
try (var in = new JarInputStream(Files.newInputStream(temp)); try (var in = new JarInputStream(Files.newInputStream(temp));
var out = new GZIPOutputStream(new SkipOutputStream(Files.newOutputStream(path), 2))) { var out = new GZIPOutputStream(new SkipOutputStream(Files.newOutputStream(path), 2))) {

@ -0,0 +1,43 @@
package dev.openrs2.asm;
import java.util.Objects;
public final class MemberDesc {
private final String name, desc;
public MemberDesc(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MemberDesc fieldRef = (MemberDesc) o;
return name.equals(fieldRef.name) &&
desc.equals(fieldRef.desc);
}
@Override
public int hashCode() {
return Objects.hash(name, desc);
}
@Override
public String toString() {
return String.format("%s %s", desc, name);
}
}

@ -5,6 +5,10 @@ import java.util.Objects;
public final class MemberRef { public final class MemberRef {
private final String owner, name, desc; private final String owner, name, desc;
public MemberRef(String owner, MemberDesc desc) {
this(owner, desc.getName(), desc.getDesc());
}
public MemberRef(String owner, String name, String desc) { public MemberRef(String owner, String name, String desc) {
this.owner = owner; this.owner = owner;
this.name = name; this.name = name;

@ -0,0 +1,36 @@
package dev.openrs2.deob;
import java.util.HashMap;
import dev.openrs2.asm.Library;
import dev.openrs2.deob.path.TypedRemapper;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.SimpleRemapper;
import org.objectweb.asm.tree.ClassNode;
public final class ClassNamePrefixer {
public static void addPrefix(Library library, String prefix) {
var mapping = new HashMap<String, String>();
for (var clazz : library) {
if (TypedRemapper.EXCLUDED_CLASSES.contains(clazz.name)) {
mapping.put(clazz.name, clazz.name);
} else {
mapping.put(clazz.name, prefix + clazz.name);
}
}
var remapper = new SimpleRemapper(mapping);
for (var name : mapping.keySet()) {
var in = library.remove(name);
var out = new ClassNode();
in.accept(new ClassRemapper(out, remapper));
library.add(out);
}
}
private ClassNamePrefixer() {
/* empty */
}
}

@ -1,6 +1,9 @@
package dev.openrs2.deob; package dev.openrs2.deob;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
@ -8,6 +11,9 @@ import java.util.Map;
import dev.openrs2.asm.Library; import dev.openrs2.asm.Library;
import dev.openrs2.asm.Transformer; import dev.openrs2.asm.Transformer;
import dev.openrs2.deob.path.ClassPath;
import dev.openrs2.deob.path.TypedRemapper;
import dev.openrs2.deob.transform.ClassForNameTransformer;
import dev.openrs2.deob.transform.OpaquePredicateTransformer; import dev.openrs2.deob.transform.OpaquePredicateTransformer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -20,32 +26,53 @@ public final class Deobfuscator {
); );
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
var deobfuscator = new Deobfuscator(Paths.get("nonfree/code")); var deobfuscator = new Deobfuscator(Paths.get("nonfree/code"), Paths.get("nonfree/code/deob"));
deobfuscator.run(); deobfuscator.run();
} }
private final Path input; private final Path input, output;
public Deobfuscator(Path input) { public Deobfuscator(Path input, Path output) {
this.input = input; this.input = input;
this.output = output;
} }
public void run() throws IOException { public void run() throws IOException {
/* read input jars/packs */
logger.info("Reading input jars");
var unpacker = Library.readJar(input.resolve("game_unpacker.dat")); var unpacker = Library.readJar(input.resolve("game_unpacker.dat"));
var glUnpacker = new Library(unpacker);
var loader = Library.readJar(input.resolve("loader.jar")); var loader = Library.readJar(input.resolve("loader.jar"));
var glLoader = Library.readJar(input.resolve("loader_gl.jar")); var glLoader = Library.readJar(input.resolve("loader_gl.jar"));
var client = Library.readJar(input.resolve("runescape.jar")); var client = Library.readJar(input.resolve("runescape.jar"));
var glClient = Library.readPack(input.resolve("runescape_gl.pack200")); var glClient = Library.readPack(input.resolve("runescape_gl.pack200"));
var unsignedClient = new Library(client);
var libraries = Map.of( /* read dependencies */
var runtime = ClassLoader.getPlatformClassLoader();
var jogl = new URLClassLoader(new URL[] {
input.resolve("jogl.jar").toUri().toURL()
}, runtime);
/* overwrite client's classes with signed classes from the loader */
logger.info("Moving signed classes from loader to runescape");
var signedClasses = SignedClassSet.create(loader, client);
logger.info("Moving signed classes from loader_gl to runescape_gl");
var glSignedClasses = SignedClassSet.create(glLoader, glClient);
/* deobfuscate */
var allLibraries = Map.of(
"unpacker", unpacker, "unpacker", unpacker,
"unpacker_gl", glUnpacker,
"loader", loader, "loader", loader,
"loader_gl", glLoader, "loader_gl", glLoader,
"runescape", client, "runescape", client,
"runescape_gl", glClient "runescape_gl", glClient,
"runescape_unsigned", unsignedClient
); );
for (var entry : libraries.entrySet()) { for (var entry : allLibraries.entrySet()) {
logger.info("Transforming library {}", entry.getKey()); logger.info("Transforming library {}", entry.getKey());
for (var transformer : TRANSFORMERS) { for (var transformer : TRANSFORMERS) {
@ -53,5 +80,74 @@ public final class Deobfuscator {
transformer.transform(entry.getValue()); transformer.transform(entry.getValue());
} }
} }
/* move unpack class out of the loader (so the unpacker and loader can both depend on it) */
logger.info("Moving unpack from loader to unpack");
var unpack = new Library();
unpack.add(loader.remove("unpack"));
logger.info("Moving unpack from loader_gl to unpack_gl");
var glUnpack = new Library();
glUnpack.add(glLoader.remove("unpack"));
/* move signed classes out of the client (so the client and loader can both depend on them) */
logger.info("Moving signed classes from runescape to signlink");
var signLink = new Library();
signedClasses.move(client, signLink);
logger.info("Moving signed classes from runescape_gl to signlink_gl");
var glSignLink = new Library();
glSignedClasses.move(glClient, glSignLink);
/* prefix remaining loader/unpacker classes (to avoid conflicts when we rename in the same classpath as the client) */
logger.info("Prefixing loader and unpacker class names");
ClassNamePrefixer.addPrefix(loader, "loader_");
ClassNamePrefixer.addPrefix(glLoader, "loader_");
ClassNamePrefixer.addPrefix(unpacker, "unpacker_");
ClassNamePrefixer.addPrefix(glUnpacker, "unpacker_");
/* remap all class, method and field names */
logger.info("Creating remappers");
var libraries = new Library[] { client, loader, signLink, unpack, unpacker };
var remapper = TypedRemapper.create(new ClassPath(runtime, libraries));
var glLibraries = new Library[] { glClient, glLoader, glSignLink, glUnpack, glUnpacker };
var glRemapper = TypedRemapper.create(new ClassPath(jogl, glLibraries));
var unsignedRemapper = TypedRemapper.create(new ClassPath(runtime, unsignedClient));
/* transform Class.forName() calls */
logger.info("Transforming Class.forName() calls");
var transformer = new ClassForNameTransformer(remapper);
for (var library : libraries) {
transformer.transform(library);
}
var glTransformer = new ClassForNameTransformer(glRemapper);
for (var library : glLibraries) {
glTransformer.transform(library);
}
var unsignedTransformer = new ClassForNameTransformer(unsignedRemapper);
unsignedTransformer.transform(unsignedClient);
/* write output jars */
logger.info("Writing output jars");
Files.createDirectories(output);
client.writeJar(output.resolve("runescape.jar"), remapper);
loader.writeJar(output.resolve("loader.jar"), remapper);
signLink.writeJar(output.resolve("signlink.jar"), remapper);
unpack.writeJar(output.resolve("unpack.jar"), remapper);
unpacker.writeJar(output.resolve("unpacker.jar"), remapper);
glClient.writeJar(output.resolve("runescape_gl.jar"), glRemapper);
glLoader.writeJar(output.resolve("loader_gl.jar"), glRemapper);
glSignLink.writeJar(output.resolve("signlink_gl.jar"), glRemapper);
glUnpack.writeJar(output.resolve("unpack_gl.jar"), glRemapper);
glUnpacker.writeJar(output.resolve("unpacker_gl.jar"), glRemapper);
unsignedClient.writeJar(output.resolve("runescape_unsigned.jar"), unsignedRemapper);
} }
} }

@ -0,0 +1,114 @@
package dev.openrs2.deob;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.Sets;
import dev.openrs2.asm.InsnMatcher;
import dev.openrs2.asm.Library;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.SimpleRemapper;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class SignedClassSet {
private static final Logger logger = LoggerFactory.getLogger(SignedClassSet.class);
private static final InsnMatcher LOAD_SIGNED_CLASS_MATCHER = InsnMatcher.compile("LDC INVOKESTATIC ASTORE ALOAD GETFIELD ALOAD INVOKEVIRTUAL ALOAD INVOKEVIRTUAL POP");
public static SignedClassSet create(Library loader, Library client) {
/* find signed classes */
var signedClasses = findSignedClasses(loader);
logger.info("Identified signed classes {}", signedClasses);
var dependencies = findDependencies(loader, signedClasses);
logger.info("Identified signed class dependencies {}", dependencies);
/* rename dependencies of signed classes so they don't clash with client classes */
var mapping = new HashMap<String, String>();
for (var dependency : dependencies) {
mapping.put(dependency, "loader_" + dependency);
}
var remapper = new SimpleRemapper(mapping);
/* move signed classes to the client */
var remappedSignedClasses = new HashSet<String>();
for (var name : Sets.union(signedClasses, dependencies)) {
var in = loader.remove(name);
var out = new ClassNode();
in.accept(new ClassRemapper(out, remapper));
remappedSignedClasses.add(out.name);
client.add(out);
}
return new SignedClassSet(remappedSignedClasses);
}
private static Set<String> findSignedClasses(Library loader) {
var clazz = loader.get("loader");
if (clazz == null) {
throw new IllegalArgumentException("Failed to find loader class");
}
for (var method : clazz.methods) {
if (method.name.equals("run") && method.desc.equals("()V")) {
return findSignedClasses(method);
}
}
throw new IllegalArgumentException("Failed to find loader.run() method");
}
private static Set<String> findSignedClasses(MethodNode method) {
var classes = new HashSet<String>();
LOAD_SIGNED_CLASS_MATCHER.match(method).forEach(match -> {
var ldc = (LdcInsnNode) match.get(0);
if (ldc.cst instanceof String && !ldc.cst.equals("unpack")) {
classes.add((String) ldc.cst);
}
});
return classes;
}
private static Set<String> findDependencies(Library loader, Set<String> signedClasses) {
var dependencies = new HashSet<String>();
for (var signedClass : signedClasses) {
var clazz = loader.get(signedClass);
for (var field : clazz.fields) {
var type = Type.getType(field.desc);
if (type.getSort() != Type.OBJECT) {
continue;
}
var name = type.getClassName();
if (loader.contains(name) && !signedClasses.contains(name)) {
dependencies.add(name);
}
}
}
return dependencies;
}
private final Set<String> signedClasses;
private SignedClassSet(Set<String> signedClasses) {
this.signedClasses = signedClasses;
}
public void move(Library client, Library signLink) {
for (var name : signedClasses) {
signLink.add(client.remove(name));
}
}
}

@ -0,0 +1,72 @@
package dev.openrs2.deob.path;
import java.util.List;
import java.util.stream.Collectors;
import dev.openrs2.asm.MemberDesc;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
public final class AsmClassMetadata extends ClassMetadata {
private final ClassPath classPath;
private final ClassNode clazz;
public AsmClassMetadata(ClassPath classPath, ClassNode clazz) {
this.classPath = classPath;
this.clazz = clazz;
}
@Override
public String getName() {
return clazz.name;
}
@Override
public boolean isMutable() {
return true;
}
@Override
public boolean isInterface() {
return (clazz.access & Opcodes.ACC_INTERFACE) != 0;
}
@Override
public ClassMetadata getSuperClass() {
if (clazz.superName != null) {
return classPath.get(clazz.superName);
}
return null;
}
@Override
public List<ClassMetadata> getSuperInterfaces() {
return clazz.interfaces.stream()
.map(classPath::get)
.collect(Collectors.toUnmodifiableList());
}
@Override
public List<MemberDesc> getFields() {
return clazz.fields.stream()
.map(f -> new MemberDesc(f.name, f.desc))
.collect(Collectors.toUnmodifiableList());
}
@Override
public List<MemberDesc> getMethods() {
return clazz.methods.stream()
.map(m -> new MemberDesc(m.name, m.desc))
.collect(Collectors.toUnmodifiableList());
}
@Override
public boolean isNative(MemberDesc method) {
for (var m : clazz.methods) {
if (m.name.equals(method.getName()) && m.desc.equals(method.getDesc())) {
return (m.access & Opcodes.ACC_NATIVE) != 0;
}
}
return false;
}
}

@ -0,0 +1,34 @@
package dev.openrs2.deob.path;
import java.util.List;
import java.util.Objects;
import dev.openrs2.asm.MemberDesc;
public abstract class ClassMetadata {
public abstract String getName();
public abstract boolean isMutable();
public abstract boolean isInterface();
public abstract ClassMetadata getSuperClass();
public abstract List<ClassMetadata> getSuperInterfaces();
public abstract List<MemberDesc> getFields();
public abstract List<MemberDesc> getMethods();
public abstract boolean isNative(MemberDesc method);
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
var that = (ClassMetadata) o;
return getName().equals(that.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName());
}
}

@ -0,0 +1,61 @@
package dev.openrs2.deob.path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dev.openrs2.asm.Library;
public final class ClassPath {
private final ClassLoader dependencyLoader;
private final List<Library> libraries;
private final Map<String, ClassMetadata> cache = new HashMap<>();
public ClassPath(ClassLoader dependencyLoader, Library... libraries) {
this.dependencyLoader = dependencyLoader;
this.libraries = List.of(libraries);
}
public List<ClassMetadata> getLibraryClasses() {
var classes = new ArrayList<ClassMetadata>();
for (var library : libraries) {
for (var clazz : library) {
classes.add(get(clazz.name));
}
}
return Collections.unmodifiableList(classes);
}
public ClassMetadata get(String name) {
var metadata = cache.get(name);
if (metadata != null) {
return metadata;
}
for (var library : libraries) {
var clazz = library.get(name);
if (clazz != null) {
metadata = new AsmClassMetadata(this, clazz);
cache.put(name, metadata);
return metadata;
}
}
var reflectionName = name.replace('/', '.');
Class<?> clazz;
try {
clazz = dependencyLoader.loadClass(reflectionName);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException("Unknown class " + name);
}
metadata = new ReflectionClassMetadata(this, clazz);
cache.put(name, metadata);
return metadata;
}
}

@ -0,0 +1,74 @@
package dev.openrs2.deob.path;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import dev.openrs2.asm.MemberDesc;
import org.objectweb.asm.Type;
public final class ReflectionClassMetadata extends ClassMetadata {
private final ClassPath classPath;
private final Class<?> clazz;
public ReflectionClassMetadata(ClassPath classPath, Class<?> clazz) {
this.classPath = classPath;
this.clazz = clazz;
}
@Override
public String getName() {
return clazz.getName().replace('.', '/');
}
@Override
public boolean isMutable() {
return false;
}
@Override
public boolean isInterface() {
return clazz.isInterface();
}
@Override
public ClassMetadata getSuperClass() {
var superClass = clazz.getSuperclass();
if (superClass != null) {
return classPath.get(superClass.getName().replace('.', '/'));
}
return null;
}
@Override
public List<ClassMetadata> getSuperInterfaces() {
return Arrays.stream(clazz.getInterfaces())
.map(i -> classPath.get(i.getName().replace('.', '/')))
.collect(Collectors.toUnmodifiableList());
}
@Override
public List<MemberDesc> getFields() {
return Arrays.stream(clazz.getDeclaredFields())
.map(f -> new MemberDesc(f.getName(), Type.getDescriptor(f.getType())))
.collect(Collectors.toUnmodifiableList());
}
@Override
public List<MemberDesc> getMethods() {
return Arrays.stream(clazz.getDeclaredMethods())
.map(m -> new MemberDesc(m.getName(), Type.getMethodDescriptor(m)))
.collect(Collectors.toUnmodifiableList());
}
@Override
public boolean isNative(MemberDesc method) {
for (var m : clazz.getDeclaredMethods()) {
if (m.getName().equals(method.getName()) && Type.getMethodDescriptor(m).equals(method.getDesc())) {
return Modifier.isNative(m.getModifiers());
}
}
return false;
}
}

@ -0,0 +1,320 @@
package dev.openrs2.deob.path;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Strings;
import dev.openrs2.asm.MemberDesc;
import dev.openrs2.asm.MemberRef;
import dev.openrs2.util.StringUtils;
import dev.openrs2.util.collect.DisjointSet;
import dev.openrs2.util.collect.ForestDisjointSet;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Remapper;
public final class TypedRemapper extends Remapper {
public static final Set<String> EXCLUDED_CLASSES = Set.of(
"client",
"jagex3/jagmisc/jagmisc",
"loader",
"unpack",
"unpackclass"
);
public static final Set<String> EXCLUDED_METHODS = Set.of(
"<clinit>",
"<init>",
"main",
"providesignlink",
"quit"
);
public static final Set<String> EXCLUDED_FIELDS = Set.of(
"cache"
);
public static TypedRemapper create(ClassPath classPath) {
var libraryClasses = classPath.getLibraryClasses();
var inheritedFieldSets = createInheritedFieldSets(libraryClasses);
var inheritedMethodSets = createInheritedMethodSets(libraryClasses);
var classes = createClassMapping(libraryClasses);
var fields = createFieldMapping(classPath, inheritedFieldSets, classes);
var methods = createMethodMapping(classPath, inheritedMethodSets);
return new TypedRemapper(classes, fields, methods);
}
private static DisjointSet<MemberRef> createInheritedFieldSets(List<ClassMetadata> classes) {
var disjointSet = new ForestDisjointSet<MemberRef>();
var ancestorCache = new HashMap<ClassMetadata, Set<MemberDesc>>();
for (var clazz : classes) {
populateInheritedFieldSets(ancestorCache, disjointSet, clazz);
}
return disjointSet;
}
private static Set<MemberDesc> populateInheritedFieldSets(Map<ClassMetadata, Set<MemberDesc>> ancestorCache, DisjointSet<MemberRef> disjointSet, ClassMetadata clazz) {
var ancestors = ancestorCache.get(clazz);
if (ancestors != null) {
return ancestors;
}
ancestors = new HashSet<>();
var superClass = clazz.getSuperClass();
if (superClass != null) {
var fields = populateInheritedFieldSets(ancestorCache, disjointSet, superClass);
for (var field : fields) {
var partition1 = disjointSet.add(new MemberRef(clazz.getName(), field));
var partition2 = disjointSet.add(new MemberRef(superClass.getName(), field));
disjointSet.union(partition1, partition2);
}
ancestors.addAll(fields);
}
for (var superInterface : clazz.getSuperInterfaces()) {
var fields = populateInheritedFieldSets(ancestorCache, disjointSet, superInterface);
for (var field : fields) {
var partition1 = disjointSet.add(new MemberRef(clazz.getName(), field));
var partition2 = disjointSet.add(new MemberRef(superInterface.getName(), field));
disjointSet.union(partition1, partition2);
}
ancestors.addAll(fields);
}
for (var field : clazz.getFields()) {
if (EXCLUDED_FIELDS.contains(field.getName())) {
continue;
}
disjointSet.add(new MemberRef(clazz.getName(), field));
ancestors.add(field);
}
ancestors = Collections.unmodifiableSet(ancestors);
ancestorCache.put(clazz, ancestors);
return ancestors;
}
private static DisjointSet<MemberRef> createInheritedMethodSets(List<ClassMetadata> classes) {
var disjointSet = new ForestDisjointSet<MemberRef>();
var ancestorCache = new HashMap<ClassMetadata, Set<MemberDesc>>();
for (var clazz : classes) {
populateInheritedMethodSets(ancestorCache, disjointSet, clazz);
}
return disjointSet;
}
private static Set<MemberDesc> populateInheritedMethodSets(Map<ClassMetadata, Set<MemberDesc>> ancestorCache, DisjointSet<MemberRef> disjointSet, ClassMetadata clazz) {
var ancestors = ancestorCache.get(clazz);
if (ancestors != null) {
return ancestors;
}
ancestors = new HashSet<>();
var superClass = clazz.getSuperClass();
if (superClass != null) {
var methods = populateInheritedMethodSets(ancestorCache, disjointSet, superClass);
for (var method : methods) {
var partition1 = disjointSet.add(new MemberRef(clazz.getName(), method));
var partition2 = disjointSet.add(new MemberRef(superClass.getName(), method));
disjointSet.union(partition1, partition2);
}
ancestors.addAll(methods);
}
for (var superInterface : clazz.getSuperInterfaces()) {
var methods = populateInheritedMethodSets(ancestorCache, disjointSet, superInterface);
for (var method : methods) {
var partition1 = disjointSet.add(new MemberRef(clazz.getName(), method));
var partition2 = disjointSet.add(new MemberRef(superInterface.getName(), method));
disjointSet.union(partition1, partition2);
}
ancestors.addAll(methods);
}
for (var method : clazz.getMethods()) {
if (EXCLUDED_METHODS.contains(method.getName())) {
continue;
}
disjointSet.add(new MemberRef(clazz.getName(), method));
ancestors.add(method);
}
ancestors = Collections.unmodifiableSet(ancestors);
ancestorCache.put(clazz, ancestors);
return ancestors;
}
private static String generateName(Map<String, Integer> prefixes, String prefix) {
return prefix + prefixes.merge(prefix, 1, Integer::sum);
}
private static Map<String, String> createClassMapping(List<ClassMetadata> classes) {
var mapping = new HashMap<String, String>();
var prefixes = new HashMap<String, Integer>();
for (var clazz : classes) {
populateClassMapping(mapping, prefixes, clazz);
}
return mapping;
}
private static String populateClassMapping(Map<String, String> mapping, Map<String, Integer> prefixes, ClassMetadata clazz) {
var name = clazz.getName();
if (mapping.containsKey(name) || EXCLUDED_CLASSES.contains(name) || !clazz.isMutable()) {
return mapping.getOrDefault(name, name);
}
var mappedName = name.substring(0, name.lastIndexOf('/') + 1);
var superClass = clazz.getSuperClass();
if (superClass != null && !superClass.getName().equals("java/lang/Object")) {
var superName = populateClassMapping(mapping, prefixes, superClass);
superName = superName.substring(superName.lastIndexOf('/') + 1);
mappedName += generateName(prefixes, superName + "_Sub");
} else if (clazz.isInterface()) {
mappedName += generateName(prefixes, "Interface");
} else {
mappedName += generateName(prefixes, "Class");
}
mapping.put(name, mappedName);
return mappedName;
}
private static Map<MemberRef, String> createFieldMapping(ClassPath classPath, DisjointSet<MemberRef> disjointSet, Map<String, String> classMapping) {
var mapping = new HashMap<MemberRef, String>();
var prefixes = new HashMap<String, Integer>();
for (var partition : disjointSet) {
boolean skip = false;
for (var field : partition) {
var clazz = classPath.get(field.getOwner());
if (!clazz.isMutable()) {
skip = true;
break;
}
}
if (skip) {
continue;
}
var prefix = "";
var type = Type.getType(partition.iterator().next().getDesc());
if (type.getSort() == Type.ARRAY) {
prefix = Strings.repeat("Array", type.getDimensions());
type = type.getElementType();
}
switch (type.getSort()) {
case Type.BOOLEAN:
case Type.BYTE:
case Type.CHAR:
case Type.SHORT:
case Type.INT:
case Type.LONG:
case Type.FLOAT:
case Type.DOUBLE:
prefix = type.getClassName() + prefix;
break;
case Type.OBJECT:
var className = classMapping.getOrDefault(type.getInternalName(), type.getInternalName());
className = className.substring(className.lastIndexOf('/') + 1);
prefix = className + prefix;
break;
default:
throw new IllegalArgumentException("Unknown field type " + type);
}
prefix = StringUtils.indefiniteArticle(prefix) + StringUtils.capitalize(prefix);
var mappedName = generateName(prefixes, prefix);
for (var field : partition) {
mapping.put(field, mappedName);
}
}
return mapping;
}
private static Map<MemberRef, String> createMethodMapping(ClassPath classPath, DisjointSet<MemberRef> disjointSet) {
var mapping = new HashMap<MemberRef, String>();
var id = 0;
for (var partition : disjointSet) {
boolean skip = false;
for (var method : partition) {
var clazz = classPath.get(method.getOwner());
if (!clazz.isMutable()) {
skip = true;
break;
}
if (clazz.isNative(new MemberDesc(method.getName(), method.getDesc()))) {
skip = true;
break;
}
}
if (skip) {
continue;
}
var mappedName = "method" + (++id);
for (var method : partition) {
mapping.put(method, mappedName);
}
}
return mapping;
}
private final Map<String, String> classes;
private final Map<MemberRef, String> fields, methods;
private TypedRemapper(Map<String, String> classes, Map<MemberRef, String> fields, Map<MemberRef, String> methods) {
this.classes = classes;
this.fields = fields;
this.methods = methods;
}
@Override
public String map(String internalName) {
return classes.getOrDefault(internalName, internalName);
}
@Override
public String mapFieldName(String owner, String name, String descriptor) {
return fields.getOrDefault(new MemberRef(owner, name, descriptor), name);
}
@Override
public String mapMethodName(String owner, String name, String descriptor) {
return methods.getOrDefault(new MemberRef(owner, name, descriptor), name);
}
}

@ -0,0 +1,42 @@
package dev.openrs2.deob.transform;
import java.util.List;
import dev.openrs2.asm.InsnMatcher;
import dev.openrs2.asm.Transformer;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
public final class ClassForNameTransformer extends Transformer {
private static final InsnMatcher INVOKE_MATCHER = InsnMatcher.compile("LDC INVOKESTATIC");
private static boolean isClassForName(List<AbstractInsnNode> match) {
var ldc = (LdcInsnNode) match.get(0);
if (!(ldc.cst instanceof String)) {
return false;
}
var invokestatic = (MethodInsnNode) match.get(1);
return invokestatic.owner.equals("java/lang/Class") &&
invokestatic.name.equals("forName") &&
invokestatic.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;");
}
private final Remapper remapper;
public ClassForNameTransformer(Remapper remapper) {
this.remapper = remapper;
}
@Override
public void transformMethod(ClassNode clazz, MethodNode method) {
INVOKE_MATCHER.match(method).filter(ClassForNameTransformer::isClassForName).forEach(match -> {
var ldc = (LdcInsnNode) match.get(0);
ldc.cst = remapper.map((String) ldc.cst);
});
}
}

@ -0,0 +1,29 @@
package dev.openrs2.util;
import com.google.common.base.Preconditions;
public final class StringUtils {
public static String indefiniteArticle(String str) {
Preconditions.checkArgument(!str.isEmpty());
var first = Character.toLowerCase(str.charAt(0));
if (first == 'a' || first == 'e' || first == 'i' || first == 'o' || first == 'u') {
return "an";
} else {
return "a";
}
}
public static String capitalize(String str) {
if (str.isEmpty()) {
return str;
}
var first = Character.toUpperCase(str.charAt(0));
return first + str.substring(1);
}
private StringUtils() {
/* empty */
}
}
Loading…
Cancel
Save