summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/gui/MatchingGui.java
diff options
context:
space:
mode:
authorGravatar jeff2015-03-09 11:03:35 -0400
committerGravatar jeff2015-03-09 11:03:35 -0400
commit93c053803404382163d728e044d6dd49e76a5007 (patch)
treea5e8c610185f83550d6ae5df158fe6b619ca096c /src/cuchaz/enigma/gui/MatchingGui.java
parentmore tweaks, improvements, and bug fixes (diff)
downloadenigma-fork-93c053803404382163d728e044d6dd49e76a5007.tar.gz
enigma-fork-93c053803404382163d728e044d6dd49e76a5007.tar.xz
enigma-fork-93c053803404382163d728e044d6dd49e76a5007.zip
add tracking for mismatched fields/methods
Diffstat (limited to 'src/cuchaz/enigma/gui/MatchingGui.java')
-rw-r--r--src/cuchaz/enigma/gui/MatchingGui.java642
1 files changed, 0 insertions, 642 deletions
diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java
deleted file mode 100644
index 85842c1..0000000
--- a/src/cuchaz/enigma/gui/MatchingGui.java
+++ /dev/null
@@ -1,642 +0,0 @@
1package cuchaz.enigma.gui;
2
3import java.awt.BorderLayout;
4import java.awt.Container;
5import java.awt.Dimension;
6import java.awt.FlowLayout;
7import java.awt.event.ActionEvent;
8import java.awt.event.ActionListener;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.List;
12import java.util.Map;
13
14import javax.swing.BoxLayout;
15import javax.swing.ButtonGroup;
16import javax.swing.JButton;
17import javax.swing.JCheckBox;
18import javax.swing.JEditorPane;
19import javax.swing.JFrame;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JRadioButton;
23import javax.swing.JScrollPane;
24import javax.swing.JSplitPane;
25import javax.swing.SwingConstants;
26import javax.swing.WindowConstants;
27
28import com.beust.jcommander.internal.Lists;
29import com.beust.jcommander.internal.Maps;
30import com.google.common.collect.BiMap;
31import com.strobel.decompiler.languages.java.ast.CompilationUnit;
32
33import cuchaz.enigma.Constants;
34import cuchaz.enigma.Deobfuscator;
35import cuchaz.enigma.analysis.SourceIndex;
36import cuchaz.enigma.analysis.Token;
37import cuchaz.enigma.convert.ClassIdentifier;
38import cuchaz.enigma.convert.ClassIdentity;
39import cuchaz.enigma.convert.ClassMatch;
40import cuchaz.enigma.convert.ClassMatching;
41import cuchaz.enigma.convert.ClassNamer;
42import cuchaz.enigma.convert.MappingsConverter;
43import cuchaz.enigma.convert.Matches;
44import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
45import cuchaz.enigma.mapping.ClassEntry;
46import cuchaz.enigma.mapping.Entry;
47import de.sciss.syntaxpane.DefaultSyntaxKit;
48
49
50public class MatchingGui {
51
52 private static enum SourceType {
53 Matched {
54
55 @Override
56 public Collection<ClassEntry> getSourceClasses(Matches matches) {
57 return matches.getUniqueMatches().keySet();
58 }
59 },
60 Unmatched {
61
62 @Override
63 public Collection<ClassEntry> getSourceClasses(Matches matches) {
64 return matches.getUnmatchedSourceClasses();
65 }
66 },
67 Ambiguous {
68
69 @Override
70 public Collection<ClassEntry> getSourceClasses(Matches matches) {
71 return matches.getAmbiguouslyMatchedSourceClasses();
72 }
73 };
74
75 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
76 JRadioButton button = new JRadioButton(name(), this == getDefault());
77 button.setActionCommand(name());
78 button.addActionListener(listener);
79 group.add(button);
80 return button;
81 }
82
83 public abstract Collection<ClassEntry> getSourceClasses(Matches matches);
84
85 public static SourceType getDefault() {
86 return values()[0];
87 }
88 }
89
90 public static interface SaveListener {
91 public void save(Matches matches);
92 }
93
94 // controls
95 private JFrame m_frame;
96 private ClassSelector m_sourceClasses;
97 private ClassSelector m_destClasses;
98 private JEditorPane m_sourceReader;
99 private JEditorPane m_destReader;
100 private JLabel m_sourceClassLabel;
101 private JLabel m_destClassLabel;
102 private JButton m_matchButton;
103 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
104 private JCheckBox m_advanceCheck;
105 private SelectionHighlightPainter m_selectionHighlightPainter;
106
107 private Matches m_matches;
108 private Deobfuscator m_sourceDeobfuscator;
109 private Deobfuscator m_destDeobfuscator;
110 private ClassEntry m_sourceClass;
111 private ClassEntry m_destClass;
112 private SourceType m_sourceType;
113 private SaveListener m_saveListener;
114
115 public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
116
117 m_matches = matches;
118 m_sourceDeobfuscator = sourceDeobfuscator;
119 m_destDeobfuscator = destDeobfuscator;
120
121 // init frame
122 m_frame = new JFrame(Constants.Name);
123 final Container pane = m_frame.getContentPane();
124 pane.setLayout(new BorderLayout());
125
126 // init source side
127 JPanel sourcePanel = new JPanel();
128 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
129 sourcePanel.setPreferredSize(new Dimension(200, 0));
130 pane.add(sourcePanel, BorderLayout.WEST);
131 sourcePanel.add(new JLabel("Source Classes"));
132
133 // init source type radios
134 JPanel sourceTypePanel = new JPanel();
135 sourcePanel.add(sourceTypePanel);
136 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
137 ActionListener sourceTypeListener = new ActionListener() {
138 @Override
139 public void actionPerformed(ActionEvent event) {
140 setSourceType(SourceType.valueOf(event.getActionCommand()));
141 }
142 };
143 ButtonGroup sourceTypeButtons = new ButtonGroup();
144 m_sourceTypeButtons = Maps.newHashMap();
145 for (SourceType sourceType : SourceType.values()) {
146 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
147 m_sourceTypeButtons.put(sourceType, button);
148 sourceTypePanel.add(button);
149 }
150
151 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
152 m_sourceClasses.setListener(new ClassSelectionListener() {
153 @Override
154 public void onSelectClass(ClassEntry classEntry) {
155 setSourceClass(classEntry);
156 }
157 });
158 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
159 sourcePanel.add(sourceScroller);
160
161 // init dest side
162 JPanel destPanel = new JPanel();
163 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
164 destPanel.setPreferredSize(new Dimension(200, 0));
165 pane.add(destPanel, BorderLayout.WEST);
166 destPanel.add(new JLabel("Destination Classes"));
167
168 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
169 m_destClasses.setListener(new ClassSelectionListener() {
170 @Override
171 public void onSelectClass(ClassEntry classEntry) {
172 setDestClass(classEntry);
173 }
174 });
175 JScrollPane destScroller = new JScrollPane(m_destClasses);
176 destPanel.add(destScroller);
177
178 JButton autoMatchButton = new JButton("AutoMatch");
179 autoMatchButton.addActionListener(new ActionListener() {
180 @Override
181 public void actionPerformed(ActionEvent event) {
182 autoMatch();
183 }
184 });
185 destPanel.add(autoMatchButton);
186
187 // init source panels
188 DefaultSyntaxKit.initKit();
189 m_sourceReader = makeReader();
190 m_destReader = makeReader();
191
192 // init all the splits
193 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
194 splitLeft.setResizeWeight(0); // let the right side take all the slack
195 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
196 splitRight.setResizeWeight(1); // let the left side take all the slack
197 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
198 splitCenter.setResizeWeight(0.5); // resize 50:50
199 pane.add(splitCenter, BorderLayout.CENTER);
200 splitCenter.resetToPreferredSizes();
201
202 // init bottom panel
203 JPanel bottomPanel = new JPanel();
204 bottomPanel.setLayout(new FlowLayout());
205
206 m_sourceClassLabel = new JLabel();
207 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
208 m_sourceClassLabel.setPreferredSize(new Dimension(300, 24));
209 m_destClassLabel = new JLabel();
210 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
211 m_destClassLabel.setPreferredSize(new Dimension(300, 24));
212
213 m_matchButton = new JButton();
214 m_matchButton.setPreferredSize(new Dimension(140, 24));
215
216 m_advanceCheck = new JCheckBox("Advance to next likely match");
217 m_advanceCheck.addActionListener(new ActionListener() {
218 @Override
219 public void actionPerformed(ActionEvent event) {
220 if (m_advanceCheck.isSelected()) {
221 advance();
222 }
223 }
224 });
225
226 bottomPanel.add(m_sourceClassLabel);
227 bottomPanel.add(m_matchButton);
228 bottomPanel.add(m_destClassLabel);
229 bottomPanel.add(m_advanceCheck);
230 pane.add(bottomPanel, BorderLayout.SOUTH);
231
232 // show the frame
233 pane.doLayout();
234 m_frame.setSize(1024, 576);
235 m_frame.setMinimumSize(new Dimension(640, 480));
236 m_frame.setVisible(true);
237 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
238
239 m_selectionHighlightPainter = new SelectionHighlightPainter();
240
241 // init state
242 updateDestMappings();
243 setSourceType(SourceType.getDefault());
244 updateMatchButton();
245 m_saveListener = null;
246 }
247
248 private JEditorPane makeReader() {
249
250 JEditorPane reader = new JEditorPane();
251 reader.setEditable(false);
252 reader.setContentType("text/java");
253
254 // turn off token highlighting (it's wrong most of the time anyway...)
255 DefaultSyntaxKit kit = (DefaultSyntaxKit)reader.getEditorKit();
256 kit.toggleComponent(reader, "de.sciss.syntaxpane.components.TokenMarker");
257
258 return reader;
259 }
260
261 public void setSaveListener(SaveListener val) {
262 m_saveListener = val;
263 }
264
265 private void updateDestMappings() {
266 m_destDeobfuscator.setMappings(MappingsConverter.newMappings(
267 m_matches,
268 m_sourceDeobfuscator.getMappings(),
269 m_sourceDeobfuscator,
270 m_destDeobfuscator
271 ), false);
272 }
273
274 protected void setSourceType(SourceType val) {
275
276 // show the source classes
277 m_sourceType = val;
278 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator));
279
280 // update counts
281 for (SourceType sourceType : SourceType.values()) {
282 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
283 sourceType.name(),
284 sourceType.getSourceClasses(m_matches).size()
285 ));
286 }
287 }
288
289 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
290 List<ClassEntry> out = Lists.newArrayList();
291 for (ClassEntry entry : in) {
292
293 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
294
295 // make sure we preserve any scores
296 if (entry instanceof ScoredClassEntry) {
297 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore());
298 }
299
300 out.add(deobf);
301 }
302 return out;
303 }
304
305 protected void setSourceClass(ClassEntry classEntry) {
306
307 Runnable onGetDestClasses = null;
308 if (m_advanceCheck.isSelected()) {
309 onGetDestClasses = new Runnable() {
310 @Override
311 public void run() {
312 pickBestDestClass();
313 }
314 };
315 }
316
317 setSourceClass(classEntry, onGetDestClasses);
318 }
319
320 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
321
322 // update the current source class
323 m_sourceClass = classEntry;
324 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
325
326 if (m_sourceClass != null) {
327
328 // show the dest class(es)
329 ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
330 assert(match != null);
331 if (match.destClasses.isEmpty()) {
332
333 m_destClasses.setClasses(null);
334
335 // run in a separate thread to keep ui responsive
336 new Thread() {
337 @Override
338 public void run() {
339 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
340 m_destClasses.expandAll();
341
342 if (onGetDestClasses != null) {
343 onGetDestClasses.run();
344 }
345 }
346 }.start();
347
348 } else {
349
350 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
351 m_destClasses.expandAll();
352
353 if (onGetDestClasses != null) {
354 onGetDestClasses.run();
355 }
356 }
357 }
358
359 setDestClass(null);
360 decompileClass(m_sourceClass, m_sourceDeobfuscator, m_sourceReader);
361
362 updateMatchButton();
363 }
364
365 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
366
367 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
368
369 // set up identifiers
370 ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches());
371 ClassIdentifier sourceIdentifier = new ClassIdentifier(
372 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
373 namer.getSourceNamer(), true
374 );
375 ClassIdentifier destIdentifier = new ClassIdentifier(
376 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
377 namer.getDestNamer(), true
378 );
379
380 try {
381
382 // rank all the unmatched dest classes against the source class
383 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
384 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
385 for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) {
386 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
387 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
388 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
389 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
390 }
391 return scoredDestClasses;
392
393 } catch (ClassNotFoundException ex) {
394 throw new Error("Unable to find class " + ex.getMessage());
395 }
396 }
397
398 protected void setDestClass(ClassEntry classEntry) {
399
400 // update the current source class
401 m_destClass = classEntry;
402 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
403
404 decompileClass(m_destClass, m_destDeobfuscator, m_destReader);
405
406 updateMatchButton();
407 }
408
409 protected void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) {
410
411 if (classEntry == null) {
412 reader.setText(null);
413 return;
414 }
415
416 reader.setText("(decompiling...)");
417
418 // run in a separate thread to keep ui responsive
419 new Thread() {
420 @Override
421 public void run() {
422
423 // get the outermost class
424 ClassEntry outermostClassEntry = classEntry;
425 while (outermostClassEntry.isInnerClass()) {
426 outermostClassEntry = outermostClassEntry.getOuterClassEntry();
427 }
428
429 // decompile it
430 CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName());
431 String source = deobfuscator.getSource(sourceTree);
432 reader.setText(source);
433 SourceIndex sourceIndex = deobfuscator.getSourceIndex(sourceTree, source);
434
435 // navigate to the class declaration
436 Token token = sourceIndex.getDeclarationToken(classEntry);
437 if (token == null) {
438 // couldn't find the class declaration token, might be an anonymous class
439 // look for any declaration in that class instead
440 for (Entry entry : sourceIndex.declarations()) {
441 if (entry.getClassEntry().equals(classEntry)) {
442 token = sourceIndex.getDeclarationToken(entry);
443 break;
444 }
445 }
446 }
447
448 if (token != null) {
449 GuiTricks.navigateToToken(reader, token, m_selectionHighlightPainter);
450 } else {
451 // couldn't find anything =(
452 System.out.println("Unable to find declaration in source for " + classEntry);
453 }
454
455 }
456 }.start();
457 }
458
459 private void updateMatchButton() {
460
461 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
462 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
463
464 BiMap<ClassEntry,ClassEntry> uniqueMatches = m_matches.getUniqueMatches();
465 boolean twoSelected = m_sourceClass != null && m_destClass != null;
466 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
467 boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest);
468
469 deactivateButton(m_matchButton);
470 if (twoSelected) {
471 if (isMatched) {
472 activateButton(m_matchButton, "Unmatch", new ActionListener() {
473 @Override
474 public void actionPerformed(ActionEvent event) {
475 onUnmatchClick();
476 }
477 });
478 } else if (canMatch) {
479 activateButton(m_matchButton, "Match", new ActionListener() {
480 @Override
481 public void actionPerformed(ActionEvent event) {
482 onMatchClick();
483 }
484 });
485 }
486 }
487 }
488
489 private void deactivateButton(JButton button) {
490 button.setEnabled(false);
491 button.setText("");
492 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
493 button.removeActionListener(listener);
494 }
495 }
496
497 private void activateButton(JButton button, String text, ActionListener newListener) {
498 button.setText(text);
499 button.setEnabled(true);
500 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
501 button.removeActionListener(listener);
502 }
503 button.addActionListener(newListener);
504 }
505
506 private void onMatchClick() {
507 // precondition: source and dest classes are set correctly
508
509 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
510 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
511
512 // remove the classes from their match
513 m_matches.removeSource(obfSource);
514 m_matches.removeDest(obfDest);
515
516 // add them as matched classes
517 m_matches.add(new ClassMatch(obfSource, obfDest));
518
519 ClassEntry nextClass = null;
520 if (m_advanceCheck.isSelected()) {
521 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
522 }
523
524 save();
525 updateMatches();
526
527 if (nextClass != null) {
528 advance(nextClass);
529 }
530 }
531
532 private void onUnmatchClick() {
533 // precondition: source and dest classes are set to a unique match
534
535 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
536
537 // remove the source to break the match, then add the source back as unmatched
538 m_matches.removeSource(obfSource);
539 m_matches.add(new ClassMatch(obfSource, null));
540
541 save();
542 updateMatches();
543 }
544
545 private void updateMatches() {
546 updateDestMappings();
547 setDestClass(null);
548 m_destClasses.setClasses(null);
549 updateMatchButton();
550
551 // remember where we were in the source tree
552 String packageName = m_sourceClasses.getSelectedPackage();
553
554 setSourceType(m_sourceType);
555
556 m_sourceClasses.expandPackage(packageName);
557 }
558
559 private void save() {
560 if (m_saveListener != null) {
561 m_saveListener.save(m_matches);
562 }
563 }
564
565 private void autoMatch() {
566
567 System.out.println("Automatching...");
568
569 // compute a new matching
570 ClassMatching matching = MappingsConverter.computeMatching(
571 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
572 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
573 m_matches.getUniqueMatches()
574 );
575 Matches newMatches = new Matches(matching.matches());
576 System.out.println(String.format("Automatch found %d new matches",
577 newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size()
578 ));
579
580 // update the current matches
581 m_matches = newMatches;
582 save();
583 updateMatches();
584 }
585
586 private void advance() {
587 advance(null);
588 }
589
590 private void advance(ClassEntry sourceClass) {
591
592 // make sure we have a source class
593 if (sourceClass == null) {
594 sourceClass = m_sourceClasses.getSelectedClass();
595 if (sourceClass != null) {
596 sourceClass = m_sourceClasses.getNextClass(sourceClass);
597 } else {
598 sourceClass = m_sourceClasses.getFirstClass();
599 }
600 }
601
602 // set the source class
603 setSourceClass(sourceClass, new Runnable() {
604 @Override
605 public void run() {
606 pickBestDestClass();
607 }
608 });
609 m_sourceClasses.setSelectionClass(sourceClass);
610 }
611
612 private void pickBestDestClass() {
613
614 // then, pick the best dest class
615 ClassEntry firstClass = null;
616 ScoredClassEntry bestDestClass = null;
617 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
618 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
619 if (firstClass == null) {
620 firstClass = classNode.getClassEntry();
621 }
622 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
623 ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry();
624 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
625 bestDestClass = scoredClass;
626 }
627 }
628 }
629 }
630
631 // pick the entry to show
632 ClassEntry destClass = null;
633 if (bestDestClass != null) {
634 destClass = bestDestClass;
635 } else if (firstClass != null) {
636 destClass = firstClass;
637 }
638
639 setDestClass(destClass);
640 m_destClasses.setSelectionClass(destClass);
641 }
642}