From 21c6f1ef47418262b2f8311248173a7d0b77e34c Mon Sep 17 00:00:00 2001 From: GraxCode Date: Sun, 13 Mar 2022 14:25:15 +0100 Subject: [PATCH 1/6] print line numbers --- .../decompilers/bytecode/InstructionPrinter.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java index 737fdd4b..7fbc44b8 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java @@ -117,7 +117,7 @@ public class InstructionPrinter { info.add("}"); return info; } - + public String printInstruction(AbstractInsnNode ain) { String line = ""; @@ -136,15 +136,15 @@ public class InstructionPrinter { } else if (ain instanceof JumpInsnNode) { line = printJumpInsnNode((JumpInsnNode) ain); } else if (ain instanceof LineNumberNode) { - line = printLineNumberNode(); + line = printLineNumberNode((LineNumberNode) ain); } else if (ain instanceof LabelNode) { if (firstLabel && BytecodeViewer.viewer.appendBracketsToLabels .isSelected()) info.add("}"); - + line = printLabelnode((LabelNode) ain); - + if (BytecodeViewer.viewer.appendBracketsToLabels.isSelected()) { if (!firstLabel) firstLabel = true; @@ -168,7 +168,7 @@ public class InstructionPrinter { line += "UNADDED OPCODE: " + nameOpcode(ain.getOpcode()) + " " + ain; } - + return line; } @@ -245,8 +245,8 @@ public class InstructionPrinter { + resolveLabel(jin.label); } - protected String printLineNumberNode() { - return ""; + protected String printLineNumberNode(LineNumberNode lnn) { + return "// line " + lnn.line; } protected String printLabelnode(LabelNode label) { From d15aa0a594f0a254278347ee23190d3195795c97 Mon Sep 17 00:00:00 2001 From: GraxCode Date: Sun, 13 Mar 2022 15:30:13 +0100 Subject: [PATCH 2/6] Various search additions / fixes. --- .../gui/components/SearchableJTextArea.java | 2 +- .../components/SearchableRSyntaxTextArea.java | 2 +- .../gui/resourcelist/ResourceListPane.java | 13 +- .../gui/resourcelist/SearchKeyAdapter.java | 219 ++++++++---------- .../gui/resourcesearch/SearchType.java | 6 +- .../impl/MemberWithAnnotationSearch.java | 83 +++++++ .../translation/TranslatedComponents.java | 5 +- 7 files changed, 201 insertions(+), 129 deletions(-) create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/searching/impl/MemberWithAnnotationSearch.java diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableJTextArea.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableJTextArea.java index e26d042b..3acce26e 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableJTextArea.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableJTextArea.java @@ -50,7 +50,7 @@ public class SearchableJTextArea extends JTextArea private final JScrollPane scrollPane = new JScrollPane(); private final JPanel searchPanel = new JPanel(new BorderLayout()); private final JTextField searchInput = new JTextField(); - private final JCheckBox caseSensitiveSearch = new TranslatedJCheckBox("Exact", TranslatedComponents.EXACT); + private final JCheckBox caseSensitiveSearch = new TranslatedJCheckBox("Match case", TranslatedComponents.MATCH_CASE); public SearchableJTextArea() { diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableRSyntaxTextArea.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableRSyntaxTextArea.java index ae88d89b..5c47163b 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableRSyntaxTextArea.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/SearchableRSyntaxTextArea.java @@ -55,7 +55,7 @@ public class SearchableRSyntaxTextArea extends RSyntaxTextArea private final RTextScrollPane scrollPane = new RTextScrollPane(this); private final JPanel searchPanel = new JPanel(new BorderLayout()); private final JTextField searchInput = new JTextField(); - private final JCheckBox caseSensitiveSearch = new TranslatedJCheckBox("Exact", TranslatedComponents.EXACT); + private final JCheckBox caseSensitiveSearch = new TranslatedJCheckBox("Match case", TranslatedComponents.MATCH_CASE); private final JLabel titleHeader = new JLabel(""); private final Color darkScrollBackground = new Color(0x3c3f41); private final Color darkScrollForeground = new Color(0x575859); diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java index 9e5c02a6..8b56b4f3 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java @@ -1,7 +1,6 @@ package the.bytecode.club.bytecodeviewer.gui.resourcelist; -import java.awt.BorderLayout; -import java.awt.Color; +import java.awt.*; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; @@ -71,7 +70,8 @@ import static the.bytecode.club.bytecodeviewer.Constants.tempDirectory; public class ResourceListPane extends TranslatedVisibleComponent implements FileDrop.Listener { public final JPopupMenu rightClickMenu = new JPopupMenu(); - public final JCheckBox exact = new TranslatedJCheckBox("Exact", TranslatedComponents.EXACT); + public final JCheckBox exact = new TranslatedJCheckBox("Exact path", TranslatedComponents.EXACT_PATH); + public final JCheckBox caseSensitive = new TranslatedJCheckBox("Match case", TranslatedComponents.MATCH_CASE); public final JButton open = new JButton("+"); public final JButton close = new JButton("-"); public final ResourceTreeNode treeRoot = new ResourceTreeNode("Loaded Files:"); @@ -118,7 +118,12 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File quickSearchPanel.setLayout(new BorderLayout()); quickSearchPanel.add(quickSearch, BorderLayout.NORTH); - exactPanel.add(exact, BorderLayout.WEST); + + JPanel btns = new JPanel(new FlowLayout()); + btns.add(exact); + btns.add(caseSensitive); + exactPanel.add(btns, BorderLayout.WEST); + buttonPanel.add(open, BorderLayout.EAST); buttonPanel.add(close, BorderLayout.WEST); exactPanel.add(buttonPanel, BorderLayout.EAST); diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/SearchKeyAdapter.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/SearchKeyAdapter.java index 114261b2..4f464dee 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/SearchKeyAdapter.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/SearchKeyAdapter.java @@ -1,11 +1,11 @@ package the.bytecode.club.bytecodeviewer.gui.resourcelist; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.Enumeration; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; -import the.bytecode.club.bytecodeviewer.BytecodeViewer; /*************************************************************************** * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * @@ -29,118 +29,103 @@ import the.bytecode.club.bytecodeviewer.BytecodeViewer; * @author Konloch * @since 6/22/2021 */ -public class SearchKeyAdapter extends KeyAdapter -{ - private final ResourceListPane resourceListPane; - - public SearchKeyAdapter(ResourceListPane resourceListPane) {this.resourceListPane = resourceListPane;} - - @Override - public void keyPressed(final KeyEvent ke) - { - //only trigger on enter - if (ke.getKeyCode() != KeyEvent.VK_ENTER) - return; - - final String qt = resourceListPane.quickSearch.getText(); - resourceListPane.quickSearch.setText(""); - - if (qt.isEmpty()) //NOPE - return; - - String[] path; - int found = 0; - - if (qt.contains(".")) - { - path = qt.split("\\."); - } - else - { - path = new String[]{qt}; - } - - ResourceTreeNode curNode = resourceListPane.treeRoot; - if (resourceListPane.exact.isSelected()) - { - pathLoop: - for (int i = 0; i < path.length; i++) - { - final String pathName = path[i]; - final boolean isLast = i == path.length - 1; - - for (int c = 0; c < curNode.getChildCount(); c++) - { - final ResourceTreeNode child = (ResourceTreeNode) curNode.getChildAt(c); - System.out.println(pathName + ":" + child.getUserObject()); - - if (child.getUserObject().equals(pathName)) - { - curNode = child; - if (isLast) - { - System.out.println("Found! " + curNode); - found++; - final TreePath pathn = new TreePath(curNode.getPath()); - resourceListPane.tree.setSelectionPath(pathn); - resourceListPane.tree.makeVisible(pathn); - resourceListPane.tree.scrollPathToVisible(pathn); - resourceListPane.openPath(pathn); //auto open - break pathLoop; - } - continue pathLoop; - } - } - - System.out.println("Could not find " + pathName); - break; - } - } - else - { - @SuppressWarnings("unchecked") - Enumeration enums = curNode.depthFirstEnumeration(); - while (enums != null && enums.hasMoreElements()) - { - ResourceTreeNode node = (ResourceTreeNode) enums.nextElement(); - if (node.isLeaf()) - { - if (((String) (node.getUserObject())).toLowerCase().contains(path[path.length - 1].toLowerCase())) - { - TreeNode[] pathArray = node.getPath(); - int k = 0; - StringBuilder fullPath = new StringBuilder(); - while (pathArray != null - && k < pathArray.length) - { - ResourceTreeNode n = (ResourceTreeNode) pathArray[k]; - String s = (String) (n.getUserObject()); - fullPath.append(s); - if (k++ != pathArray.length - 1) - { - fullPath.append("."); - } - } - String fullPathString = fullPath.toString(); - if (fullPathString.toLowerCase().contains(qt.toLowerCase())) - { - System.out.println("Found! " + node); - found++; - if (found >= 30) - { //TODO probably make this a setting, no real reason it's 30 - BytecodeViewer.showMessage("Uh oh, there could be more results but you've" - + " triggered the 30 classes at once limit. Try refining your search."); - return; - } - final TreePath pathn = new TreePath(node.getPath()); - resourceListPane.tree.setSelectionPath(pathn.getParentPath()); - resourceListPane.tree.setSelectionPath(pathn); - resourceListPane.tree.makeVisible(pathn); - resourceListPane.tree.scrollPathToVisible(pathn); - } - } - } - } - } - } +public class SearchKeyAdapter extends KeyAdapter { + private final ResourceListPane resourceListPane; + + public SearchKeyAdapter(ResourceListPane resourceListPane) { + this.resourceListPane = resourceListPane; + } + + @Override + public void keyPressed(final KeyEvent ke) { + //only trigger on enter + if (ke.getKeyCode() != KeyEvent.VK_ENTER) + return; + + final String qt = resourceListPane.quickSearch.getText(); + + if (qt.trim().isEmpty()) //NOPE + return; + + String[] path; + if (qt.contains(".")) { + path = qt.split("\\."); + } else { + path = new String[]{qt}; + } + + ResourceTreeNode curNode = resourceListPane.treeRoot; + boolean caseSensitive = resourceListPane.caseSensitive.isSelected(); + + boolean success = false; + if (resourceListPane.exact.isSelected()) { + pathLoop: + for (int i = 0; i < path.length; i++) { + final String pathName = path[i]; + final boolean isLast = i == path.length - 1; + + for (int c = 0; c < curNode.getChildCount(); c++) { + final ResourceTreeNode child = (ResourceTreeNode) curNode.getChildAt(c); + Object userObject = child.getUserObject(); + if (caseSensitive ? userObject.equals(pathName) : userObject.toString().equalsIgnoreCase(pathName)) { + curNode = child; + if (isLast) { + final TreePath pathn = new TreePath(curNode.getPath()); + resourceListPane.tree.setSelectionPath(pathn); + resourceListPane.tree.makeVisible(pathn); + resourceListPane.tree.scrollPathToVisible(pathn); + resourceListPane.openPath(pathn); //auto open + success = true; + break pathLoop; + } + continue pathLoop; + } + } + + System.out.println("Could not find " + pathName); + break; + } + } else { + @SuppressWarnings("unchecked") + Enumeration enums = curNode.depthFirstEnumeration(); + while (enums != null && enums.hasMoreElements()) { + ResourceTreeNode node = (ResourceTreeNode) enums.nextElement(); + if (node.isLeaf()) { + String userObject = (String) (node.getUserObject()); + String lastElem = path[path.length - 1]; + + if (caseSensitive ? userObject.contains(lastElem) : userObject.toLowerCase().contains(lastElem.toLowerCase())) { + TreeNode[] pathArray = node.getPath(); + int k = 0; + StringBuilder fullPath = new StringBuilder(); + while (pathArray != null + && k < pathArray.length) { + ResourceTreeNode n = (ResourceTreeNode) pathArray[k]; + String s = (String) (n.getUserObject()); + fullPath.append(s); + if (k++ != pathArray.length - 1) { + fullPath.append("."); + } + } + String fullPathString = fullPath.toString(); + + if (caseSensitive ? fullPathString.contains(qt) : fullPathString.toLowerCase().contains(qt.toLowerCase())) { + final TreePath pathn = new TreePath(node.getPath()); + resourceListPane.tree.setSelectionPath(pathn.getParentPath()); + resourceListPane.tree.setSelectionPath(pathn); + resourceListPane.tree.makeVisible(pathn); + resourceListPane.tree.scrollPathToVisible(pathn); + success = true; + break; + } + } + } + } + + } + + if (!success) { + Toolkit.getDefaultToolkit().beep(); + } + } } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcesearch/SearchType.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcesearch/SearchType.java index 4ed49f29..ec3cd451 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcesearch/SearchType.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcesearch/SearchType.java @@ -1,10 +1,7 @@ package the.bytecode.club.bytecodeviewer.gui.resourcesearch; import the.bytecode.club.bytecodeviewer.searching.SearchPanel; -import the.bytecode.club.bytecodeviewer.searching.impl.FieldCallSearch; -import the.bytecode.club.bytecodeviewer.searching.impl.LDCSearch; -import the.bytecode.club.bytecodeviewer.searching.impl.MethodCallSearch; -import the.bytecode.club.bytecodeviewer.searching.impl.RegexSearch; +import the.bytecode.club.bytecodeviewer.searching.impl.*; /*************************************************************************** * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * @@ -34,6 +31,7 @@ public enum SearchType Regex(new RegexSearch()), MethodCall(new MethodCallSearch()), FieldCall(new FieldCallSearch()), + MemberWithAnnotation(new MemberWithAnnotationSearch()) ; public final SearchPanel panel; diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/searching/impl/MemberWithAnnotationSearch.java b/src/main/java/the/bytecode/club/bytecodeviewer/searching/impl/MemberWithAnnotationSearch.java new file mode 100644 index 00000000..2b1b19c7 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/searching/impl/MemberWithAnnotationSearch.java @@ -0,0 +1,83 @@ +package the.bytecode.club.bytecodeviewer.searching.impl; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.resources.ResourceContainer; +import the.bytecode.club.bytecodeviewer.searching.EnterKeyEvent; +import the.bytecode.club.bytecodeviewer.searching.LDCSearchTreeNodeResult; +import the.bytecode.club.bytecodeviewer.searching.SearchPanel; +import the.bytecode.club.bytecodeviewer.translation.TranslatedComponents; +import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJLabel; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +/*************************************************************************** + * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * + * Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + ***************************************************************************/ + +/** + * Annotation Searching + * + * @author GraxCode + */ + +public class MemberWithAnnotationSearch implements SearchPanel { + JTextField annotation; + JPanel myPanel = null; + + public MemberWithAnnotationSearch() { + annotation = new JTextField(""); + annotation.addKeyListener(EnterKeyEvent.SINGLETON); + } + + @Override + public JPanel getPanel() { + if (myPanel == null) { + myPanel = new JPanel(new GridLayout(1, 2)); + myPanel.add(new TranslatedJLabel("Annotation name: ", TranslatedComponents.ANNOTATION_NAME)); + myPanel.add(annotation); + } + + return myPanel; + } + + public void search(final ResourceContainer container, final String resourceWorkingName, final ClassNode node, boolean caseSensitive) { + final String srchText = annotation.getText().trim(); + + if (srchText.isEmpty()) return; + + node.fields.stream().filter(fn -> hasAnnotation(srchText, fn.invisibleAnnotations, fn.visibleAnnotations)).forEach(fn -> BytecodeViewer.viewer.searchBoxPane.treeRoot.add(new LDCSearchTreeNodeResult(container, resourceWorkingName, node, null, fn, fn.name + " " + fn.desc, ""))); + node.methods.stream().filter(mn -> hasAnnotation(srchText, mn.invisibleAnnotations, mn.visibleAnnotations)).forEach(mn -> BytecodeViewer.viewer.searchBoxPane.treeRoot.add(new LDCSearchTreeNodeResult(container, resourceWorkingName, node, mn, null, mn.name + mn.desc, ""))); + } + + public static boolean hasAnnotation(String annotation, List... annoLists) { + if (annoLists == null) return false; + for (List annos : annoLists) { + if (annos == null) continue; + if (annos.stream().anyMatch(ant -> { + String internalName = Type.getType(ant.desc).getInternalName(); + return internalName.equals(annotation) || internalName.endsWith('/' + annotation) || ant.desc.endsWith('/' + annotation.replace('.', '$')); + // in case dot is used for inner class annotations + })) return true; + } + return false; + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/translation/TranslatedComponents.java b/src/main/java/the/bytecode/club/bytecodeviewer/translation/TranslatedComponents.java index 6912e611..c8005695 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/translation/TranslatedComponents.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/translation/TranslatedComponents.java @@ -251,8 +251,9 @@ public enum TranslatedComponents RESULTS, REFRESH, MIN_SDK_VERSION, - - ; + ANNOTATION_NAME, + MATCH_CASE, + EXACT_PATH; private final TranslatedComponentReference componentReference; From d051351dec31dee7923c549870edb07f3924422c Mon Sep 17 00:00:00 2001 From: GraxCode Date: Sun, 13 Mar 2022 16:06:53 +0100 Subject: [PATCH 3/6] Use the CFR API instead of old buggy method. --- .../decompilers/impl/CFRDecompiler.java | 486 +++++++----------- 1 file changed, 180 insertions(+), 306 deletions(-) diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java index f447a554..396fd0d0 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java @@ -1,32 +1,31 @@ package the.bytecode.club.bytecodeviewer.decompilers.impl; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Deque; -import java.util.LinkedList; -import java.util.Random; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; -import me.konloch.kontainer.io.DiskReader; +import com.strobel.core.StringUtilities; +import org.apache.commons.io.IOUtils; +import org.benf.cfr.reader.api.CfrDriver; +import org.benf.cfr.reader.api.ClassFileSource; +import org.benf.cfr.reader.api.OutputSinkFactory; +import org.benf.cfr.reader.api.SinkReturns; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; import org.objectweb.asm.tree.ClassNode; import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.api.ASMUtil; import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.decompilers.InternalDecompiler; import the.bytecode.club.bytecodeviewer.translation.TranslatedStrings; -import the.bytecode.club.bytecodeviewer.util.MiscUtils; -import static the.bytecode.club.bytecodeviewer.Constants.fs; +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipException; +import java.util.zip.ZipOutputStream; + import static the.bytecode.club.bytecodeviewer.Constants.nl; -import static the.bytecode.club.bytecodeviewer.Constants.tempDirectory; import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.CFR; import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.ERROR; - /*************************************************************************** * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * * Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com * @@ -48,313 +47,188 @@ import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.ERR /** * CFR Java Wrapper * - * @author Konloch + * @author GraxCode + * Taken mostly out of Threadtear. */ -public class CFRDecompiler extends InternalDecompiler -{ +public class CFRDecompiler extends InternalDecompiler { - private static final String[] WINDOWS_IS_GREAT = new String[] - { - "CON", - "PRN", - "AUX", - "NUL", - "COM1", - "COM2", - "COM3", - "COM4", - "COM5", - "COM6", - "COM7", - "COM8", - "COM9", - "LPT1", - "LPT2", - "LPT3", - "LPT4", - "LPT5", - "LPT6", - "LPT7", - "LPT8", - "LPT9" - }; + private String result; + private static final String CLASS_SUFFIX = ".class"; - public static String windowsFun(String base) { - for (String s : WINDOWS_IS_GREAT) { - if (base.contains(s.toLowerCase())) { - base = base.replace(s.toLowerCase(), "BCV"); - } + @Override + public String decompileClassNode(ClassNode cn, byte[] bytes) { + String name = cn.name; + return decompile(bytes, name); + } + + private String decompile(byte[] bytes, String name) { + try { + this.result = null; + OutputSinkFactory mySink = new OutputSinkFactory() { + @Override + public List getSupportedSinks(SinkType sinkType, Collection collection) { + if (sinkType == SinkType.JAVA && collection.contains(SinkClass.DECOMPILED)) { + return Arrays.asList(SinkClass.DECOMPILED, SinkClass.STRING); + } else { + return Collections.singletonList(SinkClass.STRING); + } } - return base; + @Override + public Sink getSink(SinkType sinkType, SinkClass sinkClass) { + if (sinkType == SinkType.JAVA && sinkClass == SinkClass.DECOMPILED) { + return x -> result = ((SinkReturns.Decompiled) x).getJava(); + } + return ignore -> { + }; + } + }; + ClassFileSource source = new ClassFileSource() { + @Override + public void informAnalysisRelativePathDetail(String a, String b) { + } + + @Override + public String getPossiblyRenamedPath(String path) { + return path; + } + + @Override + public Pair getClassFileContent(String path) throws IOException { + String clzName = path.substring(0, path.length() - 6); + if (clzName.equals(name)) { + return Pair.make(bytes, clzName); + } + URL url = CFRDecompiler.class.getResource("/" + path); + if (url != null) { + return Pair.make(IOUtils.toByteArray(url), path); + } + // don't load extra classes. we don't care about improper API usage. + ClassNode dummy = new ClassNode(); + dummy.name = clzName; + dummy.version = 52; + return Pair.make(ASMUtil.nodeToBytes(dummy), clzName); + } + + @Override + public Collection addJar(String arg0) { + throw new RuntimeException("This should not be called"); + } + }; + CfrDriver cfrDriver = new CfrDriver.Builder().withClassFileSource(source).withOutputSink(mySink).withOptions(generateOptions()).build(); + cfrDriver.analyse(Collections.singletonList(name)); + } catch (Throwable t) { + t.printStackTrace(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + return CFR + " " + ERROR + "! " + ExceptionUI.SEND_STACKTRACE_TO + nl + nl + TranslatedStrings.SUGGESTED_FIX_DECOMPILER_ERROR + nl + nl + sw; + } + if (result != null && !result.trim().isEmpty()) { + return result; } - @Override - public String decompileClassNode(ClassNode cn, byte[] b) { - String fileStart = tempDirectory + fs.toLowerCase(); + return "No CFR output received."; + } - String exception = ""; - //final File tempClass = new File(windowsFun(MiscUtils.getUniqueName(fileStart, ".class") + ".class")); - final File tempClass = new File(MiscUtils.getUniqueName(fileStart, ".class") + ".class"); + @Override + public void decompileToZip(String sourceJar, String zipName) { + try { + doSaveJarDecompiled(new File(sourceJar), new File(zipName)); + } catch (StackOverflowError | Exception e) { + BytecodeViewer.handleException(e); + } + } - try (FileOutputStream fos = new FileOutputStream(tempClass)) { - fos.write(b); - } catch (final IOException e) { - BytecodeViewer.handleException(e); - } + private void doSaveJarDecompiled(File inFile, File outFile) throws IOException { + try (JarFile jfile = new JarFile(inFile); FileOutputStream dest = new FileOutputStream(outFile); BufferedOutputStream buffDest = new BufferedOutputStream(dest); ZipOutputStream out = new ZipOutputStream(buffDest)) { + byte[] data = new byte[1024]; - String fuckery = fuckery(fileStart); - - /*if (!BytecodeViewer.fatJar) { + Enumeration ent = jfile.entries(); + Set history = new HashSet<>(); + while (ent.hasMoreElements()) { + JarEntry entry = ent.nextElement(); + if (entry.getName().endsWith(CLASS_SUFFIX)) { + JarEntry etn = new JarEntry(entry.getName().replace(CLASS_SUFFIX, ".java")); + if (history.add(etn)) { + out.putNextEntry(etn); try { - ProcessBuilder pb = new ProcessBuilder(ArrayUtils.addAll( - new String[]{BytecodeViewer.getJavaCommand(), "-jar", Resources.findLibrary("cfr")}, - generateMainMethod(tempClass.getAbsolutePath(), fuckery) - )); - BytecodeViewer.sm.stopBlocking(); - Process p = pb.start(); - BytecodeViewer.createdProcesses.add(p); - p.waitFor(); - } catch (Exception e) { - BytecodeViewer.handleException(e); + String internalName = StringUtilities.removeRight(entry.getName(), CLASS_SUFFIX); + IOUtils.write(decompile(IOUtils.toByteArray(jfile.getInputStream(entry)), internalName), out, StandardCharsets.UTF_8); } finally { - BytecodeViewer.sm.setBlocking(); + out.closeEntry(); } + } } else { - org.benf.cfr.reader.Main.main(generateMainMethod(tempClass.getAbsolutePath(), fuckery)); - }*/ - - try { - org.benf.cfr.reader.Main.main(generateMainMethod(tempClass.getAbsolutePath(), fuckery)); - } catch (StackOverflowError | Exception e) { - StringWriter exceptionWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(exceptionWriter)); - e.printStackTrace(); - exception = exceptionWriter.toString(); - } - - tempClass.delete(); - File file = new File(fuckery); - - if (file.exists()) - return findFile(MiscUtils.listFiles(file)); - - return CFR + " " + ERROR + "! " + ExceptionUI.SEND_STACKTRACE_TO + - nl + nl + TranslatedStrings.SUGGESTED_FIX_DECOMPILER_ERROR + - nl + nl + exception; - } - - Random r = new Random(); - File f; - - public String fuckery(String start) { - while (true) { - f = new File(start + r.nextInt(Integer.MAX_VALUE)); - if (!f.exists()) - return f.toString(); - } - } - - public String findFile(File[] fA) { - for (File f : fA) { - if (f.isDirectory()) - return findFile(MiscUtils.listFiles(f)); - else { - String s; - try { - s = DiskReader.loadAsString(f.getAbsolutePath()); - } catch (Exception e) { - StringWriter exceptionWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(exceptionWriter)); - e.printStackTrace(); - - return CFR + " " + ERROR + "! " + ExceptionUI.SEND_STACKTRACE_TO + - nl + nl + TranslatedStrings.SUGGESTED_FIX_DECOMPILER_ERROR + - nl + nl + exceptionWriter; + try { + JarEntry etn = new JarEntry(entry.getName()); + if (history.add(etn)) continue; + history.add(etn); + out.putNextEntry(etn); + try (InputStream in = jfile.getInputStream(entry)) { + if (in != null) { + int count; + while ((count = in.read(data, 0, 1024)) != -1) { + out.write(data, 0, count); } - - return s; + } + } finally { + out.closeEntry(); } - } - - return "CFR error!" + - nl + nl + TranslatedStrings.SUGGESTED_FIX_DECOMPILER_ERROR; - } - - public String[] generateMainMethod(String filePath, String outputPath) { - return new String[]{ - filePath, - "--outputdir", - outputPath, - "--decodeenumswitch", - String.valueOf(BytecodeViewer.viewer.decodeEnumSwitch - .isSelected()), - "--sugarenums", - String.valueOf(BytecodeViewer.viewer.sugarEnums.isSelected()), - "--decodestringswitch", - String.valueOf(BytecodeViewer.viewer.decodeStringSwitch - .isSelected()), - "--arrayiter", - String.valueOf(BytecodeViewer.viewer.arrayiter.isSelected()), - "--collectioniter", - String.valueOf(BytecodeViewer.viewer.collectioniter - .isSelected()), - "--innerclasses", - String.valueOf(BytecodeViewer.viewer.innerClasses.isSelected()), - "--removeboilerplate", - String.valueOf(BytecodeViewer.viewer.removeBoilerPlate - .isSelected()), - "--removeinnerclasssynthetics", - String.valueOf(BytecodeViewer.viewer.removeInnerClassSynthetics - .isSelected()), - "--decodelambdas", - String.valueOf(BytecodeViewer.viewer.decodeLambdas.isSelected()), - "--hidebridgemethods", - String.valueOf(BytecodeViewer.viewer.hideBridgeMethods - .isSelected()), - "--liftconstructorinit", - String.valueOf(BytecodeViewer.viewer.liftConstructorInit - .isSelected()), - "--removedeadmethods", - String.valueOf(BytecodeViewer.viewer.removeDeadMethods - .isSelected()), - "--removebadgenerics", - String.valueOf(BytecodeViewer.viewer.removeBadGenerics - .isSelected()), - "--sugarasserts", - String.valueOf(BytecodeViewer.viewer.sugarAsserts.isSelected()), - "--sugarboxing", - String.valueOf(BytecodeViewer.viewer.sugarBoxing.isSelected()), - "--showversion", - String.valueOf(BytecodeViewer.viewer.showVersion.isSelected()), - "--decodefinally", - String.valueOf(BytecodeViewer.viewer.decodeFinally.isSelected()), - "--tidymonitors", - String.valueOf(BytecodeViewer.viewer.tidyMonitors.isSelected()), - "--lenient", - String.valueOf(BytecodeViewer.viewer.lenient.isSelected()), - "--dumpclasspath", - String.valueOf(BytecodeViewer.viewer.dumpClassPath.isSelected()), - "--comments", - String.valueOf(BytecodeViewer.viewer.comments.isSelected()), - "--forcetopsort", - String.valueOf(BytecodeViewer.viewer.forceTopSort.isSelected()), - "--forcetopsortaggress", - String.valueOf(BytecodeViewer.viewer.forceTopSortAggress - .isSelected()), - "--stringbuffer", - String.valueOf(BytecodeViewer.viewer.stringBuffer.isSelected()), - "--stringbuilder", - String.valueOf(BytecodeViewer.viewer.stringBuilder.isSelected()), - "--silent", - String.valueOf(BytecodeViewer.viewer.silent.isSelected()), - "--recover", - String.valueOf(BytecodeViewer.viewer.recover.isSelected()), - "--eclipse", - String.valueOf(BytecodeViewer.viewer.eclipse.isSelected()), - "--override", - String.valueOf(BytecodeViewer.viewer.override.isSelected()), - "--showinferrable", - String.valueOf(BytecodeViewer.viewer.showInferrable - .isSelected()), - "--aexagg", - String.valueOf(BytecodeViewer.viewer.aexagg.isSelected()), - "--forcecondpropagate", - String.valueOf(BytecodeViewer.viewer.forceCondPropagate - .isSelected()), - "--hideutf", - String.valueOf(BytecodeViewer.viewer.hideUTF.isSelected()), - "--hidelongstrings", - String.valueOf(BytecodeViewer.viewer.hideLongStrings - .isSelected()), - "--commentmonitors", - String.valueOf(BytecodeViewer.viewer.commentMonitor - .isSelected()), - "--allowcorrecting", - String.valueOf(BytecodeViewer.viewer.allowCorrecting - .isSelected()), - "--labelledblocks", - String.valueOf(BytecodeViewer.viewer.labelledBlocks - .isSelected()), - "--j14classobj", - String.valueOf(BytecodeViewer.viewer.j14ClassOBJ.isSelected()), - "--hidelangimports", - String.valueOf(BytecodeViewer.viewer.hideLangImports - .isSelected()), - "--recovertypeclash", - String.valueOf(BytecodeViewer.viewer.recoveryTypeClash - .isSelected()), - "--recovertypehints", - String.valueOf(BytecodeViewer.viewer.recoveryTypehInts - .isSelected()), - "--forcereturningifs", - String.valueOf(BytecodeViewer.viewer.forceTurningIFs - .isSelected()), - "--forloopaggcapture", - String.valueOf(BytecodeViewer.viewer.forLoopAGGCapture - .isSelected()),}; - } - - @Override - public void decompileToZip(String sourceJar, String zipName) { - File tempZip = new File(sourceJar); - - String fileStart = tempDirectory + fs; - String fuckery = fuckery(fileStart); - - org.benf.cfr.reader.Main.main(generateMainMethod(tempZip.getAbsolutePath(), fuckery)); - - File fuck = new File(fuckery); - - try { - zip(fuck, new File(zipName)); - } catch (IOException e) { - BytecodeViewer.handleException(e); - } - - fuck.delete(); - } - - public void zip(File directory, File zipFile) throws IOException { - java.net.URI base = directory.toURI(); - Deque queue = new LinkedList<>(); - queue.push(directory); - try (OutputStream out = new FileOutputStream(zipFile); - ZipOutputStream zout = new ZipOutputStream(out)) { - while (!queue.isEmpty()) { - directory = queue.pop(); - for (File kid : MiscUtils.listFiles(directory)) { - String name = base.relativize(kid.toURI()).getPath(); - if (kid.isDirectory()) { - queue.push(kid); - name = name.endsWith("/") ? name : name + "/"; - zout.putNextEntry(new ZipEntry(name)); - } else { - zout.putNextEntry(new ZipEntry(name)); - copy(kid, zout); - zout.closeEntry(); - } - } + } catch (ZipException ze) { + // some jars contain duplicate pom.xml entries: ignore it + if (!ze.getMessage().contains("duplicate")) { + throw ze; } + } } + } } + } - private static void copy(InputStream in, OutputStream out) - throws IOException { - byte[] buffer = new byte[1024]; - while (true) { - int readCount = in.read(buffer); - if (readCount < 0) { - break; - } - out.write(buffer, 0, readCount); - } - } - - private static void copy(File file, OutputStream out) throws IOException { - try (InputStream in = new FileInputStream(file)) { - copy(in, out); - } - } + private Map generateOptions() { + Map options = new HashMap<>(); + options.put("decodeenumswitch", String.valueOf(BytecodeViewer.viewer.decodeEnumSwitch.isSelected())); + options.put("sugarenums", String.valueOf(BytecodeViewer.viewer.sugarEnums.isSelected())); + options.put("decodestringswitch", String.valueOf(BytecodeViewer.viewer.decodeStringSwitch.isSelected())); + options.put("arrayiter", String.valueOf(BytecodeViewer.viewer.arrayiter.isSelected())); + options.put("collectioniter", String.valueOf(BytecodeViewer.viewer.collectioniter.isSelected())); + options.put("innerclasses", String.valueOf(BytecodeViewer.viewer.innerClasses.isSelected())); + options.put("removeboilerplate", String.valueOf(BytecodeViewer.viewer.removeBoilerPlate.isSelected())); + options.put("removeinnerclasssynthetics", String.valueOf(BytecodeViewer.viewer.removeInnerClassSynthetics.isSelected())); + options.put("decodelambdas", String.valueOf(BytecodeViewer.viewer.decodeLambdas.isSelected())); + options.put("hidebridgemethods", String.valueOf(BytecodeViewer.viewer.hideBridgeMethods.isSelected())); + options.put("liftconstructorinit", String.valueOf(BytecodeViewer.viewer.liftConstructorInit.isSelected())); + options.put("removebadgenerics", String.valueOf(BytecodeViewer.viewer.removeBadGenerics.isSelected())); + options.put("sugarasserts", String.valueOf(BytecodeViewer.viewer.sugarAsserts.isSelected())); + options.put("sugarboxing", String.valueOf(BytecodeViewer.viewer.sugarBoxing.isSelected())); + options.put("showversion", String.valueOf(BytecodeViewer.viewer.showVersion.isSelected())); + options.put("decodefinally", String.valueOf(BytecodeViewer.viewer.decodeFinally.isSelected())); + options.put("tidymonitors", String.valueOf(BytecodeViewer.viewer.tidyMonitors.isSelected())); + options.put("lenient", String.valueOf(BytecodeViewer.viewer.lenient.isSelected())); + options.put("dumpclasspath", String.valueOf(BytecodeViewer.viewer.dumpClassPath.isSelected())); + options.put("comments", String.valueOf(BytecodeViewer.viewer.comments.isSelected())); + options.put("forcetopsort", String.valueOf(BytecodeViewer.viewer.forceTopSort.isSelected())); + options.put("forcetopsortaggress", String.valueOf(BytecodeViewer.viewer.forceTopSortAggress.isSelected())); + options.put("stringbuffer", String.valueOf(BytecodeViewer.viewer.stringBuffer.isSelected())); + options.put("stringbuilder", String.valueOf(BytecodeViewer.viewer.stringBuilder.isSelected())); + options.put("silent", String.valueOf(BytecodeViewer.viewer.silent.isSelected())); + options.put("recover", String.valueOf(BytecodeViewer.viewer.recover.isSelected())); + options.put("eclipse", String.valueOf(BytecodeViewer.viewer.eclipse.isSelected())); + options.put("override", String.valueOf(BytecodeViewer.viewer.override.isSelected())); + options.put("showinferrable", String.valueOf(BytecodeViewer.viewer.showInferrable.isSelected())); + options.put("aexagg", String.valueOf(BytecodeViewer.viewer.aexagg.isSelected())); + options.put("hideutf", String.valueOf(BytecodeViewer.viewer.hideUTF.isSelected())); + options.put("hidelongstrings", String.valueOf(BytecodeViewer.viewer.hideLongStrings.isSelected())); + options.put("commentmonitors", String.valueOf(BytecodeViewer.viewer.commentMonitor.isSelected())); + options.put("allowcorrecting", String.valueOf(BytecodeViewer.viewer.allowCorrecting.isSelected())); + options.put("labelledblocks", String.valueOf(BytecodeViewer.viewer.labelledBlocks.isSelected())); + options.put("j14classobj", String.valueOf(BytecodeViewer.viewer.j14ClassOBJ.isSelected())); + options.put("hidelangimports", String.valueOf(BytecodeViewer.viewer.hideLangImports.isSelected())); + options.put("recovertypehints", String.valueOf(BytecodeViewer.viewer.recoveryTypehInts.isSelected())); + options.put("forcereturningifs", String.valueOf(BytecodeViewer.viewer.forceTurningIFs.isSelected())); + options.put("forloopaggcapture", String.valueOf(BytecodeViewer.viewer.forLoopAGGCapture.isSelected())); + return options; + } } From daf870572ccd0efc593a6da2eaece73cbc10a12c Mon Sep 17 00:00:00 2001 From: GraxCode Date: Sun, 13 Mar 2022 22:46:53 +0100 Subject: [PATCH 4/6] Fix quick search foreground color for different themes. --- .../bytecodeviewer/gui/resourcelist/ResourceListPane.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java index 8b56b4f3..682ca3f7 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourcelist/ResourceListPane.java @@ -104,7 +104,7 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File tree.setRootVisible(false); tree.setShowsRootHandles(true); - quickSearch.setForeground(Color.gray); + quickSearch.setForeground(quickSearch.getDisabledTextColor()); attachTreeListeners(); attachQuickSearchListeners(); @@ -524,7 +524,7 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File if (quickSearch.getText().equals(TranslatedStrings.QUICK_FILE_SEARCH_NO_FILE_EXTENSION.toString())) { quickSearch.setText(""); - quickSearch.setForeground(Color.black); + quickSearch.setForeground(quickSearch.getSelectedTextColor()); } } @@ -534,7 +534,7 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File if (quickSearch.getText().isEmpty()) { quickSearch.setText(TranslatedStrings.QUICK_FILE_SEARCH_NO_FILE_EXTENSION.toString()); - quickSearch.setForeground(Color.gray); + quickSearch.setForeground(quickSearch.getDisabledTextColor()); } } }); From 7ba170d191d5d41eae0e9feeb76a17fcecb9e00d Mon Sep 17 00:00:00 2001 From: Nico Mexis Date: Mon, 14 Mar 2022 14:52:34 +0100 Subject: [PATCH 5/6] Update dependencies --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 75de908e..39bf5f93 100644 --- a/pom.xml +++ b/pom.xml @@ -31,8 +31,8 @@ 2.7.3 0.3.4 5.2.1.Final - v45 - b803ad9 + v46 + 5a2b2cc 2.9.0 31.0.1-jre 4.2 @@ -44,7 +44,7 @@ 3.2 0.2.0 0.6.0 - 3.1.6 + 3.2.0 2.1.1 1.7.36 2.5.2 From 4ff1d6060be6a3cdd08d43d0c4b2ce178c2d46c0 Mon Sep 17 00:00:00 2001 From: Nico Mexis Date: Mon, 14 Mar 2022 14:52:50 +0100 Subject: [PATCH 6/6] Better approach to CFR decompilation --- .../decompilers/impl/CFRDecompiler.java | 382 +++++++++--------- 1 file changed, 201 insertions(+), 181 deletions(-) diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java index 396fd0d0..86883766 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/CFRDecompiler.java @@ -1,31 +1,46 @@ package the.bytecode.club.bytecodeviewer.decompilers.impl; -import com.strobel.core.StringUtilities; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipException; +import java.util.zip.ZipOutputStream; import org.apache.commons.io.IOUtils; import org.benf.cfr.reader.api.CfrDriver; import org.benf.cfr.reader.api.ClassFileSource; import org.benf.cfr.reader.api.OutputSinkFactory; import org.benf.cfr.reader.api.SinkReturns; import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import org.benf.cfr.reader.state.ClassFileSourceImpl; +import org.benf.cfr.reader.util.getopt.Options; +import org.benf.cfr.reader.util.getopt.OptionsImpl; import org.objectweb.asm.tree.ClassNode; import the.bytecode.club.bytecodeviewer.BytecodeViewer; -import the.bytecode.club.bytecodeviewer.api.ASMUtil; import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.decompilers.InternalDecompiler; +import the.bytecode.club.bytecodeviewer.resources.ResourceContainer; import the.bytecode.club.bytecodeviewer.translation.TranslatedStrings; -import java.io.*; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.zip.ZipException; -import java.util.zip.ZipOutputStream; - import static the.bytecode.club.bytecodeviewer.Constants.nl; import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.CFR; import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.ERROR; + /*************************************************************************** * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * * Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com * @@ -48,187 +63,192 @@ import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.ERR * CFR Java Wrapper * * @author GraxCode - * Taken mostly out of Threadtear. + * Taken mostly out of Threadtear. */ - public class CFRDecompiler extends InternalDecompiler { - private String result; - private static final String CLASS_SUFFIX = ".class"; + private static final String CLASS_SUFFIX = ".class"; - @Override - public String decompileClassNode(ClassNode cn, byte[] bytes) { - String name = cn.name; - return decompile(bytes, name); - } + @Override + public String decompileClassNode(ClassNode cn, byte[] content) { + return decompile(cn, cn.name, content); + } + + private String decompile(ClassNode cn, String name, byte[] content) { + try { + String classPath = name + (name.endsWith(CLASS_SUFFIX) ? "" : CLASS_SUFFIX); + + StringBuilder builder = new StringBuilder(); + Consumer dumpDecompiled = d -> builder.append(d.getJava()); + + Options options = generateOptions(); + ClassFileSource source = new BCVDataSource(options, cn, classPath, content); + CfrDriver driver = new CfrDriver.Builder() + .withClassFileSource(source) + .withBuiltOptions(options) + .withOutputSink(new BCVOutputSinkFactory(dumpDecompiled)) + .build(); + driver.analyse(Collections.singletonList(name)); + + return builder.toString(); + } catch (Throwable t) { + t.printStackTrace(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + return CFR + " " + ERROR + "! " + ExceptionUI.SEND_STACKTRACE_TO + + nl + nl + TranslatedStrings.SUGGESTED_FIX_DECOMPILER_ERROR + + nl + nl + sw; + } + } + + @Override + public void decompileToZip(String sourceJar, String outJar) { + try (JarFile jfile = new JarFile(new File(sourceJar)); + FileOutputStream dest = new FileOutputStream(outJar); + BufferedOutputStream buffDest = new BufferedOutputStream(dest); + ZipOutputStream out = new ZipOutputStream(buffDest)) { + byte[] data = new byte[1024]; + + Enumeration ent = jfile.entries(); + Set history = new HashSet<>(); + while (ent.hasMoreElements()) { + JarEntry entry = ent.nextElement(); + if (entry.getName().endsWith(CLASS_SUFFIX)) { + JarEntry etn = new JarEntry(entry.getName().replace(CLASS_SUFFIX, ".java")); + if (history.add(etn)) { + out.putNextEntry(etn); + try { + IOUtils.write(decompile(null, entry.getName(), + IOUtils.toByteArray(jfile.getInputStream(entry))), + out, StandardCharsets.UTF_8); + } finally { + out.closeEntry(); + } + } + } else { + try { + JarEntry etn = new JarEntry(entry.getName()); + if (history.add(etn)) continue; + history.add(etn); + out.putNextEntry(etn); + try (InputStream in = jfile.getInputStream(entry)) { + if (in != null) { + int count; + while ((count = in.read(data, 0, 1024)) != -1) { + out.write(data, 0, count); + } + } + } finally { + out.closeEntry(); + } + } catch (ZipException ze) { + // some jars contain duplicate pom.xml entries: ignore it + if (!ze.getMessage().contains("duplicate")) { + throw ze; + } + } + } + } + } catch (StackOverflowError | Exception e) { + BytecodeViewer.handleException(e); + } + } + + public Options generateOptions() { + Map options = new HashMap<>(); + options.put("decodeenumswitch", String.valueOf(BytecodeViewer.viewer.decodeEnumSwitch.isSelected())); + options.put("sugarenums", String.valueOf(BytecodeViewer.viewer.sugarEnums.isSelected())); + options.put("decodestringswitch", String.valueOf(BytecodeViewer.viewer.decodeStringSwitch.isSelected())); + options.put("arrayiter", String.valueOf(BytecodeViewer.viewer.arrayiter.isSelected())); + options.put("collectioniter", String.valueOf(BytecodeViewer.viewer.collectioniter.isSelected())); + options.put("innerclasses", String.valueOf(BytecodeViewer.viewer.innerClasses.isSelected())); + options.put("removeboilerplate", String.valueOf(BytecodeViewer.viewer.removeBoilerPlate.isSelected())); + options.put("removeinnerclasssynthetics", + String.valueOf(BytecodeViewer.viewer.removeInnerClassSynthetics.isSelected())); + options.put("decodelambdas", String.valueOf(BytecodeViewer.viewer.decodeLambdas.isSelected())); + options.put("hidebridgemethods", String.valueOf(BytecodeViewer.viewer.hideBridgeMethods.isSelected())); + options.put("liftconstructorinit", String.valueOf(BytecodeViewer.viewer.liftConstructorInit.isSelected())); + options.put("removebadgenerics", String.valueOf(BytecodeViewer.viewer.removeBadGenerics.isSelected())); + options.put("sugarasserts", String.valueOf(BytecodeViewer.viewer.sugarAsserts.isSelected())); + options.put("sugarboxing", String.valueOf(BytecodeViewer.viewer.sugarBoxing.isSelected())); + options.put("showversion", String.valueOf(BytecodeViewer.viewer.showVersion.isSelected())); + options.put("decodefinally", String.valueOf(BytecodeViewer.viewer.decodeFinally.isSelected())); + options.put("tidymonitors", String.valueOf(BytecodeViewer.viewer.tidyMonitors.isSelected())); + options.put("lenient", String.valueOf(BytecodeViewer.viewer.lenient.isSelected())); + options.put("dumpclasspath", String.valueOf(BytecodeViewer.viewer.dumpClassPath.isSelected())); + options.put("comments", String.valueOf(BytecodeViewer.viewer.comments.isSelected())); + options.put("forcetopsort", String.valueOf(BytecodeViewer.viewer.forceTopSort.isSelected())); + options.put("forcetopsortaggress", String.valueOf(BytecodeViewer.viewer.forceTopSortAggress.isSelected())); + options.put("stringbuffer", String.valueOf(BytecodeViewer.viewer.stringBuffer.isSelected())); + options.put("stringbuilder", String.valueOf(BytecodeViewer.viewer.stringBuilder.isSelected())); + options.put("silent", String.valueOf(BytecodeViewer.viewer.silent.isSelected())); + options.put("recover", String.valueOf(BytecodeViewer.viewer.recover.isSelected())); + options.put("eclipse", String.valueOf(BytecodeViewer.viewer.eclipse.isSelected())); + options.put("override", String.valueOf(BytecodeViewer.viewer.override.isSelected())); + options.put("showinferrable", String.valueOf(BytecodeViewer.viewer.showInferrable.isSelected())); + options.put("aexagg", String.valueOf(BytecodeViewer.viewer.aexagg.isSelected())); + options.put("hideutf", String.valueOf(BytecodeViewer.viewer.hideUTF.isSelected())); + options.put("hidelongstrings", String.valueOf(BytecodeViewer.viewer.hideLongStrings.isSelected())); + options.put("commentmonitors", String.valueOf(BytecodeViewer.viewer.commentMonitor.isSelected())); + options.put("allowcorrecting", String.valueOf(BytecodeViewer.viewer.allowCorrecting.isSelected())); + options.put("labelledblocks", String.valueOf(BytecodeViewer.viewer.labelledBlocks.isSelected())); + options.put("j14classobj", String.valueOf(BytecodeViewer.viewer.j14ClassOBJ.isSelected())); + options.put("hidelangimports", String.valueOf(BytecodeViewer.viewer.hideLangImports.isSelected())); + options.put("recovertypehints", String.valueOf(BytecodeViewer.viewer.recoveryTypehInts.isSelected())); + options.put("forcereturningifs", String.valueOf(BytecodeViewer.viewer.forceTurningIFs.isSelected())); + options.put("forloopaggcapture", String.valueOf(BytecodeViewer.viewer.forLoopAGGCapture.isSelected())); + return new OptionsImpl(options); + } + + private static class BCVDataSource extends ClassFileSourceImpl { + + private final ResourceContainer container; + private final String classFilePath; + private final byte[] content; + + private BCVDataSource(Options options, ClassNode cn, String classFilePath, byte[] content) { + super(options); + this.container = BytecodeViewer.getResourceContainers().stream() + .filter(rc -> rc.resourceClasses.containsValue(cn)) + .findFirst().orElse(null); + this.classFilePath = classFilePath; + this.content = content; + } - private String decompile(byte[] bytes, String name) { - try { - this.result = null; - OutputSinkFactory mySink = new OutputSinkFactory() { @Override - public List getSupportedSinks(SinkType sinkType, Collection collection) { - if (sinkType == SinkType.JAVA && collection.contains(SinkClass.DECOMPILED)) { - return Arrays.asList(SinkClass.DECOMPILED, SinkClass.STRING); - } else { - return Collections.singletonList(SinkClass.STRING); - } + public Pair getClassFileContent(String classFilePath) throws IOException { + if (classFilePath.equals(this.classFilePath) && content != null) return Pair.make(content, classFilePath); + if (container == null) return super.getClassFileContent(classFilePath); + byte[] data = container.resourceClassBytes.get(classFilePath); + if (data == null) return super.getClassFileContent(classFilePath); + return Pair.make(data, classFilePath); + } + + } + + private static class BCVOutputSinkFactory implements OutputSinkFactory { + + private final Consumer dumpDecompiled; + + private BCVOutputSinkFactory(Consumer dumpDecompiled) { + this.dumpDecompiled = dumpDecompiled; + } + + @Override + public List getSupportedSinks(SinkType sinkType, Collection available) { + return Collections.singletonList(SinkClass.DECOMPILED); } @Override public Sink getSink(SinkType sinkType, SinkClass sinkClass) { - if (sinkType == SinkType.JAVA && sinkClass == SinkClass.DECOMPILED) { - return x -> result = ((SinkReturns.Decompiled) x).getJava(); - } - return ignore -> { - }; - } - }; - ClassFileSource source = new ClassFileSource() { - @Override - public void informAnalysisRelativePathDetail(String a, String b) { - } - - @Override - public String getPossiblyRenamedPath(String path) { - return path; - } - - @Override - public Pair getClassFileContent(String path) throws IOException { - String clzName = path.substring(0, path.length() - 6); - if (clzName.equals(name)) { - return Pair.make(bytes, clzName); - } - URL url = CFRDecompiler.class.getResource("/" + path); - if (url != null) { - return Pair.make(IOUtils.toByteArray(url), path); - } - // don't load extra classes. we don't care about improper API usage. - ClassNode dummy = new ClassNode(); - dummy.name = clzName; - dummy.version = 52; - return Pair.make(ASMUtil.nodeToBytes(dummy), clzName); - } - - @Override - public Collection addJar(String arg0) { - throw new RuntimeException("This should not be called"); - } - }; - CfrDriver cfrDriver = new CfrDriver.Builder().withClassFileSource(source).withOutputSink(mySink).withOptions(generateOptions()).build(); - cfrDriver.analyse(Collections.singletonList(name)); - } catch (Throwable t) { - t.printStackTrace(); - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - t.printStackTrace(pw); - return CFR + " " + ERROR + "! " + ExceptionUI.SEND_STACKTRACE_TO + nl + nl + TranslatedStrings.SUGGESTED_FIX_DECOMPILER_ERROR + nl + nl + sw; - } - if (result != null && !result.trim().isEmpty()) { - return result; - } - - return "No CFR output received."; - } - - @Override - public void decompileToZip(String sourceJar, String zipName) { - try { - doSaveJarDecompiled(new File(sourceJar), new File(zipName)); - } catch (StackOverflowError | Exception e) { - BytecodeViewer.handleException(e); - } - } - - private void doSaveJarDecompiled(File inFile, File outFile) throws IOException { - try (JarFile jfile = new JarFile(inFile); FileOutputStream dest = new FileOutputStream(outFile); BufferedOutputStream buffDest = new BufferedOutputStream(dest); ZipOutputStream out = new ZipOutputStream(buffDest)) { - byte[] data = new byte[1024]; - - Enumeration ent = jfile.entries(); - Set history = new HashSet<>(); - while (ent.hasMoreElements()) { - JarEntry entry = ent.nextElement(); - if (entry.getName().endsWith(CLASS_SUFFIX)) { - JarEntry etn = new JarEntry(entry.getName().replace(CLASS_SUFFIX, ".java")); - if (history.add(etn)) { - out.putNextEntry(etn); - try { - String internalName = StringUtilities.removeRight(entry.getName(), CLASS_SUFFIX); - IOUtils.write(decompile(IOUtils.toByteArray(jfile.getInputStream(entry)), internalName), out, StandardCharsets.UTF_8); - } finally { - out.closeEntry(); + if (sinkType == SinkType.JAVA && sinkClass == SinkClass.DECOMPILED) { + return x -> dumpDecompiled.accept((SinkReturns.Decompiled) x); } - } - } else { - try { - JarEntry etn = new JarEntry(entry.getName()); - if (history.add(etn)) continue; - history.add(etn); - out.putNextEntry(etn); - try (InputStream in = jfile.getInputStream(entry)) { - if (in != null) { - int count; - while ((count = in.read(data, 0, 1024)) != -1) { - out.write(data, 0, count); - } - } - } finally { - out.closeEntry(); - } - } catch (ZipException ze) { - // some jars contain duplicate pom.xml entries: ignore it - if (!ze.getMessage().contains("duplicate")) { - throw ze; - } - } + return ignore -> { + }; } - } - } - } - private Map generateOptions() { - Map options = new HashMap<>(); - options.put("decodeenumswitch", String.valueOf(BytecodeViewer.viewer.decodeEnumSwitch.isSelected())); - options.put("sugarenums", String.valueOf(BytecodeViewer.viewer.sugarEnums.isSelected())); - options.put("decodestringswitch", String.valueOf(BytecodeViewer.viewer.decodeStringSwitch.isSelected())); - options.put("arrayiter", String.valueOf(BytecodeViewer.viewer.arrayiter.isSelected())); - options.put("collectioniter", String.valueOf(BytecodeViewer.viewer.collectioniter.isSelected())); - options.put("innerclasses", String.valueOf(BytecodeViewer.viewer.innerClasses.isSelected())); - options.put("removeboilerplate", String.valueOf(BytecodeViewer.viewer.removeBoilerPlate.isSelected())); - options.put("removeinnerclasssynthetics", String.valueOf(BytecodeViewer.viewer.removeInnerClassSynthetics.isSelected())); - options.put("decodelambdas", String.valueOf(BytecodeViewer.viewer.decodeLambdas.isSelected())); - options.put("hidebridgemethods", String.valueOf(BytecodeViewer.viewer.hideBridgeMethods.isSelected())); - options.put("liftconstructorinit", String.valueOf(BytecodeViewer.viewer.liftConstructorInit.isSelected())); - options.put("removebadgenerics", String.valueOf(BytecodeViewer.viewer.removeBadGenerics.isSelected())); - options.put("sugarasserts", String.valueOf(BytecodeViewer.viewer.sugarAsserts.isSelected())); - options.put("sugarboxing", String.valueOf(BytecodeViewer.viewer.sugarBoxing.isSelected())); - options.put("showversion", String.valueOf(BytecodeViewer.viewer.showVersion.isSelected())); - options.put("decodefinally", String.valueOf(BytecodeViewer.viewer.decodeFinally.isSelected())); - options.put("tidymonitors", String.valueOf(BytecodeViewer.viewer.tidyMonitors.isSelected())); - options.put("lenient", String.valueOf(BytecodeViewer.viewer.lenient.isSelected())); - options.put("dumpclasspath", String.valueOf(BytecodeViewer.viewer.dumpClassPath.isSelected())); - options.put("comments", String.valueOf(BytecodeViewer.viewer.comments.isSelected())); - options.put("forcetopsort", String.valueOf(BytecodeViewer.viewer.forceTopSort.isSelected())); - options.put("forcetopsortaggress", String.valueOf(BytecodeViewer.viewer.forceTopSortAggress.isSelected())); - options.put("stringbuffer", String.valueOf(BytecodeViewer.viewer.stringBuffer.isSelected())); - options.put("stringbuilder", String.valueOf(BytecodeViewer.viewer.stringBuilder.isSelected())); - options.put("silent", String.valueOf(BytecodeViewer.viewer.silent.isSelected())); - options.put("recover", String.valueOf(BytecodeViewer.viewer.recover.isSelected())); - options.put("eclipse", String.valueOf(BytecodeViewer.viewer.eclipse.isSelected())); - options.put("override", String.valueOf(BytecodeViewer.viewer.override.isSelected())); - options.put("showinferrable", String.valueOf(BytecodeViewer.viewer.showInferrable.isSelected())); - options.put("aexagg", String.valueOf(BytecodeViewer.viewer.aexagg.isSelected())); - options.put("hideutf", String.valueOf(BytecodeViewer.viewer.hideUTF.isSelected())); - options.put("hidelongstrings", String.valueOf(BytecodeViewer.viewer.hideLongStrings.isSelected())); - options.put("commentmonitors", String.valueOf(BytecodeViewer.viewer.commentMonitor.isSelected())); - options.put("allowcorrecting", String.valueOf(BytecodeViewer.viewer.allowCorrecting.isSelected())); - options.put("labelledblocks", String.valueOf(BytecodeViewer.viewer.labelledBlocks.isSelected())); - options.put("j14classobj", String.valueOf(BytecodeViewer.viewer.j14ClassOBJ.isSelected())); - options.put("hidelangimports", String.valueOf(BytecodeViewer.viewer.hideLangImports.isSelected())); - options.put("recovertypehints", String.valueOf(BytecodeViewer.viewer.recoveryTypehInts.isSelected())); - options.put("forcereturningifs", String.valueOf(BytecodeViewer.viewer.forceTurningIFs.isSelected())); - options.put("forloopaggcapture", String.valueOf(BytecodeViewer.viewer.forLoopAGGCapture.isSelected())); - return options; - } + } + }