package the.bytecode.club.bytecodeviewer.gui.resourcelist; import java.awt.BorderLayout; import java.awt.Color; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.util.Enumeration; import java.util.Map.Entry; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import me.konloch.kontainer.io.DiskWriter; import org.apache.commons.io.FilenameUtils; import the.bytecode.club.bytecodeviewer.BytecodeViewer; import the.bytecode.club.bytecodeviewer.decompilers.Decompiler; import the.bytecode.club.bytecodeviewer.gui.contextmenu.ContextMenu; import the.bytecode.club.bytecodeviewer.resources.ResourceContainer; import the.bytecode.club.bytecodeviewer.resources.importing.Import; import the.bytecode.club.bytecodeviewer.translation.TranslatedComponents; import the.bytecode.club.bytecodeviewer.translation.TranslatedStrings; import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJCheckBox; import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJTextField; import the.bytecode.club.bytecodeviewer.translation.components.TranslatedVisibleComponent; import the.bytecode.club.bytecodeviewer.util.FileDrop; import the.bytecode.club.bytecodeviewer.util.LazyNameUtil; import the.bytecode.club.bytecodeviewer.util.MiscUtils; import static the.bytecode.club.bytecodeviewer.Constants.fs; import static the.bytecode.club.bytecodeviewer.Constants.tempDirectory; /*************************************************************************** * 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 . * ***************************************************************************/ /** * The file navigation pane. * * @author Konloch * @author WaterWolf * @author afffsdd * @since 09/26/2011 */ 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 JButton open = new JButton("+"); public final JButton close = new JButton("-"); public final ResourceTreeNode treeRoot = new ResourceTreeNode("Loaded Files:"); public final ResourceTree tree = new ResourceTree(treeRoot); public final JTextField quickSearch = new TranslatedJTextField("Quick file search (no file extension)", TranslatedComponents.QUICK_FILE_SEARCH_NO_FILE_EXTENSION); public final FileDrop fileDrop; public boolean cancel = false; public final KeyAdapter search = new SearchKeyAdapter(this); private void showContextMenu(ResourceTree tree, TreePath selPath, int x, int y) { if (selPath == null) return; ContextMenu.buildMenu(tree, selPath, null, rightClickMenu); rightClickMenu.show(this.tree, x, y); } //used to remove resources from the resource list public void removeFile(ResourceContainer resourceContainer) { while (BytecodeViewer.resourceContainers.values().remove(resourceContainer)); LazyNameUtil.removeName(resourceContainer.name); } public ResourceListPane() { super("Files", TranslatedComponents.FILES); tree.setRootVisible(false); tree.setShowsRootHandles(true); quickSearch.setForeground(Color.gray); attachTreeListeners(); attachQuickSearchListeners(); getContentPane().setLayout(new BorderLayout()); getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER); JPanel exactPanel = new JPanel(new BorderLayout()); JPanel quickSearchPanel = new JPanel(); JPanel buttonPanel = new JPanel(new BorderLayout()); quickSearchPanel.setLayout(new BorderLayout()); quickSearchPanel.add(quickSearch, BorderLayout.NORTH); exactPanel.add(exact, BorderLayout.WEST); buttonPanel.add(open, BorderLayout.EAST); buttonPanel.add(close, BorderLayout.WEST); exactPanel.add(buttonPanel, BorderLayout.EAST); quickSearchPanel.add(exactPanel, BorderLayout.SOUTH); getContentPane().add(quickSearchPanel, BorderLayout.SOUTH); this.setVisible(true); fileDrop = new FileDrop(this, this); } @Override public void filesDropped(final File[] files) { if (files.length < 1) return; BytecodeViewer.openFiles(files, true); } public void addResourceContainer(ResourceContainer container) { ResourceTreeNode root = container.treeNode = new ResourceTreeNode(container.name); treeRoot.add(root); tree.setCellRenderer(new ResourceListIconRenderer()); buildTree(container, root); treeRoot.sort(); tree.expandPath(new TreePath(tree.getModel().getRoot())); tree.updateUI(); //TODO add a setting to expand on resource import // expandAll(tree, true); } public void removeResource(ResourceContainer container) { container.treeNode.removeFromParent(); tree.updateUI(); } private void buildTree(ResourceContainer container, ResourceTreeNode root) { if (!container.resourceClasses.isEmpty()) { for (String name : container.resourceClasses.keySet()) { final String[] spl = name.split("/"); if (spl.length < 2) { root.add(new ResourceTreeNode(name + ".class")); } else { ResourceTreeNode parent = root; for (int i1 = 0; i1 < spl.length; i1++) { String s = spl[i1]; if (i1 == spl.length - 1) s += ".class"; ResourceTreeNode child = null; for (int i = 0; i < parent.getChildCount(); i++) { if (((ResourceTreeNode) parent.getChildAt(i)).getUserObject().equals(s)) { child = (ResourceTreeNode) parent.getChildAt(i); break; } } if (child == null) { child = new ResourceTreeNode(s); parent.add(child); } parent = child; } } } } if (!container.resourceFiles.isEmpty()) { for (final Entry entry : container.resourceFiles.entrySet()) { String name = entry.getKey(); final String[] spl = name.split("/"); if (spl.length < 2) { root.add(new ResourceTreeNode(name)); } else { ResourceTreeNode parent = root; for (final String s : spl) { ResourceTreeNode child = null; for (int i = 0; i < parent.getChildCount(); i++) { if (((ResourceTreeNode) parent.getChildAt(i)).getUserObject().equals(s)) { child = (ResourceTreeNode) parent.getChildAt(i); break; } } if (child == null) { child = new ResourceTreeNode(s); parent.add(child); } parent = child; } } } } } @SuppressWarnings("rawtypes") public void expandAll(final JTree tree, final TreePath parent, final boolean expand) { // Traverse children final TreeNode node = (TreeNode) parent.getLastPathComponent(); if (node.getChildCount() >= 0) { for (final Enumeration e = node.children(); e.hasMoreElements(); ) { final TreeNode n = (TreeNode) e.nextElement(); final TreePath path = parent.pathByAddingChild(n); expandAll(tree, path, expand); } } // Expansion or collapse must be done bottom-up if (expand) { tree.expandPath(parent); } else { tree.collapsePath(parent); } } public void removeNode(final JTree tree, final TreePath nodePath) { MutableTreeNode node = findNodeByPath(nodePath); if (node == null) return; node.removeFromParent(); tree.repaint(); tree.updateUI(); } private MutableTreeNode findNodeByPath(TreePath path) { MutableTreeNode node = treeRoot; for (int pathStep = 1; pathStep < path.getPathCount(); pathStep++) { TreeNode pathNode = (TreeNode) path.getPathComponent(pathStep); int childIndex = node.getIndex(pathNode); if (childIndex < 0) { return null; } node = (MutableTreeNode) node.getChildAt(childIndex); if (node == null) { return null; } } return node; } public void resetWorkspace() { treeRoot.removeAllChildren(); tree.repaint(); tree.updateUI(); } /** * Opens and decompiles the TreePath in a new tab */ public void quickDecompile(Decompiler decompiler, TreePath selPath, boolean quickEdit) { Decompiler tempDecompiler1 = BytecodeViewer.viewer.viewPane1.getSelectedDecompiler(); boolean editable1 = BytecodeViewer.viewer.viewPane1.isPaneEditable(); Decompiler tempDecompiler2 = BytecodeViewer.viewer.viewPane2.getSelectedDecompiler(); boolean editable2 = BytecodeViewer.viewer.viewPane2.isPaneEditable(); Decompiler tempDecompiler3 = BytecodeViewer.viewer.viewPane3.getSelectedDecompiler(); boolean editable3 = BytecodeViewer.viewer.viewPane3.isPaneEditable(); BytecodeViewer.viewer.viewPane1.setSelectedDecompiler(decompiler); BytecodeViewer.viewer.viewPane1.setPaneEditable(quickEdit); BytecodeViewer.viewer.viewPane2.setSelectedDecompiler(Decompiler.NONE); BytecodeViewer.viewer.viewPane2.setPaneEditable(false); BytecodeViewer.viewer.viewPane3.setSelectedDecompiler(Decompiler.NONE); BytecodeViewer.viewer.viewPane3.setPaneEditable(false); openPath(selPath); BytecodeViewer.viewer.viewPane1.setSelectedDecompiler(tempDecompiler1); BytecodeViewer.viewer.viewPane1.setPaneEditable(editable1); BytecodeViewer.viewer.viewPane2.setSelectedDecompiler(tempDecompiler2); BytecodeViewer.viewer.viewPane2.setPaneEditable(editable2); BytecodeViewer.viewer.viewPane3.setSelectedDecompiler(tempDecompiler3); BytecodeViewer.viewer.viewPane3.setPaneEditable(editable3); } public void openPath(TreePath path) { if (path == null || path.getPathCount() == 1) return; final StringBuilder nameBuffer = new StringBuilder(); for (int i = 2; i < path.getPathCount(); i++) { nameBuffer.append(path.getPathComponent(i)); if (i < path.getPathCount() - 1) nameBuffer.append("/"); } String cheapHax = path.getPathComponent(1).toString(); ResourceContainer container = null; for (ResourceContainer c : BytecodeViewer.resourceContainers.values()) { if (c.name.equals(cheapHax)) container = c; } String name = nameBuffer.toString(); boolean resourceMode = false; byte[] content = container.resourceClassBytes.get(name); if(content == null) { content = container.resourceFiles.get(name); resourceMode = true; } //view classes if (content != null && MiscUtils.getFileHeaderMagicNumber(content).equalsIgnoreCase("cafebabe") || name.endsWith(".class")) { try { if(resourceMode) { //TODO load this cn into the resource viewer //final ClassNode cn = ASMUtil.bytesToNode(content); } //display via name BytecodeViewer.viewer.workPane.addClassResource(container, name); } catch (Exception e) { e.printStackTrace(); BytecodeViewer.viewer.workPane.addFileResource(container, name); } } //view non-classfile resources else if(container.resourceFiles.containsKey(name)) { final String fn = name.toLowerCase(); final String extension = fn.contains(":") ? null : FilenameUtils.getExtension(fn); Import imp = Import.extensionMap.get(extension); if(imp == null) //show images, text files, or hex view BytecodeViewer.viewer.workPane.addFileResource(container, name); else //attempt to import known resources { int hash = (container.name + name).hashCode(); //TODO make a settings toggle to disable preservation of the original name // it should also detect if the file name is not compatible with the current OS and enable automatically File tempFile = new File(tempDirectory + fs + hash + fs + name + "." + extension); if(!tempFile.exists()) { DiskWriter.replaceFileBytes(tempFile.getAbsolutePath(), content, false); try { imp.getImporter().open(tempFile); } catch (Exception e) { e.printStackTrace(); //failsafe BytecodeViewer.viewer.workPane.addFileResource(container, name); } } else { //alert the user } } } } public void attachTreeListeners() { tree.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { if (e.isMetaDown()) { ResourceTree tree = (ResourceTree) e.getSource(); TreePath selPath = ResourceListPane.this.tree.getClosestPathForLocation(e.getX(), e.getY()); if (selPath == null) return; showContextMenu(tree, selPath, e.getX(), e.getY()); } } }); this.open.addActionListener(e -> { final TreeNode root = (TreeNode) tree.getModel().getRoot(); expandAll(tree, new TreePath(root), true); }); this.close.addActionListener(e -> { final TreeNode root = (TreeNode) tree.getModel().getRoot(); final TreePath path = new TreePath(root); expandAll(tree, path, false); tree.expandPath(path); }); this.tree.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if(e.getButton() == MouseEvent.BUTTON1) //right-click openPath(tree.getPathForLocation(e.getX(), e.getY())); } }); /*this.tree.addTreeSelectionListener(arg0 -> { if (cancel) { cancel = false; return; } openPath(arg0.getPath()); });*/ this.tree.addKeyListener(new KeyListener() { @Override public void keyReleased(KeyEvent e) { } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { if (e.getSource() instanceof ResourceTree) { ResourceTree tree = (ResourceTree) e.getSource(); openPath(tree.getSelectionPath()); } } else if ((int) e.getKeyChar() != 0 && (int) e.getKeyChar() != 8 && (int) e.getKeyChar() != 127 && (int) e.getKeyChar() != 65535 && !e.isControlDown() && !e.isAltDown()) { quickSearch.grabFocus(); quickSearch.setText("" + e.getKeyChar()); cancel = true; } else if (e.isControlDown() && (int) e.getKeyChar() == 6) //ctrl + f quickSearch.grabFocus(); else cancel = true; } }); } public void attachQuickSearchListeners() { quickSearch.addKeyListener(search); quickSearch.addFocusListener(new FocusListener() { @Override public void focusGained(final FocusEvent arg0) { if (quickSearch.getText().equals(TranslatedStrings.QUICK_FILE_SEARCH_NO_FILE_EXTENSION.toString())) { quickSearch.setText(""); quickSearch.setForeground(Color.black); } } @Override public void focusLost(final FocusEvent arg0) { if (quickSearch.getText().isEmpty()) { quickSearch.setText(TranslatedStrings.QUICK_FILE_SEARCH_NO_FILE_EXTENSION.toString()); quickSearch.setForeground(Color.gray); } } }); } }