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