summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui
diff options
context:
space:
mode:
authorGravatar lclc982016-06-30 00:49:21 +1000
committerGravatar GitHub2016-06-30 00:49:21 +1000
commit4be005617b3b8c3578cca07c5d085d12916f0d1d (patch)
treedb163431f38703e26da417ef05eaea2b27a498b9 /src/main/java/cuchaz/enigma/gui
parentSome small changes to fix idea importing (diff)
downloadenigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.gz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.xz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.zip
Json format (#2)
* Added new format * Fixed bug * Updated Version
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
-rw-r--r--src/main/java/cuchaz/enigma/gui/AboutDialog.java70
-rw-r--r--src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java64
-rw-r--r--src/main/java/cuchaz/enigma/gui/BrowserCaret.java38
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassListCellRenderer.java36
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java538
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java279
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java50
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java45
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java222
-rw-r--r--src/main/java/cuchaz/enigma/gui/CrashDialog.java94
-rw-r--r--src/main/java/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java21
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java1100
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java349
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java488
-rw-r--r--src/main/java/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java21
-rw-r--r--src/main/java/cuchaz/enigma/gui/OtherHighlightPainter.java21
-rw-r--r--src/main/java/cuchaz/enigma/gui/ProgressDialog.java100
-rw-r--r--src/main/java/cuchaz/enigma/gui/ReadableToken.java36
-rw-r--r--src/main/java/cuchaz/enigma/gui/RenameListener.java17
-rw-r--r--src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java30
-rw-r--r--src/main/java/cuchaz/enigma/gui/SelectionHighlightPainter.java29
-rw-r--r--src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java38
23 files changed, 3742 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/AboutDialog.java
new file mode 100644
index 0000000..a874399
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Container;
15import java.awt.Cursor;
16import java.awt.FlowLayout;
17import java.io.IOException;
18
19import javax.swing.*;
20
21import cuchaz.enigma.Constants;
22import cuchaz.enigma.Util;
23
24public class AboutDialog {
25
26 public static void show(JFrame parent) {
27 // init frame
28 final JFrame frame = new JFrame(Constants.Name + " - About");
29 final Container pane = frame.getContentPane();
30 pane.setLayout(new FlowLayout());
31
32 // load the content
33 try {
34 String html = Util.readResourceToString("/about.html");
35 html = String.format(html, Constants.Name, Constants.Version);
36 JLabel label = new JLabel(html);
37 label.setHorizontalAlignment(JLabel.CENTER);
38 pane.add(label);
39 } catch (IOException ex) {
40 throw new Error(ex);
41 }
42
43 // show the link
44 String html = "<html><a href=\"%s\">%s</a></html>";
45 html = String.format(html, Constants.Url, Constants.Url);
46 JButton link = new JButton(html);
47 link.addActionListener(event -> Util.openUrl(Constants.Url));
48 link.setBorderPainted(false);
49 link.setOpaque(false);
50 link.setBackground(Color.WHITE);
51 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
52 link.setFocusable(false);
53 JPanel linkPanel = new JPanel();
54 linkPanel.add(link);
55 pane.add(linkPanel);
56
57 // show ok button
58 JButton okButton = new JButton("Ok");
59 pane.add(okButton);
60 okButton.addActionListener(arg0 -> frame.dispose());
61
62 // show the frame
63 pane.doLayout();
64 frame.setSize(400, 220);
65 frame.setResizable(false);
66 frame.setLocationRelativeTo(parent);
67 frame.setVisible(true);
68 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
69 }
70}
diff --git a/src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java
new file mode 100644
index 0000000..efe6b50
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Graphics;
15import java.awt.Rectangle;
16import java.awt.Shape;
17
18import javax.swing.text.BadLocationException;
19import javax.swing.text.Highlighter;
20import javax.swing.text.JTextComponent;
21
22public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter {
23
24 private Color m_fillColor;
25 private Color m_borderColor;
26
27 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
28 m_fillColor = fillColor;
29 m_borderColor = borderColor;
30 }
31
32 @Override
33 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
34 Rectangle bounds = getBounds(text, start, end);
35
36 // fill the area
37 if (m_fillColor != null) {
38 g.setColor(m_fillColor);
39 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
40 }
41
42 // draw a box around the area
43 g.setColor(m_borderColor);
44 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
45 }
46
47 protected static Rectangle getBounds(JTextComponent text, int start, int end) {
48 try {
49 // determine the bounds of the text
50 Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
51
52 // adjust the box so it looks nice
53 bounds.x -= 2;
54 bounds.width += 2;
55 bounds.y += 1;
56 bounds.height -= 2;
57
58 return bounds;
59 } catch (BadLocationException ex) {
60 // don't care... just return something
61 return new Rectangle(0, 0, 0, 0);
62 }
63 }
64}
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
new file mode 100644
index 0000000..b4acbce
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.text.DefaultCaret;
14import javax.swing.text.Highlighter;
15
16public class BrowserCaret extends DefaultCaret {
17
18 private static final long serialVersionUID = 1158977422507969940L;
19
20 private static final Highlighter.HighlightPainter m_selectionPainter = (g, p0, p1, bounds, c) -> {
21 // don't paint anything
22 };
23
24 @Override
25 public boolean isSelectionVisible() {
26 return false;
27 }
28
29 @Override
30 public boolean isVisible() {
31 return true;
32 }
33
34 @Override
35 public Highlighter.HighlightPainter getSelectionPainter() {
36 return m_selectionPainter;
37 }
38}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/ClassListCellRenderer.java
new file mode 100644
index 0000000..89b9bdd
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javax.swing.DefaultListCellRenderer;
16import javax.swing.JLabel;
17import javax.swing.JList;
18import javax.swing.ListCellRenderer;
19
20import javassist.bytecode.Descriptor;
21
22public class ClassListCellRenderer implements ListCellRenderer<String> {
23
24 private DefaultListCellRenderer m_defaultRenderer;
25
26 public ClassListCellRenderer() {
27 m_defaultRenderer = new DefaultListCellRenderer();
28 }
29
30 @Override
31 public Component getListCellRendererComponent(JList<? extends String> list, String className, int index, boolean isSelected, boolean hasFocus) {
32 JLabel label = (JLabel) m_defaultRenderer.getListCellRendererComponent(list, className, index, isSelected, hasFocus);
33 label.setText(Descriptor.toJavaName(className));
34 return label;
35 }
36}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 0000000..440565c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,538 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16
17import java.awt.BorderLayout;
18import java.awt.Container;
19import java.awt.Dimension;
20import java.awt.FlowLayout;
21import java.awt.event.ActionListener;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.List;
25import java.util.Map;
26
27import javax.swing.*;
28
29import cuchaz.enigma.Constants;
30import cuchaz.enigma.Deobfuscator;
31import cuchaz.enigma.convert.*;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.Mappings;
34import cuchaz.enigma.mapping.MappingsChecker;
35import de.sciss.syntaxpane.DefaultSyntaxKit;
36
37
38public class ClassMatchingGui {
39
40 private enum SourceType {
41 Matched {
42 @Override
43 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
44 return matches.getUniqueMatches().keySet();
45 }
46 },
47 Unmatched {
48 @Override
49 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
50 return matches.getUnmatchedSourceClasses();
51 }
52 },
53 Ambiguous {
54 @Override
55 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
56 return matches.getAmbiguouslyMatchedSourceClasses();
57 }
58 };
59
60 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
61 JRadioButton button = new JRadioButton(name(), this == getDefault());
62 button.setActionCommand(name());
63 button.addActionListener(listener);
64 group.add(button);
65 return button;
66 }
67
68 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
69
70 public static SourceType getDefault() {
71 return values()[0];
72 }
73 }
74
75 public interface SaveListener {
76 void save(ClassMatches matches);
77 }
78
79 // controls
80 private JFrame m_frame;
81 private ClassSelector m_sourceClasses;
82 private ClassSelector m_destClasses;
83 private CodeReader m_sourceReader;
84 private CodeReader m_destReader;
85 private JLabel m_sourceClassLabel;
86 private JLabel m_destClassLabel;
87 private JButton m_matchButton;
88 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
89 private JCheckBox m_advanceCheck;
90 private JCheckBox m_top10Matches;
91
92 private ClassMatches m_classMatches;
93 private Deobfuscator m_sourceDeobfuscator;
94 private Deobfuscator m_destDeobfuscator;
95 private ClassEntry m_sourceClass;
96 private ClassEntry m_destClass;
97 private SourceType m_sourceType;
98 private SaveListener m_saveListener;
99
100 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
101
102 m_classMatches = matches;
103 m_sourceDeobfuscator = sourceDeobfuscator;
104 m_destDeobfuscator = destDeobfuscator;
105
106 // init frame
107 m_frame = new JFrame(Constants.Name + " - Class Matcher");
108 final Container pane = m_frame.getContentPane();
109 pane.setLayout(new BorderLayout());
110
111 // init source side
112 JPanel sourcePanel = new JPanel();
113 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
114 sourcePanel.setPreferredSize(new Dimension(200, 0));
115 pane.add(sourcePanel, BorderLayout.WEST);
116 sourcePanel.add(new JLabel("Source Classes"));
117
118 // init source type radios
119 JPanel sourceTypePanel = new JPanel();
120 sourcePanel.add(sourceTypePanel);
121 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
122 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
123 ButtonGroup sourceTypeButtons = new ButtonGroup();
124 m_sourceTypeButtons = Maps.newHashMap();
125 for (SourceType sourceType : SourceType.values()) {
126 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
127 m_sourceTypeButtons.put(sourceType, button);
128 sourceTypePanel.add(button);
129 }
130
131 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
132 m_sourceClasses.setListener(classEntry -> setSourceClass(classEntry));
133 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
134 sourcePanel.add(sourceScroller);
135
136 // init dest side
137 JPanel destPanel = new JPanel();
138 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
139 destPanel.setPreferredSize(new Dimension(200, 0));
140 pane.add(destPanel, BorderLayout.WEST);
141 destPanel.add(new JLabel("Destination Classes"));
142
143 m_top10Matches = new JCheckBox("Show only top 10 matches");
144 destPanel.add(m_top10Matches);
145 m_top10Matches.addActionListener(event -> toggleTop10Matches());
146
147 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
148 m_destClasses.setListener(this::setDestClass);
149 JScrollPane destScroller = new JScrollPane(m_destClasses);
150 destPanel.add(destScroller);
151
152 JButton autoMatchButton = new JButton("AutoMatch");
153 autoMatchButton.addActionListener(event -> autoMatch());
154 destPanel.add(autoMatchButton);
155
156 // init source panels
157 DefaultSyntaxKit.initKit();
158 m_sourceReader = new CodeReader();
159 m_destReader = new CodeReader();
160
161 // init all the splits
162 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
163 splitLeft.setResizeWeight(0); // let the right side take all the slack
164 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
165 splitRight.setResizeWeight(1); // let the left side take all the slack
166 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
167 splitCenter.setResizeWeight(0.5); // resize 50:50
168 pane.add(splitCenter, BorderLayout.CENTER);
169 splitCenter.resetToPreferredSizes();
170
171 // init bottom panel
172 JPanel bottomPanel = new JPanel();
173 bottomPanel.setLayout(new FlowLayout());
174
175 m_sourceClassLabel = new JLabel();
176 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
177 m_destClassLabel = new JLabel();
178 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
179
180 m_matchButton = new JButton();
181
182 m_advanceCheck = new JCheckBox("Advance to next likely match");
183 m_advanceCheck.addActionListener(event -> {
184 if (m_advanceCheck.isSelected()) {
185 advance();
186 }
187 });
188
189 bottomPanel.add(m_sourceClassLabel);
190 bottomPanel.add(m_matchButton);
191 bottomPanel.add(m_destClassLabel);
192 bottomPanel.add(m_advanceCheck);
193 pane.add(bottomPanel, BorderLayout.SOUTH);
194
195 // show the frame
196 pane.doLayout();
197 m_frame.setSize(1024, 576);
198 m_frame.setMinimumSize(new Dimension(640, 480));
199 m_frame.setVisible(true);
200 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
201
202 // init state
203 updateDestMappings();
204 setSourceType(SourceType.getDefault());
205 updateMatchButton();
206 m_saveListener = null;
207 }
208
209 public void setSaveListener(SaveListener val) {
210 m_saveListener = val;
211 }
212
213 private void updateDestMappings() {
214
215 Mappings newMappings = MappingsConverter.newMappings(
216 m_classMatches,
217 m_sourceDeobfuscator.getMappings(),
218 m_sourceDeobfuscator,
219 m_destDeobfuscator
220 );
221
222 // look for dropped mappings
223 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
224 checker.dropBrokenMappings(newMappings);
225
226 // count them
227 int numDroppedFields = checker.getDroppedFieldMappings().size();
228 int numDroppedMethods = checker.getDroppedMethodMappings().size();
229 System.out.println(String.format(
230 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
231 numDroppedFields + numDroppedMethods,
232 numDroppedFields,
233 numDroppedMethods
234 ));
235
236 m_destDeobfuscator.setMappings(newMappings);
237 }
238
239 protected void setSourceType(SourceType val) {
240
241 // show the source classes
242 m_sourceType = val;
243 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
244
245 // update counts
246 for (SourceType sourceType : SourceType.values()) {
247 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
248 sourceType.name(),
249 sourceType.getSourceClasses(m_classMatches).size()
250 ));
251 }
252 }
253
254 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
255 List<ClassEntry> out = Lists.newArrayList();
256 for (ClassEntry entry : in) {
257
258 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
259
260 // make sure we preserve any scores
261 if (entry instanceof ScoredClassEntry) {
262 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
263 }
264
265 out.add(deobf);
266 }
267 return out;
268 }
269
270 protected void setSourceClass(ClassEntry classEntry) {
271
272 Runnable onGetDestClasses = null;
273 if (m_advanceCheck.isSelected()) {
274 onGetDestClasses = this::pickBestDestClass;
275 }
276
277 setSourceClass(classEntry, onGetDestClasses);
278 }
279
280 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
281
282 // update the current source class
283 m_sourceClass = classEntry;
284 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
285
286 if (m_sourceClass != null) {
287
288 // show the dest class(es)
289 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
290 assert (match != null);
291 if (match.destClasses.isEmpty()) {
292
293 m_destClasses.setClasses(null);
294
295 // run in a separate thread to keep ui responsive
296 new Thread() {
297 @Override
298 public void run() {
299 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
300 m_destClasses.expandAll();
301
302 if (onGetDestClasses != null) {
303 onGetDestClasses.run();
304 }
305 }
306 }.start();
307
308 } else {
309
310 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
311 m_destClasses.expandAll();
312
313 if (onGetDestClasses != null) {
314 onGetDestClasses.run();
315 }
316 }
317 }
318
319 setDestClass(null);
320 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, () -> m_sourceReader.navigateToClassDeclaration(m_sourceClass));
321
322 updateMatchButton();
323 }
324
325 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
326
327 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
328
329 // set up identifiers
330 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
331 ClassIdentifier sourceIdentifier = new ClassIdentifier(
332 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
333 namer.getSourceNamer(), true
334 );
335 ClassIdentifier destIdentifier = new ClassIdentifier(
336 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
337 namer.getDestNamer(), true
338 );
339
340 try {
341
342 // rank all the unmatched dest classes against the source class
343 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
344 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
345 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
346 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
347 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
348 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
349 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
350 }
351
352 if (m_top10Matches.isSelected() && scoredDestClasses.size() > 10) {
353 Collections.sort(scoredDestClasses, (a, b) -> {
354 ScoredClassEntry sa = (ScoredClassEntry) a;
355 ScoredClassEntry sb = (ScoredClassEntry) b;
356 return -Float.compare(sa.getScore(), sb.getScore());
357 });
358 scoredDestClasses = scoredDestClasses.subList(0, 10);
359 }
360
361 return scoredDestClasses;
362
363 } catch (ClassNotFoundException ex) {
364 throw new Error("Unable to find class " + ex.getMessage());
365 }
366 }
367
368 protected void setDestClass(ClassEntry classEntry) {
369
370 // update the current source class
371 m_destClass = classEntry;
372 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
373
374 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, () -> m_destReader.navigateToClassDeclaration(m_destClass));
375
376 updateMatchButton();
377 }
378
379 private void updateMatchButton() {
380
381 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
382 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
383
384 BiMap<ClassEntry, ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
385 boolean twoSelected = m_sourceClass != null && m_destClass != null;
386 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
387 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
388
389 GuiTricks.deactivateButton(m_matchButton);
390 if (twoSelected) {
391 if (isMatched) {
392 GuiTricks.activateButton(m_matchButton, "Unmatch", event -> onUnmatchClick());
393 } else if (canMatch) {
394 GuiTricks.activateButton(m_matchButton, "Match", event -> onMatchClick());
395 }
396 }
397 }
398
399 private void onMatchClick() {
400 // precondition: source and dest classes are set correctly
401
402 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
403 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
404
405 // remove the classes from their match
406 m_classMatches.removeSource(obfSource);
407 m_classMatches.removeDest(obfDest);
408
409 // add them as matched classes
410 m_classMatches.add(new ClassMatch(obfSource, obfDest));
411
412 ClassEntry nextClass = null;
413 if (m_advanceCheck.isSelected()) {
414 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
415 }
416
417 save();
418 updateMatches();
419
420 if (nextClass != null) {
421 advance(nextClass);
422 }
423 }
424
425 private void onUnmatchClick() {
426 // precondition: source and dest classes are set to a unique match
427
428 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
429
430 // remove the source to break the match, then add the source back as unmatched
431 m_classMatches.removeSource(obfSource);
432 m_classMatches.add(new ClassMatch(obfSource, null));
433
434 save();
435 updateMatches();
436 }
437
438 private void updateMatches() {
439 updateDestMappings();
440 setDestClass(null);
441 m_destClasses.setClasses(null);
442 updateMatchButton();
443
444 // remember where we were in the source tree
445 String packageName = m_sourceClasses.getSelectedPackage();
446
447 setSourceType(m_sourceType);
448
449 m_sourceClasses.expandPackage(packageName);
450 }
451
452 private void save() {
453 if (m_saveListener != null) {
454 m_saveListener.save(m_classMatches);
455 }
456 }
457
458 private void autoMatch() {
459
460 System.out.println("Automatching...");
461
462 // compute a new matching
463 ClassMatching matching = MappingsConverter.computeMatching(
464 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
465 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
466 m_classMatches.getUniqueMatches()
467 );
468 ClassMatches newMatches = new ClassMatches(matching.matches());
469 System.out.println(String.format("Automatch found %d new matches",
470 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
471 ));
472
473 // update the current matches
474 m_classMatches = newMatches;
475 save();
476 updateMatches();
477 }
478
479 private void advance() {
480 advance(null);
481 }
482
483 private void advance(ClassEntry sourceClass) {
484
485 // make sure we have a source class
486 if (sourceClass == null) {
487 sourceClass = m_sourceClasses.getSelectedClass();
488 if (sourceClass != null) {
489 sourceClass = m_sourceClasses.getNextClass(sourceClass);
490 } else {
491 sourceClass = m_sourceClasses.getFirstClass();
492 }
493 }
494
495 // set the source class
496 setSourceClass(sourceClass, this::pickBestDestClass);
497 m_sourceClasses.setSelectionClass(sourceClass);
498 }
499
500 private void pickBestDestClass() {
501
502 // then, pick the best dest class
503 ClassEntry firstClass = null;
504 ScoredClassEntry bestDestClass = null;
505 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
506 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
507 if (firstClass == null) {
508 firstClass = classNode.getClassEntry();
509 }
510 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
511 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
512 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
513 bestDestClass = scoredClass;
514 }
515 }
516 }
517 }
518
519 // pick the entry to show
520 ClassEntry destClass = null;
521 if (bestDestClass != null) {
522 destClass = bestDestClass;
523 } else if (firstClass != null) {
524 destClass = firstClass;
525 }
526
527 setDestClass(destClass);
528 m_destClasses.setSelectionClass(destClass);
529 }
530
531 private void toggleTop10Matches() {
532 if (m_sourceClass != null) {
533 m_destClasses.clearSelection();
534 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
535 m_destClasses.expandAll();
536 }
537 }
538}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 0000000..0c93c43
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,279 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.ArrayListMultimap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Multimap;
17
18import java.awt.event.MouseAdapter;
19import java.awt.event.MouseEvent;
20import java.util.*;
21
22import javax.swing.JTree;
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.DefaultTreeModel;
25import javax.swing.tree.TreePath;
26
27import cuchaz.enigma.mapping.ClassEntry;
28
29public class ClassSelector extends JTree {
30
31 private static final long serialVersionUID = -7632046902384775977L;
32
33 public interface ClassSelectionListener {
34 void onSelectClass(ClassEntry classEntry);
35 }
36
37 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
38 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
39
40 static {
41 ObfuscatedClassEntryComparator = (a, b) -> {
42 String aname = a.getName();
43 String bname = a.getName();
44 if (aname.length() != bname.length()) {
45 return aname.length() - bname.length();
46 }
47 return aname.compareTo(bname);
48 };
49
50 DeobfuscatedClassEntryComparator = (a, b) -> {
51 if (a instanceof ScoredClassEntry && b instanceof ScoredClassEntry) {
52 return Float.compare(
53 ((ScoredClassEntry) b).getScore(),
54 ((ScoredClassEntry) a).getScore()
55 );
56 }
57 return a.getName().compareTo(b.getName());
58 };
59 }
60
61 private ClassSelectionListener m_listener;
62 private Comparator<ClassEntry> m_comparator;
63
64 public ClassSelector(Comparator<ClassEntry> comparator) {
65 m_comparator = comparator;
66
67 // configure the tree control
68 setRootVisible(false);
69 setShowsRootHandles(false);
70 setModel(null);
71
72 // hook events
73 addMouseListener(new MouseAdapter() {
74 @Override
75 public void mouseClicked(MouseEvent event) {
76 if (m_listener != null && event.getClickCount() == 2) {
77 // get the selected node
78 TreePath path = getSelectionPath();
79 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
80 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent();
81 m_listener.onSelectClass(node.getClassEntry());
82 }
83 }
84 }
85 });
86
87 // init defaults
88 m_listener = null;
89 }
90
91 public void setListener(ClassSelectionListener val) {
92 m_listener = val;
93 }
94
95 public void setClasses(Collection<ClassEntry> classEntries) {
96 if (classEntries == null) {
97 setModel(null);
98 return;
99 }
100
101 // build the package names
102 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
103 for (ClassEntry classEntry : classEntries) {
104 packages.put(classEntry.getPackageName(), null);
105 }
106
107 // sort the packages
108 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
109 Collections.sort(sortedPackageNames, (a, b) -> {
110 // I can never keep this rule straight when writing these damn things...
111 // a < b => -1, a == b => 0, a > b => +1
112
113 String[] aparts = a.split("/");
114 String[] bparts = b.split("/");
115 for (int i = 0; true; i++) {
116 if (i >= aparts.length) {
117 return -1;
118 } else if (i >= bparts.length) {
119 return 1;
120 }
121
122 int result = aparts[i].compareTo(bparts[i]);
123 if (result != 0) {
124 return result;
125 }
126 }
127 });
128
129 // create the root node and the package nodes
130 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
131 for (String packageName : sortedPackageNames) {
132 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
133 packages.put(packageName, node);
134 root.add(node);
135 }
136
137 // put the classes into packages
138 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create();
139 for (ClassEntry classEntry : classEntries) {
140 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
141 }
142
143 // build the class nodes
144 for (String packageName : packagedClassEntries.keySet()) {
145 // sort the class entries
146 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
147 Collections.sort(classEntriesInPackage, m_comparator);
148
149 // create the nodes in order
150 for (ClassEntry classEntry : classEntriesInPackage) {
151 ClassSelectorPackageNode node = packages.get(packageName);
152 node.add(new ClassSelectorClassNode(classEntry));
153 }
154 }
155
156 // finally, update the tree control
157 setModel(new DefaultTreeModel(root));
158 }
159
160 public ClassEntry getSelectedClass() {
161 if (!isSelectionEmpty()) {
162 Object selectedNode = getSelectionPath().getLastPathComponent();
163 if (selectedNode instanceof ClassSelectorClassNode) {
164 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
165 return classNode.getClassEntry();
166 }
167 }
168 return null;
169 }
170
171 public String getSelectedPackage() {
172 if (!isSelectionEmpty()) {
173 Object selectedNode = getSelectionPath().getLastPathComponent();
174 if (selectedNode instanceof ClassSelectorPackageNode) {
175 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode;
176 return packageNode.getPackageName();
177 } else if (selectedNode instanceof ClassSelectorClassNode) {
178 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
179 return classNode.getClassEntry().getPackageName();
180 }
181 }
182 return null;
183 }
184
185 public Iterable<ClassSelectorPackageNode> packageNodes() {
186 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
187 DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot();
188 Enumeration<?> children = root.children();
189 while (children.hasMoreElements()) {
190 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement();
191 nodes.add(packageNode);
192 }
193 return nodes;
194 }
195
196 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
197 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
198 Enumeration<?> children = packageNode.children();
199 while (children.hasMoreElements()) {
200 ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement();
201 nodes.add(classNode);
202 }
203 return nodes;
204 }
205
206 public void expandPackage(String packageName) {
207 if (packageName == null) {
208 return;
209 }
210 for (ClassSelectorPackageNode packageNode : packageNodes()) {
211 if (packageNode.getPackageName().equals(packageName)) {
212 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
213 return;
214 }
215 }
216 }
217
218 public void expandAll() {
219 for (ClassSelectorPackageNode packageNode : packageNodes()) {
220 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
221 }
222 }
223
224 public ClassEntry getFirstClass() {
225 for (ClassSelectorPackageNode packageNode : packageNodes()) {
226 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
227 return classNode.getClassEntry();
228 }
229 }
230 return null;
231 }
232
233 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
234 for (ClassSelectorPackageNode packageNode : packageNodes()) {
235 if (packageNode.getPackageName().equals(entry.getPackageName())) {
236 return packageNode;
237 }
238 }
239 return null;
240 }
241
242 public ClassEntry getNextClass(ClassEntry entry) {
243 boolean foundIt = false;
244 for (ClassSelectorPackageNode packageNode : packageNodes()) {
245 if (!foundIt) {
246 // skip to the package with our target in it
247 if (packageNode.getPackageName().equals(entry.getPackageName())) {
248 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
249 if (!foundIt) {
250 if (classNode.getClassEntry().equals(entry)) {
251 foundIt = true;
252 }
253 } else {
254 // return the next class
255 return classNode.getClassEntry();
256 }
257 }
258 }
259 } else {
260 // return the next class
261 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
262 return classNode.getClassEntry();
263 }
264 }
265 }
266 return null;
267 }
268
269 public void setSelectionClass(ClassEntry classEntry) {
270 expandPackage(classEntry.getPackageName());
271 for (ClassSelectorPackageNode packageNode : packageNodes()) {
272 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
273 if (classNode.getClassEntry().equals(classEntry)) {
274 setSelectionPath(new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode}));
275 }
276 }
277 }
278 }
279}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 0000000..6da9782
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.ClassEntry;
16
17public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18
19 private static final long serialVersionUID = -8956754339813257380L;
20
21 private ClassEntry m_classEntry;
22
23 public ClassSelectorClassNode(ClassEntry classEntry) {
24 m_classEntry = classEntry;
25 }
26
27 public ClassEntry getClassEntry() {
28 return m_classEntry;
29 }
30
31 @Override
32 public String toString() {
33 if (m_classEntry instanceof ScoredClassEntry) {
34 return String.format("%d%% %s", (int) ((ScoredClassEntry) m_classEntry).getScore(), m_classEntry.getSimpleName());
35 }
36 return m_classEntry.getSimpleName();
37 }
38
39 @Override
40 public boolean equals(Object other) {
41 if (other instanceof ClassSelectorClassNode) {
42 return equals((ClassSelectorClassNode) other);
43 }
44 return false;
45 }
46
47 public boolean equals(ClassSelectorClassNode other) {
48 return m_classEntry.equals(other.m_classEntry);
49 }
50}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 0000000..3622042
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
16
17 private static final long serialVersionUID = -3730868701219548043L;
18
19 private String m_packageName;
20
21 public ClassSelectorPackageNode(String packageName) {
22 m_packageName = packageName;
23 }
24
25 public String getPackageName() {
26 return m_packageName;
27 }
28
29 @Override
30 public String toString() {
31 return m_packageName;
32 }
33
34 @Override
35 public boolean equals(Object other) {
36 if (other instanceof ClassSelectorPackageNode) {
37 return equals((ClassSelectorPackageNode) other);
38 }
39 return false;
40 }
41
42 public boolean equals(ClassSelectorPackageNode other) {
43 return m_packageName.equals(other.m_packageName);
44 }
45}
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
new file mode 100644
index 0000000..93f9a75
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,222 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.strobel.decompiler.languages.java.ast.CompilationUnit;
14
15import java.awt.Rectangle;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18
19import javax.swing.JEditorPane;
20import javax.swing.SwingUtilities;
21import javax.swing.Timer;
22import javax.swing.event.CaretEvent;
23import javax.swing.event.CaretListener;
24import javax.swing.text.BadLocationException;
25import javax.swing.text.Highlighter.HighlightPainter;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.analysis.EntryReference;
29import cuchaz.enigma.analysis.SourceIndex;
30import cuchaz.enigma.analysis.Token;
31import cuchaz.enigma.mapping.ClassEntry;
32import cuchaz.enigma.mapping.Entry;
33import de.sciss.syntaxpane.DefaultSyntaxKit;
34
35
36public class CodeReader extends JEditorPane {
37
38 private static final long serialVersionUID = 3673180950485748810L;
39
40 private static final Object m_lock = new Object();
41
42 public interface SelectionListener {
43 void onSelect(EntryReference<Entry, Entry> reference);
44 }
45
46 private SelectionHighlightPainter m_selectionHighlightPainter;
47 private SourceIndex m_sourceIndex;
48 private SelectionListener m_selectionListener;
49
50 public CodeReader() {
51
52 setEditable(false);
53 setContentType("text/java");
54
55 // turn off token highlighting (it's wrong most of the time anyway...)
56 DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit();
57 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
58
59 // hook events
60 addCaretListener(new CaretListener() {
61 @Override
62 public void caretUpdate(CaretEvent event) {
63 if (m_selectionListener != null && m_sourceIndex != null) {
64 Token token = m_sourceIndex.getReferenceToken(event.getDot());
65 if (token != null) {
66 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
67 } else {
68 m_selectionListener.onSelect(null);
69 }
70 }
71 }
72 });
73
74 m_selectionHighlightPainter = new SelectionHighlightPainter();
75 m_sourceIndex = null;
76 m_selectionListener = null;
77 }
78
79 public void setSelectionListener(SelectionListener val) {
80 m_selectionListener = val;
81 }
82
83 public void setCode(String code) {
84 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
85 synchronized (m_lock) {
86 setText(code);
87 }
88 }
89
90 public SourceIndex getSourceIndex() {
91 return m_sourceIndex;
92 }
93
94 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
95 decompileClass(classEntry, deobfuscator, null);
96 }
97
98 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
99 decompileClass(classEntry, deobfuscator, null, callback);
100 }
101
102 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
103
104 if (classEntry == null) {
105 setCode(null);
106 return;
107 }
108
109 setCode("(decompiling...)");
110
111 // run decompilation in a separate thread to keep ui responsive
112 new Thread() {
113 @Override
114 public void run() {
115
116 // decompile it
117 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
118 String source = deobfuscator.getSource(sourceTree);
119 setCode(source);
120 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
121
122 if (callback != null) {
123 callback.run();
124 }
125 }
126 }.start();
127 }
128
129 public void navigateToClassDeclaration(ClassEntry classEntry) {
130
131 // navigate to the class declaration
132 Token token = m_sourceIndex.getDeclarationToken(classEntry);
133 if (token == null) {
134 // couldn't find the class declaration token, might be an anonymous class
135 // look for any declaration in that class instead
136 for (Entry entry : m_sourceIndex.declarations()) {
137 if (entry.getClassEntry().equals(classEntry)) {
138 token = m_sourceIndex.getDeclarationToken(entry);
139 break;
140 }
141 }
142 }
143
144 if (token != null) {
145 navigateToToken(token);
146 } else {
147 // couldn't find anything =(
148 System.out.println("Unable to find declaration in source for " + classEntry);
149 }
150 }
151
152 public void navigateToToken(final Token token) {
153 navigateToToken(this, token, m_selectionHighlightPainter);
154 }
155
156 // HACKHACK: someday we can update the main GUI to use this code reader
157 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
158
159 // set the caret position to the token
160 editor.setCaretPosition(token.start);
161 editor.grabFocus();
162
163 try {
164 // make sure the token is visible in the scroll window
165 Rectangle start = editor.modelToView(token.start);
166 Rectangle end = editor.modelToView(token.end);
167 final Rectangle show = start.union(end);
168 show.grow(start.width * 10, start.height * 6);
169 SwingUtilities.invokeLater(new Runnable() {
170 @Override
171 public void run() {
172 editor.scrollRectToVisible(show);
173 }
174 });
175 } catch (BadLocationException ex) {
176 throw new Error(ex);
177 }
178
179 // highlight the token momentarily
180 final Timer timer = new Timer(200, new ActionListener() {
181 private int m_counter = 0;
182 private Object m_highlight = null;
183
184 @Override
185 public void actionPerformed(ActionEvent event) {
186 if (m_counter % 2 == 0) {
187 try {
188 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
189 } catch (BadLocationException ex) {
190 // don't care
191 }
192 } else if (m_highlight != null) {
193 editor.getHighlighter().removeHighlight(m_highlight);
194 }
195
196 if (m_counter++ > 6) {
197 Timer timer = (Timer) event.getSource();
198 timer.stop();
199 }
200 }
201 });
202 timer.start();
203 }
204
205 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
206 for (Token token : tokens) {
207 setHighlightedToken(token, painter);
208 }
209 }
210
211 public void setHighlightedToken(Token token, HighlightPainter painter) {
212 try {
213 getHighlighter().addHighlight(token.start, token.end, painter);
214 } catch (BadLocationException ex) {
215 throw new IllegalArgumentException(ex);
216 }
217 }
218
219 public void clearHighlights() {
220 getHighlighter().removeAllHighlights();
221 }
222}
diff --git a/src/main/java/cuchaz/enigma/gui/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 0000000..c0c0869
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.FlowLayout;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.io.PrintWriter;
19import java.io.StringWriter;
20
21import javax.swing.*;
22
23import cuchaz.enigma.Constants;
24
25public class CrashDialog {
26
27 private static CrashDialog m_instance = null;
28
29 private JFrame m_frame;
30 private JTextArea m_text;
31
32 private CrashDialog(JFrame parent) {
33 // init frame
34 m_frame = new JFrame(Constants.Name + " - Crash Report");
35 final Container pane = m_frame.getContentPane();
36 pane.setLayout(new BorderLayout());
37
38 JLabel label = new JLabel(Constants.Name + " has crashed! =(");
39 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
40 pane.add(label, BorderLayout.NORTH);
41
42 // report panel
43 m_text = new JTextArea();
44 m_text.setTabSize(2);
45 pane.add(new JScrollPane(m_text), BorderLayout.CENTER);
46
47 // buttons panel
48 JPanel buttonsPanel = new JPanel();
49 FlowLayout buttonsLayout = new FlowLayout();
50 buttonsLayout.setAlignment(FlowLayout.RIGHT);
51 buttonsPanel.setLayout(buttonsLayout);
52 buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work.")));
53 JButton ignoreButton = new JButton("Ignore");
54 ignoreButton.addActionListener(new ActionListener() {
55 @Override
56 public void actionPerformed(ActionEvent event) {
57 // close (hide) the dialog
58 m_frame.setVisible(false);
59 }
60 });
61 buttonsPanel.add(ignoreButton);
62 JButton exitButton = new JButton("Exit");
63 exitButton.addActionListener(new ActionListener() {
64 @Override
65 public void actionPerformed(ActionEvent event) {
66 // exit enigma
67 System.exit(1);
68 }
69 });
70 buttonsPanel.add(exitButton);
71 pane.add(buttonsPanel, BorderLayout.SOUTH);
72
73 // show the frame
74 m_frame.setSize(600, 400);
75 m_frame.setLocationRelativeTo(parent);
76 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
77 }
78
79 public static void init(JFrame parent) {
80 m_instance = new CrashDialog(parent);
81 }
82
83 public static void show(Throwable ex) {
84 // get the error report
85 StringWriter buf = new StringWriter();
86 ex.printStackTrace(new PrintWriter(buf));
87 String report = buf.toString();
88
89 // show it!
90 m_instance.m_text.setText(report);
91 m_instance.m_frame.doLayout();
92 m_instance.m_frame.setVisible(true);
93 }
94}
diff --git a/src/main/java/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
new file mode 100644
index 0000000..d92bb0d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public DeobfuscatedHighlightPainter() {
18 // green ish
19 super(new Color(220, 255, 220), new Color(80, 160, 80));
20 }
21}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
new file mode 100644
index 0000000..eb26ddd
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1100 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.Lists;
14
15import java.awt.*;
16import java.awt.event.*;
17import java.io.File;
18import java.io.IOException;
19import java.lang.Thread.UncaughtExceptionHandler;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.List;
23import java.util.Vector;
24import java.util.jar.JarFile;
25
26import javax.swing.*;
27import javax.swing.event.CaretEvent;
28import javax.swing.event.CaretListener;
29import javax.swing.text.BadLocationException;
30import javax.swing.text.Highlighter;
31import javax.swing.tree.DefaultTreeModel;
32import javax.swing.tree.TreeNode;
33import javax.swing.tree.TreePath;
34
35import cuchaz.enigma.Constants;
36import cuchaz.enigma.ExceptionIgnorer;
37import cuchaz.enigma.analysis.*;
38import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
39import cuchaz.enigma.mapping.*;
40import de.sciss.syntaxpane.DefaultSyntaxKit;
41
42public class Gui {
43
44 private GuiController m_controller;
45
46 // controls
47 private JFrame m_frame;
48 private ClassSelector m_obfClasses;
49 private ClassSelector m_deobfClasses;
50 private JEditorPane m_editor;
51 private JPanel m_classesPanel;
52 private JSplitPane m_splitClasses;
53 private JPanel m_infoPanel;
54 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
55 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
56 private OtherHighlightPainter m_otherHighlightPainter;
57 private SelectionHighlightPainter m_selectionHighlightPainter;
58 private JTree m_inheritanceTree;
59 private JTree m_implementationsTree;
60 private JTree m_callsTree;
61 private JList<Token> m_tokens;
62 private JTabbedPane m_tabs;
63
64 // dynamic menu items
65 private JMenuItem m_closeJarMenu;
66 private JMenuItem m_openMappingsMenu;
67 private JMenuItem m_openOldMappingsMenu;
68 private JMenuItem m_saveMappingsMenu;
69 private JMenuItem m_saveMappingsAsMenu;
70 private JMenuItem m_closeMappingsMenu;
71 private JMenuItem m_renameMenu;
72 private JMenuItem m_showInheritanceMenu;
73 private JMenuItem m_openEntryMenu;
74 private JMenuItem m_openPreviousMenu;
75 private JMenuItem m_showCallsMenu;
76 private JMenuItem m_showImplementationsMenu;
77 private JMenuItem m_toggleMappingMenu;
78 private JMenuItem m_exportSourceMenu;
79 private JMenuItem m_exportJarMenu;
80
81 // state
82 private EntryReference<Entry, Entry> m_reference;
83 private JFileChooser m_jarFileChooser;
84 private JFileChooser m_mappingsFileChooser;
85 private JFileChooser m_oldMappingsFileChooser;
86
87 private JFileChooser m_exportSourceFileChooser;
88 private JFileChooser m_exportJarFileChooser;
89
90 public Gui() {
91
92 // init frame
93 m_frame = new JFrame(Constants.Name);
94 final Container pane = m_frame.getContentPane();
95 pane.setLayout(new BorderLayout());
96
97 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
98 // install a global exception handler to the event thread
99 CrashDialog.init(m_frame);
100 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
101 @Override
102 public void uncaughtException(Thread thread, Throwable t) {
103 t.printStackTrace(System.err);
104 if (!ExceptionIgnorer.shouldIgnore(t)) {
105 CrashDialog.show(t);
106 }
107 }
108 });
109 }
110
111 m_controller = new GuiController(this);
112
113 // init file choosers
114 m_jarFileChooser = new JFileChooser();
115 m_mappingsFileChooser = new JFileChooser();
116 m_mappingsFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
117 m_mappingsFileChooser.setAcceptAllFileFilterUsed(false);
118
119 m_oldMappingsFileChooser= new JFileChooser();
120 m_exportSourceFileChooser = new JFileChooser();
121 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
122 m_exportJarFileChooser = new JFileChooser();
123
124 // init obfuscated classes list
125 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
126 m_obfClasses.setListener(new ClassSelectionListener() {
127 @Override
128 public void onSelectClass(ClassEntry classEntry) {
129 navigateTo(classEntry);
130 }
131 });
132 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
133 JPanel obfPanel = new JPanel();
134 obfPanel.setLayout(new BorderLayout());
135 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
136 obfPanel.add(obfScroller, BorderLayout.CENTER);
137
138 // init deobfuscated classes list
139 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
140 m_deobfClasses.setListener(new ClassSelectionListener() {
141 @Override
142 public void onSelectClass(ClassEntry classEntry) {
143 navigateTo(classEntry);
144 }
145 });
146 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
147 JPanel deobfPanel = new JPanel();
148 deobfPanel.setLayout(new BorderLayout());
149 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
150 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
151
152 // set up classes panel (don't add the splitter yet)
153 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
154 m_splitClasses.setResizeWeight(0.3);
155 m_classesPanel = new JPanel();
156 m_classesPanel.setLayout(new BorderLayout());
157 m_classesPanel.setPreferredSize(new Dimension(250, 0));
158
159 // init info panel
160 m_infoPanel = new JPanel();
161 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
162 m_infoPanel.setPreferredSize(new Dimension(0, 100));
163 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
164 clearReference();
165
166 // init editor
167 DefaultSyntaxKit.initKit();
168 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
169 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
170 m_otherHighlightPainter = new OtherHighlightPainter();
171 m_selectionHighlightPainter = new SelectionHighlightPainter();
172 m_editor = new JEditorPane();
173 m_editor.setEditable(false);
174 m_editor.setCaret(new BrowserCaret());
175 JScrollPane sourceScroller = new JScrollPane(m_editor);
176 m_editor.setContentType("text/java");
177 m_editor.addCaretListener(new CaretListener() {
178 @Override
179 public void caretUpdate(CaretEvent event) {
180 onCaretMove(event.getDot());
181 }
182 });
183 m_editor.addKeyListener(new KeyAdapter() {
184 @Override
185 public void keyPressed(KeyEvent event) {
186 switch (event.getKeyCode()) {
187 case KeyEvent.VK_R:
188 m_renameMenu.doClick();
189 break;
190
191 case KeyEvent.VK_I:
192 m_showInheritanceMenu.doClick();
193 break;
194
195 case KeyEvent.VK_M:
196 m_showImplementationsMenu.doClick();
197 break;
198
199 case KeyEvent.VK_N:
200 m_openEntryMenu.doClick();
201 break;
202
203 case KeyEvent.VK_P:
204 m_openPreviousMenu.doClick();
205 break;
206
207 case KeyEvent.VK_C:
208 m_showCallsMenu.doClick();
209 break;
210
211 case KeyEvent.VK_T:
212 m_toggleMappingMenu.doClick();
213 break;
214 }
215 }
216 });
217
218 // turn off token highlighting (it's wrong most of the time anyway...)
219 DefaultSyntaxKit kit = (DefaultSyntaxKit) m_editor.getEditorKit();
220 kit.toggleComponent(m_editor, "de.sciss.syntaxpane.components.TokenMarker");
221
222 // init editor popup menu
223 JPopupMenu popupMenu = new JPopupMenu();
224 m_editor.setComponentPopupMenu(popupMenu);
225 {
226 JMenuItem menu = new JMenuItem("Rename");
227 menu.addActionListener(new ActionListener() {
228 @Override
229 public void actionPerformed(ActionEvent event) {
230 startRename();
231 }
232 });
233 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
234 menu.setEnabled(false);
235 popupMenu.add(menu);
236 m_renameMenu = menu;
237 }
238 {
239 JMenuItem menu = new JMenuItem("Show Inheritance");
240 menu.addActionListener(new ActionListener() {
241 @Override
242 public void actionPerformed(ActionEvent event) {
243 showInheritance();
244 }
245 });
246 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
247 menu.setEnabled(false);
248 popupMenu.add(menu);
249 m_showInheritanceMenu = menu;
250 }
251 {
252 JMenuItem menu = new JMenuItem("Show Implementations");
253 menu.addActionListener(new ActionListener() {
254 @Override
255 public void actionPerformed(ActionEvent event) {
256 showImplementations();
257 }
258 });
259 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
260 menu.setEnabled(false);
261 popupMenu.add(menu);
262 m_showImplementationsMenu = menu;
263 }
264 {
265 JMenuItem menu = new JMenuItem("Show Calls");
266 menu.addActionListener(new ActionListener() {
267 @Override
268 public void actionPerformed(ActionEvent event) {
269 showCalls();
270 }
271 });
272 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
273 menu.setEnabled(false);
274 popupMenu.add(menu);
275 m_showCallsMenu = menu;
276 }
277 {
278 JMenuItem menu = new JMenuItem("Go to Declaration");
279 menu.addActionListener(new ActionListener() {
280 @Override
281 public void actionPerformed(ActionEvent event) {
282 navigateTo(m_reference.entry);
283 }
284 });
285 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
286 menu.setEnabled(false);
287 popupMenu.add(menu);
288 m_openEntryMenu = menu;
289 }
290 {
291 JMenuItem menu = new JMenuItem("Go to previous");
292 menu.addActionListener(new ActionListener() {
293 @Override
294 public void actionPerformed(ActionEvent event) {
295 m_controller.openPreviousReference();
296 }
297 });
298 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
299 menu.setEnabled(false);
300 popupMenu.add(menu);
301 m_openPreviousMenu = menu;
302 }
303 {
304 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
305 menu.addActionListener(new ActionListener() {
306 @Override
307 public void actionPerformed(ActionEvent event) {
308 toggleMapping();
309 }
310 });
311 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
312 menu.setEnabled(false);
313 popupMenu.add(menu);
314 m_toggleMappingMenu = menu;
315 }
316
317 // init inheritance panel
318 m_inheritanceTree = new JTree();
319 m_inheritanceTree.setModel(null);
320 m_inheritanceTree.addMouseListener(new MouseAdapter() {
321 @Override
322 public void mouseClicked(MouseEvent event) {
323 if (event.getClickCount() == 2) {
324 // get the selected node
325 TreePath path = m_inheritanceTree.getSelectionPath();
326 if (path == null) {
327 return;
328 }
329
330 Object node = path.getLastPathComponent();
331 if (node instanceof ClassInheritanceTreeNode) {
332 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node;
333 navigateTo(new ClassEntry(classNode.getObfClassName()));
334 } else if (node instanceof MethodInheritanceTreeNode) {
335 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node;
336 if (methodNode.isImplemented()) {
337 navigateTo(methodNode.getMethodEntry());
338 }
339 }
340 }
341 }
342 });
343 JPanel inheritancePanel = new JPanel();
344 inheritancePanel.setLayout(new BorderLayout());
345 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
346
347 // init implementations panel
348 m_implementationsTree = new JTree();
349 m_implementationsTree.setModel(null);
350 m_implementationsTree.addMouseListener(new MouseAdapter() {
351 @Override
352 public void mouseClicked(MouseEvent event) {
353 if (event.getClickCount() == 2) {
354 // get the selected node
355 TreePath path = m_implementationsTree.getSelectionPath();
356 if (path == null) {
357 return;
358 }
359
360 Object node = path.getLastPathComponent();
361 if (node instanceof ClassImplementationsTreeNode) {
362 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node;
363 navigateTo(classNode.getClassEntry());
364 } else if (node instanceof MethodImplementationsTreeNode) {
365 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node;
366 navigateTo(methodNode.getMethodEntry());
367 }
368 }
369 }
370 });
371 JPanel implementationsPanel = new JPanel();
372 implementationsPanel.setLayout(new BorderLayout());
373 implementationsPanel.add(new JScrollPane(m_implementationsTree));
374
375 // init call panel
376 m_callsTree = new JTree();
377 m_callsTree.setModel(null);
378 m_callsTree.addMouseListener(new MouseAdapter() {
379 @SuppressWarnings("unchecked")
380 @Override
381 public void mouseClicked(MouseEvent event) {
382 if (event.getClickCount() == 2) {
383 // get the selected node
384 TreePath path = m_callsTree.getSelectionPath();
385 if (path == null) {
386 return;
387 }
388
389 Object node = path.getLastPathComponent();
390 if (node instanceof ReferenceTreeNode) {
391 ReferenceTreeNode<Entry, Entry> referenceNode = ((ReferenceTreeNode<Entry, Entry>) node);
392 if (referenceNode.getReference() != null) {
393 navigateTo(referenceNode.getReference());
394 } else {
395 navigateTo(referenceNode.getEntry());
396 }
397 }
398 }
399 }
400 });
401 m_tokens = new JList<Token>();
402 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
403 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
404 m_tokens.setLayoutOrientation(JList.VERTICAL);
405 m_tokens.addMouseListener(new MouseAdapter() {
406 @Override
407 public void mouseClicked(MouseEvent event) {
408 if (event.getClickCount() == 2) {
409 Token selected = m_tokens.getSelectedValue();
410 if (selected != null) {
411 showToken(selected);
412 }
413 }
414 }
415 });
416 m_tokens.setPreferredSize(new Dimension(0, 200));
417 m_tokens.setMinimumSize(new Dimension(0, 200));
418 JSplitPane callPanel = new JSplitPane(
419 JSplitPane.VERTICAL_SPLIT,
420 true,
421 new JScrollPane(m_callsTree),
422 new JScrollPane(m_tokens)
423 );
424 callPanel.setResizeWeight(1); // let the top side take all the slack
425 callPanel.resetToPreferredSizes();
426
427 // layout controls
428 JPanel centerPanel = new JPanel();
429 centerPanel.setLayout(new BorderLayout());
430 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
431 centerPanel.add(sourceScroller, BorderLayout.CENTER);
432 m_tabs = new JTabbedPane();
433 m_tabs.setPreferredSize(new Dimension(250, 0));
434 m_tabs.addTab("Inheritance", inheritancePanel);
435 m_tabs.addTab("Implementations", implementationsPanel);
436 m_tabs.addTab("Call Graph", callPanel);
437 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
438 splitRight.setResizeWeight(1); // let the left side take all the slack
439 splitRight.resetToPreferredSizes();
440 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
441 splitCenter.setResizeWeight(0); // let the right side take all the slack
442 pane.add(splitCenter, BorderLayout.CENTER);
443
444 // init menus
445 JMenuBar menuBar = new JMenuBar();
446 m_frame.setJMenuBar(menuBar);
447 {
448 JMenu menu = new JMenu("File");
449 menuBar.add(menu);
450 {
451 JMenuItem item = new JMenuItem("Open Jar...");
452 menu.add(item);
453 item.addActionListener(new ActionListener() {
454 @Override
455 public void actionPerformed(ActionEvent event) {
456 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
457 // load the jar in a separate thread
458 new Thread() {
459 @Override
460 public void run() {
461 try {
462 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
463 } catch (IOException ex) {
464 throw new Error(ex);
465 }
466 }
467 }.start();
468 }
469 }
470 });
471 }
472 {
473 JMenuItem item = new JMenuItem("Close Jar");
474 menu.add(item);
475 item.addActionListener(new ActionListener() {
476 @Override
477 public void actionPerformed(ActionEvent event) {
478 m_controller.closeJar();
479 }
480 });
481 m_closeJarMenu = item;
482 }
483 menu.addSeparator();
484 {
485 JMenuItem item = new JMenuItem("Open Mappings...");
486 menu.add(item);
487 item.addActionListener(new ActionListener() {
488 @Override
489 public void actionPerformed(ActionEvent event) {
490 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
491 try {
492 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
493 } catch (IOException ex) {
494 throw new Error(ex);
495 } catch (MappingParseException ex) {
496 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
497 }
498 }
499 }
500 });
501 m_openMappingsMenu = item;
502 }
503 {
504 JMenuItem item = new JMenuItem("Open Old Mappings...");
505 menu.add(item);
506 item.addActionListener(event -> {
507 if (m_oldMappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
508 try {
509 m_controller.openOldMappings(m_oldMappingsFileChooser.getSelectedFile());
510 } catch (IOException ex) {
511 throw new Error(ex);
512 } catch (MappingParseException ex) {
513 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
514 }
515 }
516 });
517 m_openOldMappingsMenu = item;
518 }
519 menu.addSeparator();
520 {
521 JMenuItem item = new JMenuItem("Save Mappings");
522 menu.add(item);
523 item.addActionListener(new ActionListener() {
524 @Override
525 public void actionPerformed(ActionEvent event) {
526 try {
527 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
528 } catch (IOException ex) {
529 throw new Error(ex);
530 }
531 }
532 });
533 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
534 m_saveMappingsMenu = item;
535 }
536 {
537 JMenuItem item = new JMenuItem("Save Mappings As...");
538 menu.add(item);
539 item.addActionListener(new ActionListener() {
540 @Override
541 public void actionPerformed(ActionEvent event) {
542 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
543 try {
544 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
545 m_saveMappingsMenu.setEnabled(true);
546 } catch (IOException ex) {
547 throw new Error(ex);
548 }
549 }
550 }
551 });
552 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
553 m_saveMappingsAsMenu = item;
554 }
555 {
556 JMenuItem item = new JMenuItem("Close Mappings");
557 menu.add(item);
558 item.addActionListener(new ActionListener() {
559 @Override
560 public void actionPerformed(ActionEvent event) {
561 m_controller.closeMappings();
562 }
563 });
564 m_closeMappingsMenu = item;
565 }
566 menu.addSeparator();
567 {
568 JMenuItem item = new JMenuItem("Export Source...");
569 menu.add(item);
570 item.addActionListener(new ActionListener() {
571 @Override
572 public void actionPerformed(ActionEvent event) {
573 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
574 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
575 }
576 }
577 });
578 m_exportSourceMenu = item;
579 }
580 {
581 JMenuItem item = new JMenuItem("Export Jar...");
582 menu.add(item);
583 item.addActionListener(new ActionListener() {
584 @Override
585 public void actionPerformed(ActionEvent event) {
586 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
587 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
588 }
589 }
590 });
591 m_exportJarMenu = item;
592 }
593 menu.addSeparator();
594 {
595 JMenuItem item = new JMenuItem("Exit");
596 menu.add(item);
597 item.addActionListener(new ActionListener() {
598 @Override
599 public void actionPerformed(ActionEvent event) {
600 close();
601 }
602 });
603 }
604 }
605 {
606 JMenu menu = new JMenu("Help");
607 menuBar.add(menu);
608 {
609 JMenuItem item = new JMenuItem("About");
610 menu.add(item);
611 item.addActionListener(new ActionListener() {
612 @Override
613 public void actionPerformed(ActionEvent event) {
614 AboutDialog.show(m_frame);
615 }
616 });
617 }
618 }
619
620 // init state
621 onCloseJar();
622
623 m_frame.addWindowListener(new WindowAdapter() {
624 @Override
625 public void windowClosing(WindowEvent event) {
626 close();
627 }
628 });
629
630 // show the frame
631 pane.doLayout();
632 m_frame.setSize(1024, 576);
633 m_frame.setMinimumSize(new Dimension(640, 480));
634 m_frame.setVisible(true);
635 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
636 }
637
638 public JFrame getFrame() {
639 return m_frame;
640 }
641
642 public GuiController getController() {
643 return m_controller;
644 }
645
646 public void onStartOpenJar() {
647 m_classesPanel.removeAll();
648 JPanel panel = new JPanel();
649 panel.setLayout(new FlowLayout());
650 panel.add(new JLabel("Loading..."));
651 m_classesPanel.add(panel);
652 redraw();
653 }
654
655 public void onFinishOpenJar(String jarName) {
656 // update gui
657 m_frame.setTitle(Constants.Name + " - " + jarName);
658 m_classesPanel.removeAll();
659 m_classesPanel.add(m_splitClasses);
660 setSource(null);
661
662 // update menu
663 m_closeJarMenu.setEnabled(true);
664 m_openOldMappingsMenu.setEnabled(true);
665 m_openMappingsMenu.setEnabled(true);
666 m_saveMappingsMenu.setEnabled(false);
667 m_saveMappingsAsMenu.setEnabled(true);
668 m_closeMappingsMenu.setEnabled(true);
669 m_exportSourceMenu.setEnabled(true);
670 m_exportJarMenu.setEnabled(true);
671
672 redraw();
673 }
674
675 public void onCloseJar() {
676 // update gui
677 m_frame.setTitle(Constants.Name);
678 setObfClasses(null);
679 setDeobfClasses(null);
680 setSource(null);
681 m_classesPanel.removeAll();
682
683 // update menu
684 m_closeJarMenu.setEnabled(false);
685 m_openOldMappingsMenu.setEnabled(false);
686 m_openMappingsMenu.setEnabled(false);
687 m_saveMappingsMenu.setEnabled(false);
688 m_saveMappingsAsMenu.setEnabled(false);
689 m_closeMappingsMenu.setEnabled(false);
690 m_exportSourceMenu.setEnabled(false);
691 m_exportJarMenu.setEnabled(false);
692
693 redraw();
694 }
695
696 public void setObfClasses(Collection<ClassEntry> obfClasses) {
697 m_obfClasses.setClasses(obfClasses);
698 }
699
700 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
701 m_deobfClasses.setClasses(deobfClasses);
702 }
703
704 public void setMappingsFile(File file) {
705 m_mappingsFileChooser.setSelectedFile(file);
706 m_saveMappingsMenu.setEnabled(file != null);
707 }
708
709 public void setSource(String source) {
710 m_editor.getHighlighter().removeAllHighlights();
711 m_editor.setText(source);
712 }
713
714 public void showToken(final Token token) {
715 if (token == null) {
716 throw new IllegalArgumentException("Token cannot be null!");
717 }
718 CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter);
719 redraw();
720 }
721
722 public void showTokens(Collection<Token> tokens) {
723 Vector<Token> sortedTokens = new Vector<Token>(tokens);
724 Collections.sort(sortedTokens);
725 if (sortedTokens.size() > 1) {
726 // sort the tokens and update the tokens panel
727 m_tokens.setListData(sortedTokens);
728 m_tokens.setSelectedIndex(0);
729 } else {
730 m_tokens.setListData(new Vector<Token>());
731 }
732
733 // show the first token
734 showToken(sortedTokens.get(0));
735 }
736
737 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
738
739 // remove any old highlighters
740 m_editor.getHighlighter().removeAllHighlights();
741
742 // color things based on the index
743 if (obfuscatedTokens != null) {
744 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
745 }
746 if (deobfuscatedTokens != null) {
747 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
748 }
749 if (otherTokens != null) {
750 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
751 }
752
753 redraw();
754 }
755
756 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
757 for (Token token : tokens) {
758 try {
759 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
760 } catch (BadLocationException ex) {
761 throw new IllegalArgumentException(ex);
762 }
763 }
764 }
765
766 private void clearReference() {
767 m_infoPanel.removeAll();
768 JLabel label = new JLabel("No identifier selected");
769 GuiTricks.unboldLabel(label);
770 label.setHorizontalAlignment(JLabel.CENTER);
771 m_infoPanel.add(label);
772
773 redraw();
774 }
775
776 private void showReference(EntryReference<Entry, Entry> reference) {
777 if (reference == null) {
778 clearReference();
779 return;
780 }
781
782 m_reference = reference;
783
784 m_infoPanel.removeAll();
785 if (reference.entry instanceof ClassEntry) {
786 showClassEntry((ClassEntry) m_reference.entry);
787 } else if (m_reference.entry instanceof FieldEntry) {
788 showFieldEntry((FieldEntry) m_reference.entry);
789 } else if (m_reference.entry instanceof MethodEntry) {
790 showMethodEntry((MethodEntry) m_reference.entry);
791 } else if (m_reference.entry instanceof ConstructorEntry) {
792 showConstructorEntry((ConstructorEntry) m_reference.entry);
793 } else if (m_reference.entry instanceof ArgumentEntry) {
794 showArgumentEntry((ArgumentEntry) m_reference.entry);
795 } else {
796 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
797 }
798
799 redraw();
800 }
801
802 private void showClassEntry(ClassEntry entry) {
803 addNameValue(m_infoPanel, "Class", entry.getName());
804 }
805
806 private void showFieldEntry(FieldEntry entry) {
807 addNameValue(m_infoPanel, "Field", entry.getName());
808 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
809 addNameValue(m_infoPanel, "Type", entry.getType().toString());
810 }
811
812 private void showMethodEntry(MethodEntry entry) {
813 addNameValue(m_infoPanel, "Method", entry.getName());
814 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
815 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
816 }
817
818 private void showConstructorEntry(ConstructorEntry entry) {
819 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
820 if (!entry.isStatic()) {
821 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
822 }
823 }
824
825 private void showArgumentEntry(ArgumentEntry entry) {
826 addNameValue(m_infoPanel, "Argument", entry.getName());
827 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
828 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
829 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
830 }
831
832 private void addNameValue(JPanel container, String name, String value) {
833 JPanel panel = new JPanel();
834 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
835 container.add(panel);
836
837 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
838 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
839 panel.add(label);
840
841 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
842 }
843
844 private void onCaretMove(int pos) {
845
846 Token token = m_controller.getToken(pos);
847 boolean isToken = token != null;
848
849 m_reference = m_controller.getDeobfReference(token);
850 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
851 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
852 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
853 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
854 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
855 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
856
857 if (isToken) {
858 showReference(m_reference);
859 } else {
860 clearReference();
861 }
862
863 m_renameMenu.setEnabled(isRenameable && isToken);
864 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
865 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
866 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
867 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
868 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
869 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
870
871 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
872 m_toggleMappingMenu.setText("Reset to obfuscated");
873 } else {
874 m_toggleMappingMenu.setText("Mark as deobfuscated");
875 }
876 }
877
878 private void navigateTo(Entry entry) {
879 if (!m_controller.entryIsInJar(entry)) {
880 // entry is not in the jar. Ignore it
881 return;
882 }
883 if (m_reference != null) {
884 m_controller.savePreviousReference(m_reference);
885 }
886 m_controller.openDeclaration(entry);
887 }
888
889 private void navigateTo(EntryReference<Entry, Entry> reference) {
890 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
891 // reference is not in the jar. Ignore it
892 return;
893 }
894 if (m_reference != null) {
895 m_controller.savePreviousReference(m_reference);
896 }
897 m_controller.openReference(reference);
898 }
899
900 private void startRename() {
901
902 // init the text box
903 final JTextField text = new JTextField();
904 text.setText(m_reference.getNamableName());
905 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
906 text.addKeyListener(new KeyAdapter() {
907 @Override
908 public void keyPressed(KeyEvent event) {
909 switch (event.getKeyCode()) {
910 case KeyEvent.VK_ENTER:
911 finishRename(text, true);
912 break;
913
914 case KeyEvent.VK_ESCAPE:
915 finishRename(text, false);
916 break;
917 }
918 }
919 });
920
921 // find the label with the name and replace it with the text box
922 JPanel panel = (JPanel) m_infoPanel.getComponent(0);
923 panel.remove(panel.getComponentCount() - 1);
924 panel.add(text);
925 text.grabFocus();
926 text.selectAll();
927
928 redraw();
929 }
930
931 private void finishRename(JTextField text, boolean saveName) {
932 String newName = text.getText();
933 if (saveName && newName != null && newName.length() > 0) {
934 try {
935 m_controller.rename(m_reference, newName);
936 } catch (IllegalNameException ex) {
937 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
938 text.setToolTipText(ex.getReason());
939 GuiTricks.showToolTipNow(text);
940 }
941 return;
942 }
943
944 // abort the rename
945 JPanel panel = (JPanel) m_infoPanel.getComponent(0);
946 panel.remove(panel.getComponentCount() - 1);
947 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
948
949 m_editor.grabFocus();
950
951 redraw();
952 }
953
954 private void showInheritance() {
955
956 if (m_reference == null) {
957 return;
958 }
959
960 m_inheritanceTree.setModel(null);
961
962 if (m_reference.entry instanceof ClassEntry) {
963 // get the class inheritance
964 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry) m_reference.entry);
965
966 // show the tree at the root
967 TreePath path = getPathToRoot(classNode);
968 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
969 m_inheritanceTree.expandPath(path);
970 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
971 } else if (m_reference.entry instanceof MethodEntry) {
972 // get the method inheritance
973 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry) m_reference.entry);
974
975 // show the tree at the root
976 TreePath path = getPathToRoot(classNode);
977 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
978 m_inheritanceTree.expandPath(path);
979 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
980 }
981
982 m_tabs.setSelectedIndex(0);
983 redraw();
984 }
985
986 private void showImplementations() {
987
988 if (m_reference == null) {
989 return;
990 }
991
992 m_implementationsTree.setModel(null);
993
994 if (m_reference.entry instanceof ClassEntry) {
995 // get the class implementations
996 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry) m_reference.entry);
997 if (node != null) {
998 // show the tree at the root
999 TreePath path = getPathToRoot(node);
1000 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
1001 m_implementationsTree.expandPath(path);
1002 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1003 }
1004 } else if (m_reference.entry instanceof MethodEntry) {
1005 // get the method implementations
1006 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry) m_reference.entry);
1007 if (node != null) {
1008 // show the tree at the root
1009 TreePath path = getPathToRoot(node);
1010 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
1011 m_implementationsTree.expandPath(path);
1012 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1013 }
1014 }
1015
1016 m_tabs.setSelectedIndex(1);
1017 redraw();
1018 }
1019
1020 private void showCalls() {
1021
1022 if (m_reference == null) {
1023 return;
1024 }
1025
1026 if (m_reference.entry instanceof ClassEntry) {
1027 // look for calls to the default constructor
1028 // TODO: get a list of all the constructors and find calls to all of them
1029 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry) m_reference.entry, new Signature("()V")));
1030 m_callsTree.setModel(new DefaultTreeModel(node));
1031 } else if (m_reference.entry instanceof FieldEntry) {
1032 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry) m_reference.entry);
1033 m_callsTree.setModel(new DefaultTreeModel(node));
1034 } else if (m_reference.entry instanceof MethodEntry) {
1035 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry) m_reference.entry);
1036 m_callsTree.setModel(new DefaultTreeModel(node));
1037 } else if (m_reference.entry instanceof ConstructorEntry) {
1038 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry) m_reference.entry);
1039 m_callsTree.setModel(new DefaultTreeModel(node));
1040 }
1041
1042 m_tabs.setSelectedIndex(2);
1043 redraw();
1044 }
1045
1046 private void toggleMapping() {
1047 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1048 m_controller.removeMapping(m_reference);
1049 } else {
1050 m_controller.markAsDeobfuscated(m_reference);
1051 }
1052 }
1053
1054 private TreePath getPathToRoot(TreeNode node) {
1055 List<TreeNode> nodes = Lists.newArrayList();
1056 TreeNode n = node;
1057 do {
1058 nodes.add(n);
1059 n = n.getParent();
1060 } while (n != null);
1061 Collections.reverse(nodes);
1062 return new TreePath(nodes.toArray());
1063 }
1064
1065 private void close() {
1066 if (!m_controller.isDirty()) {
1067 // everything is saved, we can exit safely
1068 m_frame.dispose();
1069 } else {
1070 // ask to save before closing
1071 String[] options = {"Save and exit", "Discard changes", "Cancel"};
1072 int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
1073 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1074 switch (response) {
1075 case JOptionPane.YES_OPTION: // save and exit
1076 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1077 try {
1078 m_controller.saveMappings(m_mappingsFileChooser.getCurrentDirectory());
1079 m_frame.dispose();
1080 } catch (IOException ex) {
1081 throw new Error(ex);
1082 }
1083 }
1084 break;
1085
1086 case JOptionPane.NO_OPTION:
1087 // don't save, exit
1088 m_frame.dispose();
1089 break;
1090
1091 // cancel means do nothing
1092 }
1093 }
1094 }
1095
1096 private void redraw() {
1097 m_frame.validate();
1098 m_frame.repaint();
1099 }
1100}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 0000000..aa6acdc
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,349 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Queues;
15
16import com.strobel.decompiler.languages.java.ast.CompilationUnit;
17
18import java.io.File;
19import java.io.FileReader;
20import java.io.FileWriter;
21import java.io.IOException;
22import java.util.Collection;
23import java.util.Deque;
24import java.util.List;
25import java.util.jar.JarFile;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.Deobfuscator.ProgressListener;
29import cuchaz.enigma.analysis.*;
30import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable;
31import cuchaz.enigma.mapping.*;
32
33public class GuiController {
34
35 private Deobfuscator m_deobfuscator;
36 private Gui m_gui;
37 private SourceIndex m_index;
38 private ClassEntry m_currentObfClass;
39 private boolean m_isDirty;
40 private Deque<EntryReference<Entry, Entry>> m_referenceStack;
41
42 public GuiController(Gui gui) {
43 m_gui = gui;
44 m_deobfuscator = null;
45 m_index = null;
46 m_currentObfClass = null;
47 m_isDirty = false;
48 m_referenceStack = Queues.newArrayDeque();
49 }
50
51 public boolean isDirty() {
52 return m_isDirty;
53 }
54
55 public void openJar(final JarFile jar) throws IOException {
56 m_gui.onStartOpenJar();
57 m_deobfuscator = new Deobfuscator(jar);
58 m_gui.onFinishOpenJar(m_deobfuscator.getJarName());
59 refreshClasses();
60 }
61
62 public void closeJar() {
63 m_deobfuscator = null;
64 m_gui.onCloseJar();
65 }
66
67 public void openOldMappings(File file) throws IOException, MappingParseException {
68 FileReader in = new FileReader(file);
69 m_deobfuscator.setMappings(new MappingsReaderOld().read(in));
70 in.close();
71 m_isDirty = false;
72 m_gui.setMappingsFile(file);
73 refreshClasses();
74 refreshCurrentClass();
75 }
76
77 public void openMappings(File file) throws IOException, MappingParseException {
78 m_deobfuscator.setMappings(new MappingsReader().read(file));
79 m_isDirty = false;
80 m_gui.setMappingsFile(file);
81 refreshClasses();
82 refreshCurrentClass();
83 }
84
85 public void saveMappings(File file) throws IOException {
86 new MappingsWriter().write(file, m_deobfuscator.getMappings());
87 m_isDirty = false;
88 }
89
90 public void closeMappings() {
91 m_deobfuscator.setMappings(null);
92 m_gui.setMappingsFile(null);
93 refreshClasses();
94 refreshCurrentClass();
95 }
96
97 public void exportSource(final File dirOut) {
98 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
99 @Override
100 public void run(ProgressListener progress) throws Exception {
101 m_deobfuscator.writeSources(dirOut, progress);
102 }
103 });
104 }
105
106 public void exportJar(final File fileOut) {
107 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
108 @Override
109 public void run(ProgressListener progress) {
110 m_deobfuscator.writeJar(fileOut, progress);
111 }
112 });
113 }
114
115 public Token getToken(int pos) {
116 if (m_index == null) {
117 return null;
118 }
119 return m_index.getReferenceToken(pos);
120 }
121
122 public EntryReference<Entry, Entry> getDeobfReference(Token token) {
123 if (m_index == null) {
124 return null;
125 }
126 return m_index.getDeobfReference(token);
127 }
128
129 public ReadableToken getReadableToken(Token token) {
130 if (m_index == null) {
131 return null;
132 }
133 return new ReadableToken(
134 m_index.getLineNumber(token.start),
135 m_index.getColumnNumber(token.start),
136 m_index.getColumnNumber(token.end)
137 );
138 }
139
140 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
141 return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry));
142 }
143
144 public boolean entryIsInJar(Entry deobfEntry) {
145 return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry));
146 }
147
148 public boolean referenceIsRenameable(EntryReference<Entry, Entry> deobfReference) {
149 return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference));
150 }
151
152 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
153 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
154 ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance(
155 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
156 obfClassEntry
157 );
158 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
159 }
160
161 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
162 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
163 return m_deobfuscator.getJarIndex().getClassImplementations(
164 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
165 obfClassEntry
166 );
167 }
168
169 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
170 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
171 MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance(
172 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
173 obfMethodEntry
174 );
175 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
176 }
177
178 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
179 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
180 List<MethodImplementationsTreeNode> rootNodes = m_deobfuscator.getJarIndex().getMethodImplementations(
181 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
182 obfMethodEntry
183 );
184 if (rootNodes.isEmpty()) {
185 return null;
186 }
187 if (rootNodes.size() > 1) {
188 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
189 }
190 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
191 }
192
193 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
194 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
195 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
196 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
197 obfFieldEntry
198 );
199 rootNode.load(m_deobfuscator.getJarIndex(), true);
200 return rootNode;
201 }
202
203 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
204 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
205 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
206 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
207 obfBehaviorEntry
208 );
209 rootNode.load(m_deobfuscator.getJarIndex(), true);
210 return rootNode;
211 }
212
213 public void rename(EntryReference<Entry, Entry> deobfReference, String newName) {
214 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
215 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
216 m_isDirty = true;
217 refreshClasses();
218 refreshCurrentClass(obfReference);
219 }
220
221 public void removeMapping(EntryReference<Entry, Entry> deobfReference) {
222 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
223 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
224 m_isDirty = true;
225 refreshClasses();
226 refreshCurrentClass(obfReference);
227 }
228
229 public void markAsDeobfuscated(EntryReference<Entry, Entry> deobfReference) {
230 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
231 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
232 m_isDirty = true;
233 refreshClasses();
234 refreshCurrentClass(obfReference);
235 }
236
237 public void openDeclaration(Entry deobfEntry) {
238 if (deobfEntry == null) {
239 throw new IllegalArgumentException("Entry cannot be null!");
240 }
241 openReference(new EntryReference<Entry, Entry>(deobfEntry, deobfEntry.getName()));
242 }
243
244 public void openReference(EntryReference<Entry, Entry> deobfReference) {
245 if (deobfReference == null) {
246 throw new IllegalArgumentException("Reference cannot be null!");
247 }
248
249 // get the reference target class
250 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
251 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry();
252 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
253 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
254 }
255 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
256 // deobfuscate the class, then navigate to the reference
257 m_currentObfClass = obfClassEntry;
258 deobfuscate(m_currentObfClass, obfReference);
259 } else {
260 showReference(obfReference);
261 }
262 }
263
264 private void showReference(EntryReference<Entry, Entry> obfReference) {
265 EntryReference<Entry, Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
266 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
267 if (tokens.isEmpty()) {
268 // DEBUG
269 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
270 } else {
271 m_gui.showTokens(tokens);
272 }
273 }
274
275 public void savePreviousReference(EntryReference<Entry, Entry> deobfReference) {
276 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
277 }
278
279 public void openPreviousReference() {
280 if (hasPreviousLocation()) {
281 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
282 }
283 }
284
285 public boolean hasPreviousLocation() {
286 return !m_referenceStack.isEmpty();
287 }
288
289 private void refreshClasses() {
290 List<ClassEntry> obfClasses = Lists.newArrayList();
291 List<ClassEntry> deobfClasses = Lists.newArrayList();
292 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
293 m_gui.setObfClasses(obfClasses);
294 m_gui.setDeobfClasses(deobfClasses);
295 }
296
297 private void refreshCurrentClass() {
298 refreshCurrentClass(null);
299 }
300
301 private void refreshCurrentClass(EntryReference<Entry, Entry> obfReference) {
302 if (m_currentObfClass != null) {
303 deobfuscate(m_currentObfClass, obfReference);
304 }
305 }
306
307 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry, Entry> obfReference) {
308
309 m_gui.setSource("(deobfuscating...)");
310
311 // run the deobfuscator in a separate thread so we don't block the GUI event queue
312 new Thread() {
313 @Override
314 public void run() {
315 // decompile,deobfuscate the bytecode
316 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
317 if (sourceTree == null) {
318 // decompilation of this class is not supported
319 m_gui.setSource("Unable to find class: " + classEntry);
320 return;
321 }
322 String source = m_deobfuscator.getSource(sourceTree);
323 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
324 m_gui.setSource(m_index.getSource());
325 if (obfReference != null) {
326 showReference(obfReference);
327 }
328
329 // set the highlighted tokens
330 List<Token> obfuscatedTokens = Lists.newArrayList();
331 List<Token> deobfuscatedTokens = Lists.newArrayList();
332 List<Token> otherTokens = Lists.newArrayList();
333 for (Token token : m_index.referenceTokens()) {
334 EntryReference<Entry, Entry> reference = m_index.getDeobfReference(token);
335 if (referenceIsRenameable(reference)) {
336 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
337 deobfuscatedTokens.add(token);
338 } else {
339 obfuscatedTokens.add(token);
340 }
341 } else {
342 otherTokens.add(token);
343 }
344 }
345 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
346 }
347 }.start();
348 }
349}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 0000000..da2ec74
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.ActionListener;
15import java.awt.event.MouseEvent;
16import java.util.Arrays;
17
18import javax.swing.JButton;
19import javax.swing.JComponent;
20import javax.swing.JLabel;
21import javax.swing.ToolTipManager;
22
23public class GuiTricks {
24
25 public static JLabel unboldLabel(JLabel label) {
26 Font font = label.getFont();
27 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
28 return label;
29 }
30
31 public static void showToolTipNow(JComponent component) {
32 // HACKHACK: trick the tooltip manager into showing the tooltip right now
33 ToolTipManager manager = ToolTipManager.sharedInstance();
34 int oldDelay = manager.getInitialDelay();
35 manager.setInitialDelay(0);
36 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
37 manager.setInitialDelay(oldDelay);
38 }
39
40 public static void deactivateButton(JButton button) {
41 button.setEnabled(false);
42 button.setText("");
43 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
44 button.removeActionListener(listener);
45 }
46 }
47
48 public static void activateButton(JButton button, String text, ActionListener newListener) {
49 button.setText(text);
50 button.setEnabled(true);
51 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
52 button.removeActionListener(listener);
53 }
54 button.addActionListener(newListener);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 0000000..4b79b77
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,488 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import java.awt.BorderLayout;
17import java.awt.Container;
18import java.awt.Dimension;
19import java.awt.FlowLayout;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.util.Collection;
25import java.util.List;
26import java.util.Map;
27
28import javax.swing.*;
29import javax.swing.text.Highlighter.HighlightPainter;
30
31import cuchaz.enigma.Constants;
32import cuchaz.enigma.Deobfuscator;
33import cuchaz.enigma.analysis.EntryReference;
34import cuchaz.enigma.analysis.SourceIndex;
35import cuchaz.enigma.analysis.Token;
36import cuchaz.enigma.convert.ClassMatches;
37import cuchaz.enigma.convert.MemberMatches;
38import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import de.sciss.syntaxpane.DefaultSyntaxKit;
42
43
44public class MemberMatchingGui<T extends Entry> {
45
46 private enum SourceType {
47 Matched {
48 @Override
49 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
50 return matches.getSourceClassesWithoutUnmatchedEntries();
51 }
52 },
53 Unmatched {
54 @Override
55 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
56 return matches.getSourceClassesWithUnmatchedEntries();
57 }
58 };
59
60 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
61 JRadioButton button = new JRadioButton(name(), this == getDefault());
62 button.setActionCommand(name());
63 button.addActionListener(listener);
64 group.add(button);
65 return button;
66 }
67
68 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
69
70 public static SourceType getDefault() {
71 return values()[0];
72 }
73 }
74
75 public interface SaveListener<T extends Entry> {
76 void save(MemberMatches<T> matches);
77 }
78
79 // controls
80 private JFrame m_frame;
81 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
82 private ClassSelector m_sourceClasses;
83 private CodeReader m_sourceReader;
84 private CodeReader m_destReader;
85 private JButton m_matchButton;
86 private JButton m_unmatchableButton;
87 private JLabel m_sourceLabel;
88 private JLabel m_destLabel;
89 private HighlightPainter m_unmatchedHighlightPainter;
90 private HighlightPainter m_matchedHighlightPainter;
91
92 private ClassMatches m_classMatches;
93 private MemberMatches<T> m_memberMatches;
94 private Deobfuscator m_sourceDeobfuscator;
95 private Deobfuscator m_destDeobfuscator;
96 private SaveListener<T> m_saveListener;
97 private SourceType m_sourceType;
98 private ClassEntry m_obfSourceClass;
99 private ClassEntry m_obfDestClass;
100 private T m_obfSourceEntry;
101 private T m_obfDestEntry;
102
103 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
104
105 m_classMatches = classMatches;
106 m_memberMatches = fieldMatches;
107 m_sourceDeobfuscator = sourceDeobfuscator;
108 m_destDeobfuscator = destDeobfuscator;
109
110 // init frame
111 m_frame = new JFrame(Constants.Name + " - Member Matcher");
112 final Container pane = m_frame.getContentPane();
113 pane.setLayout(new BorderLayout());
114
115 // init classes side
116 JPanel classesPanel = new JPanel();
117 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
118 classesPanel.setPreferredSize(new Dimension(200, 0));
119 pane.add(classesPanel, BorderLayout.WEST);
120 classesPanel.add(new JLabel("Classes"));
121
122 // init source type radios
123 JPanel sourceTypePanel = new JPanel();
124 classesPanel.add(sourceTypePanel);
125 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
126 ActionListener sourceTypeListener = new ActionListener() {
127 @Override
128 public void actionPerformed(ActionEvent event) {
129 setSourceType(SourceType.valueOf(event.getActionCommand()));
130 }
131 };
132 ButtonGroup sourceTypeButtons = new ButtonGroup();
133 m_sourceTypeButtons = Maps.newHashMap();
134 for (SourceType sourceType : SourceType.values()) {
135 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
136 m_sourceTypeButtons.put(sourceType, button);
137 sourceTypePanel.add(button);
138 }
139
140 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
141 m_sourceClasses.setListener(new ClassSelectionListener() {
142 @Override
143 public void onSelectClass(ClassEntry classEntry) {
144 setSourceClass(classEntry);
145 }
146 });
147 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
148 classesPanel.add(sourceScroller);
149
150 // init readers
151 DefaultSyntaxKit.initKit();
152 m_sourceReader = new CodeReader();
153 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
154 @Override
155 public void onSelect(EntryReference<Entry, Entry> reference) {
156 if (reference != null) {
157 onSelectSource(reference.entry);
158 } else {
159 onSelectSource(null);
160 }
161 }
162 });
163 m_destReader = new CodeReader();
164 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
165 @Override
166 public void onSelect(EntryReference<Entry, Entry> reference) {
167 if (reference != null) {
168 onSelectDest(reference.entry);
169 } else {
170 onSelectDest(null);
171 }
172 }
173 });
174
175 // add key bindings
176 KeyAdapter keyListener = new KeyAdapter() {
177 @Override
178 public void keyPressed(KeyEvent event) {
179 switch (event.getKeyCode()) {
180 case KeyEvent.VK_M:
181 m_matchButton.doClick();
182 break;
183 }
184 }
185 };
186 m_sourceReader.addKeyListener(keyListener);
187 m_destReader.addKeyListener(keyListener);
188
189 // init all the splits
190 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
191 splitRight.setResizeWeight(0.5); // resize 50:50
192 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
193 splitLeft.setResizeWeight(0); // let the right side take all the slack
194 pane.add(splitLeft, BorderLayout.CENTER);
195 splitLeft.resetToPreferredSizes();
196
197 // init bottom panel
198 JPanel bottomPanel = new JPanel();
199 bottomPanel.setLayout(new FlowLayout());
200 pane.add(bottomPanel, BorderLayout.SOUTH);
201
202 m_matchButton = new JButton();
203 m_unmatchableButton = new JButton();
204
205 m_sourceLabel = new JLabel();
206 bottomPanel.add(m_sourceLabel);
207 bottomPanel.add(m_matchButton);
208 bottomPanel.add(m_unmatchableButton);
209 m_destLabel = new JLabel();
210 bottomPanel.add(m_destLabel);
211
212 // show the frame
213 pane.doLayout();
214 m_frame.setSize(1024, 576);
215 m_frame.setMinimumSize(new Dimension(640, 480));
216 m_frame.setVisible(true);
217 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
218
219 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
220 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
221
222 // init state
223 m_saveListener = null;
224 m_obfSourceClass = null;
225 m_obfDestClass = null;
226 m_obfSourceEntry = null;
227 m_obfDestEntry = null;
228 setSourceType(SourceType.getDefault());
229 updateButtons();
230 }
231
232 protected void setSourceType(SourceType val) {
233 m_sourceType = val;
234 updateSourceClasses();
235 }
236
237 public void setSaveListener(SaveListener<T> val) {
238 m_saveListener = val;
239 }
240
241 private void updateSourceClasses() {
242
243 String selectedPackage = m_sourceClasses.getSelectedPackage();
244
245 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
246 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
247 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
248 }
249 m_sourceClasses.setClasses(deobfClassEntries);
250
251 if (selectedPackage != null) {
252 m_sourceClasses.expandPackage(selectedPackage);
253 }
254
255 for (SourceType sourceType : SourceType.values()) {
256 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
257 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
258 ));
259 }
260 }
261
262 protected void setSourceClass(ClassEntry sourceClass) {
263
264 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
265 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
266 if (m_obfDestClass == null) {
267 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
268 }
269
270 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
271 @Override
272 public void run() {
273 updateSourceHighlights();
274 }
275 });
276 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
277 @Override
278 public void run() {
279 updateDestHighlights();
280 }
281 });
282 }
283
284 protected void updateSourceHighlights() {
285 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
286 }
287
288 protected void updateDestHighlights() {
289 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
290 }
291
292 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
293 reader.clearHighlights();
294 SourceIndex index = reader.getSourceIndex();
295
296 // matched fields
297 for (T obfT : obfMatchedEntries) {
298 T deobfT = deobfuscator.deobfuscateEntry(obfT);
299 Token token = index.getDeclarationToken(deobfT);
300 if (token != null) {
301 reader.setHighlightedToken(token, m_matchedHighlightPainter);
302 }
303 }
304
305 // unmatched fields
306 for (T obfT : obfUnmatchedEntries) {
307 T deobfT = deobfuscator.deobfuscateEntry(obfT);
308 Token token = index.getDeclarationToken(deobfT);
309 if (token != null) {
310 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
311 }
312 }
313 }
314
315 private boolean isSelectionMatched() {
316 return m_obfSourceEntry != null && m_obfDestEntry != null
317 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
318 }
319
320 protected void onSelectSource(Entry source) {
321
322 // start with no selection
323 if (isSelectionMatched()) {
324 setDest(null);
325 }
326 setSource(null);
327
328 // then look for a valid source selection
329 if (source != null) {
330
331 // this looks really scary, but it's actually ok
332 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
333 // and MemberMatches.hasSource() will only pass entries that actually match T
334 @SuppressWarnings("unchecked")
335 T sourceEntry = (T) source;
336
337 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
338 if (m_memberMatches.hasSource(obfSourceEntry)) {
339 setSource(obfSourceEntry);
340
341 // look for a matched dest too
342 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
343 if (obfDestEntry != null) {
344 setDest(obfDestEntry);
345 }
346 }
347 }
348
349 updateButtons();
350 }
351
352 protected void onSelectDest(Entry dest) {
353
354 // start with no selection
355 if (isSelectionMatched()) {
356 setSource(null);
357 }
358 setDest(null);
359
360 // then look for a valid dest selection
361 if (dest != null) {
362
363 // this looks really scary, but it's actually ok
364 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
365 // and MemberMatches.hasSource() will only pass entries that actually match T
366 @SuppressWarnings("unchecked")
367 T destEntry = (T) dest;
368
369 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
370 if (m_memberMatches.hasDest(obfDestEntry)) {
371 setDest(obfDestEntry);
372
373 // look for a matched source too
374 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
375 if (obfSourceEntry != null) {
376 setSource(obfSourceEntry);
377 }
378 }
379 }
380
381 updateButtons();
382 }
383
384 private void setSource(T obfEntry) {
385 if (obfEntry == null) {
386 m_obfSourceEntry = obfEntry;
387 m_sourceLabel.setText("");
388 } else {
389 m_obfSourceEntry = obfEntry;
390 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
391 }
392 }
393
394 private void setDest(T obfEntry) {
395 if (obfEntry == null) {
396 m_obfDestEntry = obfEntry;
397 m_destLabel.setText("");
398 } else {
399 m_obfDestEntry = obfEntry;
400 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
401 }
402 }
403
404 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
405 // show obfuscated and deobfuscated names, but no types/signatures
406 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
407 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
408 }
409
410 private void updateButtons() {
411
412 GuiTricks.deactivateButton(m_matchButton);
413 GuiTricks.deactivateButton(m_unmatchableButton);
414
415 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
416 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
417 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
418 @Override
419 public void actionPerformed(ActionEvent event) {
420 unmatch();
421 }
422 });
423 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
424 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
425 @Override
426 public void actionPerformed(ActionEvent event) {
427 match();
428 }
429 });
430 }
431 } else if (m_obfSourceEntry != null) {
432 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
433 @Override
434 public void actionPerformed(ActionEvent event) {
435 unmatchable();
436 }
437 });
438 }
439 }
440
441 protected void match() {
442
443 // update the field matches
444 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
445 save();
446
447 // update the ui
448 onSelectSource(null);
449 onSelectDest(null);
450 updateSourceHighlights();
451 updateDestHighlights();
452 updateSourceClasses();
453 }
454
455 protected void unmatch() {
456
457 // update the field matches
458 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
459 save();
460
461 // update the ui
462 onSelectSource(null);
463 onSelectDest(null);
464 updateSourceHighlights();
465 updateDestHighlights();
466 updateSourceClasses();
467 }
468
469 protected void unmatchable() {
470
471 // update the field matches
472 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
473 save();
474
475 // update the ui
476 onSelectSource(null);
477 onSelectDest(null);
478 updateSourceHighlights();
479 updateDestHighlights();
480 updateSourceClasses();
481 }
482
483 private void save() {
484 if (m_saveListener != null) {
485 m_saveListener.save(m_memberMatches);
486 }
487 }
488}
diff --git a/src/main/java/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
new file mode 100644
index 0000000..caaf99c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class ObfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public ObfuscatedHighlightPainter() {
18 // red ish
19 super(new Color(255, 220, 220), new Color(160, 80, 80));
20 }
21}
diff --git a/src/main/java/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/OtherHighlightPainter.java
new file mode 100644
index 0000000..d2a2f02
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class OtherHighlightPainter extends BoxHighlightPainter {
16
17 public OtherHighlightPainter() {
18 // grey
19 super(null, new Color(180, 180, 180));
20 }
21}
diff --git a/src/main/java/cuchaz/enigma/gui/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/ProgressDialog.java
new file mode 100644
index 0000000..087d843
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,100 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17
18import javax.swing.*;
19
20import cuchaz.enigma.Constants;
21import cuchaz.enigma.Deobfuscator.ProgressListener;
22
23public class ProgressDialog implements ProgressListener, AutoCloseable {
24
25 private JFrame m_frame;
26 private JLabel m_title;
27 private JLabel m_text;
28 private JProgressBar m_progress;
29
30 public ProgressDialog(JFrame parent) {
31
32 // init frame
33 m_frame = new JFrame(Constants.Name + " - Operation in progress");
34 final Container pane = m_frame.getContentPane();
35 FlowLayout layout = new FlowLayout();
36 layout.setAlignment(FlowLayout.LEFT);
37 pane.setLayout(layout);
38
39 m_title = new JLabel();
40 pane.add(m_title);
41
42 // set up the progress bar
43 JPanel panel = new JPanel();
44 pane.add(panel);
45 panel.setLayout(new BorderLayout());
46 m_text = GuiTricks.unboldLabel(new JLabel());
47 m_progress = new JProgressBar();
48 m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
49 panel.add(m_text, BorderLayout.NORTH);
50 panel.add(m_progress, BorderLayout.CENTER);
51 panel.setPreferredSize(new Dimension(360, 50));
52
53 // show the frame
54 pane.doLayout();
55 m_frame.setSize(400, 120);
56 m_frame.setResizable(false);
57 m_frame.setLocationRelativeTo(parent);
58 m_frame.setVisible(true);
59 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
60 }
61
62 public void close() {
63 m_frame.dispose();
64 }
65
66 @Override
67 public void init(int totalWork, String title) {
68 m_title.setText(title);
69 m_progress.setMinimum(0);
70 m_progress.setMaximum(totalWork);
71 m_progress.setValue(0);
72 }
73
74 @Override
75 public void onProgress(int numDone, String message) {
76 m_text.setText(message);
77 m_progress.setValue(numDone);
78
79 // update the frame
80 m_frame.validate();
81 m_frame.repaint();
82 }
83
84 public interface ProgressRunnable {
85 void run(ProgressListener listener) throws Exception;
86 }
87
88 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
89 new Thread() {
90 @Override
91 public void run() {
92 try (ProgressDialog progress = new ProgressDialog(parent)) {
93 runnable.run(progress);
94 } catch (Exception ex) {
95 throw new Error(ex);
96 }
97 }
98 }.start();
99 }
100}
diff --git a/src/main/java/cuchaz/enigma/gui/ReadableToken.java b/src/main/java/cuchaz/enigma/gui/ReadableToken.java
new file mode 100644
index 0000000..feec8c0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13public class ReadableToken {
14
15 public int line;
16 public int startColumn;
17 public int endColumn;
18
19 public ReadableToken(int line, int startColumn, int endColumn) {
20 this.line = line;
21 this.startColumn = startColumn;
22 this.endColumn = endColumn;
23 }
24
25 @Override
26 public String toString() {
27 StringBuilder buf = new StringBuilder();
28 buf.append("line ");
29 buf.append(line);
30 buf.append(" columns ");
31 buf.append(startColumn);
32 buf.append("-");
33 buf.append(endColumn);
34 return buf.toString();
35 }
36}
diff --git a/src/main/java/cuchaz/enigma/gui/RenameListener.java b/src/main/java/cuchaz/enigma/gui/RenameListener.java
new file mode 100644
index 0000000..f0f9dcc
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/RenameListener.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface RenameListener {
16 void rename(Entry obfEntry, String newName);
17}
diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
new file mode 100644
index 0000000..d1e2de0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.ClassEntry;
14
15
16public class ScoredClassEntry extends ClassEntry {
17
18 private static final long serialVersionUID = -8798725308554217105L;
19
20 private float m_score;
21
22 public ScoredClassEntry(ClassEntry other, float score) {
23 super(other);
24 m_score = score;
25 }
26
27 public float getScore() {
28 return m_score;
29 }
30}
diff --git a/src/main/java/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/SelectionHighlightPainter.java
new file mode 100644
index 0000000..fcad07c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.*;
14
15import javax.swing.text.Highlighter;
16import javax.swing.text.JTextComponent;
17
18public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
19
20 @Override
21 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
22 // draw a thick border
23 Graphics2D g2d = (Graphics2D) g;
24 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
25 g2d.setColor(Color.black);
26 g2d.setStroke(new BasicStroke(2.0f));
27 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
28 }
29}
diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
new file mode 100644
index 0000000..efc8df8
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javax.swing.DefaultListCellRenderer;
16import javax.swing.JLabel;
17import javax.swing.JList;
18import javax.swing.ListCellRenderer;
19
20import cuchaz.enigma.analysis.Token;
21
22public class TokenListCellRenderer implements ListCellRenderer<Token> {
23
24 private GuiController m_controller;
25 private DefaultListCellRenderer m_defaultRenderer;
26
27 public TokenListCellRenderer(GuiController controller) {
28 m_controller = controller;
29 m_defaultRenderer = new DefaultListCellRenderer();
30 }
31
32 @Override
33 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
34 JLabel label = (JLabel) m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
35 label.setText(m_controller.getReadableToken(token).toString());
36 return label;
37 }
38}