539 lines
20 KiB
Java
539 lines
20 KiB
Java
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 <http://www.gnu.org/licenses/>. *
|
|
***************************************************************************/
|
|
|
|
/**
|
|
* 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<String, byte[]> 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);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|