summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java536
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java435
2 files changed, 0 insertions, 971 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
deleted file mode 100644
index 833a534..0000000
--- a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
+++ /dev/null
@@ -1,536 +0,0 @@
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 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17import cuchaz.enigma.Constants;
18import cuchaz.enigma.Deobfuscator;
19import cuchaz.enigma.convert.*;
20import cuchaz.enigma.gui.node.ClassSelectorClassNode;
21import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Mappings;
24import cuchaz.enigma.mapping.MappingsChecker;
25import cuchaz.enigma.throwables.MappingConflict;
26import de.sciss.syntaxpane.DefaultSyntaxKit;
27
28import javax.swing.*;
29import java.awt.*;
30import java.awt.event.ActionListener;
31import java.util.Collection;
32import java.util.List;
33import java.util.Map;
34
35public class ClassMatchingGui {
36
37 // controls
38 private JFrame frame;
39 private ClassSelector sourceClasses;
40 private ClassSelector destClasses;
41 private CodeReader sourceReader;
42 private CodeReader destReader;
43 private JLabel sourceClassLabel;
44 private JLabel destClassLabel;
45 private JButton matchButton;
46 private Map<SourceType, JRadioButton> sourceTypeButtons;
47 private JCheckBox advanceCheck;
48 private JCheckBox top10Matches;
49 private ClassMatches classMatches;
50 private Deobfuscator sourceDeobfuscator;
51 private Deobfuscator destDeobfuscator;
52 private ClassEntry sourceClass;
53 private ClassEntry destClass;
54 private SourceType sourceType;
55 private SaveListener saveListener;
56
57 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
58
59 classMatches = matches;
60 this.sourceDeobfuscator = sourceDeobfuscator;
61 this.destDeobfuscator = destDeobfuscator;
62
63 // init frame
64 frame = new JFrame(Constants.NAME + " - Class Matcher");
65 final Container pane = frame.getContentPane();
66 pane.setLayout(new BorderLayout());
67
68 // init source side
69 JPanel sourcePanel = new JPanel();
70 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
71 sourcePanel.setPreferredSize(new Dimension(200, 0));
72 pane.add(sourcePanel, BorderLayout.WEST);
73 sourcePanel.add(new JLabel("Source Classes"));
74
75 // init source type radios
76 JPanel sourceTypePanel = new JPanel();
77 sourcePanel.add(sourceTypePanel);
78 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
79 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
80 ButtonGroup sourceTypeButtons = new ButtonGroup();
81 this.sourceTypeButtons = Maps.newHashMap();
82 for (SourceType sourceType : SourceType.values()) {
83 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
84 this.sourceTypeButtons.put(sourceType, button);
85 sourceTypePanel.add(button);
86 }
87
88 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
89 sourceClasses.setSelectionListener(this::setSourceClass);
90 JScrollPane sourceScroller = new JScrollPane(sourceClasses);
91 sourcePanel.add(sourceScroller);
92
93 // init dest side
94 JPanel destPanel = new JPanel();
95 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
96 destPanel.setPreferredSize(new Dimension(200, 0));
97 pane.add(destPanel, BorderLayout.WEST);
98 destPanel.add(new JLabel("Destination Classes"));
99
100 top10Matches = new JCheckBox("Show only top 10 matches");
101 destPanel.add(top10Matches);
102 top10Matches.addActionListener(event -> toggleTop10Matches());
103
104 destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
105 destClasses.setSelectionListener(this::setDestClass);
106 JScrollPane destScroller = new JScrollPane(destClasses);
107 destPanel.add(destScroller);
108
109 JButton autoMatchButton = new JButton("AutoMatch");
110 autoMatchButton.addActionListener(event -> autoMatch());
111 destPanel.add(autoMatchButton);
112
113 // init source panels
114 DefaultSyntaxKit.initKit();
115 sourceReader = new CodeReader();
116 destReader = new CodeReader();
117
118 // init all the splits
119 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(
120 sourceReader));
121 splitLeft.setResizeWeight(0); // let the right side take all the slack
122 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(destReader), destPanel);
123 splitRight.setResizeWeight(1); // let the left side take all the slack
124 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
125 splitCenter.setResizeWeight(0.5); // resize 50:50
126 pane.add(splitCenter, BorderLayout.CENTER);
127 splitCenter.resetToPreferredSizes();
128
129 // init bottom panel
130 JPanel bottomPanel = new JPanel();
131 bottomPanel.setLayout(new FlowLayout());
132
133 sourceClassLabel = new JLabel();
134 sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
135 destClassLabel = new JLabel();
136 destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
137
138 matchButton = new JButton();
139
140 advanceCheck = new JCheckBox("Advance to next likely match");
141 advanceCheck.addActionListener(event -> {
142 if (advanceCheck.isSelected()) {
143 advance();
144 }
145 });
146
147 bottomPanel.add(sourceClassLabel);
148 bottomPanel.add(matchButton);
149 bottomPanel.add(destClassLabel);
150 bottomPanel.add(advanceCheck);
151 pane.add(bottomPanel, BorderLayout.SOUTH);
152
153 // show the frame
154 pane.doLayout();
155 frame.setSize(1024, 576);
156 frame.setMinimumSize(new Dimension(640, 480));
157 frame.setVisible(true);
158 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
159
160 // init state
161 updateDestMappings();
162 setSourceType(SourceType.getDefault());
163 updateMatchButton();
164 saveListener = null;
165 }
166
167 public void setSaveListener(SaveListener val) {
168 saveListener = val;
169 }
170
171 private void updateDestMappings() {
172 try {
173 Mappings newMappings = MappingsConverter.newMappings(classMatches,
174 sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator
175 );
176
177 // look for dropped mappings
178 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
179 checker.dropBrokenMappings(newMappings);
180
181 // count them
182 int numDroppedFields = checker.getDroppedFieldMappings().size();
183 int numDroppedMethods = checker.getDroppedMethodMappings().size();
184 System.out.println(String.format(
185 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
186 numDroppedFields + numDroppedMethods,
187 numDroppedFields,
188 numDroppedMethods
189 ));
190
191 destDeobfuscator.setMappings(newMappings);
192 } catch (MappingConflict ex) {
193 System.out.println(ex.getMessage());
194 ex.printStackTrace();
195 }
196 }
197
198 protected void setSourceType(SourceType val) {
199
200 // show the source classes
201 sourceType = val;
202 sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator));
203
204 // update counts
205 for (SourceType sourceType : SourceType.values()) {
206 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
207 sourceType.name(),
208 sourceType.getSourceClasses(classMatches).size()
209 ));
210 }
211 }
212
213 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
214 List<ClassEntry> out = Lists.newArrayList();
215 for (ClassEntry entry : in) {
216
217 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
218
219 // make sure we preserve any scores
220 if (entry instanceof ScoredClassEntry) {
221 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
222 }
223
224 out.add(deobf);
225 }
226 return out;
227 }
228
229 protected void setSourceClass(ClassEntry classEntry) {
230
231 Runnable onGetDestClasses = null;
232 if (advanceCheck.isSelected()) {
233 onGetDestClasses = this::pickBestDestClass;
234 }
235
236 setSourceClass(classEntry, onGetDestClasses);
237 }
238
239 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
240
241 // update the current source class
242 sourceClass = classEntry;
243 sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : "");
244
245 if (sourceClass != null) {
246
247 // show the dest class(es)
248 ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass));
249 assert (match != null);
250 if (match.destClasses.isEmpty()) {
251
252 destClasses.setClasses(null);
253
254 // run in a separate thread to keep ui responsive
255 new Thread(() ->
256 {
257 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator));
258 destClasses.expandAll();
259
260 if (onGetDestClasses != null) {
261 onGetDestClasses.run();
262 }
263 }).start();
264
265 } else {
266
267 destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator));
268 destClasses.expandAll();
269
270 if (onGetDestClasses != null) {
271 onGetDestClasses.run();
272 }
273 }
274 }
275
276 setDestClass(null);
277 sourceReader.decompileClass(
278 sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass));
279
280 updateMatchButton();
281 }
282
283 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
284
285 ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass);
286
287 // set up identifiers
288 ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches());
289 ClassIdentifier sourceIdentifier = new ClassIdentifier(
290 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(),
291 namer.getSourceNamer(), true
292 );
293 ClassIdentifier destIdentifier = new ClassIdentifier(
294 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(),
295 namer.getDestNamer(), true
296 );
297
298 try {
299
300 // rank all the unmatched dest classes against the source class
301 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
302 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
303 for (ClassEntry unmatchedDestClass : classMatches.getUnmatchedDestClasses()) {
304 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
305 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
306 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
307 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
308 }
309
310 if (top10Matches.isSelected() && scoredDestClasses.size() > 10) {
311 scoredDestClasses.sort((a, b) ->
312 {
313 ScoredClassEntry sa = (ScoredClassEntry) a;
314 ScoredClassEntry sb = (ScoredClassEntry) b;
315 return -Float.compare(sa.getScore(), sb.getScore());
316 });
317 scoredDestClasses = scoredDestClasses.subList(0, 10);
318 }
319
320 return scoredDestClasses;
321
322 } catch (ClassNotFoundException ex) {
323 throw new Error("Unable to find class " + ex.getMessage());
324 }
325 }
326
327 protected void setDestClass(ClassEntry classEntry) {
328
329 // update the current source class
330 destClass = classEntry;
331 destClassLabel.setText(destClass != null ? destClass.getName() : "");
332
333 destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass));
334
335 updateMatchButton();
336 }
337
338 private void updateMatchButton() {
339
340 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
341 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass);
342
343 BiMap<ClassEntry, ClassEntry> uniqueMatches = classMatches.getUniqueMatches();
344 boolean twoSelected = sourceClass != null && destClass != null;
345 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
346 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
347
348 GuiTricks.deactivateButton(matchButton);
349 if (twoSelected) {
350 if (isMatched) {
351 GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick());
352 } else if (canMatch) {
353 GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick());
354 }
355 }
356 }
357
358 private void onMatchClick() {
359 // precondition: source and dest classes are set correctly
360
361 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
362 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass);
363
364 // remove the classes from their match
365 classMatches.removeSource(obfSource);
366 classMatches.removeDest(obfDest);
367
368 // add them as matched classes
369 classMatches.add(new ClassMatch(obfSource, obfDest));
370
371 ClassEntry nextClass = null;
372 if (advanceCheck.isSelected()) {
373 nextClass = sourceClasses.getNextClass(sourceClass);
374 }
375
376 save();
377 updateMatches();
378
379 if (nextClass != null) {
380 advance(nextClass);
381 }
382 }
383
384 private void onUnmatchClick() {
385 // precondition: source and dest classes are set to a unique match
386
387 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
388
389 // remove the source to break the match, then add the source back as unmatched
390 classMatches.removeSource(obfSource);
391 classMatches.add(new ClassMatch(obfSource, null));
392
393 save();
394 updateMatches();
395 }
396
397 private void updateMatches() {
398 updateDestMappings();
399 setDestClass(null);
400 destClasses.setClasses(null);
401 updateMatchButton();
402
403 // remember where we were in the source tree
404 String packageName = sourceClasses.getSelectedPackage();
405
406 setSourceType(sourceType);
407
408 sourceClasses.expandPackage(packageName);
409 }
410
411 private void save() {
412 if (saveListener != null) {
413 saveListener.save(classMatches);
414 }
415 }
416
417 private void autoMatch() {
418
419 System.out.println("Automatching...");
420
421 // compute a new matching
422 ClassMatching matching = MappingsConverter.computeMatching(
423 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(),
424 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(),
425 classMatches.getUniqueMatches()
426 );
427 ClassMatches newMatches = new ClassMatches(matching.matches());
428 System.out.println(String.format("Automatch found %d new matches",
429 newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size()
430 ));
431
432 // update the current matches
433 classMatches = newMatches;
434 save();
435 updateMatches();
436 }
437
438 private void advance() {
439 advance(null);
440 }
441
442 private void advance(ClassEntry sourceClass) {
443
444 // make sure we have a source class
445 if (sourceClass == null) {
446 sourceClass = sourceClasses.getSelectedClass();
447 if (sourceClass != null) {
448 sourceClass = sourceClasses.getNextClass(sourceClass);
449 } else {
450 sourceClass = sourceClasses.getFirstClass();
451 }
452 }
453
454 // set the source class
455 setSourceClass(sourceClass, this::pickBestDestClass);
456 sourceClasses.setSelectionClass(sourceClass);
457 }
458
459 private void pickBestDestClass() {
460
461 // then, pick the best dest class
462 ClassEntry firstClass = null;
463 ScoredClassEntry bestDestClass = null;
464 for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) {
465 for (ClassSelectorClassNode classNode : destClasses.classNodes(packageNode)) {
466 if (firstClass == null) {
467 firstClass = classNode.getClassEntry();
468 }
469 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
470 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
471 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
472 bestDestClass = scoredClass;
473 }
474 }
475 }
476 }
477
478 // pick the entry to show
479 ClassEntry destClass = null;
480 if (bestDestClass != null) {
481 destClass = bestDestClass;
482 } else if (firstClass != null) {
483 destClass = firstClass;
484 }
485
486 setDestClass(destClass);
487 destClasses.setSelectionClass(destClass);
488 }
489
490 private void toggleTop10Matches() {
491 if (sourceClass != null) {
492 destClasses.clearSelection();
493 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator));
494 destClasses.expandAll();
495 }
496 }
497
498 private enum SourceType {
499 Matched {
500 @Override
501 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
502 return matches.getUniqueMatches().keySet();
503 }
504 },
505 Unmatched {
506 @Override
507 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
508 return matches.getUnmatchedSourceClasses();
509 }
510 },
511 Ambiguous {
512 @Override
513 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
514 return matches.getAmbiguouslyMatchedSourceClasses();
515 }
516 };
517
518 public static SourceType getDefault() {
519 return values()[0];
520 }
521
522 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
523 JRadioButton button = new JRadioButton(name(), this == getDefault());
524 button.setActionCommand(name());
525 button.addActionListener(listener);
526 group.add(button);
527 return button;
528 }
529
530 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
531 }
532
533 public interface SaveListener {
534 void save(ClassMatches matches);
535 }
536}
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
deleted file mode 100644
index fe6a3b0..0000000
--- a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
+++ /dev/null
@@ -1,435 +0,0 @@
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 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.Constants;
17import cuchaz.enigma.Deobfuscator;
18import cuchaz.enigma.analysis.SourceIndex;
19import cuchaz.enigma.analysis.Token;
20import cuchaz.enigma.convert.ClassMatches;
21import cuchaz.enigma.convert.MemberMatches;
22import cuchaz.enigma.gui.highlight.DeobfuscatedHighlightPainter;
23import cuchaz.enigma.gui.highlight.ObfuscatedHighlightPainter;
24import cuchaz.enigma.mapping.ClassEntry;
25import cuchaz.enigma.mapping.Entry;
26import de.sciss.syntaxpane.DefaultSyntaxKit;
27
28import javax.swing.*;
29import javax.swing.text.Highlighter.HighlightPainter;
30import java.awt.*;
31import java.awt.event.ActionListener;
32import java.awt.event.KeyAdapter;
33import java.awt.event.KeyEvent;
34import java.util.Collection;
35import java.util.List;
36import java.util.Map;
37
38public class MemberMatchingGui<T extends Entry> {
39
40 // controls
41 private JFrame frame;
42 private Map<SourceType, JRadioButton> sourceTypeButtons;
43 private ClassSelector sourceClasses;
44 private CodeReader sourceReader;
45 private CodeReader destReader;
46 private JButton matchButton;
47 private JButton unmatchableButton;
48 private JLabel sourceLabel;
49 private JLabel destLabel;
50 private HighlightPainter unmatchedHighlightPainter;
51 private HighlightPainter matchedHighlightPainter;
52 private ClassMatches classMatches;
53 private MemberMatches<T> memberMatches;
54 private Deobfuscator sourceDeobfuscator;
55 private Deobfuscator destDeobfuscator;
56 private SaveListener<T> saveListener;
57 private SourceType sourceType;
58 private ClassEntry obfSourceClass;
59 private ClassEntry obfDestClass;
60 private T obfSourceEntry;
61 private T obfDestEntry;
62
63 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
64
65 this.classMatches = classMatches;
66 memberMatches = fieldMatches;
67 this.sourceDeobfuscator = sourceDeobfuscator;
68 this.destDeobfuscator = destDeobfuscator;
69
70 // init frame
71 frame = new JFrame(Constants.NAME + " - Member Matcher");
72 final Container pane = frame.getContentPane();
73 pane.setLayout(new BorderLayout());
74
75 // init classes side
76 JPanel classesPanel = new JPanel();
77 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
78 classesPanel.setPreferredSize(new Dimension(200, 0));
79 pane.add(classesPanel, BorderLayout.WEST);
80 classesPanel.add(new JLabel("Classes"));
81
82 // init source type radios
83 JPanel sourceTypePanel = new JPanel();
84 classesPanel.add(sourceTypePanel);
85 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
86 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
87 ButtonGroup sourceTypeButtons = new ButtonGroup();
88 this.sourceTypeButtons = Maps.newHashMap();
89 for (SourceType sourceType : SourceType.values()) {
90 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
91 this.sourceTypeButtons.put(sourceType, button);
92 sourceTypePanel.add(button);
93 }
94
95 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
96 sourceClasses.setSelectionListener(this::setSourceClass);
97 JScrollPane sourceScroller = new JScrollPane(sourceClasses);
98 classesPanel.add(sourceScroller);
99
100 // init readers
101 DefaultSyntaxKit.initKit();
102 sourceReader = new CodeReader();
103 sourceReader.setSelectionListener(reference ->
104 {
105 if (reference != null) {
106 onSelectSource(reference.entry);
107 } else {
108 onSelectSource(null);
109 }
110 });
111 destReader = new CodeReader();
112 destReader.setSelectionListener(reference ->
113 {
114 if (reference != null) {
115 onSelectDest(reference.entry);
116 } else {
117 onSelectDest(null);
118 }
119 });
120
121 // add key bindings
122 KeyAdapter keyListener = new KeyAdapter() {
123 @Override
124 public void keyPressed(KeyEvent event) {
125 if (event.getKeyCode() == KeyEvent.VK_M)
126 matchButton.doClick();
127 }
128 };
129 sourceReader.addKeyListener(keyListener);
130 destReader.addKeyListener(keyListener);
131
132 // init all the splits
133 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane(
134 destReader));
135 splitRight.setResizeWeight(0.5); // resize 50:50
136 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
137 splitLeft.setResizeWeight(0); // let the right side take all the slack
138 pane.add(splitLeft, BorderLayout.CENTER);
139 splitLeft.resetToPreferredSizes();
140
141 // init bottom panel
142 JPanel bottomPanel = new JPanel();
143 bottomPanel.setLayout(new FlowLayout());
144 pane.add(bottomPanel, BorderLayout.SOUTH);
145
146 matchButton = new JButton();
147 unmatchableButton = new JButton();
148
149 sourceLabel = new JLabel();
150 bottomPanel.add(sourceLabel);
151 bottomPanel.add(matchButton);
152 bottomPanel.add(unmatchableButton);
153 destLabel = new JLabel();
154 bottomPanel.add(destLabel);
155
156 // show the frame
157 pane.doLayout();
158 frame.setSize(1024, 576);
159 frame.setMinimumSize(new Dimension(640, 480));
160 frame.setVisible(true);
161 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
162
163 unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
164 matchedHighlightPainter = new DeobfuscatedHighlightPainter();
165
166 // init state
167 saveListener = null;
168 obfSourceClass = null;
169 obfDestClass = null;
170 obfSourceEntry = null;
171 obfDestEntry = null;
172 setSourceType(SourceType.getDefault());
173 updateButtons();
174 }
175
176 protected void setSourceType(SourceType val) {
177 sourceType = val;
178 updateSourceClasses();
179 }
180
181 public void setSaveListener(SaveListener<T> val) {
182 saveListener = val;
183 }
184
185 private void updateSourceClasses() {
186
187 String selectedPackage = sourceClasses.getSelectedPackage();
188
189 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
190 for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) {
191 deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry));
192 }
193 sourceClasses.setClasses(deobfClassEntries);
194
195 if (selectedPackage != null) {
196 sourceClasses.expandPackage(selectedPackage);
197 }
198
199 for (SourceType sourceType : SourceType.values()) {
200 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
201 sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size()
202 ));
203 }
204 }
205
206 protected void setSourceClass(ClassEntry sourceClass) {
207
208 obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass);
209 obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
210 if (obfDestClass == null) {
211 throw new Error("No matching dest class for source class: " + obfSourceClass);
212 }
213
214 sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights);
215 destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights);
216 }
217
218 protected void updateSourceHighlights() {
219 highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries());
220 }
221
222 protected void updateDestHighlights() {
223 highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries());
224 }
225
226 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
227 reader.clearHighlights();
228 // matched fields
229 updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter);
230 // unmatched fields
231 updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter);
232 }
233
234 private void updateHighlighted(Collection<T> entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) {
235 SourceIndex index = reader.getSourceIndex();
236 for (T obfT : entries) {
237 T deobfT = deobfuscator.deobfuscateEntry(obfT);
238 Token token = index.getDeclarationToken(deobfT);
239 if (token != null) {
240 reader.setHighlightedToken(token, painter);
241 }
242 }
243 }
244
245 private boolean isSelectionMatched() {
246 return obfSourceEntry != null && obfDestEntry != null
247 && memberMatches.isMatched(obfSourceEntry, obfDestEntry);
248 }
249
250 protected void onSelectSource(Entry source) {
251
252 // start with no selection
253 if (isSelectionMatched()) {
254 setDest(null);
255 }
256 setSource(null);
257
258 // then look for a valid source selection
259 if (source != null) {
260
261 // this looks really scary, but it's actually ok
262 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
263 // and MemberMatches.hasSource() will only pass entries that actually match T
264 @SuppressWarnings("unchecked")
265 T sourceEntry = (T) source;
266
267 T obfSourceEntry = sourceDeobfuscator.obfuscateEntry(sourceEntry);
268 if (memberMatches.hasSource(obfSourceEntry)) {
269 setSource(obfSourceEntry);
270
271 // look for a matched dest too
272 T obfDestEntry = memberMatches.matches().get(obfSourceEntry);
273 if (obfDestEntry != null) {
274 setDest(obfDestEntry);
275 }
276 }
277 }
278
279 updateButtons();
280 }
281
282 protected void onSelectDest(Entry dest) {
283
284 // start with no selection
285 if (isSelectionMatched()) {
286 setSource(null);
287 }
288 setDest(null);
289
290 // then look for a valid dest selection
291 if (dest != null) {
292
293 // this looks really scary, but it's actually ok
294 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
295 // and MemberMatches.hasSource() will only pass entries that actually match T
296 @SuppressWarnings("unchecked")
297 T destEntry = (T) dest;
298
299 T obfDestEntry = destDeobfuscator.obfuscateEntry(destEntry);
300 if (memberMatches.hasDest(obfDestEntry)) {
301 setDest(obfDestEntry);
302
303 // look for a matched source too
304 T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry);
305 if (obfSourceEntry != null) {
306 setSource(obfSourceEntry);
307 }
308 }
309 }
310
311 updateButtons();
312 }
313
314 private void setSource(T obfEntry) {
315 if (obfEntry == null) {
316 obfSourceEntry = null;
317 sourceLabel.setText("");
318 } else {
319 obfSourceEntry = obfEntry;
320 sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator));
321 }
322 }
323
324 private void setDest(T obfEntry) {
325 if (obfEntry == null) {
326 obfDestEntry = null;
327 destLabel.setText("");
328 } else {
329 obfDestEntry = obfEntry;
330 destLabel.setText(getEntryLabel(obfEntry, destDeobfuscator));
331 }
332 }
333
334 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
335 // show obfuscated and deobfuscated names, but no types/signatures
336 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
337 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
338 }
339
340 private void updateButtons() {
341
342 GuiTricks.deactivateButton(matchButton);
343 GuiTricks.deactivateButton(unmatchableButton);
344
345 if (obfSourceEntry != null && obfDestEntry != null) {
346 if (memberMatches.isMatched(obfSourceEntry, obfDestEntry))
347 GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch());
348 else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry(
349 obfDestEntry))
350 GuiTricks.activateButton(matchButton, "Match", event -> match());
351 } else if (obfSourceEntry != null)
352 GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable());
353 }
354
355 protected void match() {
356
357 // update the field matches
358 memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator);
359 save();
360
361 // update the ui
362 onSelectSource(null);
363 onSelectDest(null);
364 updateSourceHighlights();
365 updateDestHighlights();
366 updateSourceClasses();
367 }
368
369 protected void unmatch() {
370
371 // update the field matches
372 memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator);
373 save();
374
375 // update the ui
376 onSelectSource(null);
377 onSelectDest(null);
378 updateSourceHighlights();
379 updateDestHighlights();
380 updateSourceClasses();
381 }
382
383 protected void unmatchable() {
384
385 // update the field matches
386 memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator);
387 save();
388
389 // update the ui
390 onSelectSource(null);
391 onSelectDest(null);
392 updateSourceHighlights();
393 updateDestHighlights();
394 updateSourceClasses();
395 }
396
397 private void save() {
398 if (saveListener != null) {
399 saveListener.save(memberMatches);
400 }
401 }
402
403 private enum SourceType {
404 Matched {
405 @Override
406 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
407 return matches.getSourceClassesWithoutUnmatchedEntries();
408 }
409 },
410 Unmatched {
411 @Override
412 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
413 return matches.getSourceClassesWithUnmatchedEntries();
414 }
415 };
416
417 public static SourceType getDefault() {
418 return values()[0];
419 }
420
421 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
422 JRadioButton button = new JRadioButton(name(), this == getDefault());
423 button.setActionCommand(name());
424 button.addActionListener(listener);
425 group.add(button);
426 return button;
427 }
428
429 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
430 }
431
432 public interface SaveListener<T extends Entry> {
433 void save(MemberMatches<T> matches);
434 }
435}