From d1988a80b71c93ca41e5c95213925f2cd21287cc Mon Sep 17 00:00:00 2001 From: Thog Date: Fri, 19 Aug 2016 22:31:38 +0200 Subject: Starting working on a JavaDoc system using fake annotations TODO: - File format - Add @return support - Clean up --- src/main/java/cuchaz/enigma/Deobfuscator.java | 78 ++++- .../java/cuchaz/enigma/TranslatingTypeLoader.java | 26 +- .../enigma/analysis/JavadocIndexVisitor.java | 375 +++++++++++++++++++++ .../java/cuchaz/enigma/analysis/SourceIndex.java | 37 +- src/main/java/cuchaz/enigma/analysis/Token.java | 34 +- .../cuchaz/enigma/bytecode/ClassTranslator.java | 24 +- .../cuchaz/enigma/convert/ClassIdentifier.java | 2 +- .../enigma/mapping/javadoc/BaseJavaDocEntry.java | 50 +++ .../enigma/mapping/javadoc/JavaDocClass.java | 29 ++ .../enigma/mapping/javadoc/JavaDocField.java | 32 ++ .../enigma/mapping/javadoc/JavaDocMapping.java | 263 +++++++++++++++ .../enigma/mapping/javadoc/JavaDocMethod.java | 49 +++ 12 files changed, 954 insertions(+), 45 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/analysis/JavadocIndexVisitor.java create mode 100644 src/main/java/cuchaz/enigma/mapping/javadoc/BaseJavaDocEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocClass.java create mode 100644 src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocField.java create mode 100644 src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMapping.java create mode 100644 src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMethod.java (limited to 'src/main/java/cuchaz') diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java index e9a998da..82d0775b 100644 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ b/src/main/java/cuchaz/enigma/Deobfuscator.java @@ -21,13 +21,12 @@ import com.strobel.decompiler.DecompilerContext; import com.strobel.decompiler.DecompilerSettings; import com.strobel.decompiler.PlainTextOutput; import com.strobel.decompiler.languages.java.JavaOutputVisitor; -import com.strobel.decompiler.languages.java.ast.AstBuilder; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; +import com.strobel.decompiler.languages.java.ast.*; import cuchaz.enigma.analysis.*; import cuchaz.enigma.bytecode.ClassProtectifier; import cuchaz.enigma.bytecode.ClassPublifier; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.javadoc.JavaDocMapping; import cuchaz.enigma.utils.Utils; import javassist.CtClass; import javassist.bytecode.Descriptor; @@ -40,6 +39,8 @@ import java.util.jar.JarOutputStream; public class Deobfuscator { + private final JavaDocMapping docMapping; + public interface ProgressListener { void init(int totalWork, String title); @@ -75,6 +76,8 @@ public class Deobfuscator { this.renamer = new MappingsRenamer(this.jarIndex, null); // init mappings setMappings(new Mappings()); + + this.docMapping = new JavaDocMapping(); } public JarFile getJar() { @@ -93,6 +96,11 @@ public class Deobfuscator { return this.mappings; } + public JavaDocMapping getDocMapping() + { + return docMapping; + } + public void setMappings(Mappings val) { setMappings(val, true); } @@ -176,7 +184,8 @@ public class Deobfuscator { this.jar, this.jarIndex, getTranslator(TranslationDirection.Obfuscating), - getTranslator(TranslationDirection.Deobfuscating) + getTranslator(TranslationDirection.Deobfuscating), + this.docMapping ); this.settings.setTypeLoader(loader); @@ -204,6 +213,14 @@ public class Deobfuscator { } public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) { + List annotations = new ArrayList<>(); + + sourceTree.acceptVisitor(new JavadocIndexVisitor(), annotations); + + // DEBUG + // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); + + annotations.forEach(AstNode::remove); // build the source index SourceIndex index; @@ -214,9 +231,6 @@ public class Deobfuscator { } sourceTree.acceptVisitor(new SourceIndexVisitor(), index); - // DEBUG - // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); - // resolve all the classes in the source references for (Token token : index.referenceTokens()) { EntryReference deobfReference = index.getDeobfReference(token); @@ -234,11 +248,52 @@ public class Deobfuscator { deobfReference.entry = deobfuscateEntry(obfEntry); index.replaceDeobfReference(token, deobfReference); } - // DEBUG // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); } + source = index.getSource(); + + for (Annotation annotation : annotations) + { + // Get the decompiler before calling getText (after that we can get it...) + int pos = annotation.getRegion().getBeginLine(); + + // Get the exact annotation + String original = annotation.getText(); + + int originalAnnotationPosInSource = source.indexOf(original) - 1; + int prevLineBreak; + for (prevLineBreak = originalAnnotationPosInSource; prevLineBreak < source.length(); prevLineBreak--) + { + if (source.charAt(prevLineBreak) != ' ') + break; + } + + // Format + String theComment = docMapping.convertAnnotationToJavaDoc(annotation, originalAnnotationPosInSource - prevLineBreak); + + // Replace the annotation by the JavaDoc + source = source.replace(original, theComment); + + // Calculate the offset of the changes for tokens + int offset = theComment.length() - original.length(); + + // Propagate positions changes + for (Token token : index.referenceTokens()) + if (pos <= token.getRegion().getBeginLine()) + token.offsetColum += offset; + } + + // Set the source because of JavaDoc changes + index.setSource(source); + + // Reccompute the start and end using the offset + for (Token token : index.referenceTokens()) + token.computePos(); + + annotations.clear(); + return index; } @@ -387,8 +442,7 @@ public class Deobfuscator { this.jar, this.jarIndex, getTranslator(TranslationDirection.Obfuscating), - getTranslator(TranslationDirection.Deobfuscating) - ); + getTranslator(TranslationDirection.Deobfuscating), this.docMapping); transformJar(out, progress, loader::transformClass); } @@ -422,7 +476,7 @@ public class Deobfuscator { outJar.write(c.toBytecode()); outJar.closeEntry(); } catch (Throwable t) { - throw new Error("Unable to transform class " + c.getName(), t); + throw new Error("Unable to tryToAddJavaDoc class " + c.getName(), t); } } if (progress != null) { @@ -508,7 +562,7 @@ public class Deobfuscator { } public boolean isRenameable(EntryReference obfReference, boolean activeHack) { - return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry(), activeHack); + return obfReference != null && obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry(), activeHack); } public boolean isRenameable(EntryReference obfReference) { diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java index e4c162da..6ddfa634 100644 --- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java @@ -12,10 +12,17 @@ package cuchaz.enigma; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - import com.strobel.assembler.metadata.Buffer; 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.*; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.javadoc.JavaDocMapping; +import javassist.*; +import javassist.bytecode.Descriptor; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -25,14 +32,6 @@ import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import cuchaz.enigma.analysis.BridgeMarker; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.bytecode.*; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Translator; -import javassist.*; -import javassist.bytecode.Descriptor; - public class TranslatingTypeLoader implements ITypeLoader { private JarFile jar; @@ -41,14 +40,18 @@ public class TranslatingTypeLoader implements ITypeLoader { private Translator deobfuscatingTranslator; private Map cache; private ClasspathTypeLoader defaultTypeLoader; + private JavaDocMapping docMapping; - public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { + public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, + Translator deobfuscatingTranslator, JavaDocMapping docMapping) { this.jar = jar; this.jarIndex = jarIndex; this.obfuscatingTranslator = obfuscatingTranslator; this.deobfuscatingTranslator = deobfuscatingTranslator; this.cache = Maps.newHashMap(); this.defaultTypeLoader = new ClasspathTypeLoader(); + this.docMapping = docMapping; + this.docMapping.cleanBehaviors(); } public void clearCache() { @@ -214,8 +217,7 @@ public class TranslatingTypeLoader implements ITypeLoader { new BridgeMarker(this.jarIndex).markBridges(c); new MethodParameterWriter(this.deobfuscatingTranslator).writeMethodArguments(c); new LocalVariableRenamer(this.deobfuscatingTranslator).rename(c); - new ClassTranslator(this.deobfuscatingTranslator).translate(c); - + new ClassTranslator(this.deobfuscatingTranslator, this.docMapping).translate(c); return c; } diff --git a/src/main/java/cuchaz/enigma/analysis/JavadocIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/JavadocIndexVisitor.java new file mode 100644 index 00000000..2845b497 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/JavadocIndexVisitor.java @@ -0,0 +1,375 @@ +package cuchaz.enigma.analysis; + +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.patterns.Pattern; + +import java.util.List; + +/** + * Permit to data all javadoc markers + * Created by Thog + * 19/08/2016 + */ +public class JavadocIndexVisitor implements IAstVisitor, Void> +{ + + protected Void recurse(AstNode node, List data) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, data); + } + return null; + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, List data) { + return recurse(node, data); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, List data) { + return recurse(node, data); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, List data) { + return recurse(node, data); + } + + @Override public Void visitTypeDeclaration(TypeDeclaration node, List data) + { + return recurse(node, data); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, List data) { + return recurse(node, data); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, List data) { + return recurse(node, data); + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitSimpleType(SimpleType node, List data) { + return recurse(node, data); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitComment(Comment node, List data) { + return recurse(node, data); + } + + @Override + public Void visitPatternPlaceholder(AstNode node, Pattern pattern, List data) { + return recurse(node, data); + } + + @Override + public Void visitTypeReference(TypeReferenceExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitJavaTokenNode(JavaTokenNode node, List data) { + return recurse(node, data); + } + + @Override + public Void visitIdentifier(Identifier node, List data) { + return recurse(node, data); + } + + @Override + public Void visitNullReferenceExpression(NullReferenceExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitThisReferenceExpression(ThisReferenceExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitSuperReferenceExpression(SuperReferenceExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitClassOfExpression(ClassOfExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitBlockStatement(BlockStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitExpressionStatement(ExpressionStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitBreakStatement(BreakStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitContinueStatement(ContinueStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitDoWhileStatement(DoWhileStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitEmptyStatement(EmptyStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitIfElseStatement(IfElseStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitLabelStatement(LabelStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitLabeledStatement(LabeledStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitReturnStatement(ReturnStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitSwitchStatement(SwitchStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitSwitchSection(SwitchSection node, List data) { + return recurse(node, data); + } + + @Override + public Void visitCaseLabel(CaseLabel node, List data) { + return recurse(node, data); + } + + @Override + public Void visitThrowStatement(ThrowStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitCatchClause(CatchClause node, List data) { + return recurse(node, data); + } + + @Override + public Void visitAnnotation(Annotation node, List data) { + String annotationType = node.getFirstChild().toString(); + if (annotationType.startsWith("FieldDoc") || annotationType.startsWith("MethodDoc") || annotationType.startsWith("ClassDoc")) + data.add(node); + return recurse(node, data); + } + + @Override + public Void visitNewLine(NewLineNode node, List data) { + return recurse(node, data); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitVariableInitializer(VariableInitializer node, List data) { + return recurse(node, data); + } + + @Override + public Void visitText(TextNode node, List data) { + return recurse(node, data); + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, List data) { + return recurse(node, data); + } + + @Override + public Void visitInitializerBlock(InstanceInitializer node, List data) { + return recurse(node, data); + } + + @Override + public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, List data) { + return recurse(node, data); + } + + @Override + public Void visitCompilationUnit(CompilationUnit node, List data) { + return recurse(node, data); + } + + @Override + public Void visitPackageDeclaration(PackageDeclaration node, List data) { + return recurse(node, data); + } + + @Override + public Void visitArraySpecifier(ArraySpecifier node, List data) { + return recurse(node, data); + } + + @Override + public Void visitComposedType(ComposedType node, List data) { + return recurse(node, data); + } + + @Override + public Void visitWhileStatement(WhileStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitPrimitiveExpression(PrimitiveExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitCastExpression(CastExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitInstanceOfExpression(InstanceOfExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitIndexerExpression(IndexerExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitConditionalExpression(ConditionalExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitArrayInitializerExpression(ArrayInitializerExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitArrayCreationExpression(ArrayCreationExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitAssignmentExpression(AssignmentExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitForStatement(ForStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitTryCatchStatement(TryCatchStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitGotoStatement(GotoStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitParenthesizedExpression(ParenthesizedExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitSynchronizedStatement(SynchronizedStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitWildcardType(WildcardType node, List data) { + return recurse(node, data); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitAssertStatement(AssertStatement node, List data) { + return recurse(node, data); + } + + @Override + public Void visitLambdaExpression(LambdaExpression node, List data) { + return recurse(node, data); + } + + @Override + public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, List data) { + return recurse(node, data); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java index 719930e9..d7bbafa9 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java @@ -14,18 +14,16 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; - import com.strobel.decompiler.languages.Region; import com.strobel.decompiler.languages.java.ast.AstNode; import com.strobel.decompiler.languages.java.ast.Identifier; +import cuchaz.enigma.mapping.Entry; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.TreeMap; -import cuchaz.enigma.mapping.Entry; - public class SourceIndex { private String source; @@ -56,10 +54,27 @@ public class SourceIndex { } } + public void setSource(String source) + { + this.source = source; + } + public String getSource() { return this.source; } + public int getLineNumber(int offset) + { + int i = 0; + for (Integer lineOffset : lineOffsets) + { + if (lineOffset <= offset) + return i; + i++; + } + return i; + } + public Token getToken(AstNode node) { // get the text of the node @@ -75,7 +90,7 @@ public class SourceIndex { System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region)); return null; } - Token token = new Token(toPos(region.getBeginLine(), region.getBeginColumn()), toPos(region.getEndLine(), region.getEndColumn()), this.source); + Token token = new Token(this, region, this.source); if (token.start == 0) { // DEBUG System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region)); @@ -157,24 +172,12 @@ public class SourceIndex { return this.declarationToToken.get(deobfEntry); } - public int getLineNumber(int pos) { - // line number is 1-based - int line = 0; - for (Integer offset : this.lineOffsets) { - if (offset > pos) { - break; - } - line++; - } - return line; - } - public int getColumnNumber(int pos) { // column number is 1-based return pos - this.lineOffsets.get(getLineNumber(pos) - 1) + 1; } - private int toPos(int line, int col) { + public int toPos(int line, int col) { // line and col are 1-based return this.lineOffsets.get(line - 1) + col - 1; } diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java index 419842af..0f730cb4 100644 --- a/src/main/java/cuchaz/enigma/analysis/Token.java +++ b/src/main/java/cuchaz/enigma/analysis/Token.java @@ -10,18 +10,48 @@ ******************************************************************************/ package cuchaz.enigma.analysis; +import com.strobel.decompiler.languages.Region; + public class Token implements Comparable { public int start; public int end; + public int offsetLine; + public int offsetColum; public String text; + private Region region; + private SourceIndex index; + + public Token(SourceIndex index, Region region, String source) + { + this(index.toPos(region.getBeginLine(), region.getBeginColumn()), index + .toPos(region.getEndLine(), region.getEndColumn()), source); + this.region = region; + this.index = index; + //toPos(region.getBeginLine(), region.getBeginColumn()), toPos(region.getEndLine(), region.getEndColumn()) + } + + public void computePos() + { + if (region == null) + return; + this.start = index.toPos(region.getBeginLine() + offsetLine, region.getBeginColumn() + offsetColum); + this.end = index.toPos(region.getEndLine() + offsetLine, region.getEndColumn() + offsetColum); + this.offsetLine = 0; + this.offsetColum = 0; + this.region = null; + } + + public Region getRegion() + { + return region; + } public Token(int start, int end, String source) { this.start = start; this.end = end; - if (source != null) { + if (source != null) this.text = source.substring(start, end); - } } public boolean contains(int pos) { diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java index 6c05b838..7f8779fc 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java @@ -11,6 +11,7 @@ package cuchaz.enigma.bytecode; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.javadoc.JavaDocMapping; import javassist.CtBehavior; import javassist.CtClass; import javassist.CtField; @@ -22,10 +23,13 @@ import javassist.bytecode.SourceFileAttribute; public class ClassTranslator { + private JavaDocMapping docMapping; private Translator translator; - public ClassTranslator(Translator translator) { + public ClassTranslator(Translator translator, JavaDocMapping docMapping) { this.translator = translator; + this.docMapping = docMapping == null ? new JavaDocMapping() : docMapping; + this.docMapping.setTranslator(translator); } public void translate(CtClass c) { @@ -75,14 +79,22 @@ public class ClassTranslator { ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + // Try to add javadoc to constructor + docMapping.tryToAddJavaDoc(c, classEntry); // translate all the fields for (CtField field : c.getDeclaredFields()) { // translate the name FieldEntry entry = EntryFactory.getFieldEntry(field); String translatedName = this.translator.translate(entry); + + // Try to add javadoc as ob + docMapping.tryToAddJavaDoc(field, entry); if (translatedName != null) { field.setName(translatedName); + + // Try to add javadoc as ob + docMapping.tryToAddJavaDoc(field, entry); } // translate the type @@ -98,12 +110,22 @@ public class ClassTranslator { if (behavior instanceof CtMethod) { CtMethod method = (CtMethod) behavior; + // Try to add javadoc as ob + docMapping.tryToAddJavaDoc(method, entry); // translate the name String translatedName = this.translator.translate(entry); if (translatedName != null) { + MethodEntry deObfuscatedMethod = new MethodEntry(entry.getClassEntry(), translatedName, entry.getSignature()); method.setName(translatedName); + // Try to add javadoc as ob + docMapping.tryToAddJavaDoc(method, entry, deObfuscatedMethod); } } + else + { + // Try to add javadoc to constructor + docMapping.tryToAddJavaDoc(behavior, entry); + } if (entry.getSignature() != null) { // translate the signature diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java index 557e6083..ce1e1dc3 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java +++ b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java @@ -35,7 +35,7 @@ public class ClassIdentifier { this.index = index; this.namer = namer; this.useReferences = useReferences; - this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator()); + this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator(), null); this.cache = Maps.newHashMap(); } diff --git a/src/main/java/cuchaz/enigma/mapping/javadoc/BaseJavaDocEntry.java b/src/main/java/cuchaz/enigma/mapping/javadoc/BaseJavaDocEntry.java new file mode 100644 index 00000000..fa373a57 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/javadoc/BaseJavaDocEntry.java @@ -0,0 +1,50 @@ +package cuchaz.enigma.mapping.javadoc; + +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.Type; + +/** + * Base of any JavaDoc entry + * Created by Thog + * 19/08/2016 + */ +public abstract class BaseJavaDocEntry +{ + private String identifier; + private String comment; + + public BaseJavaDocEntry(String identifier, String comment) + { + this.identifier = identifier; + this.comment = comment; + } + + public void setIdentifier(String identifier) + { + this.identifier = identifier; + } + + public String getIdentifier() + { + return identifier; + } + + public void setComment(String comment) + { + this.comment = comment; + } + + public String getComment() + { + return comment; + } + + public Type getType() + { + return new Type(getIdentifier()); + } + + public abstract JavaDocClass getClassDocEntry(); + + public abstract Entry getEntry(); +} diff --git a/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocClass.java b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocClass.java new file mode 100644 index 00000000..9bc7e734 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocClass.java @@ -0,0 +1,29 @@ +package cuchaz.enigma.mapping.javadoc; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; + +/** + * JavaDoc representation of a class + * Created by Thog + * 19/08/2016 + */ +public class JavaDocClass extends BaseJavaDocEntry +{ + + public JavaDocClass(String identifier, String comment) + { + super(identifier, comment); + } + + @Override public JavaDocClass getClassDocEntry() + { + return this; + } + + @Override + public Entry getEntry() + { + return new ClassEntry(getIdentifier()); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocField.java b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocField.java new file mode 100644 index 00000000..a2a67f45 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocField.java @@ -0,0 +1,32 @@ +package cuchaz.enigma.mapping.javadoc; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; + +/** + * JavaDoc of a Field + * Created by Thog + * 19/08/2016 + */ +public class JavaDocField extends BaseJavaDocEntry +{ + private final JavaDocClass classDocEntry; + private final String name; + public JavaDocField(JavaDocClass classDocEntry, String name, String identifier, String comment) + { + super(identifier, comment); + this.name = name; + this.classDocEntry = classDocEntry; + } + + @Override public JavaDocClass getClassDocEntry() + { + return classDocEntry; + } + + @Override public Entry getEntry() + { + return new FieldEntry((ClassEntry) classDocEntry.getEntry(), name, getType()); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMapping.java b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMapping.java new file mode 100644 index 00000000..98996fdb --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMapping.java @@ -0,0 +1,263 @@ +package cuchaz.enigma.mapping.javadoc; + +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.mapping.*; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.ConstPool; +import javassist.bytecode.annotation.*; +import javassist.bytecode.annotation.Annotation; + +import java.io.File; +import java.util.*; + +/** + * Representation of a javadoc mapping + * Created by Thog + * 19/08/2016 + */ +public class JavaDocMapping +{ + private final Map javaDocClassByID; + private final Map javaDocFieldByID; + private final Map javaDocMethodByID; + private final List behaviorByID; + private Translator translator; + + public JavaDocMapping() + { + this.javaDocClassByID = new HashMap<>(); + this.javaDocFieldByID = new HashMap<>(); + this.javaDocMethodByID = new HashMap<>(); + this.behaviorByID = new ArrayList<>(); + + // TODO: File format + addField("a", "none/akw", "Lnone/kp;", "Hello from Enigma"); + addMethod("", "none/akw", "(Lnone/ayo;)V", "You know what? I love constructors!", "The material of the block!"); + addClass("none/akw", "HEY I'M A BLOCK YOU KNOW THAT?!"); + } + + private void addClass(String className, String comment) + { + this.javaDocClassByID.put(new ClassEntry(className), new JavaDocClass(className, comment)); + } + + public void addField(String fieldName, String className, String type, String comment) + { + this.javaDocFieldByID.put(new FieldEntry(new ClassEntry(className), fieldName, new Type(type)), new JavaDocField(new JavaDocClass(className, "LOL"), fieldName, type, comment)); + } + + public void addMethod(String methodName, String className, String signature, String comment, String... argComment) + { + BehaviorEntry behaviorEntry; + if (methodName.equals("")) + behaviorEntry = new ConstructorEntry(new ClassEntry(className), new Signature(signature)); + else + behaviorEntry = new MethodEntry(new ClassEntry(className), methodName, new Signature(signature)); + + this.javaDocMethodByID.put(behaviorEntry, new JavaDocMethod(new JavaDocClass(className, "LOL"), methodName, signature, comment, + Arrays.asList(argComment))); + } + + public void cleanBehaviors() + { + behaviorByID.clear(); + } + + public void loadMapping(File mappingDirectory) + { + // NOP + } + + public void closeMapping() + { + behaviorByID.clear(); + javaDocClassByID.clear(); + javaDocFieldByID.clear(); + javaDocMethodByID.clear(); + } + + public CtClass tryToAddJavaDoc(CtClass ctClass, ClassEntry entry) + { + if (javaDocClassByID.containsKey(entry)) + { + JavaDocClass javaDocClass = javaDocClassByID.get(entry); + + // Get the constant pool + ConstPool constpool = ctClass.getClassFile().getConstPool(); + + // Prepare the annotation + AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); + Annotation annot = new Annotation("enigma.remapper.ClassDoc", constpool); + annot.addMemberValue("comment", new StringMemberValue(javaDocClass.getComment(), constpool)); + attr.addAnnotation(annot); + ctClass.getClassFile().addAttribute(attr); + } + return ctClass; + } + + public CtBehavior tryToAddJavaDoc(CtBehavior ctBehavior, BehaviorEntry obEntry, BehaviorEntry deobEntry) + { + if (javaDocMethodByID.containsKey(obEntry)) + { + int id = getEntryID(obEntry); + if (id != -1) + behaviorByID.set(id, deobEntry); + } + return tryToAddJavaDoc(ctBehavior, deobEntry); + } + + public CtBehavior tryToAddJavaDoc(CtBehavior ctBehavior, BehaviorEntry entry) + { + if (javaDocMethodByID.containsKey(entry)) + { + JavaDocMethod javaDocMethod = javaDocMethodByID.get(entry); + + // Get the constant pool + ConstPool constpool = ctBehavior.getDeclaringClass().getClassFile().getConstPool(); + + // Prepare the annotation + AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); + Annotation annot = new Annotation("enigma.remapper.MethodDoc", constpool); + annot.addMemberValue("comment", new StringMemberValue(javaDocMethod.getComment(), constpool)); + behaviorByID.add(entry); + annot.addMemberValue("behavior", new IntegerMemberValue(constpool, behaviorByID.size() - 1)); + annot.addMemberValue("args", translateStringArray(constpool, javaDocMethod.getArgsComments())); + attr.addAnnotation(annot); + ctBehavior.getMethodInfo().addAttribute(attr); + } + return ctBehavior; + } + + public CtField tryToAddJavaDoc(CtField ctField, FieldEntry entry) + { + if (javaDocFieldByID.containsKey(entry)) + { + JavaDocField javaDocField = javaDocFieldByID.get(entry); + + // Get the constant pool + ConstPool constpool = ctField.getDeclaringClass().getClassFile().getConstPool(); + + // Prepare the annotation + AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); + Annotation annot = new Annotation("enigma.remapper.FieldDoc", constpool); + annot.addMemberValue("comment", new StringMemberValue(javaDocField.getComment(), constpool)); + attr.addAnnotation(annot); + ctField.getFieldInfo().addAttribute(attr); + } + return ctField; + } + + private MemberValue[] toMemberValue(ConstPool pool, String[] args) + { + MemberValue[] result = new MemberValue[args.length]; + for (int i = 0; i < result.length; i++) + result[i] = new StringMemberValue(args[i], pool); + + return result; + } + + private ArrayMemberValue translateStringArray(ConstPool pool, String[] args) + { + ArrayMemberValue res = new ArrayMemberValue(new StringMemberValue(pool), pool); + res.setValue(toMemberValue(pool, args)); + return res; + } + + public BehaviorEntry getEntry(int id) + { + if (id == -1) + return null; + return this.behaviorByID.get(id); + } + + public int getEntryID(BehaviorEntry taget) + { + for (int i = 0; i < behaviorByID.size(); i++) + if (behaviorByID.get(i).equals(taget)) + return i; + return -1; + } + + /** + * + * @param annotation + * @param spacesCount + * @return + */ + public String convertAnnotationToJavaDoc(com.strobel.decompiler.languages.java.ast.Annotation annotation, int spacesCount) + { + int behaviorID = -1; + StringBuilder builder = new StringBuilder(); + String spaces = buildLineSpace(spacesCount); + builder.append("/**\n"); + AstNodeCollection annotationArgs = annotation.getArguments(); + for (Expression expression : annotationArgs) + { + String id = expression.getFirstChild().toString(); + for (AstNode child : expression.getChildren()) + { + if (child instanceof PrimitiveExpression) + { + PrimitiveExpression data = (PrimitiveExpression) child; + if (id.equals("comment")) + { + builder.append(spaces); + builder.append(" * "); + builder.append(data.getValue()); + builder.append("\n"); + } + else if (id.equals("behavior")) + behaviorID = (Integer) data.getValue(); + } + else if (child instanceof ArrayInitializerExpression) + { + ArrayInitializerExpression data = (ArrayInitializerExpression) child; + if (id.equals("args")) + { + BehaviorEntry entry = getEntry(behaviorID); + if (entry == null) + System.err.println("(SEVERE): CANNOT FIND BEHAVIOR ENTRY FOR ID " + behaviorID); + + int i = 0; + for (Expression expArg : data.getElements()) + { + if (expArg instanceof PrimitiveExpression) + { + PrimitiveExpression primitiveExpression = (PrimitiveExpression) expArg; + String argName = entry == null ? null : this.translator.translate(new ArgumentEntry(entry, i, "")); + if (argName == null) + argName = "a" + (i + 1); + builder.append(spaces); + builder.append(" * @param "); + builder.append(argName); + builder.append(" "); + builder.append(primitiveExpression.getValue()); + builder.append("\n"); + } + i++; + } + } + } + } + } + builder.append(spaces); + builder.append(" */"); + return builder.toString(); + } + + private String buildLineSpace(int spacesCount) + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i< spacesCount; i++) + builder.append(" "); + return builder.toString(); + } + + public void setTranslator(Translator translator) + { + this.translator = translator; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMethod.java b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMethod.java new file mode 100644 index 00000000..1508aede --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/javadoc/JavaDocMethod.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.mapping.javadoc; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; + +import java.util.List; + +/** + * Javadoc of a Method + * TODO: @return + * Created by Thog + * 19/08/2016 + */ +public class JavaDocMethod extends BaseJavaDocEntry +{ + private final JavaDocClass classDocEntry; + private final String name; + private final List argsComments; + + public JavaDocMethod(JavaDocClass classDocEntry, String name, String identifier, String comment, List argsComments) + { + super(identifier, comment); + this.name = name; + this.classDocEntry = classDocEntry; + this.argsComments = argsComments; + } + + @Override public JavaDocClass getClassDocEntry() + { + return classDocEntry; + } + + @Override public Entry getEntry() + { + return new MethodEntry((ClassEntry) classDocEntry.getEntry(), name, new Signature(getIdentifier())); + } + + public String[] getArgsComments() + { + return argsComments.toArray(new String[argsComments.size()]); + } + + public String getArgsComment(int id) + { + return argsComments.size() > id ? argsComments.get(id) : null; + } +} -- cgit v1.2.3