- * Contributors:
- * Jeff Martin - initial API and implementation
- ******************************************************************************/
-
-package cuchaz.enigma;
-
-import cuchaz.enigma.convert.*;
-import cuchaz.enigma.gui.ClassMatchingGui;
-import cuchaz.enigma.gui.MemberMatchingGui;
-import cuchaz.enigma.mapping.*;
-import cuchaz.enigma.throwables.MappingConflict;
-import cuchaz.enigma.throwables.MappingParseException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.jar.JarFile;
-
-public class ConvertMain {
-
- public static void main(String[] args)
- throws IOException, MappingParseException {
- try {
- //Get all are args
- String JarOld = getArg(args, 1, "Path to Old Jar", true);
- String JarNew = getArg(args, 2, "Path to New Jar", true);
- String OldMappings = getArg(args, 3, "Path to old .mappings file", true);
- String NewMappings = getArg(args, 4, "Path to new .mappings file", true);
- String ClassMatches = getArg(args, 5, "Path to Class .matches file", true);
- String FieldMatches = getArg(args, 6, "Path to Field .matches file", true);
- String MethodMatches = getArg(args, 7, "Path to Method .matches file", true);
- //OldJar
- JarFile sourceJar = new JarFile(new File(JarOld));
- //NewJar
- JarFile destJar = new JarFile(new File(JarNew));
- //Get the mapping files
- File inMappingsFile = new File(OldMappings);
- File outMappingsFile = new File(NewMappings);
- Mappings mappings = new MappingsEnigmaReader().read(inMappingsFile);
- //Make the Match Files..
- File classMatchesFile = new File(ClassMatches);
- File fieldMatchesFile = new File(FieldMatches);
- File methodMatchesFile = new File(MethodMatches);
-
- String command = getArg(args, 0, "command", true);
-
- if (command.equalsIgnoreCase("computeClassMatches")) {
- computeClassMatches(classMatchesFile, sourceJar, destJar, mappings);
- convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
- } else if (command.equalsIgnoreCase("editClassMatches")) {
- editClasssMatches(classMatchesFile, sourceJar, destJar, mappings);
- convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
- } else if (command.equalsIgnoreCase("computeFieldMatches")) {
- computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile);
- convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
- } else if (command.equalsIgnoreCase("editFieldMatches")) {
- editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile);
- convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
- } else if (command.equalsIgnoreCase("computeMethodMatches")) {
- computeMethodMatches(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
- convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
- } else if (command.equalsIgnoreCase("editMethodMatches")) {
- editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile);
- convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
- } else if (command.equalsIgnoreCase("convertMappings")) {
- convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
- }
- } catch (MappingConflict ex) {
- System.out.println(ex.getMessage());
- ex.printStackTrace();
- } catch (IllegalArgumentException ex) {
- System.out.println(ex.getMessage());
- printHelp();
- }
- }
-
- private static void printHelp() {
- System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION));
- System.out.println("Usage:");
- System.out.println("\tjava -cp enigma.jar cuchaz.enigma.ConvertMain ");
- System.out.println("\tWhere is one of:");
- System.out.println("\t\tcomputeClassMatches");
- System.out.println("\t\teditClassMatches");
- System.out.println("\t\tcomputeFieldMatches");
- System.out.println("\t\teditFieldMatches");
- System.out.println("\t\teditMethodMatches");
- System.out.println("\t\tconvertMappings");
- System.out.println("\tWhere is the already mapped jar.");
- System.out.println("\tWhere is the unmapped jar.");
- System.out.println("\tWhere is the path to the mappings for the old jar.");
- System.out.println("\tWhere is the new mappings. (Where you want to save them and there name)");
- System.out.println("\tWhere is the class matches file.");
- System.out.println("\tWhere is the field matches file.");
- System.out.println("\tWhere is the method matches file.");
- }
-
- //Copy of getArg from CommandMain.... Should make a utils class.
- private static String getArg(String[] args, int i, String name, boolean required) {
- if (i >= args.length) {
- if (required) {
- throw new IllegalArgumentException(name + " is required");
- } else {
- return null;
- }
- }
- return args[i];
- }
-
- private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
- throws IOException {
- ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings);
- MatchesWriter.writeClasses(classMatches, classMatchesFile);
- System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath());
- }
-
- private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
- throws IOException {
- System.out.println("Reading class matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
- deobfuscators.source.setMappings(mappings);
- System.out.println("Starting GUI...");
- new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(matches ->
- {
- try {
- MatchesWriter.writeClasses(matches, classMatchesFile);
- } catch (IOException ex) {
- throw new Error(ex);
- }
- });
- }
-
- @SuppressWarnings("unused")
- private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
- throws IOException, MappingConflict {
- System.out.println("Reading class matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
- deobfuscators.source.setMappings(mappings);
-
- Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
- new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
- System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
- }
-
- private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
- throws IOException, MappingParseException {
-
- System.out.println("Reading class matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- System.out.println("Reading mappings...");
- Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
- System.out.println("Indexing dest jar...");
- Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
-
- System.out.println("Writing matches...");
-
- // get the matched and unmatched mappings
- MemberMatches fieldMatches = MappingsConverter.computeMemberMatches(
- destDeobfuscator,
- destMappings,
- classMatches,
- MappingsConverter.getFieldDoer()
- );
-
- MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
- System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
- }
-
- private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
- throws IOException, MappingParseException {
-
- System.out.println("Reading matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
-
- // prep deobfuscators
- Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
- deobfuscators.source.setMappings(sourceMappings);
- Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
- MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
- checker.dropBrokenMappings(destMappings);
- deobfuscators.dest.setMappings(destMappings);
-
- new MemberMatchingGui<>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(
- matches ->
- {
- try {
- MatchesWriter.writeMembers(matches, fieldMatchesFile);
- } catch (IOException ex) {
- throw new Error(ex);
- }
- });
- }
-
- @SuppressWarnings("unused")
- private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
- throws IOException, MappingConflict {
-
- System.out.println("Reading matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
-
- Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
- deobfuscators.source.setMappings(mappings);
-
- // apply matches
- Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
- MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
-
- // write out the converted mappings
-
- new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
- System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
- }
-
- private static void computeMethodMatches(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings sourceMappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
- throws IOException, MappingParseException {
-
- System.out.println("Reading class matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- System.out.println("Reading dest mappings...");
- Mappings destMappings = new MappingsEnigmaReader().read(outMappingsFile);
- System.out.println("Indexing dest jar...");
- Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
- System.out.println("Indexing source jar...");
- Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar);
-
- System.out.println("Writing method matches...");
-
- // get the matched and unmatched mappings
- MemberMatches methodMatches = MappingsConverter.computeMethodsMatches(
- destDeobfuscator,
- destMappings,
- sourceDeobfuscator,
- sourceMappings,
- classMatches,
- MappingsConverter.getMethodDoer()
- );
-
- MatchesWriter.writeMembers(methodMatches, methodMatchesFile);
- System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath());
- }
-
- private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile)
- throws IOException, MappingParseException {
-
- System.out.println("Reading matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile);
-
- // prep deobfuscators
- Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
- deobfuscators.source.setMappings(sourceMappings);
- Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
- MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
- checker.dropBrokenMappings(destMappings);
- deobfuscators.dest.setMappings(destMappings);
-
- new MemberMatchingGui<>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(
- matches ->
- {
- try {
- MatchesWriter.writeMembers(matches, methodMatchesFile);
- } catch (IOException ex) {
- throw new Error(ex);
- }
- });
- }
-
- private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
- throws IOException, MappingConflict {
-
- System.out.println("Reading matches...");
- ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
- MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
- MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile);
-
- Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
- deobfuscators.source.setMappings(mappings);
-
- // apply matches
- Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
- MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
- MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
-
- // check the final mappings
- MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
- checker.dropBrokenMappings(newMappings);
-
- for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) {
- System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
- }
- for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) {
- System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
- }
- for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) {
- System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
- }
- for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) {
- System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
- }
-
- // write out the converted mappings
- new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
- System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
- }
-
- private static class Deobfuscators {
-
- public Deobfuscator source;
- public Deobfuscator dest;
-
- public Deobfuscators(JarFile sourceJar, JarFile destJar) {
- System.out.println("Indexing source jar...");
- IndexerThread sourceIndexer = new IndexerThread(sourceJar);
- sourceIndexer.start();
- System.out.println("Indexing dest jar...");
- IndexerThread destIndexer = new IndexerThread(destJar);
- destIndexer.start();
- sourceIndexer.joinOrBail();
- destIndexer.joinOrBail();
- source = sourceIndexer.deobfuscator;
- dest = destIndexer.deobfuscator;
- }
- }
-
- private static class IndexerThread extends Thread {
-
- public Deobfuscator deobfuscator;
- private JarFile jarFile;
-
- public IndexerThread(JarFile jarFile) {
- this.jarFile = jarFile;
- deobfuscator = null;
- }
-
- public void joinOrBail() {
- try {
- join();
- } catch (InterruptedException ex) {
- throw new Error(ex);
- }
- }
-
- @Override
- public void run() {
- deobfuscator = new Deobfuscator(jarFile);
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
index 7304f722..2a2041a0 100644
--- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
+++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
@@ -18,10 +18,10 @@ import com.strobel.assembler.metadata.ClasspathTypeLoader;
import com.strobel.assembler.metadata.ITypeLoader;
import cuchaz.enigma.analysis.BridgeMarker;
import cuchaz.enigma.analysis.JarIndex;
-import cuchaz.enigma.bytecode.ClassTranslator;
-import cuchaz.enigma.bytecode.InnerClassWriter;
-import cuchaz.enigma.bytecode.LocalVariableRenamer;
-import cuchaz.enigma.bytecode.MethodParameterWriter;
+import cuchaz.enigma.bytecode.translators.ClassTranslator;
+import cuchaz.enigma.bytecode.translators.InnerClassWriter;
+import cuchaz.enigma.bytecode.translators.LocalVariableTranslator;
+import cuchaz.enigma.bytecode.translators.MethodParameterTranslator;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.Translator;
import javassist.*;
@@ -51,6 +51,7 @@ public class TranslatingTypeLoader implements ITypeLoader {
this.deobfuscatingTranslator = deobfuscatingTranslator;
this.cache = Maps.newHashMap();
this.defaultTypeLoader = new ClasspathTypeLoader();
+
}
public void clearCache() {
@@ -200,7 +201,7 @@ public class TranslatingTypeLoader implements ITypeLoader {
throws IOException, NotFoundException, CannotCompileException {
// reconstruct inner classes
- new InnerClassWriter(this.jarIndex, this.deobfuscatingTranslator).write(c);
+ InnerClassWriter.write(jarIndex, c);
// re-get the javassist handle since we changed class names
ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
@@ -213,10 +214,10 @@ public class TranslatingTypeLoader implements ITypeLoader {
assertClassName(c, obfClassEntry);
// do all kinds of deobfuscating transformations on the class
- new BridgeMarker(this.jarIndex).markBridges(c);
- new MethodParameterWriter(this.deobfuscatingTranslator).writeMethodArguments(c);
- new LocalVariableRenamer(this.deobfuscatingTranslator).rename(c);
- new ClassTranslator(this.deobfuscatingTranslator).translate(c);
+ BridgeMarker.markBridges(this.jarIndex, c);
+ MethodParameterTranslator.translate(this.deobfuscatingTranslator, c);
+ LocalVariableTranslator.translate(this.deobfuscatingTranslator, c);
+ ClassTranslator.translate(this.deobfuscatingTranslator, c);
return c;
}
diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
index 81e750c1..a2f1f909 100644
--- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
+++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
@@ -19,19 +19,13 @@ import javassist.bytecode.AccessFlag;
public class BridgeMarker {
- private JarIndex jarIndex;
-
- public BridgeMarker(JarIndex jarIndex) {
- this.jarIndex = jarIndex;
- }
-
- public void markBridges(CtClass c) {
+ public static void markBridges(JarIndex jarIndex, CtClass c) {
for (CtMethod method : c.getDeclaredMethods()) {
MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
// is this a bridge method?
- MethodEntry bridgedMethodEntry = this.jarIndex.getBridgedMethod(methodEntry);
+ MethodEntry bridgedMethodEntry = jarIndex.getBridgedMethod(methodEntry);
if (bridgedMethodEntry != null) {
// it's a bridge method! add the bridge flag
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java
deleted file mode 100644
index 1ebf6561..00000000
--- a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Jeff Martin.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Lesser General Public
- * License v3.0 which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Contributors:
- * Jeff Martin - initial API and implementation
- ******************************************************************************/
-
-package cuchaz.enigma.bytecode;
-
-import cuchaz.enigma.mapping.*;
-import javassist.CtBehavior;
-import javassist.CtClass;
-import javassist.CtField;
-import javassist.CtMethod;
-import javassist.bytecode.*;
-
-public class ClassTranslator {
-
- private Translator translator;
-
- public ClassTranslator(Translator translator) {
- this.translator = translator;
- }
-
- public void translate(CtClass c) {
-
- // NOTE: the order of these translations is very important
-
- // translate all the field and method references in the code by editing the constant pool
- ConstPool constants = c.getClassFile().getConstPool();
- ConstPoolEditor editor = new ConstPoolEditor(constants);
- for (int i = 1; i < constants.getSize(); i++) {
- switch (constants.getTag(i)) {
-
- case ConstPool.CONST_Fieldref: {
-
- // translate the name and type
- FieldEntry entry = EntryFactory.getFieldEntry(
- Descriptor.toJvmName(constants.getFieldrefClassName(i)),
- constants.getFieldrefName(i),
- constants.getFieldrefType(i)
- );
- FieldEntry translatedEntry = this.translator.translateEntry(entry);
- if (!entry.equals(translatedEntry)) {
- editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
- }
- }
- break;
-
- case ConstPool.CONST_Methodref:
- case ConstPool.CONST_InterfaceMethodref: {
-
- // translate the name and type (ie signature)
- BehaviorEntry entry = EntryFactory.getBehaviorEntry(
- Descriptor.toJvmName(editor.getMemberrefClassname(i)),
- editor.getMemberrefName(i),
- editor.getMemberrefType(i)
- );
- BehaviorEntry translatedEntry = this.translator.translateEntry(entry);
- if (!entry.equals(translatedEntry)) {
- editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
- }
- }
- break;
- default:
- break;
- }
- }
-
- ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
- Mappings.EntryModifier modifier = this.translator.getModifier(classEntry);
- if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
- ClassRenamer.applyModifier(c, modifier);
-
- // translate all the fields
- for (CtField field : c.getDeclaredFields()) {
-
- // translate the name
- FieldEntry entry = EntryFactory.getFieldEntry(field);
- String translatedName = this.translator.translate(entry);
- modifier = this.translator.getModifier(entry);
- if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
- ClassRenamer.applyModifier(field, modifier);
-
- if (translatedName != null) {
- field.setName(translatedName);
- }
-
- // translate the type
- Type translatedType = this.translator.translateType(entry.getType());
- field.getFieldInfo().setDescriptor(translatedType.toString());
- }
-
- // translate all the methods and constructors
- for (CtBehavior behavior : c.getDeclaredBehaviors()) {
-
- BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
-
- modifier = this.translator.getModifier(entry);
- if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
- ClassRenamer.applyModifier(behavior, modifier);
-
- if (behavior instanceof CtMethod) {
- CtMethod method = (CtMethod) behavior;
-
- // translate the name
- String translatedName = this.translator.translate(entry);
- if (translatedName != null) {
- method.setName(translatedName);
- }
- }
-
- if (entry.getSignature() != null) {
- // translate the signature
- Signature translatedSignature = this.translator.translateSignature(entry.getSignature());
- behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
- }
- }
-
- // translate the EnclosingMethod attribute
- EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
- if (enclosingMethodAttr != null) {
-
- if (enclosingMethodAttr.methodIndex() == 0) {
- BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
- BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry);
- c.getClassFile().addAttribute(new EnclosingMethodAttribute(
- constants,
- deobfBehaviorEntry.getClassName()
- ));
- } else {
- BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
- Descriptor.toJvmName(enclosingMethodAttr.className()),
- enclosingMethodAttr.methodName(),
- enclosingMethodAttr.methodDescriptor()
- );
- BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry);
- c.getClassFile().addAttribute(new EnclosingMethodAttribute(
- constants,
- deobfBehaviorEntry.getClassName(),
- deobfBehaviorEntry.getName(),
- deobfBehaviorEntry.getSignature().toString()
- ));
- }
- }
-
- // translate all the class names referenced in the code
- // the above code only changed method/field/reference names and types, but not the rest of the class references
- ClassRenamer.renameClasses(c, this.translator);
-
- // translate the source file attribute too
- ClassEntry deobfClassEntry = this.translator.translateEntry(classEntry);
- if (deobfClassEntry != null) {
- String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
- c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
- }
- InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
- if (attr != null)
- InnerClassWriter.changeModifier(c, attr, translator);
- }
-}
diff --git a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
deleted file mode 100644
index f1c3dd77..00000000
--- a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Jeff Martin.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Lesser General Public
- * License v3.0 which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Contributors:
- * Jeff Martin - initial API and implementation
- ******************************************************************************/
-
-package cuchaz.enigma.bytecode;
-
-import com.google.common.collect.Lists;
-import cuchaz.enigma.analysis.JarIndex;
-import cuchaz.enigma.mapping.*;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.NotFoundException;
-import javassist.bytecode.*;
-
-import java.util.Collection;
-import java.util.List;
-
-public class InnerClassWriter {
-
- private JarIndex index;
- private Translator deobfuscatorTranslator;
-
- public InnerClassWriter(JarIndex index, Translator deobfuscatorTranslator) {
- this.index = index;
- this.deobfuscatorTranslator = deobfuscatorTranslator;
- }
-
- // FIXME: modifier is not applied to inner class
- public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) {
- ClassPool pool = c.getClassPool();
- for (int i = 0; i < attr.tableLength(); i++) {
-
- String innerName = attr.innerClass(i);
- // get the inner class full name (which has already been translated)
- ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName));
- try {
- CtClass innerClass = pool.get(innerName);
- Mappings.EntryModifier modifier = translator.getModifier(classEntry);
- if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
- ClassRenamer.applyModifier(innerClass, modifier);
- } catch (NotFoundException e) {
- // This shouldn't be possible in theory
- //e.printStackTrace();
- }
- }
- }
-
- public void write(CtClass c) {
-
- // don't change anything if there's already an attribute there
- InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
- if (oldAttr != null) {
- // bail!
- return;
- }
-
- ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
- List obfClassChain = this.index.getObfClassChain(obfClassEntry);
-
- boolean isInnerClass = obfClassChain.size() > 1;
- if (isInnerClass) {
-
- // it's an inner class, rename it to the fully qualified name
- c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
-
- BehaviorEntry caller = this.index.getAnonymousClassCaller(obfClassEntry);
- if (caller != null) {
-
- // write the enclosing method attribute
- if (caller.getName().equals("")) {
- c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
- } else {
- c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
- }
- }
- }
-
- // does this class have any inner classes?
- Collection obfInnerClassEntries = this.index.getInnerClasses(obfClassEntry);
-
- if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
-
- // create an inner class attribute
- InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
- c.getClassFile().addAttribute(attr);
-
- // write the ancestry, but not the outermost class
- for (int i = 1; i < obfClassChain.size(); i++) {
- ClassEntry obfInnerClassEntry = obfClassChain.get(i);
- writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
-
- // update references to use the fully qualified inner class name
- c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
- }
-
- // write the inner classes
- for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
-
- // extend the class chain
- List extendedObfClassChain = Lists.newArrayList(obfClassChain);
- extendedObfClassChain.add(obfInnerClassEntry);
-
- writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
-
- // update references to use the fully qualified inner class name
- c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
- }
- }
- }
-
- private void writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) {
-
- // get the new inner class name
- ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
- ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
-
- // here's what the JVM spec says about the InnerClasses attribute
- // append(inner, parent, 0 if anonymous else simple name, flags);
-
- // update the attribute with this inner class
- ConstPool constPool = attr.getConstPool();
- int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
- int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
- int innerClassNameIndex = 0;
- int accessFlags = AccessFlag.PUBLIC;
- // TODO: need to figure out if we can put static or not
- if (!this.index.isAnonymousClass(obfClassEntry)) {
- innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
- }
-
- attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
-
- /* DEBUG
- System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
- obfClassEntry,
- attr.innerClass(attr.tableLength() - 1),
- attr.outerClass(attr.tableLength() - 1),
- attr.innerName(attr.tableLength() - 1),
- Constants.NonePackage + "/" + obfInnerClassName,
- obfClassEntry.getName()
- ));
- */
- }
-}
diff --git a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java
deleted file mode 100644
index 878e30a7..00000000
--- a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Jeff Martin.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Lesser General Public
- * License v3.0 which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Contributors:
- * Jeff Martin - initial API and implementation
- ******************************************************************************/
-
-package cuchaz.enigma.bytecode;
-
-import cuchaz.enigma.mapping.*;
-import javassist.CtBehavior;
-import javassist.CtClass;
-import javassist.bytecode.*;
-
-public class LocalVariableRenamer {
-
- private Translator translator;
-
- public LocalVariableRenamer(Translator translator) {
- this.translator = translator;
- }
-
- public void rename(CtClass c) {
- for (CtBehavior behavior : c.getDeclaredBehaviors()) {
-
- // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
- CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
- if (codeAttribute == null) {
- continue;
- }
-
- BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
- ConstPool constants = c.getClassFile().getConstPool();
-
- LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
- if (table != null) {
- renameLVT(behaviorEntry, constants, table, c);
- }
-
- LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
- if (typeTable != null) {
- renameLVTT(typeTable, table);
- }
- }
- }
-
- // DEBUG
- @SuppressWarnings("unused")
- private void dumpTable(LocalVariableAttribute table) {
- for (int i = 0; i < table.tableLength(); i++) {
- System.out.println(String.format("\t%d (%d): %s %s",
- i, table.index(i), table.variableName(i), table.descriptor(i)
- ));
- }
- }
-
- private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table, CtClass ctClass) {
-
- // skip empty tables
- if (table.tableLength() <= 0) {
- return;
- }
-
- // where do we start counting variables?
- int starti = 0;
- if (table.variableName(0).equals("this")) {
- // skip the "this" variable
- starti++;
- }
-
- // rename method arguments first
- int numArgs = 0;
- if (behaviorEntry.getSignature() != null) {
- numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
- boolean isNestedClassConstructor = false;
-
- // If the behavior is a constructor and if it have more than one arg, it's probably from a nested!
- if (behaviorEntry instanceof ConstructorEntry && behaviorEntry.getClassEntry() != null && behaviorEntry.getClassEntry().isInnerClass() && numArgs >= 1) {
- // Get the first arg type
- Type firstArg = behaviorEntry.getSignature().getArgumentTypes().get(0);
-
- // If the arg is a class and if the class name match the outer class name of the constructor, it's definitely a constructor of a nested class
- if (firstArg.isClass() && firstArg.getClassEntry().equals(behaviorEntry.getClassEntry().getOuterClassEntry())) {
- isNestedClassConstructor = true;
- numArgs--;
- }
- }
-
- for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) {
- int argi = i - starti;
- if (ctClass.isEnum())
- argi += 2;
- if (behaviorEntry.getClassEntry().getName().contains("ahd") && behaviorEntry instanceof ConstructorEntry)
- System.out.println(behaviorEntry.getClassEntry() + " " + i);
- String argName = this.translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
- if (argName == null) {
- int argIndex = isNestedClassConstructor ? argi + 1 : argi;
- if (ctClass.isEnum())
- argIndex -= 2;
- Type argType = behaviorEntry.getSignature().getArgumentTypes().get(argIndex);
- // Unfortunately each of these have different name getters, so they have different code paths
- if (argType.isPrimitive()) {
- Type.Primitive argCls = argType.getPrimitive();
- argName = "a" + argCls.name() + (argIndex + 1);
- } else if (argType.isArray()) {
- // List types would require this whole block again, so just go with aListx
- argName = "aList" + (argIndex + 1);
- } else if (argType.isClass()) {
- ClassEntry argClsTrans = this.translator.translateEntry(argType.getClassEntry());
- argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argIndex + 1);
- } else {
- argName = "a" + (argIndex + 1);
- }
- }
- renameVariable(table, i, constants.addUtf8Info(argName));
- }
- }
-
- // then rename the rest of the args, if any
- for (int i = starti + numArgs; i < table.tableLength(); i++) {
- int firstIndex = Math.min(table.index(starti + numArgs), table.index(i));
- renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
- }
- }
-
- private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
- // rename args to the same names as in the LVT
- for (int i = 0; i < typeTable.tableLength(); i++) {
- renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
- }
- }
-
- private void renameVariable(LocalVariableAttribute table, int i, int stringId) {
- // based off of LocalVariableAttribute.nameIndex()
- ByteArray.write16bit(stringId, table.get(), i * 10 + 6);
- }
-
- private int getNameIndex(LocalVariableAttribute table, int index) {
- for (int i = 0; i < table.tableLength(); i++) {
- if (table.index(i) == index) {
- return table.nameIndex(i);
- }
- }
- return 0;
- }
-}
diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java
deleted file mode 100644
index d63572e9..00000000
--- a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Jeff Martin.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Lesser General Public
- * License v3.0 which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Contributors:
- * Jeff Martin - initial API and implementation
- ******************************************************************************/
-
-package cuchaz.enigma.bytecode;
-
-import cuchaz.enigma.mapping.*;
-import javassist.CtBehavior;
-import javassist.CtClass;
-import javassist.bytecode.CodeAttribute;
-import javassist.bytecode.LocalVariableAttribute;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class MethodParameterWriter {
-
- private Translator translator;
-
- public MethodParameterWriter(Translator translator) {
- this.translator = translator;
- }
-
- public void writeMethodArguments(CtClass c) {
-
- // Procyon will read method arguments from the "MethodParameters" attribute, so write those
- for (CtBehavior behavior : c.getDeclaredBehaviors()) {
-
- // if there's a local variable table here, don't write a MethodParameters attribute
- // let the local variable writer deal with it instead
- // procyon starts doing really weird things if we give it both attributes
- CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
- if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
- continue;
- }
-
- BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
-
- // get the number of arguments
- Signature signature = behaviorEntry.getSignature();
- if (signature == null) {
- // static initializers have no signatures, or arguments
- continue;
- }
- int numParams = signature.getArgumentTypes().size();
- if (numParams <= 0) {
- continue;
- }
-
- // get the list of argument names
- List names = new ArrayList<>(numParams);
- for (int i = 0; i < numParams; i++) {
- names.add(this.translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
- }
-
- // save the mappings to the class
- MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
- }
- }
-}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java
new file mode 100644
index 00000000..4ac5a8b0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+
+package cuchaz.enigma.bytecode.translators;
+
+import cuchaz.enigma.bytecode.ClassRenamer;
+import cuchaz.enigma.bytecode.ConstPoolEditor;
+import cuchaz.enigma.mapping.*;
+import javassist.CtBehavior;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.CtMethod;
+import javassist.bytecode.*;
+
+public class ClassTranslator {
+
+ public static void translate(Translator translator, CtClass c) {
+
+ // NOTE: the order of these translations is very important
+
+ // translate all the field and method references in the code by editing the constant pool
+ ConstPool constants = c.getClassFile().getConstPool();
+ ConstPoolEditor editor = new ConstPoolEditor(constants);
+ for (int i = 1; i < constants.getSize(); i++) {
+ switch (constants.getTag(i)) {
+
+ case ConstPool.CONST_Fieldref: {
+
+ // translate the name and type
+ FieldEntry entry = EntryFactory.getFieldEntry(
+ Descriptor.toJvmName(constants.getFieldrefClassName(i)),
+ constants.getFieldrefName(i),
+ constants.getFieldrefType(i)
+ );
+ FieldEntry translatedEntry = translator.translateEntry(entry);
+ if (!entry.equals(translatedEntry)) {
+ editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
+ }
+ }
+ break;
+
+ case ConstPool.CONST_Methodref:
+ case ConstPool.CONST_InterfaceMethodref: {
+
+ // translate the name and type (ie signature)
+ BehaviorEntry entry = EntryFactory.getBehaviorEntry(
+ Descriptor.toJvmName(editor.getMemberrefClassname(i)),
+ editor.getMemberrefName(i),
+ editor.getMemberrefType(i)
+ );
+ BehaviorEntry translatedEntry = translator.translateEntry(entry);
+ if (!entry.equals(translatedEntry)) {
+ editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
+ Mappings.EntryModifier modifier = translator.getModifier(classEntry);
+ if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
+ ClassRenamer.applyModifier(c, modifier);
+
+ // translate all the fields
+ for (CtField field : c.getDeclaredFields()) {
+
+ // translate the name
+ FieldEntry entry = EntryFactory.getFieldEntry(field);
+ String translatedName = translator.translate(entry);
+ modifier = translator.getModifier(entry);
+ if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
+ ClassRenamer.applyModifier(field, modifier);
+
+ if (translatedName != null) {
+ field.setName(translatedName);
+ }
+
+ // translate the type
+ Type translatedType = translator.translateType(entry.getType());
+ field.getFieldInfo().setDescriptor(translatedType.toString());
+ }
+
+ // translate all the methods and constructors
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+
+ BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
+
+ modifier = translator.getModifier(entry);
+ if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
+ ClassRenamer.applyModifier(behavior, modifier);
+
+ if (behavior instanceof CtMethod) {
+ CtMethod method = (CtMethod) behavior;
+
+ // translate the name
+ String translatedName = translator.translate(entry);
+ if (translatedName != null) {
+ method.setName(translatedName);
+ }
+ }
+
+ if (entry.getSignature() != null) {
+ // translate the signature
+ Signature translatedSignature = translator.translateSignature(entry.getSignature());
+ behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
+ }
+ }
+
+ // translate the EnclosingMethod attribute
+ EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
+ if (enclosingMethodAttr != null) {
+
+ if (enclosingMethodAttr.methodIndex() == 0) {
+ BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
+ BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry);
+ c.getClassFile().addAttribute(new EnclosingMethodAttribute(
+ constants,
+ deobfBehaviorEntry.getClassName()
+ ));
+ } else {
+ BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
+ Descriptor.toJvmName(enclosingMethodAttr.className()),
+ enclosingMethodAttr.methodName(),
+ enclosingMethodAttr.methodDescriptor()
+ );
+ BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry);
+ c.getClassFile().addAttribute(new EnclosingMethodAttribute(
+ constants,
+ deobfBehaviorEntry.getClassName(),
+ deobfBehaviorEntry.getName(),
+ deobfBehaviorEntry.getSignature().toString()
+ ));
+ }
+ }
+
+ // translate all the class names referenced in the code
+ // the above code only changed method/field/reference names and types, but not the rest of the class references
+ ClassRenamer.renameClasses(c, translator);
+
+ // translate the source file attribute too
+ ClassEntry deobfClassEntry = translator.translateEntry(classEntry);
+ if (deobfClassEntry != null) {
+ String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
+ c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
+ }
+ InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
+ if (attr != null)
+ InnerClassWriter.changeModifier(c, attr, translator);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java
new file mode 100644
index 00000000..0e359386
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+
+package cuchaz.enigma.bytecode.translators;
+
+import com.google.common.collect.Lists;
+import cuchaz.enigma.analysis.JarIndex;
+import cuchaz.enigma.bytecode.ClassRenamer;
+import cuchaz.enigma.mapping.*;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.bytecode.*;
+
+import java.util.Collection;
+import java.util.List;
+
+public class InnerClassWriter {
+
+ // FIXME: modifier is not applied to inner class
+ public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) {
+ ClassPool pool = c.getClassPool();
+ for (int i = 0; i < attr.tableLength(); i++) {
+
+ String innerName = attr.innerClass(i);
+ // get the inner class full name (which has already been translated)
+ ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName));
+ try {
+ CtClass innerClass = pool.get(innerName);
+ Mappings.EntryModifier modifier = translator.getModifier(classEntry);
+ if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
+ ClassRenamer.applyModifier(innerClass, modifier);
+ } catch (NotFoundException e) {
+ // This shouldn't be possible in theory
+ //e.printStackTrace();
+ }
+ }
+ }
+
+ public static void write(JarIndex index, CtClass c) {
+
+ // don't change anything if there's already an attribute there
+ InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
+ if (oldAttr != null) {
+ // bail!
+ return;
+ }
+
+ ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
+ List obfClassChain = index.getObfClassChain(obfClassEntry);
+
+ boolean isInnerClass = obfClassChain.size() > 1;
+ if (isInnerClass) {
+
+ // it's an inner class, rename it to the fully qualified name
+ c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
+
+ BehaviorEntry caller = index.getAnonymousClassCaller(obfClassEntry);
+ if (caller != null) {
+
+ // write the enclosing method attribute
+ if (caller.getName().equals("")) {
+ c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
+ } else {
+ c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
+ }
+ }
+ }
+
+ // does this class have any inner classes?
+ Collection obfInnerClassEntries = index.getInnerClasses(obfClassEntry);
+
+ if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
+
+ // create an inner class attribute
+ InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
+ c.getClassFile().addAttribute(attr);
+
+ // write the ancestry, but not the outermost class
+ for (int i = 1; i < obfClassChain.size(); i++) {
+ ClassEntry obfInnerClassEntry = obfClassChain.get(i);
+ writeInnerClass(index, attr, obfClassChain, obfInnerClassEntry);
+
+ // update references to use the fully qualified inner class name
+ c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
+ }
+
+ // write the inner classes
+ for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
+
+ // extend the class chain
+ List extendedObfClassChain = Lists.newArrayList(obfClassChain);
+ extendedObfClassChain.add(obfInnerClassEntry);
+
+ writeInnerClass(index, attr, extendedObfClassChain, obfInnerClassEntry);
+
+ // update references to use the fully qualified inner class name
+ c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
+ }
+ }
+ }
+
+ private static void writeInnerClass(JarIndex index, InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) {
+
+ // get the new inner class name
+ ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
+ ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
+
+ // here's what the JVM spec says about the InnerClasses attribute
+ // append(inner, parent, 0 if anonymous else simple name, flags);
+
+ // update the attribute with this inner class
+ ConstPool constPool = attr.getConstPool();
+ int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
+ int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
+ int innerClassNameIndex = 0;
+ int accessFlags = AccessFlag.PUBLIC;
+ // TODO: need to figure out if we can put static or not
+ if (!index.isAnonymousClass(obfClassEntry)) {
+ innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
+ }
+
+ attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
+
+ /* DEBUG
+ System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
+ obfClassEntry,
+ attr.innerClass(attr.tableLength() - 1),
+ attr.outerClass(attr.tableLength() - 1),
+ attr.innerName(attr.tableLength() - 1),
+ Constants.NonePackage + "/" + obfInnerClassName,
+ obfClassEntry.getName()
+ ));
+ */
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java
new file mode 100644
index 00000000..51b3d2df
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+
+package cuchaz.enigma.bytecode.translators;
+
+import cuchaz.enigma.mapping.*;
+import javassist.CtBehavior;
+import javassist.CtClass;
+import javassist.bytecode.*;
+
+public class LocalVariableTranslator {
+
+ public static void translate(Translator translator, CtClass c) {
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+
+ // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
+ CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
+ if (codeAttribute == null) {
+ continue;
+ }
+
+ BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
+ ConstPool constants = c.getClassFile().getConstPool();
+
+ LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
+ if (table != null) {
+ renameLVT(translator, behaviorEntry, constants, table, c);
+ }
+
+ LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
+ if (typeTable != null) {
+ renameLVTT(typeTable, table);
+ }
+ }
+ }
+
+ // DEBUG
+ @SuppressWarnings("unused")
+ private static void dumpTable(LocalVariableAttribute table) {
+ for (int i = 0; i < table.tableLength(); i++) {
+ System.out.println(String.format("\t%d (%d): %s %s",
+ i, table.index(i), table.variableName(i), table.descriptor(i)
+ ));
+ }
+ }
+
+ private static void renameLVT(Translator translator, BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table, CtClass ctClass) {
+
+ // skip empty tables
+ if (table.tableLength() <= 0) {
+ return;
+ }
+
+ // where do we start counting variables?
+ int starti = 0;
+ if (table.variableName(0).equals("this")) {
+ // skip the "this" variable
+ starti++;
+ }
+
+ // rename method arguments first
+ int numArgs = 0;
+ if (behaviorEntry.getSignature() != null) {
+ numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
+ boolean isNestedClassConstructor = false;
+
+ // If the behavior is a constructor and if it have more than one arg, it's probably from a nested!
+ if (behaviorEntry instanceof ConstructorEntry && behaviorEntry.getClassEntry() != null && behaviorEntry.getClassEntry().isInnerClass() && numArgs >= 1) {
+ // Get the first arg type
+ Type firstArg = behaviorEntry.getSignature().getArgumentTypes().get(0);
+
+ // If the arg is a class and if the class name match the outer class name of the constructor, it's definitely a constructor of a nested class
+ if (firstArg.isClass() && firstArg.getClassEntry().equals(behaviorEntry.getClassEntry().getOuterClassEntry())) {
+ isNestedClassConstructor = true;
+ numArgs--;
+ }
+ }
+
+ for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) {
+ int argi = i - starti;
+ if (ctClass.isEnum())
+ argi += 2;
+ String argName = translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
+ if (argName == null) {
+ int argIndex = isNestedClassConstructor ? argi + 1 : argi;
+ if (ctClass.isEnum())
+ argIndex -= 2;
+ Type argType = behaviorEntry.getSignature().getArgumentTypes().get(argIndex);
+ // Unfortunately each of these have different name getters, so they have different code paths
+ if (argType.isPrimitive()) {
+ Type.Primitive argCls = argType.getPrimitive();
+ argName = "a" + argCls.name() + (argIndex + 1);
+ } else if (argType.isArray()) {
+ // List types would require this whole block again, so just go with aListx
+ argName = "aList" + (argIndex + 1);
+ } else if (argType.isClass()) {
+ ClassEntry argClsTrans = translator.translateEntry(argType.getClassEntry());
+ argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argIndex + 1);
+ } else {
+ argName = "a" + (argIndex + 1);
+ }
+ }
+ renameVariable(table, i, constants.addUtf8Info(argName));
+ }
+ }
+
+ // then rename the rest of the args, if any
+ for (int i = starti + numArgs; i < table.tableLength(); i++) {
+ int firstIndex = Math.min(table.index(starti + numArgs), table.index(i));
+ renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
+ }
+ }
+
+ private static void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
+ // rename args to the same names as in the LVT
+ for (int i = 0; i < typeTable.tableLength(); i++) {
+ renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
+ }
+ }
+
+ private static void renameVariable(LocalVariableAttribute table, int i, int stringId) {
+ // based off of LocalVariableAttribute.nameIndex()
+ ByteArray.write16bit(stringId, table.get(), i * 10 + 6);
+ }
+
+ private static int getNameIndex(LocalVariableAttribute table, int index) {
+ for (int i = 0; i < table.tableLength(); i++) {
+ if (table.index(i) == index) {
+ return table.nameIndex(i);
+ }
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java
new file mode 100644
index 00000000..4e632b94
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+
+package cuchaz.enigma.bytecode.translators;
+
+import cuchaz.enigma.bytecode.MethodParametersAttribute;
+import cuchaz.enigma.mapping.*;
+import javassist.CtBehavior;
+import javassist.CtClass;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.LocalVariableAttribute;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MethodParameterTranslator {
+
+ public static void translate(Translator translator, CtClass c) {
+
+ // Procyon will read method arguments from the "MethodParameters" attribute, so write those
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+
+ // if there's a local variable table here, don't write a MethodParameters attribute
+ // let the local variable writer deal with it instead
+ // procyon starts doing really weird things if we give it both attributes
+ CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
+ if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
+ continue;
+ }
+
+ BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
+
+ // get the number of arguments
+ Signature signature = behaviorEntry.getSignature();
+ if (signature == null) {
+ // static initializers have no signatures, or arguments
+ continue;
+ }
+ int numParams = signature.getArgumentTypes().size();
+ if (numParams <= 0) {
+ continue;
+ }
+
+ // get the list of argument names
+ List names = new ArrayList<>(numParams);
+ for (int i = 0; i < numParams; i++) {
+ names.add(translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
+ }
+
+ // save the mappings to the class
+ MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java
deleted file mode 100644
index 4542fb33..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassForest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Jeff Martin.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Lesser General Public
- * License v3.0 which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Contributors:
- * Jeff Martin - initial API and implementation
- ******************************************************************************/
-
-package cuchaz.enigma.convert;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import cuchaz.enigma.mapping.ClassEntry;
-
-import java.util.Collection;
-
-public class ClassForest {
-
- private ClassIdentifier identifier;
- private Multimap forest;
-
- public ClassForest(ClassIdentifier identifier) {
- this.identifier = identifier;
- this.forest = HashMultimap.create();
- }
-
- public void addAll(Iterable entries) {
- for (ClassEntry entry : entries) {
- add(entry);
- }
- }
-
- public void add(ClassEntry entry) {
- try {
- this.forest.put(this.identifier.identify(entry), entry);
- } catch (ClassNotFoundException ex) {
- throw new Error("Unable to find class " + entry.getName());
- }
- }
-
- public Collection identities() {
- return this.forest.keySet();
- }
-
- public Collection classes() {
- return this.forest.values();
- }
-
- public Collection getClasses(ClassIdentity identity) {
- return this.forest.get(identity);
- }
-
- public boolean containsIdentity(ClassIdentity identity) {
- return this.forest.containsKey(identity);
- }
-}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
deleted file mode 100644
index 0a72073c..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Jeff Martin.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Lesser General Public
- * License v3.0 which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Contributors:
- * Jeff Martin - initial API and implementation
- ******************************************************************************/
-
-package cuchaz.enigma.convert;
-
-import com.google.common.collect.Maps;
-import cuchaz.enigma.TranslatingTypeLoader;
-import cuchaz.enigma.analysis.JarIndex;
-import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
-import cuchaz.enigma.mapping.ClassEntry;
-import cuchaz.enigma.mapping.Translator;
-import javassist.CtClass;
-
-import java.util.Map;
-import java.util.jar.JarFile;
-
-public class ClassIdentifier {
-
- private JarIndex index;
- private SidedClassNamer namer;
- private boolean useReferences;
- private TranslatingTypeLoader loader;
- private Map cache;
-
- public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
- this.index = index;
- this.namer = namer;
- this.useReferences = useReferences;
- this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator());
- this.cache = Maps.newHashMap();
- }
-
- public ClassIdentity identify(ClassEntry classEntry)
- throws ClassNotFoundException {
- ClassIdentity identity = this.cache.get(classEntry);
- if (identity == null) {
- CtClass c = this.loader.loadClass(classEntry.getName());
- if (c == null) {
- throw new ClassNotFoundException(classEntry.getName());
- }
- identity = new ClassIdentity(c, this.namer, this.index, this.useReferences);
- this.cache.put(classEntry, identity);
- }
- return identity;
- }
-}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
deleted file mode 100644
index a395b755..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Jeff Martin.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Lesser General Public
- * License v3.0 which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/lgpl.html
- *