From b197b127b94660e0779a2ae4dbdd0bd4e3f6ab05 Mon Sep 17 00:00:00 2001 From: Florian Riedl Date: Sun, 31 Oct 2021 02:37:07 +0200 Subject: [PATCH] Switched to obfuscation detection by name, rewrote name sets, added tests --- .gitignore | 3 +- build.gradle | 21 +- .../mappings_hasher/HashedNameProvider.java | 200 +++++++++--------- .../org/quiltmc/mappings_hasher/Main.java | 17 +- .../mappings_hasher/MappingsHasher.java | 64 ++---- .../mappings_hasher/asm/ClassInfo.java | 16 -- .../mappings_hasher/asm/ClassResolver.java | 53 +---- .../mappings_hasher/asm/FieldInfo.java | 16 -- .../mappings_hasher/asm/MethodInfo.java | 29 +-- .../quiltmc/mappings_hasher/BasicTests.java | 102 +++++++++ 10 files changed, 262 insertions(+), 259 deletions(-) create mode 100644 src/test/java/org/quiltmc/mappings_hasher/BasicTests.java diff --git a/.gitignore b/.gitignore index 8b7e68d..a29bd45 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /.gradle/ /.idea/ /cache -/mappings \ No newline at end of file +/mappings +/test \ No newline at end of file diff --git a/build.gradle b/build.gradle index 26a2351..7c8dda2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import java.nio.file.Files + plugins { id 'java' id 'application' @@ -29,11 +31,26 @@ repositories { } } +test { + useJUnitPlatform() + testLogging { + outputs.upToDateWhen {false} + showStandardStreams = true + } + delete 'test' + mkdir 'test' + workingDir 'test' +} + dependencies { implementation 'org.quiltmc:quilt-json5:1.0.0' implementation 'org.quiltmc:lorenz-tiny:3.0.0' - implementation 'org.cadixdev:lorenz-io-proguard:0.5.6' - implementation "org.ow2.asm:asm:9.1" + implementation 'org.cadixdev:lorenz-io-proguard:0.5.7' + implementation 'org.ow2.asm:asm:9.2' + + testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.1') + testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.1') + testImplementation 'org.quiltmc:tiny-remapper:0.7.1' } tasks.getByName("run").setArgsString(minecraftVersion) diff --git a/src/main/java/org/quiltmc/mappings_hasher/HashedNameProvider.java b/src/main/java/org/quiltmc/mappings_hasher/HashedNameProvider.java index af4dac3..52e6275 100644 --- a/src/main/java/org/quiltmc/mappings_hasher/HashedNameProvider.java +++ b/src/main/java/org/quiltmc/mappings_hasher/HashedNameProvider.java @@ -4,6 +4,7 @@ import org.cadixdev.lorenz.MappingSet; import org.cadixdev.lorenz.model.ClassMapping; import org.cadixdev.lorenz.model.FieldMapping; +import org.cadixdev.lorenz.model.Mapping; import org.cadixdev.lorenz.model.MethodMapping; import org.quiltmc.mappings_hasher.asm.ClassInfo; import org.quiltmc.mappings_hasher.asm.FieldInfo; @@ -57,101 +58,97 @@ private static Map> computeSimpleClassNameSet(Set> computeMethodNameSets(Set methods) { Map> nameSets = new HashMap<>(); - // Add override information to name sets + // Merge name sets for (MethodInfo method : methods) { - for (MethodInfo override : method.overrides()) { - Set nameSet = nameSets.computeIfAbsent(override, m -> new HashSet<>()); - nameSet.addAll(method.overrides()); - nameSet.add(method); - } + // Create name set if it doesn't exist yet Set nameSet = nameSets.computeIfAbsent(method, m -> new HashSet<>()); - nameSet.addAll(method.overrides()); - nameSet.add(method); - } - // Resolve name sets - for (MethodInfo method : methods) { - Set nameSet = nameSets.computeIfAbsent(method, m -> new HashSet<>()); + // Add method itself to the set + nameSet.add(method); - // Methods that don't override and aren't overridden are only dependent on themselves - if (nameSet.isEmpty()) { - nameSet.add(method); - continue; + // Merge all superMethod name sets into this name set + for (MethodInfo superMethod : method.overrides()) { + Set superNameSet = nameSets.computeIfAbsent(superMethod, m -> new HashSet<>()); + superNameSet.add(superMethod); + nameSet.addAll(superNameSet); } - // All methods in a name set must have the same name set, we check this recursively - Set toCheck = new HashSet<>(nameSet); - while (!toCheck.isEmpty()) { - Set currentSet = toCheck; - toCheck = new HashSet<>(); - - for (MethodInfo current : currentSet) { - Set currentNameSet = nameSets.get(current); - for (MethodInfo nextToCheck : currentNameSet) { - if (!nameSet.contains(nextToCheck)) { - nameSet.add(nextToCheck); - toCheck.add(nextToCheck); - } - } - } + // Redirect all methods in this name set to this name set + for (MethodInfo setMethod : nameSet) { + nameSets.put(setMethod, nameSet); } } + // Only keep top-level methods in the name sets + for (Set nameSet : nameSets.values()) { + nameSet.removeIf(m -> m.overrides().size() > 0); + } + return nameSets; } - public String getRawClassName(ClassInfo clazz) { - // Don't look up unobfuscated names - if (!clazz.isObfuscated()) { - return clazz.name(); - } - + private String getRawClassName(ClassInfo clazz) { // Get the mapping ClassMapping classMapping = mappings.getClassMapping(clazz.name()) .orElseThrow(() -> new RuntimeException("Missing mapping for class " + clazz.name())); - // Simple name: Full name without the package, e.g. net/example/Class$Inner -> Class$Inner + // Don't obfuscate non-obfuscated classes + if (!isObfuscated(classMapping)) { + return clazz.name(); + } + + // Ful name: Package + Outer Class + Inner Class String fullName = classMapping.getFullDeobfuscatedName(); + + // Simple name: Full name without the package, e.g. net/example/Class$Inner -> Class$Inner String simpleName = fullName.substring(fullName.lastIndexOf('/') + 1); - // Use the simple name for unique classes, otherwise the full names - if (simpleClassNameSet.get(simpleName).size() > 1) { - return fullName; - } - else { - return simpleName; - } + // Raw name: The simple name if unique, otherwise the full name + return simpleClassNameSet.get(simpleName).size() == 1 ? simpleName : fullName; } - public String getClassName(ClassInfo clazz) { - // No need for a mapping if the method isn't obfuscated - if (!clazz.isObfuscated()) { - return null; + public Optional getClassName(ClassInfo clazz) { + // Get the mapping + ClassMapping classMapping = mappings.getClassMapping(clazz.name()) + .orElseThrow(() -> new RuntimeException("Missing mapping for class " + clazz.name())); + + // Don't obfuscate non-obfuscated classes + if (!isObfuscated(classMapping)) { + return Optional.empty(); } + // Prefix: None for inner classes, otherwise the default package (if non-empty) String prefix = clazz.name().contains("$") || this.defaultPackage.isEmpty() ? "" : this.defaultPackage + "/"; - return prefix + "C_" + getHashedString(getRawClassName(clazz)); + // Hashed name: prefix plus class identifier plus hash of raw name + return Optional.of(prefix + "C_" + getHashedString(getRawClassName(clazz))); } public String getRawMethodName(MethodInfo method) { - // Don't look up unobfuscated names - if (!method.isObfuscated()) { - return method.name(); - } - // Get the mappings ClassMapping classMapping = mappings.getClassMapping(method.owner().name()) .orElseThrow(() -> new RuntimeException("Missing mapping for class " + method.owner().name())); MethodMapping methodMapping = classMapping.getMethodMapping(method.name(), method.descriptor()) .orElseThrow(() -> new RuntimeException("Missing mapping for method " + method.getFullName())); - String className = getRawClassName(method.owner()); - boolean isMethodNameUnique = classMapping.getMethodMappings().stream() + // No need for a mapping if the method isn't obfuscated + if (!isObfuscated(methodMapping)) { + return method.name(); + } + + // Check if there's a method with the same name (but different descriptor) + boolean isMethodNameNonUnique = classMapping.getMethodMappings().stream() .filter(m -> m.getDeobfuscatedName().equals(methodMapping.getDeobfuscatedName())) - .count() == 1; + .count() > 1; + + // Get the raw class name + String className = getRawClassName(method.owner()); + + // Get the method name String methodName = methodMapping.getDeobfuscatedName(); - String methodDescriptor = isMethodNameUnique ? "" : methodMapping.getDeobfuscatedDescriptor(); + + // Omit the descriptor for unique method names + String methodDescriptor = isMethodNameNonUnique ? methodMapping.getDeobfuscatedDescriptor() : ""; // "m;" prefix: methods with omitted descriptors need to be different to fields // Note that ";" and "." are illegal in jvm identifiers, so this should be safe @@ -159,57 +156,57 @@ public String getRawMethodName(MethodInfo method) { return "m;" + className + "." + methodName + ";" + methodDescriptor; } - public String getMethodName(MethodInfo method) { - // No need for a mapping if the method isn't obfuscated - if (!method.isObfuscated()) { - return null; - } + public Optional getMethodName(MethodInfo method) { + // Get the mappings + ClassMapping classMapping = mappings.getClassMapping(method.owner().name()) + .orElseThrow(() -> new RuntimeException("Missing mapping for class " + method.owner().name())); + MethodMapping methodMapping = classMapping.getMethodMapping(method.name(), method.descriptor()) + .orElseThrow(() -> new RuntimeException("Missing mapping for method " + method.getFullName())); - // No need to map certain special method names - if (method.name().equals("") || method.name().equals("")) { - return null; + // No need for a mapping if the method isn't obfuscated + if (!isObfuscated(methodMapping)) { + return Optional.empty(); } + // The name of this method is determined by the first of its name set Set nameSet = methodNameSets.get(method); + MethodInfo nameSource = nameSet.stream().min(Comparator.comparing(this::getRawMethodName)) + .orElseThrow(() -> new RuntimeException("No name source for method " + method.getFullName())); - String rawName = getRawMethodName(method); - for (MethodInfo current : nameSet) { - // No need for a mapping if there's an unobfuscated method in the name set - if (!current.isObfuscated()) { - return null; - } - - String currentRawName = getRawMethodName(current); - - // Take the lexicographically "biggest" raw name - // If this is method isn't name-giving, no need to map it - if (currentRawName.compareTo(rawName) > 0) { - return null; - } + // No mapping is needed if the name doesn't come from this method + if (nameSource != method) { + return Optional.empty(); } - return "m_" + getHashedString(rawName); + return Optional.of("m_" + getHashedString(getRawMethodName(nameSource))); } public String getRawFieldName(FieldInfo field) { - // Don't look up unobfuscated names - if (!field.isObfuscated()) { - return field.name(); - } - // Get the mappings ClassMapping classMapping = mappings.getClassMapping(field.owner().name()) .orElseThrow(() -> new RuntimeException("Missing mapping for class " + field.owner().name())); FieldMapping fieldMapping = classMapping.getFieldMapping(FieldSignature.of(field.name(), field.descriptor())) .orElseThrow(() -> new RuntimeException("Missing mapping for field " + field.name())); - String className = getRawClassName(field.owner()); - String fieldName = fieldMapping.getDeobfuscatedName(); + // No need for a mapping if the field isn't obfuscated + if (!isObfuscated(fieldMapping)) { + return field.name(); + } + + // Check if there's a field with the same name (but different descriptor) // While java doesn't allow it, the jvm allows fields that only differ in their descriptor. - boolean isFieldNameUnique = classMapping.getFieldMappings().stream() + boolean isFieldNameNonUnique = classMapping.getFieldMappings().stream() .filter(f -> f.getDeobfuscatedName().equals(fieldMapping.getDeobfuscatedName())) - .count() == 1; - String fieldDescriptor = isFieldNameUnique ? "" : fieldMapping.getType().get().toString(); + .count() > 1; + + // Get the raw class name + String className = getRawClassName(field.owner()); + + // Get the field name + String fieldName = fieldMapping.getDeobfuscatedName(); + + // Omit the descriptor for unique field names + String fieldDescriptor = isFieldNameNonUnique ? fieldMapping.getType().get().toString() : ""; // "f;" prefix: fields need to be different to methods with omitted descriptors // Note that ";" and "." are illegal in jvm identifiers, so this should be safe @@ -217,13 +214,24 @@ public String getRawFieldName(FieldInfo field) { return "f;" + className + "." + fieldName + ";" + fieldDescriptor; } - public String getFieldName(FieldInfo field) { + public Optional getFieldName(FieldInfo field) { + // Get the mappings + ClassMapping classMapping = mappings.getClassMapping(field.owner().name()) + .orElseThrow(() -> new RuntimeException("Missing mapping for class " + field.owner().name())); + FieldMapping fieldMapping = classMapping.getFieldMapping(FieldSignature.of(field.name(), field.descriptor())) + .orElseThrow(() -> new RuntimeException("Missing mapping for field " + field.name())); + // No need for a mapping if the field isn't obfuscated - if (!field.isObfuscated()) { - return null; + if (!isObfuscated(fieldMapping)) { + return Optional.empty(); } - return "f_" + getHashedString(getRawFieldName(field)); + return Optional.of("f_" + getHashedString(getRawFieldName(field))); + } + + private boolean isObfuscated(Mapping mapping) { + return mapping.getDeobfuscatedName().length() == 1 || + !mapping.getDeobfuscatedName().equals(mapping.getObfuscatedName()); } private String getHashedString(String string) { diff --git a/src/main/java/org/quiltmc/mappings_hasher/Main.java b/src/main/java/org/quiltmc/mappings_hasher/Main.java index b1ca4e6..7262592 100644 --- a/src/main/java/org/quiltmc/mappings_hasher/Main.java +++ b/src/main/java/org/quiltmc/mappings_hasher/Main.java @@ -15,11 +15,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import java.util.zip.ZipEntry; public class Main { - public static void main(String[] args) throws IOException { + public static void main(String... args) throws IOException { if (args.length != 1) { System.out.println("Usage: "); return; @@ -29,6 +27,7 @@ public static void main(String[] args) throws IOException { URL manifestUrl = new URL("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"); InputStreamReader manifestReader = new InputStreamReader(manifestUrl.openConnection().getInputStream()); JsonReader manifestJson = JsonReader.json(new BufferedReader(manifestReader)); + VersionManifest manifest = VersionManifest.fromJson(manifestJson); System.out.println("Reading version..."); @@ -51,15 +50,15 @@ public static void main(String[] args) throws IOException { MappingSet obf_to_mojmap = mappingsReader.read().reverse(); MappingsHasher mappingsHasher = new MappingsHasher(obf_to_mojmap, "net/minecraft/unmapped"); - System.out.println("Loading libs..."); - for (LibraryEntry lib : version.libraries()) { - JarFile libJar = new JarFile(lib.getOrDownload()); - mappingsHasher.addLibrary(libJar); - } - System.out.println("Loading client jar..."); JarFile clientJar = new JarFile(version.downloads().get("client").getOrDownload()); + System.out.println("Loading library jars..."); + for (LibraryEntry library : version.libraries()) { + JarFile libJar = new JarFile(library.getOrDownload()); + mappingsHasher.addLibrary(libJar); + } + System.out.println("Generating mappings..."); MappingSet obf_to_hashed = mappingsHasher.generate(clientJar); diff --git a/src/main/java/org/quiltmc/mappings_hasher/MappingsHasher.java b/src/main/java/org/quiltmc/mappings_hasher/MappingsHasher.java index 9802dd0..7af2d0d 100644 --- a/src/main/java/org/quiltmc/mappings_hasher/MappingsHasher.java +++ b/src/main/java/org/quiltmc/mappings_hasher/MappingsHasher.java @@ -1,5 +1,6 @@ package org.quiltmc.mappings_hasher; +import java.util.Optional; import java.util.Set; import java.util.jar.JarFile; @@ -34,68 +35,33 @@ public MappingSet generate(JarFile jar) { // The class generating hashed names from class information and the original mappings HashedNameProvider nameProvider = new HashedNameProvider(classes, original, defaultPackage); - // Detect unobfuscated methods - for (ClassInfo classInfo : classes) { - ClassMapping classMapping = original.getClassMapping(classInfo.name()) - .orElseThrow(() -> new RuntimeException("Missing mapping for class " + classInfo.name())); - - if (classInfo.name().equals(classMapping.getFullDeobfuscatedName())) { - classInfo.dontObfuscate(); - } - - for (MethodInfo methodInfo : classInfo.methods()) { - MethodMapping methodMapping = classMapping.getMethodMapping(methodInfo.name(), methodInfo.descriptor()) - .orElseThrow(() -> new RuntimeException("Missing mapping for method " + methodInfo.name())); - - if (methodInfo.name().equals(methodMapping.getDeobfuscatedName())) { - methodInfo.dontObfuscate(); - } - } - - for (FieldInfo fieldInfo : classInfo.fields()) { - FieldMapping fieldMapping = classMapping.getFieldMapping(FieldSignature.of(fieldInfo.name(), fieldInfo.descriptor())) - .orElseThrow(() -> new RuntimeException("Missing mapping for field " + fieldInfo.name())); - - if (fieldInfo.name().equals(fieldMapping.getDeobfuscatedName())) { - fieldInfo.dontObfuscate(); - } - } - } - // Create the mappings MappingSet hashed = MappingSet.create(); for (ClassInfo classInfo : classes) { // Create class mapping ClassMapping classHashed = hashed.getOrCreateClassMapping(classInfo.name()); - // If null, assume no mapping is required - String className = nameProvider.getClassName(classInfo); - if (className != null) { - classHashed.setDeobfuscatedName(nameProvider.getClassName(classInfo)); - } + // Use identity mapping for non-obfuscated classes + classHashed.setDeobfuscatedName(nameProvider.getClassName(classInfo).orElse(classInfo.name())); for (MethodInfo methodInfo : classInfo.methods()) { - // If null, assume no mapping is required - String methodName = nameProvider.getMethodName(methodInfo); - if (methodName == null) { - continue; - } + Optional hashedName = nameProvider.getMethodName(methodInfo); - // Create method mapping - MethodMapping methodHashed = classHashed.createMethodMapping(methodInfo.name(), methodInfo.descriptor()); - methodHashed.setDeobfuscatedName(methodName); + // Create method mapping if required + if (hashedName.isPresent()) { + MethodMapping methodHashed = classHashed.createMethodMapping(methodInfo.name(), methodInfo.descriptor()); + methodHashed.setDeobfuscatedName(hashedName.get()); + } } for (FieldInfo fieldInfo : classInfo.fields()) { - // If null, assume no mapping is required - String fieldName = nameProvider.getFieldName(fieldInfo); - if (fieldName == null) { - continue; - } + Optional hashedName = nameProvider.getFieldName(fieldInfo); - // Create field mapping - FieldMapping fieldHashed = classHashed.createFieldMapping(FieldSignature.of(fieldInfo.name(), fieldInfo.descriptor())); - fieldHashed.setDeobfuscatedName(fieldName); + // Create field mapping if required+ + if (hashedName.isPresent()) { + FieldMapping fieldHashed = classHashed.createFieldMapping(FieldSignature.of(fieldInfo.name(), fieldInfo.descriptor())); + fieldHashed.setDeobfuscatedName(hashedName.get()); + } } } diff --git a/src/main/java/org/quiltmc/mappings_hasher/asm/ClassInfo.java b/src/main/java/org/quiltmc/mappings_hasher/asm/ClassInfo.java index b544991..a14a3df 100644 --- a/src/main/java/org/quiltmc/mappings_hasher/asm/ClassInfo.java +++ b/src/main/java/org/quiltmc/mappings_hasher/asm/ClassInfo.java @@ -6,18 +6,14 @@ public class ClassInfo { private final String name; private final int access; - private boolean obfuscated; private final Set superClasses = new HashSet<>(); private final Set methods = new HashSet<>(); private final Set fields = new HashSet<>(); - private final Set annotations = new HashSet<>(); - public ClassInfo(String name, int access) { this.name = name; this.access = access; - this.obfuscated = true; } public String name() { @@ -44,16 +40,4 @@ public Set methods() { public Set fields() { return fields; } - - public Set annotations() { - return annotations; - } - - public void dontObfuscate() { - this.obfuscated = false; - } - - public boolean isObfuscated() { - return obfuscated; - } } diff --git a/src/main/java/org/quiltmc/mappings_hasher/asm/ClassResolver.java b/src/main/java/org/quiltmc/mappings_hasher/asm/ClassResolver.java index 00f250e..91db4d1 100644 --- a/src/main/java/org/quiltmc/mappings_hasher/asm/ClassResolver.java +++ b/src/main/java/org/quiltmc/mappings_hasher/asm/ClassResolver.java @@ -11,13 +11,12 @@ public class ClassResolver { private final Map classToReader = new HashMap<>(); - private final Map classToObfuscated = new HashMap<>(); private final Map classInfoCache = new HashMap<>(); public ClassResolver() { } public Set extractClassInfo(JarFile jar) { - addJar(jar, true); + addJar(jar); Set classes = new HashSet<>(); jar.stream().forEach(jarEntry -> { @@ -32,16 +31,15 @@ public Set extractClassInfo(JarFile jar) { } public void addLibrary(JarFile library) { - addJar(library, false); + addJar(library); } - private void addJar(JarFile jar, boolean obfuscated) { + private void addJar(JarFile jar) { jar.stream().forEach(entry -> { if (entry.getName().endsWith(".class")) { String className = entry.getName().substring(0, entry.getName().lastIndexOf('.')); try { classToReader.put(className, new ClassReader(jar.getInputStream(entry))); - classToObfuscated.put(className, obfuscated); } catch (IOException exception) { throw new RuntimeException(exception); @@ -66,11 +64,7 @@ private ClassInfo getClassInfo(String name) { } } - boolean obfuscated = classToObfuscated.getOrDefault(name, false); ClassVisitor visitor = new ClassVisitor(this); - if (!obfuscated) { - visitor.dontObfuscate(); - } reader.accept(visitor,ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); classInfoCache.put(name, visitor.getClassInfo()); return visitor.getClassInfo(); @@ -78,18 +72,12 @@ private ClassInfo getClassInfo(String name) { private static class ClassVisitor extends org.objectweb.asm.ClassVisitor { private final ClassResolver resolver; - private boolean dontObfuscate; private ClassInfo classInfo; public ClassVisitor(ClassResolver resolver) { super(Opcodes.ASM7); this.resolver = resolver; - this.dontObfuscate = false; - } - - public void dontObfuscate() { - this.dontObfuscate = true; } public ClassInfo getClassInfo() { @@ -100,10 +88,6 @@ public ClassInfo getClassInfo() { public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.classInfo = new ClassInfo(name, access); - if (this.dontObfuscate) { - this.classInfo.dontObfuscate(); - } - // This is only null for java/lang/Object if (superName != null) { this.classInfo.superClasses().add(resolver.getClassInfo(superName)); @@ -114,47 +98,20 @@ public void visit(int version, int access, String name, String signature, String } } - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - String annotationClassName = descriptor.substring(1, descriptor.length() - 1); - this.classInfo.annotations().add(annotationClassName); - return null; - } - @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { FieldInfo fieldInfo = new FieldInfo(this.classInfo, name, descriptor); classInfo.fields().add(fieldInfo); - if (this.dontObfuscate) { - fieldInfo.dontObfuscate(); - } - return new FieldVisitor(this.api) { - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - String annotationClassName = descriptor.substring(1, descriptor.length() - 1); - fieldInfo.annotations().add(annotationClassName); - return null; - } - }; + return null; } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodInfo methodInfo = new MethodInfo(this.classInfo, name, descriptor, access); this.classInfo.methods().add(methodInfo); - if(this.dontObfuscate) { - methodInfo.dontObfuscate(); - } - return new MethodVisitor(this.api) { - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - String annotationClassName = descriptor.substring(1, descriptor.length() - 1); - methodInfo.annotations().add(annotationClassName); - return null; - } - }; + return null; } } } diff --git a/src/main/java/org/quiltmc/mappings_hasher/asm/FieldInfo.java b/src/main/java/org/quiltmc/mappings_hasher/asm/FieldInfo.java index dd3da6c..b19bb7c 100644 --- a/src/main/java/org/quiltmc/mappings_hasher/asm/FieldInfo.java +++ b/src/main/java/org/quiltmc/mappings_hasher/asm/FieldInfo.java @@ -8,15 +8,11 @@ public class FieldInfo { private final ClassInfo owner; private final String name; private final String descriptor; - private boolean obfuscated; - - private final Set annotations = new HashSet<>(); public FieldInfo(ClassInfo owner, String name, String descriptor) { this.owner = owner; this.name = name; this.descriptor = descriptor; - this.obfuscated = true; } public ClassInfo owner() { @@ -30,16 +26,4 @@ public String name() { public String descriptor() { return descriptor; } - - public Set annotations() { - return annotations; - } - - public void dontObfuscate() { - this.obfuscated = false; - } - - public boolean isObfuscated() { - return obfuscated; - } } diff --git a/src/main/java/org/quiltmc/mappings_hasher/asm/MethodInfo.java b/src/main/java/org/quiltmc/mappings_hasher/asm/MethodInfo.java index 322ace9..5f88af3 100644 --- a/src/main/java/org/quiltmc/mappings_hasher/asm/MethodInfo.java +++ b/src/main/java/org/quiltmc/mappings_hasher/asm/MethodInfo.java @@ -10,18 +10,14 @@ public class MethodInfo { private final String name; private final String descriptor; private final int access; - private boolean obfuscated; private final Set overrides; - private final Set annotations = new HashSet<>(); - public MethodInfo(ClassInfo owner, String name, String descriptor, int access) { this.owner = owner; this.name = name; this.descriptor = descriptor; this.access = access; - this.obfuscated = true; // Check which methods this method overrides this.overrides = computeOverrides(); @@ -39,22 +35,10 @@ public String descriptor() { return descriptor; } - public void dontObfuscate() { - this.obfuscated = false; - } - - public boolean isObfuscated() { - return obfuscated; - } - public Set overrides() { return overrides; } - public Set annotations() { - return annotations; - } - public String getFullName() { return owner.name() + "/" + name + descriptor; } @@ -105,12 +89,13 @@ private Set computeOverrides() { // Can override public and protected methods, and non-private methods in same package if (superMethod.isPublic() || superMethod.isProtected() || !superMethod.isPrivate() && owner.getPackage().equals(superClass.getPackage())) { - if (!superMethod.overrides.isEmpty()) { - overrides.addAll(superMethod.overrides); - } - else { - overrides.add(superMethod); - } + // Direct override + overrides.add(superMethod); + + // Indirect overrides + overrides.addAll(superMethod.overrides); + + // If override was found, no need to check further super classes continue; } } diff --git a/src/test/java/org/quiltmc/mappings_hasher/BasicTests.java b/src/test/java/org/quiltmc/mappings_hasher/BasicTests.java new file mode 100644 index 0000000..fb868eb --- /dev/null +++ b/src/test/java/org/quiltmc/mappings_hasher/BasicTests.java @@ -0,0 +1,102 @@ +package org.quiltmc.mappings_hasher; + +import net.fabricmc.tinyremapper.NonClassCopyMode; +import net.fabricmc.tinyremapper.OutputConsumerPath; +import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.tinyremapper.TinyUtils; +import org.junit.jupiter.api.*; +import org.quiltmc.json5.JsonReader; +import org.quiltmc.mappings_hasher.Main; +import org.quiltmc.mappings_hasher.manifest.LibraryEntry; +import org.quiltmc.mappings_hasher.manifest.VersionEntry; +import org.quiltmc.mappings_hasher.manifest.VersionManifest; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarFile; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BasicTests { + @Test + @Order(1) + public void hash_1_17_1() throws IOException { + Main.main("1.17.1"); + } + + @Test + @Order(2) + public void remap_1_17_1() throws IOException { + Path input = Paths.get("cache", "versions", "1.17.1", "client.jar"); + Path output = Paths.get("mappings", "remapped.jar"); + + Files.deleteIfExists(output); + + TinyRemapper remapper = TinyRemapper.newRemapper() + .withMappings(TinyUtils.createTinyMappingProvider(Paths.get("mappings", "hashed-1.17.1.tiny"), "official", "hashed")) + .rebuildSourceFilenames(true) + .ignoreConflicts(true).build(); + + OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build(); + outputConsumer.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, remapper); + remapper.readInputs(input); + + + URL manifestUrl = new URL("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"); + InputStreamReader manifestReader = new InputStreamReader(manifestUrl.openConnection().getInputStream()); + JsonReader manifestJson = JsonReader.json(new BufferedReader(manifestReader)); + VersionManifest manifest = VersionManifest.fromJson(manifestJson); + VersionEntry version = manifest.versions().get("1.17.1"); + version.resolve(); + + List libs = new ArrayList<>(); + for (LibraryEntry lib : version.libraries()) { + libs.add(lib.getOrDownload().toPath()); + } + + remapper.readClassPath(libs.toArray(new Path[0])); + remapper.apply(outputConsumer); + outputConsumer.close(); + remapper.finish(); + } + + @Test + @Order(3) + public void load_1_17_1() throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + URL manifestUrl = new URL("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"); + InputStreamReader manifestReader = new InputStreamReader(manifestUrl.openConnection().getInputStream()); + JsonReader manifestJson = JsonReader.json(new BufferedReader(manifestReader)); + VersionManifest manifest = VersionManifest.fromJson(manifestJson); + VersionEntry version = manifest.versions().get("1.17.1"); + version.resolve(); + + List jars = new ArrayList<>(); + for (LibraryEntry lib : version.libraries()) { + jars.add(lib.getOrDownload().toURI().toURL()); + } + + File clientJarFile = new File("mappings/remapped.jar"); + jars.add(clientJarFile.toURI().toURL()); + + ClassLoader loader = new URLClassLoader(jars.toArray(new URL[0])); + + JarFile clientJar = new JarFile(clientJarFile); + List classNames = clientJar.stream().map(ZipEntry::getName).filter(name -> name.endsWith(".class")) + .map(name -> name.substring(0, name.length() - 6)).collect(Collectors.toList()); + + Class dataMain = loader.loadClass("net.minecraft.data.Main"); + dataMain.getMethod("main", String[].class).invoke(null, (Object)new String[] {"--all"}); + + for (String name : classNames) { + Class.forName(name.replace('/', '.'), false, loader); + } + } +}