forked from openrs2/openrs2
parent
7e4dee02d0
commit
c1358f6105
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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 */ |
||||||
|
} |
||||||
|
} |
@ -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…
Reference in new issue