summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui
diff options
context:
space:
mode:
authorGravatar asiekierka2016-08-17 18:35:12 +0200
committerGravatar asiekierka2016-08-17 18:35:12 +0200
commit5540c815de36e316d0749ce2163f12c61895b327 (patch)
tree2b30d5ae98735ee7cba7d1c0087c51d68ed3ebf9 /src/main/java/cuchaz/enigma/gui
parentRevert "Removed util" (diff)
downloadenigma-fork-5540c815de36e316d0749ce2163f12c61895b327.tar.gz
enigma-fork-5540c815de36e316d0749ce2163f12c61895b327.tar.xz
enigma-fork-5540c815de36e316d0749ce2163f12c61895b327.zip
Revert "Removed unused methods"
This reverts commit 1742190f784d0d62e7cc869eebafdfe1927e448f.
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
-rw-r--r--src/main/java/cuchaz/enigma/gui/BrowserCaret.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java546
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java120
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java223
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java490
-rw-r--r--src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java30
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java4
9 files changed, 1471 insertions, 2 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
index f58d012..f9701f2 100644
--- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
+++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
@@ -30,6 +30,6 @@ public class BrowserCaret extends DefaultCaret {
30 30
31 @Override 31 @Override
32 public Highlighter.HighlightPainter getSelectionPainter() { 32 public Highlighter.HighlightPainter getSelectionPainter() {
33 return selectionPainter; 33 return this.selectionPainter;
34 } 34 }
35} 35}
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..ec63900
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,546 @@
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.gui.node.ClassSelectorClassNode;
33import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
34import cuchaz.enigma.mapping.ClassEntry;
35import cuchaz.enigma.mapping.Mappings;
36import cuchaz.enigma.mapping.MappingsChecker;
37import cuchaz.enigma.throwables.MappingConflict;
38import de.sciss.syntaxpane.DefaultSyntaxKit;
39
40
41public class ClassMatchingGui {
42
43 private enum SourceType {
44 Matched {
45 @Override
46 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
47 return matches.getUniqueMatches().keySet();
48 }
49 },
50 Unmatched {
51 @Override
52 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
53 return matches.getUnmatchedSourceClasses();
54 }
55 },
56 Ambiguous {
57 @Override
58 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
59 return matches.getAmbiguouslyMatchedSourceClasses();
60 }
61 };
62
63 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
64 JRadioButton button = new JRadioButton(name(), this == getDefault());
65 button.setActionCommand(name());
66 button.addActionListener(listener);
67 group.add(button);
68 return button;
69 }
70
71 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
72
73 public static SourceType getDefault() {
74 return values()[0];
75 }
76 }
77
78 public interface SaveListener {
79 void save(ClassMatches matches);
80 }
81
82 // controls
83 private JFrame m_frame;
84 private ClassSelector m_sourceClasses;
85 private ClassSelector m_destClasses;
86 private CodeReader m_sourceReader;
87 private CodeReader m_destReader;
88 private JLabel m_sourceClassLabel;
89 private JLabel m_destClassLabel;
90 private JButton m_matchButton;
91 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
92 private JCheckBox m_advanceCheck;
93 private JCheckBox m_top10Matches;
94
95 private ClassMatches m_classMatches;
96 private Deobfuscator m_sourceDeobfuscator;
97 private Deobfuscator m_destDeobfuscator;
98 private ClassEntry m_sourceClass;
99 private ClassEntry m_destClass;
100 private SourceType m_sourceType;
101 private SaveListener m_saveListener;
102
103 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
104
105 m_classMatches = matches;
106 m_sourceDeobfuscator = sourceDeobfuscator;
107 m_destDeobfuscator = destDeobfuscator;
108
109 // init frame
110 m_frame = new JFrame(Constants.NAME + " - Class Matcher");
111 final Container pane = m_frame.getContentPane();
112 pane.setLayout(new BorderLayout());
113
114 // init source side
115 JPanel sourcePanel = new JPanel();
116 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
117 sourcePanel.setPreferredSize(new Dimension(200, 0));
118 pane.add(sourcePanel, BorderLayout.WEST);
119 sourcePanel.add(new JLabel("Source Classes"));
120
121 // init source type radios
122 JPanel sourceTypePanel = new JPanel();
123 sourcePanel.add(sourceTypePanel);
124 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
125 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
126 ButtonGroup sourceTypeButtons = new ButtonGroup();
127 m_sourceTypeButtons = Maps.newHashMap();
128 for (SourceType sourceType : SourceType.values()) {
129 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
130 m_sourceTypeButtons.put(sourceType, button);
131 sourceTypePanel.add(button);
132 }
133
134 m_sourceClasses = new ClassSelector(ClassSelector.DEOBF_CLASS_COMPARATOR);
135 m_sourceClasses.setListener(classEntry -> setSourceClass(classEntry));
136 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
137 sourcePanel.add(sourceScroller);
138
139 // init dest side
140 JPanel destPanel = new JPanel();
141 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
142 destPanel.setPreferredSize(new Dimension(200, 0));
143 pane.add(destPanel, BorderLayout.WEST);
144 destPanel.add(new JLabel("Destination Classes"));
145
146 m_top10Matches = new JCheckBox("Show only top 10 matches");
147 destPanel.add(m_top10Matches);
148 m_top10Matches.addActionListener(event -> toggleTop10Matches());
149
150 m_destClasses = new ClassSelector(ClassSelector.DEOBF_CLASS_COMPARATOR);
151 m_destClasses.setListener(this::setDestClass);
152 JScrollPane destScroller = new JScrollPane(m_destClasses);
153 destPanel.add(destScroller);
154
155 JButton autoMatchButton = new JButton("AutoMatch");
156 autoMatchButton.addActionListener(event -> autoMatch());
157 destPanel.add(autoMatchButton);
158
159 // init source panels
160 DefaultSyntaxKit.initKit();
161 m_sourceReader = new CodeReader();
162 m_destReader = new CodeReader();
163
164 // init all the splits
165 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
166 splitLeft.setResizeWeight(0); // let the right side take all the slack
167 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
168 splitRight.setResizeWeight(1); // let the left side take all the slack
169 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
170 splitCenter.setResizeWeight(0.5); // resize 50:50
171 pane.add(splitCenter, BorderLayout.CENTER);
172 splitCenter.resetToPreferredSizes();
173
174 // init bottom panel
175 JPanel bottomPanel = new JPanel();
176 bottomPanel.setLayout(new FlowLayout());
177
178 m_sourceClassLabel = new JLabel();
179 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
180 m_destClassLabel = new JLabel();
181 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
182
183 m_matchButton = new JButton();
184
185 m_advanceCheck = new JCheckBox("Advance to next likely match");
186 m_advanceCheck.addActionListener(event -> {
187 if (m_advanceCheck.isSelected()) {
188 advance();
189 }
190 });
191
192 bottomPanel.add(m_sourceClassLabel);
193 bottomPanel.add(m_matchButton);
194 bottomPanel.add(m_destClassLabel);
195 bottomPanel.add(m_advanceCheck);
196 pane.add(bottomPanel, BorderLayout.SOUTH);
197
198 // show the frame
199 pane.doLayout();
200 m_frame.setSize(1024, 576);
201 m_frame.setMinimumSize(new Dimension(640, 480));
202 m_frame.setVisible(true);
203 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
204
205 // init state
206 updateDestMappings();
207 setSourceType(SourceType.getDefault());
208 updateMatchButton();
209 m_saveListener = null;
210 }
211
212 public void setSaveListener(SaveListener val) {
213 m_saveListener = val;
214 }
215
216 private void updateDestMappings() {
217 try {
218 Mappings newMappings = MappingsConverter.newMappings(
219 m_classMatches,
220 m_sourceDeobfuscator.getMappings(),
221 m_sourceDeobfuscator,
222 m_destDeobfuscator
223 );
224
225 // look for dropped mappings
226 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
227 checker.dropBrokenMappings(newMappings);
228
229 // count them
230 int numDroppedFields = checker.getDroppedFieldMappings().size();
231 int numDroppedMethods = checker.getDroppedMethodMappings().size();
232 System.out.println(String.format(
233 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
234 numDroppedFields + numDroppedMethods,
235 numDroppedFields,
236 numDroppedMethods
237 ));
238
239 m_destDeobfuscator.setMappings(newMappings);
240 } catch (MappingConflict ex) {
241 System.out.println(ex.getMessage());
242 ex.printStackTrace();
243 return;
244 }
245 }
246
247 protected void setSourceType(SourceType val) {
248
249 // show the source classes
250 m_sourceType = val;
251 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
252
253 // update counts
254 for (SourceType sourceType : SourceType.values()) {
255 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
256 sourceType.name(),
257 sourceType.getSourceClasses(m_classMatches).size()
258 ));
259 }
260 }
261
262 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
263 List<ClassEntry> out = Lists.newArrayList();
264 for (ClassEntry entry : in) {
265
266 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
267
268 // make sure we preserve any scores
269 if (entry instanceof ScoredClassEntry) {
270 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
271 }
272
273 out.add(deobf);
274 }
275 return out;
276 }
277
278 protected void setSourceClass(ClassEntry classEntry) {
279
280 Runnable onGetDestClasses = null;
281 if (m_advanceCheck.isSelected()) {
282 onGetDestClasses = this::pickBestDestClass;
283 }
284
285 setSourceClass(classEntry, onGetDestClasses);
286 }
287
288 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
289
290 // update the current source class
291 m_sourceClass = classEntry;
292 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
293
294 if (m_sourceClass != null) {
295
296 // show the dest class(es)
297 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
298 assert (match != null);
299 if (match.destClasses.isEmpty()) {
300
301 m_destClasses.setClasses(null);
302
303 // run in a separate thread to keep ui responsive
304 new Thread() {
305 @Override
306 public void run() {
307 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
308 m_destClasses.expandAll();
309
310 if (onGetDestClasses != null) {
311 onGetDestClasses.run();
312 }
313 }
314 }.start();
315
316 } else {
317
318 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
319 m_destClasses.expandAll();
320
321 if (onGetDestClasses != null) {
322 onGetDestClasses.run();
323 }
324 }
325 }
326
327 setDestClass(null);
328 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, () -> m_sourceReader.navigateToClassDeclaration(m_sourceClass));
329
330 updateMatchButton();
331 }
332
333 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
334
335 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
336
337 // set up identifiers
338 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
339 ClassIdentifier sourceIdentifier = new ClassIdentifier(
340 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
341 namer.getSourceNamer(), true
342 );
343 ClassIdentifier destIdentifier = new ClassIdentifier(
344 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
345 namer.getDestNamer(), true
346 );
347
348 try {
349
350 // rank all the unmatched dest classes against the source class
351 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
352 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
353 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
354 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
355 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
356 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
357 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
358 }
359
360 if (m_top10Matches.isSelected() && scoredDestClasses.size() > 10) {
361 Collections.sort(scoredDestClasses, (a, b) -> {
362 ScoredClassEntry sa = (ScoredClassEntry) a;
363 ScoredClassEntry sb = (ScoredClassEntry) b;
364 return -Float.compare(sa.getScore(), sb.getScore());
365 });
366 scoredDestClasses = scoredDestClasses.subList(0, 10);
367 }
368
369 return scoredDestClasses;
370
371 } catch (ClassNotFoundException ex) {
372 throw new Error("Unable to find class " + ex.getMessage());
373 }
374 }
375
376 protected void setDestClass(ClassEntry classEntry) {
377
378 // update the current source class
379 m_destClass = classEntry;
380 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
381
382 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, () -> m_destReader.navigateToClassDeclaration(m_destClass));
383
384 updateMatchButton();
385 }
386
387 private void updateMatchButton() {
388
389 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
390 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
391
392 BiMap<ClassEntry, ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
393 boolean twoSelected = m_sourceClass != null && m_destClass != null;
394 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
395 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
396
397 GuiTricks.deactivateButton(m_matchButton);
398 if (twoSelected) {
399 if (isMatched) {
400 GuiTricks.activateButton(m_matchButton, "Unmatch", event -> onUnmatchClick());
401 } else if (canMatch) {
402 GuiTricks.activateButton(m_matchButton, "Match", event -> onMatchClick());
403 }
404 }
405 }
406
407 private void onMatchClick() {
408 // precondition: source and dest classes are set correctly
409
410 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
411 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
412
413 // remove the classes from their match
414 m_classMatches.removeSource(obfSource);
415 m_classMatches.removeDest(obfDest);
416
417 // add them as matched classes
418 m_classMatches.add(new ClassMatch(obfSource, obfDest));
419
420 ClassEntry nextClass = null;
421 if (m_advanceCheck.isSelected()) {
422 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
423 }
424
425 save();
426 updateMatches();
427
428 if (nextClass != null) {
429 advance(nextClass);
430 }
431 }
432
433 private void onUnmatchClick() {
434 // precondition: source and dest classes are set to a unique match
435
436 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
437
438 // remove the source to break the match, then add the source back as unmatched
439 m_classMatches.removeSource(obfSource);
440 m_classMatches.add(new ClassMatch(obfSource, null));
441
442 save();
443 updateMatches();
444 }
445
446 private void updateMatches() {
447 updateDestMappings();
448 setDestClass(null);
449 m_destClasses.setClasses(null);
450 updateMatchButton();
451
452 // remember where we were in the source tree
453 String packageName = m_sourceClasses.getSelectedPackage();
454
455 setSourceType(m_sourceType);
456
457 m_sourceClasses.expandPackage(packageName);
458 }
459
460 private void save() {
461 if (m_saveListener != null) {
462 m_saveListener.save(m_classMatches);
463 }
464 }
465
466 private void autoMatch() {
467
468 System.out.println("Automatching...");
469
470 // compute a new matching
471 ClassMatching matching = MappingsConverter.computeMatching(
472 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
473 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
474 m_classMatches.getUniqueMatches()
475 );
476 ClassMatches newMatches = new ClassMatches(matching.matches());
477 System.out.println(String.format("Automatch found %d new matches",
478 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
479 ));
480
481 // update the current matches
482 m_classMatches = newMatches;
483 save();
484 updateMatches();
485 }
486
487 private void advance() {
488 advance(null);
489 }
490
491 private void advance(ClassEntry sourceClass) {
492
493 // make sure we have a source class
494 if (sourceClass == null) {
495 sourceClass = m_sourceClasses.getSelectedClass();
496 if (sourceClass != null) {
497 sourceClass = m_sourceClasses.getNextClass(sourceClass);
498 } else {
499 sourceClass = m_sourceClasses.getFirstClass();
500 }
501 }
502
503 // set the source class
504 setSourceClass(sourceClass, this::pickBestDestClass);
505 m_sourceClasses.setSelectionClass(sourceClass);
506 }
507
508 private void pickBestDestClass() {
509
510 // then, pick the best dest class
511 ClassEntry firstClass = null;
512 ScoredClassEntry bestDestClass = null;
513 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
514 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
515 if (firstClass == null) {
516 firstClass = classNode.getClassEntry();
517 }
518 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
519 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
520 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
521 bestDestClass = scoredClass;
522 }
523 }
524 }
525 }
526
527 // pick the entry to show
528 ClassEntry destClass = null;
529 if (bestDestClass != null) {
530 destClass = bestDestClass;
531 } else if (firstClass != null) {
532 destClass = firstClass;
533 }
534
535 setDestClass(destClass);
536 m_destClasses.setSelectionClass(destClass);
537 }
538
539 private void toggleTop10Matches() {
540 if (m_sourceClass != null) {
541 m_destClasses.clearSelection();
542 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
543 m_destClasses.expandAll();
544 }
545 }
546}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 27b4d36..3df9042 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -138,6 +138,31 @@ public class ClassSelector extends JTree {
138 restoreExpanstionState(this, 0, state); 138 restoreExpanstionState(this, 0, state);
139 } 139 }
140 140
141 public ClassEntry getSelectedClass() {
142 if (!isSelectionEmpty()) {
143 Object selectedNode = getSelectionPath().getLastPathComponent();
144 if (selectedNode instanceof ClassSelectorClassNode) {
145 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
146 return classNode.getClassEntry();
147 }
148 }
149 return null;
150 }
151
152 public String getSelectedPackage() {
153 if (!isSelectionEmpty()) {
154 Object selectedNode = getSelectionPath().getLastPathComponent();
155 if (selectedNode instanceof ClassSelectorPackageNode) {
156 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode;
157 return packageNode.getPackageName();
158 } else if (selectedNode instanceof ClassSelectorClassNode) {
159 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
160 return classNode.getClassEntry().getPackageName();
161 }
162 }
163 return null;
164 }
165
141 public boolean isDescendant(TreePath path1, TreePath path2) { 166 public boolean isDescendant(TreePath path1, TreePath path2) {
142 int count1 = path1.getPathCount(); 167 int count1 = path1.getPathCount();
143 int count2 = path2.getPathCount(); 168 int count2 = path2.getPathCount();
@@ -175,4 +200,99 @@ public class ClassSelector extends JTree {
175 tree.expandRow(token); 200 tree.expandRow(token);
176 } 201 }
177 } 202 }
203
204 public Iterable<ClassSelectorPackageNode> packageNodes() {
205 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
206 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
207 Enumeration<?> children = root.children();
208 while (children.hasMoreElements()) {
209 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement();
210 nodes.add(packageNode);
211 }
212 return nodes;
213 }
214
215 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
216 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
217 Enumeration<?> children = packageNode.children();
218 while (children.hasMoreElements()) {
219 ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement();
220 nodes.add(classNode);
221 }
222 return nodes;
223 }
224
225 public void expandPackage(String packageName) {
226 if (packageName == null) {
227 return;
228 }
229 for (ClassSelectorPackageNode packageNode : packageNodes()) {
230 if (packageNode.getPackageName().equals(packageName)) {
231 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
232 return;
233 }
234 }
235 }
236
237 public void expandAll() {
238 for (ClassSelectorPackageNode packageNode : packageNodes()) {
239 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
240 }
241 }
242
243 public ClassEntry getFirstClass() {
244 for (ClassSelectorPackageNode packageNode : packageNodes()) {
245 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
246 return classNode.getClassEntry();
247 }
248 }
249 return null;
250 }
251
252 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
253 for (ClassSelectorPackageNode packageNode : packageNodes()) {
254 if (packageNode.getPackageName().equals(entry.getPackageName())) {
255 return packageNode;
256 }
257 }
258 return null;
259 }
260
261 public ClassEntry getNextClass(ClassEntry entry) {
262 boolean foundIt = false;
263 for (ClassSelectorPackageNode packageNode : packageNodes()) {
264 if (!foundIt) {
265 // skip to the package with our target in it
266 if (packageNode.getPackageName().equals(entry.getPackageName())) {
267 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
268 if (!foundIt) {
269 if (classNode.getClassEntry().equals(entry)) {
270 foundIt = true;
271 }
272 } else {
273 // return the next class
274 return classNode.getClassEntry();
275 }
276 }
277 }
278 } else {
279 // return the next class
280 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
281 return classNode.getClassEntry();
282 }
283 }
284 }
285 return null;
286 }
287
288 public void setSelectionClass(ClassEntry classEntry) {
289 expandPackage(classEntry.getPackageName());
290 for (ClassSelectorPackageNode packageNode : packageNodes()) {
291 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
292 if (classNode.getClassEntry().equals(classEntry)) {
293 setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode}));
294 }
295 }
296 }
297 }
178} 298}
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..601e5b9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,223 @@
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.gui.highlight.SelectionHighlightPainter;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.Entry;
34import de.sciss.syntaxpane.DefaultSyntaxKit;
35
36
37public class CodeReader extends JEditorPane {
38
39 private static final long serialVersionUID = 3673180950485748810L;
40
41 private static final Object m_lock = new Object();
42
43 public interface SelectionListener {
44 void onSelect(EntryReference<Entry, Entry> reference);
45 }
46
47 private SelectionHighlightPainter m_selectionHighlightPainter;
48 private SourceIndex m_sourceIndex;
49 private SelectionListener m_selectionListener;
50
51 public CodeReader() {
52
53 setEditable(false);
54 setContentType("text/java");
55
56 // turn off token highlighting (it's wrong most of the time anyway...)
57 DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit();
58 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
59
60 // hook events
61 addCaretListener(new CaretListener() {
62 @Override
63 public void caretUpdate(CaretEvent event) {
64 if (m_selectionListener != null && m_sourceIndex != null) {
65 Token token = m_sourceIndex.getReferenceToken(event.getDot());
66 if (token != null) {
67 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
68 } else {
69 m_selectionListener.onSelect(null);
70 }
71 }
72 }
73 });
74
75 m_selectionHighlightPainter = new SelectionHighlightPainter();
76 m_sourceIndex = null;
77 m_selectionListener = null;
78 }
79
80 public void setSelectionListener(SelectionListener val) {
81 m_selectionListener = val;
82 }
83
84 public void setCode(String code) {
85 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
86 synchronized (m_lock) {
87 setText(code);
88 }
89 }
90
91 public SourceIndex getSourceIndex() {
92 return m_sourceIndex;
93 }
94
95 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
96 decompileClass(classEntry, deobfuscator, null);
97 }
98
99 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
100 decompileClass(classEntry, deobfuscator, null, callback);
101 }
102
103 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
104
105 if (classEntry == null) {
106 setCode(null);
107 return;
108 }
109
110 setCode("(decompiling...)");
111
112 // run decompilation in a separate thread to keep ui responsive
113 new Thread() {
114 @Override
115 public void run() {
116
117 // decompile it
118 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
119 String source = deobfuscator.getSource(sourceTree);
120 setCode(source);
121 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
122
123 if (callback != null) {
124 callback.run();
125 }
126 }
127 }.start();
128 }
129
130 public void navigateToClassDeclaration(ClassEntry classEntry) {
131
132 // navigate to the class declaration
133 Token token = m_sourceIndex.getDeclarationToken(classEntry);
134 if (token == null) {
135 // couldn't find the class declaration token, might be an anonymous class
136 // look for any declaration in that class instead
137 for (Entry entry : m_sourceIndex.declarations()) {
138 if (entry.getClassEntry().equals(classEntry)) {
139 token = m_sourceIndex.getDeclarationToken(entry);
140 break;
141 }
142 }
143 }
144
145 if (token != null) {
146 navigateToToken(token);
147 } else {
148 // couldn't find anything =(
149 System.out.println("Unable to find declaration in source for " + classEntry);
150 }
151 }
152
153 public void navigateToToken(final Token token) {
154 navigateToToken(this, token, m_selectionHighlightPainter);
155 }
156
157 // HACKHACK: someday we can update the main GUI to use this code reader
158 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
159
160 // set the caret position to the token
161 editor.setCaretPosition(token.start);
162 editor.grabFocus();
163
164 try {
165 // make sure the token is visible in the scroll window
166 Rectangle start = editor.modelToView(token.start);
167 Rectangle end = editor.modelToView(token.end);
168 final Rectangle show = start.union(end);
169 show.grow(start.width * 10, start.height * 6);
170 SwingUtilities.invokeLater(new Runnable() {
171 @Override
172 public void run() {
173 editor.scrollRectToVisible(show);
174 }
175 });
176 } catch (BadLocationException ex) {
177 throw new Error(ex);
178 }
179
180 // highlight the token momentarily
181 final Timer timer = new Timer(200, new ActionListener() {
182 private int m_counter = 0;
183 private Object m_highlight = null;
184
185 @Override
186 public void actionPerformed(ActionEvent event) {
187 if (m_counter % 2 == 0) {
188 try {
189 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
190 } catch (BadLocationException ex) {
191 // don't care
192 }
193 } else if (m_highlight != null) {
194 editor.getHighlighter().removeHighlight(m_highlight);
195 }
196
197 if (m_counter++ > 6) {
198 Timer timer = (Timer) event.getSource();
199 timer.stop();
200 }
201 }
202 });
203 timer.start();
204 }
205
206 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
207 for (Token token : tokens) {
208 setHighlightedToken(token, painter);
209 }
210 }
211
212 public void setHighlightedToken(Token token, HighlightPainter painter) {
213 try {
214 getHighlighter().addHighlight(token.start, token.end, painter);
215 } catch (BadLocationException ex) {
216 throw new IllegalArgumentException(ex);
217 }
218 }
219
220 public void clearHighlights() {
221 getHighlighter().removeAllHighlights();
222 }
223}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index 80cb3ed..fd59a81 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -370,7 +370,7 @@ public class Gui {
370 if (token == null) { 370 if (token == null) {
371 throw new IllegalArgumentException("Token cannot be null!"); 371 throw new IllegalArgumentException("Token cannot be null!");
372 } 372 }
373 Utils.navigateToToken(this.editor, token, m_selectionHighlightPainter); 373 CodeReader.navigateToToken(this.editor, token, m_selectionHighlightPainter);
374 redraw(); 374 redraw();
375 } 375 }
376 376
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..60c6d8e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,490 @@
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.gui.highlight.DeobfuscatedHighlightPainter;
40import cuchaz.enigma.gui.highlight.ObfuscatedHighlightPainter;
41import cuchaz.enigma.mapping.ClassEntry;
42import cuchaz.enigma.mapping.Entry;
43import de.sciss.syntaxpane.DefaultSyntaxKit;
44
45
46public class MemberMatchingGui<T extends Entry> {
47
48 private enum SourceType {
49 Matched {
50 @Override
51 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
52 return matches.getSourceClassesWithoutUnmatchedEntries();
53 }
54 },
55 Unmatched {
56 @Override
57 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
58 return matches.getSourceClassesWithUnmatchedEntries();
59 }
60 };
61
62 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
63 JRadioButton button = new JRadioButton(name(), this == getDefault());
64 button.setActionCommand(name());
65 button.addActionListener(listener);
66 group.add(button);
67 return button;
68 }
69
70 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
71
72 public static SourceType getDefault() {
73 return values()[0];
74 }
75 }
76
77 public interface SaveListener<T extends Entry> {
78 void save(MemberMatches<T> matches);
79 }
80
81 // controls
82 private JFrame m_frame;
83 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
84 private ClassSelector m_sourceClasses;
85 private CodeReader m_sourceReader;
86 private CodeReader m_destReader;
87 private JButton m_matchButton;
88 private JButton m_unmatchableButton;
89 private JLabel m_sourceLabel;
90 private JLabel m_destLabel;
91 private HighlightPainter m_unmatchedHighlightPainter;
92 private HighlightPainter m_matchedHighlightPainter;
93
94 private ClassMatches m_classMatches;
95 private MemberMatches<T> m_memberMatches;
96 private Deobfuscator m_sourceDeobfuscator;
97 private Deobfuscator m_destDeobfuscator;
98 private SaveListener<T> m_saveListener;
99 private SourceType m_sourceType;
100 private ClassEntry m_obfSourceClass;
101 private ClassEntry m_obfDestClass;
102 private T m_obfSourceEntry;
103 private T m_obfDestEntry;
104
105 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
106
107 m_classMatches = classMatches;
108 m_memberMatches = fieldMatches;
109 m_sourceDeobfuscator = sourceDeobfuscator;
110 m_destDeobfuscator = destDeobfuscator;
111
112 // init frame
113 m_frame = new JFrame(Constants.NAME + " - Member Matcher");
114 final Container pane = m_frame.getContentPane();
115 pane.setLayout(new BorderLayout());
116
117 // init classes side
118 JPanel classesPanel = new JPanel();
119 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
120 classesPanel.setPreferredSize(new Dimension(200, 0));
121 pane.add(classesPanel, BorderLayout.WEST);
122 classesPanel.add(new JLabel("Classes"));
123
124 // init source type radios
125 JPanel sourceTypePanel = new JPanel();
126 classesPanel.add(sourceTypePanel);
127 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
128 ActionListener sourceTypeListener = new ActionListener() {
129 @Override
130 public void actionPerformed(ActionEvent event) {
131 setSourceType(SourceType.valueOf(event.getActionCommand()));
132 }
133 };
134 ButtonGroup sourceTypeButtons = new ButtonGroup();
135 m_sourceTypeButtons = Maps.newHashMap();
136 for (SourceType sourceType : SourceType.values()) {
137 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
138 m_sourceTypeButtons.put(sourceType, button);
139 sourceTypePanel.add(button);
140 }
141
142 m_sourceClasses = new ClassSelector(ClassSelector.DEOBF_CLASS_COMPARATOR);
143 m_sourceClasses.setListener(new ClassSelectionListener() {
144 @Override
145 public void onSelectClass(ClassEntry classEntry) {
146 setSourceClass(classEntry);
147 }
148 });
149 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
150 classesPanel.add(sourceScroller);
151
152 // init readers
153 DefaultSyntaxKit.initKit();
154 m_sourceReader = new CodeReader();
155 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
156 @Override
157 public void onSelect(EntryReference<Entry, Entry> reference) {
158 if (reference != null) {
159 onSelectSource(reference.entry);
160 } else {
161 onSelectSource(null);
162 }
163 }
164 });
165 m_destReader = new CodeReader();
166 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
167 @Override
168 public void onSelect(EntryReference<Entry, Entry> reference) {
169 if (reference != null) {
170 onSelectDest(reference.entry);
171 } else {
172 onSelectDest(null);
173 }
174 }
175 });
176
177 // add key bindings
178 KeyAdapter keyListener = new KeyAdapter() {
179 @Override
180 public void keyPressed(KeyEvent event) {
181 switch (event.getKeyCode()) {
182 case KeyEvent.VK_M:
183 m_matchButton.doClick();
184 break;
185 }
186 }
187 };
188 m_sourceReader.addKeyListener(keyListener);
189 m_destReader.addKeyListener(keyListener);
190
191 // init all the splits
192 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
193 splitRight.setResizeWeight(0.5); // resize 50:50
194 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
195 splitLeft.setResizeWeight(0); // let the right side take all the slack
196 pane.add(splitLeft, BorderLayout.CENTER);
197 splitLeft.resetToPreferredSizes();
198
199 // init bottom panel
200 JPanel bottomPanel = new JPanel();
201 bottomPanel.setLayout(new FlowLayout());
202 pane.add(bottomPanel, BorderLayout.SOUTH);
203
204 m_matchButton = new JButton();
205 m_unmatchableButton = new JButton();
206
207 m_sourceLabel = new JLabel();
208 bottomPanel.add(m_sourceLabel);
209 bottomPanel.add(m_matchButton);
210 bottomPanel.add(m_unmatchableButton);
211 m_destLabel = new JLabel();
212 bottomPanel.add(m_destLabel);
213
214 // show the frame
215 pane.doLayout();
216 m_frame.setSize(1024, 576);
217 m_frame.setMinimumSize(new Dimension(640, 480));
218 m_frame.setVisible(true);
219 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
220
221 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
222 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
223
224 // init state
225 m_saveListener = null;
226 m_obfSourceClass = null;
227 m_obfDestClass = null;
228 m_obfSourceEntry = null;
229 m_obfDestEntry = null;
230 setSourceType(SourceType.getDefault());
231 updateButtons();
232 }
233
234 protected void setSourceType(SourceType val) {
235 m_sourceType = val;
236 updateSourceClasses();
237 }
238
239 public void setSaveListener(SaveListener<T> val) {
240 m_saveListener = val;
241 }
242
243 private void updateSourceClasses() {
244
245 String selectedPackage = m_sourceClasses.getSelectedPackage();
246
247 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
248 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
249 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
250 }
251 m_sourceClasses.setClasses(deobfClassEntries);
252
253 if (selectedPackage != null) {
254 m_sourceClasses.expandPackage(selectedPackage);
255 }
256
257 for (SourceType sourceType : SourceType.values()) {
258 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
259 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
260 ));
261 }
262 }
263
264 protected void setSourceClass(ClassEntry sourceClass) {
265
266 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
267 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
268 if (m_obfDestClass == null) {
269 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
270 }
271
272 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
273 @Override
274 public void run() {
275 updateSourceHighlights();
276 }
277 });
278 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
279 @Override
280 public void run() {
281 updateDestHighlights();
282 }
283 });
284 }
285
286 protected void updateSourceHighlights() {
287 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
288 }
289
290 protected void updateDestHighlights() {
291 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
292 }
293
294 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
295 reader.clearHighlights();
296 SourceIndex index = reader.getSourceIndex();
297
298 // matched fields
299 for (T obfT : obfMatchedEntries) {
300 T deobfT = deobfuscator.deobfuscateEntry(obfT);
301 Token token = index.getDeclarationToken(deobfT);
302 if (token != null) {
303 reader.setHighlightedToken(token, m_matchedHighlightPainter);
304 }
305 }
306
307 // unmatched fields
308 for (T obfT : obfUnmatchedEntries) {
309 T deobfT = deobfuscator.deobfuscateEntry(obfT);
310 Token token = index.getDeclarationToken(deobfT);
311 if (token != null) {
312 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
313 }
314 }
315 }
316
317 private boolean isSelectionMatched() {
318 return m_obfSourceEntry != null && m_obfDestEntry != null
319 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
320 }
321
322 protected void onSelectSource(Entry source) {
323
324 // start with no selection
325 if (isSelectionMatched()) {
326 setDest(null);
327 }
328 setSource(null);
329
330 // then look for a valid source selection
331 if (source != null) {
332
333 // this looks really scary, but it's actually ok
334 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
335 // and MemberMatches.hasSource() will only pass entries that actually match T
336 @SuppressWarnings("unchecked")
337 T sourceEntry = (T) source;
338
339 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
340 if (m_memberMatches.hasSource(obfSourceEntry)) {
341 setSource(obfSourceEntry);
342
343 // look for a matched dest too
344 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
345 if (obfDestEntry != null) {
346 setDest(obfDestEntry);
347 }
348 }
349 }
350
351 updateButtons();
352 }
353
354 protected void onSelectDest(Entry dest) {
355
356 // start with no selection
357 if (isSelectionMatched()) {
358 setSource(null);
359 }
360 setDest(null);
361
362 // then look for a valid dest selection
363 if (dest != null) {
364
365 // this looks really scary, but it's actually ok
366 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
367 // and MemberMatches.hasSource() will only pass entries that actually match T
368 @SuppressWarnings("unchecked")
369 T destEntry = (T) dest;
370
371 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
372 if (m_memberMatches.hasDest(obfDestEntry)) {
373 setDest(obfDestEntry);
374
375 // look for a matched source too
376 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
377 if (obfSourceEntry != null) {
378 setSource(obfSourceEntry);
379 }
380 }
381 }
382
383 updateButtons();
384 }
385
386 private void setSource(T obfEntry) {
387 if (obfEntry == null) {
388 m_obfSourceEntry = obfEntry;
389 m_sourceLabel.setText("");
390 } else {
391 m_obfSourceEntry = obfEntry;
392 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
393 }
394 }
395
396 private void setDest(T obfEntry) {
397 if (obfEntry == null) {
398 m_obfDestEntry = obfEntry;
399 m_destLabel.setText("");
400 } else {
401 m_obfDestEntry = obfEntry;
402 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
403 }
404 }
405
406 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
407 // show obfuscated and deobfuscated names, but no types/signatures
408 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
409 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
410 }
411
412 private void updateButtons() {
413
414 GuiTricks.deactivateButton(m_matchButton);
415 GuiTricks.deactivateButton(m_unmatchableButton);
416
417 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
418 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
419 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
420 @Override
421 public void actionPerformed(ActionEvent event) {
422 unmatch();
423 }
424 });
425 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
426 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
427 @Override
428 public void actionPerformed(ActionEvent event) {
429 match();
430 }
431 });
432 }
433 } else if (m_obfSourceEntry != null) {
434 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
435 @Override
436 public void actionPerformed(ActionEvent event) {
437 unmatchable();
438 }
439 });
440 }
441 }
442
443 protected void match() {
444
445 // update the field matches
446 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
447 save();
448
449 // update the ui
450 onSelectSource(null);
451 onSelectDest(null);
452 updateSourceHighlights();
453 updateDestHighlights();
454 updateSourceClasses();
455 }
456
457 protected void unmatch() {
458
459 // update the field matches
460 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
461 save();
462
463 // update the ui
464 onSelectSource(null);
465 onSelectDest(null);
466 updateSourceHighlights();
467 updateDestHighlights();
468 updateSourceClasses();
469 }
470
471 protected void unmatchable() {
472
473 // update the field matches
474 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
475 save();
476
477 // update the ui
478 onSelectSource(null);
479 onSelectDest(null);
480 updateSourceHighlights();
481 updateDestHighlights();
482 updateSourceClasses();
483 }
484
485 private void save() {
486 if (m_saveListener != null) {
487 m_saveListener.save(m_memberMatches);
488 }
489 }
490}
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/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
index 805b3a8..dfdc765 100644
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
+++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
@@ -20,6 +20,10 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
20 this.packageName = packageName; 20 this.packageName = packageName;
21 } 21 }
22 22
23 public String getPackageName() {
24 return packageName;
25 }
26
23 @Override 27 @Override
24 public String toString() { 28 public String toString() {
25 return this.packageName; 29 return this.packageName;