package the.bytecode.club.bytecodeviewer.gui;
import com.jhe.hexed.JHexEditor;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.objectweb.asm.tree.ClassNode;
import the.bytecode.club.bytecodeviewer.BytecodeViewer;
import the.bytecode.club.bytecodeviewer.Resources;
import the.bytecode.club.bytecodeviewer.decompilers.Decompiler;
import javax.swing.*;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/***************************************************************************
* 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/>. *
***************************************************************************/
/**
* This represents the opened classfile.
*
* @author Konloch
* @author WaterWolf
*/
public class ClassViewer extends Viewer {
private static final long serialVersionUID = -8650495368920680024L;
private List<Thread> decompileThreads = new ArrayList<>();
public void setPanes() {
for (int i = 0; i < BytecodeViewer.viewer.allPanes.size(); i++) {
ButtonGroup group = BytecodeViewer.viewer.allPanes.get(i);
for (Map.Entry<JRadioButtonMenuItem, Decompiler> entry : BytecodeViewer.viewer.allDecompilers.get(group).entrySet()) {
if (group.isSelected(entry.getKey().getModel())) {
decompilers.set(i, entry.getValue());
}
}
}
}
public boolean isPaneEditable(int pane) {
setPanes();
ButtonGroup buttonGroup = BytecodeViewer.viewer.allPanes.get(pane);
Decompiler selected = decompilers.get(pane);
if (buttonGroup != null && BytecodeViewer.viewer.editButtons.get(buttonGroup) != null && BytecodeViewer.viewer.editButtons.get(buttonGroup).get(selected)!= null && BytecodeViewer.viewer.editButtons.get(buttonGroup).get(selected).isSelected()) {
return true;
}
return false;
}
public void requestFocus(int pane) {
this.fields.get(pane).requestFocus();
}
public void updatePane(int pane, RSyntaxTextArea text, Decompiler decompiler) {
if (decompiler == Decompiler.KRAKATAU_DA) {
krakataus.set(pane, text);
} else if (decompiler == Decompiler.SMALI) {
smalis.set(pane, text);
} else {
javas.set(pane, text);
}
}
/**
* Whoever wrote this function, THANK YOU!
*
* @param splitter
* @param proportion
* @return
*/
public static JSplitPane setDividerLocation(final JSplitPane splitter,
final double proportion) {
if (splitter.isShowing()) {
if (splitter.getWidth() > 0 && splitter.getHeight() > 0) {
splitter.setDividerLocation(proportion);
} else {
splitter.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent ce) {
splitter.removeComponentListener(this);
setDividerLocation(splitter, proportion);
}
});
}
} else {
splitter.addHierarchyListener(new HierarchyListener() {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0
&& splitter.isShowing()) {
splitter.removeHierarchyListener(this);
setDividerLocation(splitter, proportion);
}
}
});
}
return splitter;
}
JSplitPane sp;
JSplitPane sp2;
public List<Decompiler> decompilers = Arrays.asList(null, null, null);
public List<JPanel> panels = Arrays.asList(new JPanel(new BorderLayout()), new JPanel(new BorderLayout()), new JPanel(new BorderLayout()));
public List<JPanel> searches = Arrays.asList(new JPanel(new BorderLayout()), new JPanel(new BorderLayout()), new JPanel(new BorderLayout()));
public List<JCheckBox> exacts = Arrays.asList(new JCheckBox("Exact"), new JCheckBox("Exact"), new JCheckBox("Exact"));
public List<JTextField> fields = Arrays.asList(new JTextField(), new JTextField(), new JTextField());
public List<RSyntaxTextArea> javas = Arrays.asList(null, null, null);
public List<RSyntaxTextArea> smalis = Arrays.asList(null, null, null);
public List<RSyntaxTextArea> krakataus = Arrays.asList(null, null, null);
/**
* This was really interesting to write.
*
* @author Konloch
*/
public void search(int pane, String search, boolean next) {
try {
Component[] com = panels.get(pane).getComponents();
for (Component c : com) {
if (c instanceof RTextScrollPane) {
RSyntaxTextArea area = (RSyntaxTextArea) ((RTextScrollPane) c)
.getViewport().getComponent(0);
if (search.isEmpty()) {
highlight(pane, area, "");
return;
}
int startLine = area.getDocument().getDefaultRootElement()
.getElementIndex(area.getCaretPosition()) + 1;
int currentLine = 1;
boolean canSearch = false;
String[] test = null;
if (area.getText().split("\n").length >= 2)
test = area.getText().split("\n");
else
test = area.getText().split("\r");
int lastGoodLine = -1;
int firstPos = -1;
boolean found = false;
if (next) {
for (String s : test) {
if (pane == 0 && !exacts.get(0).isSelected() || pane == 1
&& !exacts.get(1).isSelected()) {
s = s.toLowerCase();
search = search.toLowerCase();
}
if (currentLine == startLine) {
canSearch = true;
} else if (s.contains(search)) {
if (canSearch) {
area.setCaretPosition(area.getDocument()
.getDefaultRootElement()
.getElement(currentLine - 1)
.getStartOffset());
canSearch = false;
found = true;
}
if (firstPos == -1)
firstPos = currentLine;
}
currentLine++;
}
if (!found && firstPos != -1) {
area.setCaretPosition(area.getDocument()
.getDefaultRootElement()
.getElement(firstPos - 1).getStartOffset());
}
} else {
canSearch = true;
for (String s : test) {
if (pane == 0 && !exacts.get(0).isSelected() || pane == 1
&& !exacts.get(1).isSelected() || pane == 2
&& !exacts.get(2).isSelected()) {
s = s.toLowerCase();
search = search.toLowerCase();
}
if (s.contains(search)) {
if (lastGoodLine != -1 && canSearch)
area.setCaretPosition(area.getDocument()
.getDefaultRootElement()
.getElement(lastGoodLine - 1)
.getStartOffset());
lastGoodLine = currentLine;
if (currentLine >= startLine)
canSearch = false;
}
currentLine++;
}
if (lastGoodLine != -1
&& area.getDocument()
.getDefaultRootElement()
.getElementIndex(
area.getCaretPosition()) + 1 == startLine) {
area.setCaretPosition(area.getDocument()
.getDefaultRootElement()
.getElement(lastGoodLine - 1)
.getStartOffset());
}
}
highlight(pane, area, search);
}
}
} catch (Exception e) {
new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e);
}
}
private DefaultHighlighter.DefaultHighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter(
new Color(255, 62, 150));
public void highlight(int pane, JTextComponent textComp, String pattern) {
if (pattern.isEmpty()) {
textComp.getHighlighter().removeAllHighlights();
return;
}
try {
Highlighter hilite = textComp.getHighlighter();
hilite.removeAllHighlights();
javax.swing.text.Document doc = textComp.getDocument();
String text = doc.getText(0, doc.getLength());
int pos = 0;
if ((pane == 0 && !exacts.get(0).isSelected()) || pane == 1
&& !exacts.get(1).isSelected() || pane == 2
&& !exacts.get(2).isSelected()) {
pattern = pattern.toLowerCase();
text = text.toLowerCase();
}
// Search for pattern
while ((pos = text.indexOf(pattern, pos)) >= 0) {
// Create highlighter using private painter and apply around
// pattern
hilite.addHighlight(pos, pos + pattern.length(), painter);
pos += pattern.length();
}
} catch (Exception e) {
new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e);
}
}
public ClassViewer(final String name, final String container, final ClassNode cn) {
for (int i = 0; i < panels.size(); i++) {
final JTextField textField = fields.get(i);
JPanel searchPanel = searches.get(i);
JCheckBox checkBox = exacts.get(i);
JButton byteSearchNext = new JButton();
JButton byteSearchPrev = new JButton();
JPanel byteButtonPane = new JPanel(new BorderLayout());
byteButtonPane.add(byteSearchNext, BorderLayout.WEST);
byteButtonPane.add(byteSearchPrev, BorderLayout.EAST);
byteSearchNext.setIcon(Resources.nextIcon);
byteSearchPrev.setIcon(Resources.prevIcon);
searchPanel.add(byteButtonPane, BorderLayout.WEST);
searchPanel.add(textField, BorderLayout.CENTER);
searchPanel.add(checkBox, BorderLayout.EAST);
byteSearchNext.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent arg0) {
search(0, textField.getText(), true);
}
});
byteSearchPrev.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent arg0) {
search(0, textField.getText(), false);
}
});
textField.addKeyListener(new KeyListener() {
@Override
public void keyReleased(KeyEvent arg0) {
if (arg0.getKeyCode() == KeyEvent.VK_ENTER)
search(0, textField.getText(), true);
}
@Override
public void keyPressed(KeyEvent arg0) {
}
@Override
public void keyTyped(KeyEvent arg0) {
}
});
}
this.name = name;
this.container = container;
this.cn = cn;
updateName();
this.setLayout(new BorderLayout());
this.sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panels.get(0), panels.get(1));
JHexEditor hex = new JHexEditor(BytecodeViewer.getClassBytes(container, cn.name + ".class"));
this.sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp, panels.get(2));
this.add(sp2, BorderLayout.CENTER);
hex.setMaximumSize(new Dimension(0, Integer.MAX_VALUE));
hex.setSize(0, Integer.MAX_VALUE);
BytecodeViewer.viewer.setIcon(true);
startPaneUpdater(null);
this.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
resetDivider();
}
});
}
public void resetDivider() {
sp.setResizeWeight(0.5);
if (decompilers.get(1) != null && decompilers.get(0) != null)
sp = setDividerLocation(sp, 0.5);
else if (decompilers.get(0) != null)
sp = setDividerLocation(sp, 1);
else if (decompilers.get(1) != null) {
sp.setResizeWeight(1);
sp = setDividerLocation(sp, 0);
} else
sp = setDividerLocation(sp, 0);
if (decompilers.get(2) != null) {
sp2.setResizeWeight(0.7);
sp2 = setDividerLocation(sp2, 0.7);
if ((decompilers.get(1) == null && decompilers.get(0) != null) || (decompilers.get(0) == null && decompilers.get(1) != null))
sp2 = setDividerLocation(sp2, 0.5);
else if (decompilers.get(0) == null && decompilers.get(1) == null)
sp2 = setDividerLocation(sp2, 0);
} else {
sp.setResizeWeight(1);
sp2.setResizeWeight(0);
sp2 = setDividerLocation(sp2, 1);
}
}
public void startPaneUpdater(final JButton button) {
this.cn = BytecodeViewer.getClassNode(container, cn.name); //update the classnode
setPanes();
for (JPanel jpanel : panels) {
jpanel.removeAll();
}
for (int i = 0; i < javas.size(); i++) {
javas.set(i, null);
}
for (int i = 0; i < smalis.size(); i++) {
smalis.set(i, null);
}
if (this.cn == null) {
for (JPanel jpanel : panels) {
jpanel.add(new JLabel("This file has been removed from the reload."));
}
return;
}
for (int i = 0; i < decompilers.size(); i++) {
if (decompilers.get(i) != null) {
if (decompilers.get(i) != Decompiler.HEXCODE) {
panels.get(i).add(searches.get(i), BorderLayout.NORTH);
}
PaneUpdaterThread t = new PaneUpdaterThread(this, decompilers.get(i), i, panels.get(i), button);
decompileThreads.add(t);
t.start();
}
}
}
public Object[] getSmali() {
for (int i = 0; i < smalis.size(); i++) {
RSyntaxTextArea text = smalis.get(i);
if (text != null) {
return new Object[]{cn, text.getText()};
}
}
return null;
}
public Object[] getKrakatau() {
for (int i = 0; i < krakataus.size(); i++) {
RSyntaxTextArea text = krakataus.get(i);
if (text != null) {
return new Object[]{cn, text.getText()};
}
}
return null;
}
public Object[] getJava() {
for (int i = 0; i < javas.size(); i++) {
RSyntaxTextArea text = javas.get(i);
if (text != null) {
return new Object[]{cn, text.getText()};
}
}
return null;
}
public void reset() {
for (Thread t : decompileThreads) {
t.stop();
}
}
}