summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java')
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java536
1 files changed, 0 insertions, 536 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}