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