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