From e733c91783af25ea79615c126de40a543048d0de Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 26 Mar 2016 17:19:54 +0300 Subject: [PATCH] gui: support images view/unpack --- NOTICE | 16 ++++- .../src/main/java/jadx/api/ResourceFile.java | 2 +- .../java/jadx/api/ResourceFileContent.java | 2 +- .../src/main/java/jadx/api/ResourceType.java | 2 +- .../main/java/jadx/api/ResourcesLoader.java | 3 + .../java/jadx/core/codegen/CodeWriter.java | 18 +---- .../java/jadx/core/utils/files/FileUtils.java | 19 +++++ .../java/jadx/core/xmlgen/ResContainer.java | 45 ++++++++---- .../java/jadx/core/xmlgen/ResourcesSaver.java | 39 ++++++++-- jadx-gui/build.gradle | 1 + .../java/jadx/gui/treemodel/JResource.java | 14 +++- .../main/java/jadx/gui/treemodel/JRoot.java | 6 +- .../ui/{ContentArea.java => CodeArea.java} | 24 +++---- .../src/main/java/jadx/gui/ui/CodePanel.java | 72 +++++++++++++++++++ .../java/jadx/gui/ui/CommonSearchDialog.java | 4 +- .../main/java/jadx/gui/ui/ContentPanel.java | 56 ++------------- .../src/main/java/jadx/gui/ui/ImagePanel.java | 27 +++++++ .../main/java/jadx/gui/ui/LineNumbers.java | 36 +++++----- .../src/main/java/jadx/gui/ui/MainWindow.java | 15 ++-- .../src/main/java/jadx/gui/ui/TabbedPane.java | 71 +++++++++++++----- 20 files changed, 323 insertions(+), 149 deletions(-) rename jadx-gui/src/main/java/jadx/gui/ui/{ContentArea.java => CodeArea.java} (92%) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java diff --git a/NOTICE b/NOTICE index 664344923..5c0b69a0f 100644 --- a/NOTICE +++ b/NOTICE @@ -193,9 +193,21 @@ limitations under the License. ******************************************************************************* +Image Viewer (https://github.com/kazocsaba/imageviewer) + +******************************************************************************* +Copyright (c) 2008-2012 Kazó Csaba + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************* + +JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/ + Icons copied from several places: - Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html) - famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/) - -JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/ diff --git a/jadx-core/src/main/java/jadx/api/ResourceFile.java b/jadx-core/src/main/java/jadx/api/ResourceFile.java index 6e739ff17..48da90881 100644 --- a/jadx-core/src/main/java/jadx/api/ResourceFile.java +++ b/jadx-core/src/main/java/jadx/api/ResourceFile.java @@ -48,7 +48,7 @@ public class ResourceFile { return type; } - public ResContainer getContent() { + public ResContainer loadContent() { return ResourcesLoader.loadContent(decompiler, this); } diff --git a/jadx-core/src/main/java/jadx/api/ResourceFileContent.java b/jadx-core/src/main/java/jadx/api/ResourceFileContent.java index 8525ec46d..409aa5435 100644 --- a/jadx-core/src/main/java/jadx/api/ResourceFileContent.java +++ b/jadx-core/src/main/java/jadx/api/ResourceFileContent.java @@ -13,7 +13,7 @@ public class ResourceFileContent extends ResourceFile { } @Override - public ResContainer getContent() { + public ResContainer loadContent() { return ResContainer.singleFile(getName(), content); } } diff --git a/jadx-core/src/main/java/jadx/api/ResourceType.java b/jadx-core/src/main/java/jadx/api/ResourceType.java index 702d72bba..5e058b221 100644 --- a/jadx-core/src/main/java/jadx/api/ResourceType.java +++ b/jadx-core/src/main/java/jadx/api/ResourceType.java @@ -36,13 +36,13 @@ public enum ResourceType { case CODE: case LIB: case FONT: - case IMG: case UNKNOWN: return false; case MANIFEST: case XML: case ARSC: + case IMG: return true; } return false; diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index 8b1afb291..4f7e39f97 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -108,6 +108,9 @@ public final class ResourcesLoader { case ARSC: return new ResTableParser().decodeFiles(inputStream); + + case IMG: + return ResContainer.singleImageFile(rf.getName(), inputStream); } if (size > LOAD_SIZE_LIMIT) { return ResContainer.singleFile(rf.getName(), diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java index 59da069e6..3fb1e9150 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -20,7 +20,6 @@ import static jadx.core.utils.files.FileUtils.close; public class CodeWriter { private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class); - private static final int MAX_FILENAME_LENGTH = 128; public static final String NL = System.getProperty("line.separator"); public static final String INDENT = " "; @@ -286,22 +285,10 @@ public class CodeWriter { if (code == null) { finish(); } - String name = file.getName(); - if (name.length() > MAX_FILENAME_LENGTH) { - int dotIndex = name.indexOf('.'); - int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1; - if (cutAt <= 0) { - name = name.substring(0, MAX_FILENAME_LENGTH - 1); - } else { - name = name.substring(0, cutAt) + name.substring(dotIndex); - } - file = new File(file.getParentFile(), name); - } - + File outFile = FileUtils.prepareFile(file); PrintWriter out = null; try { - FileUtils.makeDirsForFile(file); - out = new PrintWriter(file, "UTF-8"); + out = new PrintWriter(outFile, "UTF-8"); out.println(code); } catch (Exception e) { LOG.error("Save file error", e); @@ -309,4 +296,5 @@ public class CodeWriter { close(out); } } + } diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java index 8bc4c5ebc..775210698 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java @@ -12,6 +12,7 @@ import java.io.OutputStream; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,6 +20,7 @@ public class FileUtils { private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class); public static final int READ_BUFFER_SIZE = 8 * 1024; + private static final int MAX_FILENAME_LENGTH = 128; private FileUtils() { } @@ -81,4 +83,21 @@ public class FileUtils { LOG.error("Close exception for {}", c, e); } } + + @NotNull + public static File prepareFile(File file) { + String name = file.getName(); + if (name.length() > MAX_FILENAME_LENGTH) { + int dotIndex = name.indexOf('.'); + int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1; + if (cutAt <= 0) { + name = name.substring(0, MAX_FILENAME_LENGTH - 1); + } else { + name = name.substring(0, cutAt) + name.substring(dotIndex); + } + file = new File(file.getParentFile(), name); + } + makeDirsForFile(file); + return file; + } } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java index d0bf76d23..903bbcae8 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResContainer.java @@ -1,34 +1,52 @@ package jadx.core.xmlgen; import jadx.core.codegen.CodeWriter; +import jadx.core.utils.exceptions.JadxRuntimeException; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.File; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ResContainer implements Comparable { private final String name; - @Nullable - private CodeWriter content; - private final List subFiles; - private ResContainer(String name, @Nullable CodeWriter content, List subFiles) { + @Nullable + private CodeWriter content; + @Nullable + private BufferedImage image; + + private ResContainer(String name, List subFiles) { this.name = name; - this.content = content; this.subFiles = subFiles; } public static ResContainer singleFile(String name, CodeWriter content) { - return new ResContainer(name, content, Collections.emptyList()); + ResContainer resContainer = new ResContainer(name, Collections.emptyList()); + resContainer.content = content; + return resContainer; + } + + public static ResContainer singleImageFile(String name, InputStream content) { + ResContainer resContainer = new ResContainer(name, Collections.emptyList()); + try { + resContainer.image = ImageIO.read(content); + } catch (Exception e) { + throw new JadxRuntimeException("Image load error", e); + } + return resContainer; } public static ResContainer multiFile(String name) { - return new ResContainer(name, null, new ArrayList()); + return new ResContainer(name, new ArrayList()); } public String getName() { @@ -48,21 +66,22 @@ public class ResContainer implements Comparable { this.content = content; } + @Nullable + public BufferedImage getImage() { + return image; + } + public List getSubFiles() { return subFiles; } @Override - public int compareTo(ResContainer o) { + public int compareTo(@NotNull ResContainer o) { return name.compareTo(o.name); } @Override public String toString() { - return "ResContainer{" + - "name='" + name + "'" + - ", content=" + content + - ", subFiles=" + subFiles + - "}"; + return "Res{" + name + ", subFiles=" + subFiles + "}"; } } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java index 97260ded6..57936d8fb 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java @@ -4,10 +4,21 @@ import jadx.api.ResourceFile; import jadx.api.ResourceType; import jadx.core.codegen.CodeWriter; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; import java.util.List; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static jadx.core.utils.files.FileUtils.prepareFile; + public class ResourcesSaver implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(ResourcesSaver.class); + private final ResourceFile resourceFile; private File outDir; @@ -21,7 +32,7 @@ public class ResourcesSaver implements Runnable { if (!ResourceType.isSupportedForUnpack(resourceFile.getType())) { return; } - ResContainer rc = resourceFile.getContent(); + ResContainer rc = resourceFile.loadContent(); if (rc != null) { saveResources(rc); } @@ -33,14 +44,32 @@ public class ResourcesSaver implements Runnable { } List subFiles = rc.getSubFiles(); if (subFiles.isEmpty()) { - CodeWriter cw = rc.getContent(); - if (cw != null) { - cw.save(new File(outDir, rc.getFileName())); - } + save(rc, outDir); } else { for (ResContainer subFile : subFiles) { saveResources(subFile); } } } + + private void save(ResContainer rc, File outDir) { + File outFile = new File(outDir, rc.getFileName()); + BufferedImage image = rc.getImage(); + if (image != null) { + String ext = FilenameUtils.getExtension(outFile.getName()); + try { + outFile = prepareFile(outFile); + ImageIO.write(image, ext, outFile); + } catch (IOException e) { + LOG.error("Failed to save image: {}", rc.getName(), e); + } + return; + } + CodeWriter cw = rc.getContent(); + if (cw != null) { + cw.save(outFile); + return; + } + LOG.warn("Resource '{}' not saved, unknown type", rc.getName()); + } } diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 1b7c93d13..50bf72e99 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -8,6 +8,7 @@ dependencies { compile 'com.fifesoft:rsyntaxtextarea:2.5.8' compile 'com.google.code.gson:gson:2.3.1' compile files('libs/jfontchooser-1.0.5.jar') + compile 'hu.kazocsaba:image-viewer:1.2.3' } applicationDistribution.with { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 654f6ce76..c85af5f72 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -81,7 +81,7 @@ public class JResource extends JNode implements Comparable { if (!loaded && resFile != null && type == JResType.FILE) { loaded = true; if (isSupportedForView(resFile.getType())) { - ResContainer rc = resFile.getContent(); + ResContainer rc = resFile.loadContent(); if (rc != null) { addSubFiles(rc, this, 0); } @@ -149,9 +149,13 @@ public class JResource extends JNode implements Comparable { @Override public String getSyntaxName() { + if (resFile == null) { + return null; + } switch (resFile.getType()) { case CODE: return super.getSyntaxName(); + case MANIFEST: case XML: return SyntaxConstants.SYNTAX_STYLE_XML; @@ -205,23 +209,27 @@ public class JResource extends JNode implements Comparable { return FILE_ICON; } - private boolean isSupportedForView(ResourceType type) { + public static boolean isSupportedForView(ResourceType type) { switch (type) { case CODE: case FONT: - case IMG: case LIB: return false; case MANIFEST: case XML: case ARSC: + case IMG: case UNKNOWN: return true; } return true; } + public ResourceFile getResFile() { + return resFile; + } + @Override public JClass getJParent() { return null; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java index cb996bc9e..ee5e3b2a3 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -52,7 +52,11 @@ public class JRoot extends JNode { String name = parts[i]; JResource subRF = getResourceByName(curRf, name); if (subRF == null) { - subRF = new JResource(rf, name, i != count - 1 ? JResType.DIR : JResType.FILE); + if (i != count - 1) { + subRF = new JResource(null, name, JResType.DIR); + } else { + subRF = new JResource(rf, name, JResType.FILE); + } curRf.getFiles().add(subRF); } curRf = subRF; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java b/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java similarity index 92% rename from jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java rename to jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java index 18cae067b..0be5f2759 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java @@ -34,18 +34,18 @@ import org.fife.ui.rsyntaxtextarea.TokenTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class ContentArea extends RSyntaxTextArea { - private static final Logger LOG = LoggerFactory.getLogger(ContentArea.class); +class CodeArea extends RSyntaxTextArea { + private static final Logger LOG = LoggerFactory.getLogger(CodeArea.class); private static final long serialVersionUID = 6312736869579635796L; public static final Color BACKGROUND = new Color(0xFAFAFA); public static final Color JUMP_TOKEN_FGD = new Color(0x491BA1); - private final ContentPanel contentPanel; + private final CodePanel contentPanel; private final JNode node; - ContentArea(ContentPanel panel) { + CodeArea(CodePanel panel) { this.contentPanel = panel; this.node = panel.getNode(); @@ -76,8 +76,8 @@ class ContentArea extends RSyntaxTextArea { setText(node.getContent()); } - private void addMenuItems(ContentArea contentArea, JClass jCls) { - Action findUsage = new FindUsageAction(contentArea, jCls); + private void addMenuItems(CodeArea codeArea, JClass jCls) { + Action findUsage = new FindUsageAction(codeArea, jCls); // TODO: hotkey works only when popup menu is shown // findUsage.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_F7, KeyEvent.ALT_DOWN_MASK)); @@ -197,14 +197,14 @@ class ContentArea extends RSyntaxTextArea { private class FindUsageAction extends AbstractAction implements PopupMenuListener { private static final long serialVersionUID = 4692546569977976384L; - private final ContentArea contentArea; + private final CodeArea codeArea; private final JClass jCls; private JavaNode node; - public FindUsageAction(ContentArea contentArea, JClass jCls) { + public FindUsageAction(CodeArea codeArea, JClass jCls) { super("Find Usage"); - this.contentArea = contentArea; + this.codeArea = codeArea; this.jCls = jCls; } @@ -222,11 +222,11 @@ class ContentArea extends RSyntaxTextArea { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { node = null; - Point pos = contentArea.getMousePosition(); + Point pos = codeArea.getMousePosition(); if (pos != null) { - Token token = contentArea.viewToToken(pos); + Token token = codeArea.viewToToken(pos); if (token != null) { - node = getJavaNodeAtOffset(jCls, contentArea, token.getOffset()); + node = getJavaNodeAtOffset(jCls, codeArea, token.getOffset()); } } setEnabled(node != null); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java new file mode 100644 index 000000000..0f84ac90e --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java @@ -0,0 +1,72 @@ +package jadx.gui.ui; + +import jadx.gui.treemodel.JNode; +import jadx.gui.utils.Utils; + +import javax.swing.AbstractAction; +import javax.swing.JScrollPane; +import javax.swing.KeyStroke; +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +class CodePanel extends ContentPanel { + + private static final long serialVersionUID = 5310536092010045565L; + + private final SearchBar searchBar; + private final CodeArea codeArea; + private final JScrollPane scrollPane; + + CodePanel(TabbedPane panel, JNode jnode) { + super(panel, jnode); + + codeArea = new CodeArea(this); + searchBar = new SearchBar(codeArea); + + scrollPane = new JScrollPane(codeArea); + scrollPane.setRowHeaderView(new LineNumbers(codeArea)); + + setLayout(new BorderLayout()); + add(searchBar, BorderLayout.NORTH); + add(scrollPane); + + KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK); + Utils.addKeyBinding(codeArea, key, "SearchAction", new SearchAction()); + } + + private class SearchAction extends AbstractAction { + private static final long serialVersionUID = 8650568214755387093L; + + @Override + public void actionPerformed(ActionEvent e) { + searchBar.toggle(); + } + } + + @Override + public void loadSettings() { + codeArea.loadSettings(); + } + + TabbedPane getTabbedPane() { + return tabbedPane; + } + + JNode getNode() { + return node; + } + + SearchBar getSearchBar() { + return searchBar; + } + + CodeArea getCodeArea() { + return codeArea; + } + + JScrollPane getScrollPane() { + return scrollPane; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index 9a3646610..1c5778f79 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -162,7 +162,7 @@ public abstract class CommonSearchDialog extends JDialog { resultsTable.setShowHorizontalLines(false); resultsTable.setDragEnabled(false); resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - resultsTable.setBackground(ContentArea.BACKGROUND); + resultsTable.setBackground(CodeArea.BACKGROUND); resultsTable.setColumnSelectionAllowed(false); resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); resultsTable.setAutoscrolls(false); @@ -352,7 +352,7 @@ public abstract class CommonSearchDialog extends JDialog { comp.setBackground(selectedBackground); comp.setForeground(selectedForeground); } else { - comp.setBackground(ContentArea.BACKGROUND); + comp.setBackground(CodeArea.BACKGROUND); comp.setForeground(foreground); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java index 92b03871c..885283c09 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java @@ -1,52 +1,20 @@ package jadx.gui.ui; import jadx.gui.treemodel.JNode; -import jadx.gui.utils.Utils; -import javax.swing.AbstractAction; import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.KeyStroke; -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -class ContentPanel extends JPanel { +abstract class ContentPanel extends JPanel { - private static final long serialVersionUID = 5310536092010045565L; + protected final TabbedPane tabbedPane; + protected final JNode node; - private final TabbedPane tabbedPane; - private final JNode node; - private final SearchBar searchBar; - private final ContentArea contentArea; - private final JScrollPane scrollPane; - - ContentPanel(TabbedPane panel, JNode node) { + ContentPanel(TabbedPane panel, JNode jnode) { tabbedPane = panel; - this.node = node; - contentArea = new ContentArea(this); - searchBar = new SearchBar(contentArea); - - scrollPane = new JScrollPane(contentArea); - scrollPane.setRowHeaderView(new LineNumbers(contentArea)); - - setLayout(new BorderLayout()); - add(searchBar, BorderLayout.NORTH); - add(scrollPane); - - KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK); - Utils.addKeyBinding(contentArea, key, "SearchAction", new SearchAction()); + node = jnode; } - private class SearchAction extends AbstractAction { - private static final long serialVersionUID = 8650568214755387093L; - - @Override - public void actionPerformed(ActionEvent e) { - searchBar.toggle(); - } - } + public abstract void loadSettings(); TabbedPane getTabbedPane() { return tabbedPane; @@ -55,16 +23,4 @@ class ContentPanel extends JPanel { JNode getNode() { return node; } - - SearchBar getSearchBar() { - return searchBar; - } - - ContentArea getContentArea() { - return contentArea; - } - - JScrollPane getScrollPane() { - return scrollPane; - } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java new file mode 100644 index 000000000..442d1394c --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java @@ -0,0 +1,27 @@ +package jadx.gui.ui; + +import hu.kazocsaba.imageviewer.ImageViewer; +import jadx.api.ResourceFile; +import jadx.gui.treemodel.JResource; + +import java.awt.BorderLayout; +import java.awt.image.BufferedImage; + +public class ImagePanel extends ContentPanel { + + ImagePanel(TabbedPane panel, JResource res) { + super(panel, res); + + ResourceFile resFile = res.getResFile(); + BufferedImage img = resFile.loadContent().getImage(); + ImageViewer imageViewer = new ImageViewer(img); + imageViewer.setZoomFactor(2.); + + setLayout(new BorderLayout()); + add(imageViewer.getComponent()); + } + + @Override + public void loadSettings() { + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/LineNumbers.java b/jadx-gui/src/main/java/jadx/gui/ui/LineNumbers.java index 2f971b5f5..b651ea5ba 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/LineNumbers.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/LineNumbers.java @@ -32,18 +32,18 @@ public class LineNumbers extends JPanel implements CaretListener { private static final int HEIGHT = Integer.MAX_VALUE - 1000000; private static final Color FOREGROUND = Color.GRAY; - private static final Color BACKGROUND = ContentArea.BACKGROUND; + private static final Color BACKGROUND = CodeArea.BACKGROUND; private static final Color CURRENT_LINE_FOREGROUND = new Color(227, 0, 0); - private ContentArea contentArea; + private CodeArea codeArea; private boolean useSourceLines = true; private int lastDigits; private int lastLine; private Map fonts; - public LineNumbers(ContentArea component) { - this.contentArea = component; + public LineNumbers(CodeArea component) { + this.codeArea = component; setFont(component.getFont()); setBackground(BACKGROUND); setForeground(FOREGROUND); @@ -70,7 +70,7 @@ public class LineNumbers extends JPanel implements CaretListener { } private void setPreferredWidth() { - Element root = contentArea.getDocument().getDefaultRootElement(); + Element root = codeArea.getDocument().getDefaultRootElement(); int lines = root.getElementCount(); int digits = Math.max(String.valueOf(lines).length(), 3); if (lastDigits != digits) { @@ -92,12 +92,12 @@ public class LineNumbers extends JPanel implements CaretListener { @Override public void paintComponent(Graphics g) { super.paintComponent(g); - FontMetrics fontMetrics = contentArea.getFontMetrics(contentArea.getFont()); + FontMetrics fontMetrics = codeArea.getFontMetrics(codeArea.getFont()); Insets insets = getInsets(); int availableWidth = getSize().width - insets.left - insets.right; Rectangle clip = g.getClipBounds(); - int rowStartOffset = contentArea.viewToModel(new Point(0, clip.y)); - int endOffset = contentArea.viewToModel(new Point(0, clip.y + clip.height)); + int rowStartOffset = codeArea.viewToModel(new Point(0, clip.y)); + int endOffset = codeArea.viewToModel(new Point(0, clip.y + clip.height)); while (rowStartOffset <= endOffset) { try { @@ -111,7 +111,7 @@ public class LineNumbers extends JPanel implements CaretListener { int x = availableWidth - stringWidth + insets.left; int y = getOffsetY(rowStartOffset, fontMetrics); g.drawString(lineNumber, x, y); - rowStartOffset = Utilities.getRowEnd(contentArea, rowStartOffset) + 1; + rowStartOffset = Utilities.getRowEnd(codeArea, rowStartOffset) + 1; } catch (Exception e) { break; } @@ -119,19 +119,19 @@ public class LineNumbers extends JPanel implements CaretListener { } private boolean isCurrentLine(int rowStartOffset) { - int caretPosition = contentArea.getCaretPosition(); - Element root = contentArea.getDocument().getDefaultRootElement(); + int caretPosition = codeArea.getCaretPosition(); + Element root = codeArea.getDocument().getDefaultRootElement(); return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition); } protected String getTextLineNumber(int rowStartOffset) { - Element root = contentArea.getDocument().getDefaultRootElement(); + Element root = codeArea.getDocument().getDefaultRootElement(); int index = root.getElementIndex(rowStartOffset); Element line = root.getElement(index); if (line.getStartOffset() == rowStartOffset) { int lineNumber = index + 1; if (useSourceLines) { - Integer sourceLine = contentArea.getSourceLine(lineNumber); + Integer sourceLine = codeArea.getSourceLine(lineNumber); if (sourceLine != null) { return String.valueOf(sourceLine); } @@ -143,7 +143,7 @@ public class LineNumbers extends JPanel implements CaretListener { } private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException { - Rectangle r = contentArea.modelToView(rowStartOffset); + Rectangle r = codeArea.modelToView(rowStartOffset); if (r == null) { throw new BadLocationException("Can't get Y offset", rowStartOffset); } @@ -156,7 +156,7 @@ public class LineNumbers extends JPanel implements CaretListener { if (fonts == null) { fonts = new HashMap(); } - Element root = contentArea.getDocument().getDefaultRootElement(); + Element root = codeArea.getDocument().getDefaultRootElement(); int index = root.getElementIndex(rowStartOffset); Element line = root.getElement(index); for (int i = 0; i < line.getElementCount(); i++) { @@ -168,7 +168,7 @@ public class LineNumbers extends JPanel implements CaretListener { FontMetrics fm = fonts.get(key); if (fm == null) { Font font = new Font(fontFamily, Font.PLAIN, fontSize); - fm = contentArea.getFontMetrics(font); + fm = codeArea.getFontMetrics(font); fonts.put(key, fm); } descent = Math.max(descent, fm.getDescent()); @@ -179,8 +179,8 @@ public class LineNumbers extends JPanel implements CaretListener { @Override public void caretUpdate(CaretEvent e) { - int caretPosition = contentArea.getCaretPosition(); - Element root = contentArea.getDocument().getDefaultRootElement(); + int caretPosition = codeArea.getCaretPosition(); + Element root = codeArea.getDocument().getDefaultRootElement(); int currentLine = root.getElementIndex(caretPosition); if (lastLine != currentLine) { repaint(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 0d67517ce..c13644d84 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -1,5 +1,6 @@ package jadx.gui.ui; +import jadx.api.ResourceFile; import jadx.gui.JadxWrapper; import jadx.gui.jobs.BackgroundWorker; import jadx.gui.jobs.DecompileJob; @@ -139,7 +140,7 @@ public class MainWindow extends JFrame { setLocationAndPosition(); setVisible(true); setLocationRelativeTo(null); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); if (settings.getInput().isEmpty()) { openFile(); @@ -169,7 +170,7 @@ public class MainWindow extends JFrame { public void openFile() { JFileChooser fileChooser = new JFileChooser(); fileChooser.setAcceptAllFileFilterUsed(true); - String[] exts = {"apk", "dex", "jar", "class", "zip"}; + String[] exts = {"apk", "dex", "jar", "class", "zip", "aar"}; String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')'); fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts)); fileChooser.setToolTipText(NLS.str("file.open")); @@ -245,7 +246,7 @@ public class MainWindow extends JFrame { if (ret == JFileChooser.APPROVE_OPTION) { settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath()); ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100); - progressMonitor.setMillisToPopup(500); + progressMonitor.setMillisToPopup(0); wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor); } } @@ -296,11 +297,11 @@ public class MainWindow extends JFrame { Object obj = tree.getLastSelectedPathComponent(); if (obj instanceof JResource) { JResource res = (JResource) obj; - if (res.getContent() != null) { - tabbedPane.codeJump(new Position(res, res.getLine())); + ResourceFile resFile = res.getResFile(); + if (resFile != null && JResource.isSupportedForView(resFile.getType())) { + tabbedPane.showResource(res); } - } - if (obj instanceof JNode) { + } else if (obj instanceof JNode) { JNode node = (JNode) obj; JClass cls = node.getRootClass(); if (cls != null) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index 52215b9d2..677de94bb 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -1,6 +1,9 @@ package jadx.gui.ui; +import jadx.api.ResourceFile; +import jadx.api.ResourceType; import jadx.gui.treemodel.JNode; +import jadx.gui.treemodel.JResource; import jadx.gui.utils.JumpManager; import jadx.gui.utils.NLS; import jadx.gui.utils.Position; @@ -72,23 +75,39 @@ class TabbedPane extends JTabbedPane { } private void showCode(final Position pos) { - final ContentPanel contentPanel = getCodePanel(pos.getNode()); + final CodePanel contentPanel = (CodePanel) getContentPanel(pos.getNode()); + if (contentPanel == null) { + return; + } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { setSelectedComponent(contentPanel); - ContentArea contentArea = contentPanel.getContentArea(); + CodeArea codeArea = contentPanel.getCodeArea(); int line = pos.getLine(); if (line < 0) { try { - line = 1 + contentArea.getLineOfOffset(-line); + line = 1 + codeArea.getLineOfOffset(-line); } catch (BadLocationException e) { LOG.error("Can't get line for: {}", pos, e); line = pos.getNode().getLine(); } } - contentArea.scrollToLine(line); - contentArea.requestFocus(); + codeArea.scrollToLine(line); + codeArea.requestFocus(); + } + }); + } + + public void showResource(JResource res) { + final ContentPanel contentPanel = getContentPanel(res); + if (contentPanel == null) { + return; + } + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + setSelectedComponent(contentPanel); } }); } @@ -105,10 +124,10 @@ class TabbedPane extends JTabbedPane { @Nullable private Position getCurrentPosition() { ContentPanel selectedCodePanel = getSelectedCodePanel(); - if (selectedCodePanel == null) { - return null; + if (selectedCodePanel instanceof CodePanel) { + return ((CodePanel) selectedCodePanel).getCodeArea().getCurrentPosition(); } - return selectedCodePanel.getContentArea().getCurrentPosition(); + return null; } public void navBack() { @@ -125,11 +144,7 @@ class TabbedPane extends JTabbedPane { } } - public JumpManager getJumpManager() { - return jumps; - } - - private void addCodePanel(ContentPanel contentPanel) { + private void addContentPanel(ContentPanel contentPanel) { openTabs.put(contentPanel.getNode(), contentPanel); add(contentPanel); } @@ -139,16 +154,36 @@ class TabbedPane extends JTabbedPane { remove(contentPanel); } - private ContentPanel getCodePanel(JNode cls) { - ContentPanel panel = openTabs.get(cls); + @Nullable + private ContentPanel getContentPanel(JNode node) { + ContentPanel panel = openTabs.get(node); if (panel == null) { - panel = new ContentPanel(this, cls); - addCodePanel(panel); + panel = makeContentPanel(node); + if (panel == null) { + return null; + } + addContentPanel(panel); setTabComponentAt(indexOfComponent(panel), makeTabComponent(panel)); } return panel; } + @Nullable + private ContentPanel makeContentPanel(JNode node) { + if (node instanceof JResource) { + JResource res = (JResource) node; + ResourceFile resFile = res.getResFile(); + if (resFile != null) { + if (resFile.getType() == ResourceType.IMG) { + return new ImagePanel(this, res); + } + } else { + return null; + } + } + return new CodePanel(this, node); + } + @Nullable ContentPanel getSelectedCodePanel() { return (ContentPanel) getSelectedComponent(); @@ -271,7 +306,7 @@ class TabbedPane extends JTabbedPane { public void loadSettings() { for (ContentPanel panel : openTabs.values()) { - panel.getContentArea().loadSettings(); + panel.loadSettings(); } } }