summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
diff options
context:
space:
mode:
authorGravatar lclc982016-06-30 00:49:21 +1000
committerGravatar GitHub2016-06-30 00:49:21 +1000
commit4be005617b3b8c3578cca07c5d085d12916f0d1d (patch)
treedb163431f38703e26da417ef05eaea2b27a498b9 /src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
parentSome small changes to fix idea importing (diff)
downloadenigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.gz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.xz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.zip
Json format (#2)
* Added new format * Fixed bug * Updated Version
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java')
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java538
1 files changed, 538 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 0000000..440565c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,538 @@
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 m_frame;
81 private ClassSelector m_sourceClasses;
82 private ClassSelector m_destClasses;
83 private CodeReader m_sourceReader;
84 private CodeReader m_destReader;
85 private JLabel m_sourceClassLabel;
86 private JLabel m_destClassLabel;
87 private JButton m_matchButton;
88 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
89 private JCheckBox m_advanceCheck;
90 private JCheckBox m_top10Matches;
91
92 private ClassMatches m_classMatches;
93 private Deobfuscator m_sourceDeobfuscator;
94 private Deobfuscator m_destDeobfuscator;
95 private ClassEntry m_sourceClass;
96 private ClassEntry m_destClass;
97 private SourceType m_sourceType;
98 private SaveListener m_saveListener;
99
100 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
101
102 m_classMatches = matches;
103 m_sourceDeobfuscator = sourceDeobfuscator;
104 m_destDeobfuscator = destDeobfuscator;
105
106 // init frame
107 m_frame = new JFrame(Constants.Name + " - Class Matcher");
108 final Container pane = m_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 m_sourceTypeButtons = Maps.newHashMap();
125 for (SourceType sourceType : SourceType.values()) {
126 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
127 m_sourceTypeButtons.put(sourceType, button);
128 sourceTypePanel.add(button);
129 }
130
131 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
132 m_sourceClasses.setListener(classEntry -> setSourceClass(classEntry));
133 JScrollPane sourceScroller = new JScrollPane(m_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 m_top10Matches = new JCheckBox("Show only top 10 matches");
144 destPanel.add(m_top10Matches);
145 m_top10Matches.addActionListener(event -> toggleTop10Matches());
146
147 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
148 m_destClasses.setListener(this::setDestClass);
149 JScrollPane destScroller = new JScrollPane(m_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 m_sourceReader = new CodeReader();
159 m_destReader = new CodeReader();
160
161 // init all the splits
162 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
163 splitLeft.setResizeWeight(0); // let the right side take all the slack
164 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_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 m_sourceClassLabel = new JLabel();
176 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
177 m_destClassLabel = new JLabel();
178 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
179
180 m_matchButton = new JButton();
181
182 m_advanceCheck = new JCheckBox("Advance to next likely match");
183 m_advanceCheck.addActionListener(event -> {
184 if (m_advanceCheck.isSelected()) {
185 advance();
186 }
187 });
188
189 bottomPanel.add(m_sourceClassLabel);
190 bottomPanel.add(m_matchButton);
191 bottomPanel.add(m_destClassLabel);
192 bottomPanel.add(m_advanceCheck);
193 pane.add(bottomPanel, BorderLayout.SOUTH);
194
195 // show the frame
196 pane.doLayout();
197 m_frame.setSize(1024, 576);
198 m_frame.setMinimumSize(new Dimension(640, 480));
199 m_frame.setVisible(true);
200 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
201
202 // init state
203 updateDestMappings();
204 setSourceType(SourceType.getDefault());
205 updateMatchButton();
206 m_saveListener = null;
207 }
208
209 public void setSaveListener(SaveListener val) {
210 m_saveListener = val;
211 }
212
213 private void updateDestMappings() {
214
215 Mappings newMappings = MappingsConverter.newMappings(
216 m_classMatches,
217 m_sourceDeobfuscator.getMappings(),
218 m_sourceDeobfuscator,
219 m_destDeobfuscator
220 );
221
222 // look for dropped mappings
223 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
224 checker.dropBrokenMappings(newMappings);
225
226 // count them
227 int numDroppedFields = checker.getDroppedFieldMappings().size();
228 int numDroppedMethods = checker.getDroppedMethodMappings().size();
229 System.out.println(String.format(
230 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
231 numDroppedFields + numDroppedMethods,
232 numDroppedFields,
233 numDroppedMethods
234 ));
235
236 m_destDeobfuscator.setMappings(newMappings);
237 }
238
239 protected void setSourceType(SourceType val) {
240
241 // show the source classes
242 m_sourceType = val;
243 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
244
245 // update counts
246 for (SourceType sourceType : SourceType.values()) {
247 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
248 sourceType.name(),
249 sourceType.getSourceClasses(m_classMatches).size()
250 ));
251 }
252 }
253
254 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
255 List<ClassEntry> out = Lists.newArrayList();
256 for (ClassEntry entry : in) {
257
258 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
259
260 // make sure we preserve any scores
261 if (entry instanceof ScoredClassEntry) {
262 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
263 }
264
265 out.add(deobf);
266 }
267 return out;
268 }
269
270 protected void setSourceClass(ClassEntry classEntry) {
271
272 Runnable onGetDestClasses = null;
273 if (m_advanceCheck.isSelected()) {
274 onGetDestClasses = this::pickBestDestClass;
275 }
276
277 setSourceClass(classEntry, onGetDestClasses);
278 }
279
280 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
281
282 // update the current source class
283 m_sourceClass = classEntry;
284 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
285
286 if (m_sourceClass != null) {
287
288 // show the dest class(es)
289 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
290 assert (match != null);
291 if (match.destClasses.isEmpty()) {
292
293 m_destClasses.setClasses(null);
294
295 // run in a separate thread to keep ui responsive
296 new Thread() {
297 @Override
298 public void run() {
299 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
300 m_destClasses.expandAll();
301
302 if (onGetDestClasses != null) {
303 onGetDestClasses.run();
304 }
305 }
306 }.start();
307
308 } else {
309
310 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
311 m_destClasses.expandAll();
312
313 if (onGetDestClasses != null) {
314 onGetDestClasses.run();
315 }
316 }
317 }
318
319 setDestClass(null);
320 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, () -> m_sourceReader.navigateToClassDeclaration(m_sourceClass));
321
322 updateMatchButton();
323 }
324
325 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
326
327 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
328
329 // set up identifiers
330 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
331 ClassIdentifier sourceIdentifier = new ClassIdentifier(
332 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
333 namer.getSourceNamer(), true
334 );
335 ClassIdentifier destIdentifier = new ClassIdentifier(
336 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
337 namer.getDestNamer(), true
338 );
339
340 try {
341
342 // rank all the unmatched dest classes against the source class
343 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
344 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
345 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
346 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
347 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
348 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
349 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
350 }
351
352 if (m_top10Matches.isSelected() && scoredDestClasses.size() > 10) {
353 Collections.sort(scoredDestClasses, (a, b) -> {
354 ScoredClassEntry sa = (ScoredClassEntry) a;
355 ScoredClassEntry sb = (ScoredClassEntry) b;
356 return -Float.compare(sa.getScore(), sb.getScore());
357 });
358 scoredDestClasses = scoredDestClasses.subList(0, 10);
359 }
360
361 return scoredDestClasses;
362
363 } catch (ClassNotFoundException ex) {
364 throw new Error("Unable to find class " + ex.getMessage());
365 }
366 }
367
368 protected void setDestClass(ClassEntry classEntry) {
369
370 // update the current source class
371 m_destClass = classEntry;
372 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
373
374 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, () -> m_destReader.navigateToClassDeclaration(m_destClass));
375
376 updateMatchButton();
377 }
378
379 private void updateMatchButton() {
380
381 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
382 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
383
384 BiMap<ClassEntry, ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
385 boolean twoSelected = m_sourceClass != null && m_destClass != null;
386 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
387 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
388
389 GuiTricks.deactivateButton(m_matchButton);
390 if (twoSelected) {
391 if (isMatched) {
392 GuiTricks.activateButton(m_matchButton, "Unmatch", event -> onUnmatchClick());
393 } else if (canMatch) {
394 GuiTricks.activateButton(m_matchButton, "Match", event -> onMatchClick());
395 }
396 }
397 }
398
399 private void onMatchClick() {
400 // precondition: source and dest classes are set correctly
401
402 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
403 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
404
405 // remove the classes from their match
406 m_classMatches.removeSource(obfSource);
407 m_classMatches.removeDest(obfDest);
408
409 // add them as matched classes
410 m_classMatches.add(new ClassMatch(obfSource, obfDest));
411
412 ClassEntry nextClass = null;
413 if (m_advanceCheck.isSelected()) {
414 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
415 }
416
417 save();
418 updateMatches();
419
420 if (nextClass != null) {
421 advance(nextClass);
422 }
423 }
424
425 private void onUnmatchClick() {
426 // precondition: source and dest classes are set to a unique match
427
428 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
429
430 // remove the source to break the match, then add the source back as unmatched
431 m_classMatches.removeSource(obfSource);
432 m_classMatches.add(new ClassMatch(obfSource, null));
433
434 save();
435 updateMatches();
436 }
437
438 private void updateMatches() {
439 updateDestMappings();
440 setDestClass(null);
441 m_destClasses.setClasses(null);
442 updateMatchButton();
443
444 // remember where we were in the source tree
445 String packageName = m_sourceClasses.getSelectedPackage();
446
447 setSourceType(m_sourceType);
448
449 m_sourceClasses.expandPackage(packageName);
450 }
451
452 private void save() {
453 if (m_saveListener != null) {
454 m_saveListener.save(m_classMatches);
455 }
456 }
457
458 private void autoMatch() {
459
460 System.out.println("Automatching...");
461
462 // compute a new matching
463 ClassMatching matching = MappingsConverter.computeMatching(
464 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
465 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
466 m_classMatches.getUniqueMatches()
467 );
468 ClassMatches newMatches = new ClassMatches(matching.matches());
469 System.out.println(String.format("Automatch found %d new matches",
470 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
471 ));
472
473 // update the current matches
474 m_classMatches = newMatches;
475 save();
476 updateMatches();
477 }
478
479 private void advance() {
480 advance(null);
481 }
482
483 private void advance(ClassEntry sourceClass) {
484
485 // make sure we have a source class
486 if (sourceClass == null) {
487 sourceClass = m_sourceClasses.getSelectedClass();
488 if (sourceClass != null) {
489 sourceClass = m_sourceClasses.getNextClass(sourceClass);
490 } else {
491 sourceClass = m_sourceClasses.getFirstClass();
492 }
493 }
494
495 // set the source class
496 setSourceClass(sourceClass, this::pickBestDestClass);
497 m_sourceClasses.setSelectionClass(sourceClass);
498 }
499
500 private void pickBestDestClass() {
501
502 // then, pick the best dest class
503 ClassEntry firstClass = null;
504 ScoredClassEntry bestDestClass = null;
505 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
506 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
507 if (firstClass == null) {
508 firstClass = classNode.getClassEntry();
509 }
510 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
511 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
512 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
513 bestDestClass = scoredClass;
514 }
515 }
516 }
517 }
518
519 // pick the entry to show
520 ClassEntry destClass = null;
521 if (bestDestClass != null) {
522 destClass = bestDestClass;
523 } else if (firstClass != null) {
524 destClass = firstClass;
525 }
526
527 setDestClass(destClass);
528 m_destClasses.setSelectionClass(destClass);
529 }
530
531 private void toggleTop10Matches() {
532 if (m_sourceClass != null) {
533 m_destClasses.clearSelection();
534 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
535 m_destClasses.expandAll();
536 }
537 }
538}