summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java')
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java488
1 files changed, 488 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 0000000..4b79b77
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,488 @@
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.Lists;
14import com.google.common.collect.Maps;
15
16import java.awt.BorderLayout;
17import java.awt.Container;
18import java.awt.Dimension;
19import java.awt.FlowLayout;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.util.Collection;
25import java.util.List;
26import java.util.Map;
27
28import javax.swing.*;
29import javax.swing.text.Highlighter.HighlightPainter;
30
31import cuchaz.enigma.Constants;
32import cuchaz.enigma.Deobfuscator;
33import cuchaz.enigma.analysis.EntryReference;
34import cuchaz.enigma.analysis.SourceIndex;
35import cuchaz.enigma.analysis.Token;
36import cuchaz.enigma.convert.ClassMatches;
37import cuchaz.enigma.convert.MemberMatches;
38import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import de.sciss.syntaxpane.DefaultSyntaxKit;
42
43
44public class MemberMatchingGui<T extends Entry> {
45
46 private enum SourceType {
47 Matched {
48 @Override
49 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
50 return matches.getSourceClassesWithoutUnmatchedEntries();
51 }
52 },
53 Unmatched {
54 @Override
55 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
56 return matches.getSourceClassesWithUnmatchedEntries();
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 <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
69
70 public static SourceType getDefault() {
71 return values()[0];
72 }
73 }
74
75 public interface SaveListener<T extends Entry> {
76 void save(MemberMatches<T> matches);
77 }
78
79 // controls
80 private JFrame m_frame;
81 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
82 private ClassSelector m_sourceClasses;
83 private CodeReader m_sourceReader;
84 private CodeReader m_destReader;
85 private JButton m_matchButton;
86 private JButton m_unmatchableButton;
87 private JLabel m_sourceLabel;
88 private JLabel m_destLabel;
89 private HighlightPainter m_unmatchedHighlightPainter;
90 private HighlightPainter m_matchedHighlightPainter;
91
92 private ClassMatches m_classMatches;
93 private MemberMatches<T> m_memberMatches;
94 private Deobfuscator m_sourceDeobfuscator;
95 private Deobfuscator m_destDeobfuscator;
96 private SaveListener<T> m_saveListener;
97 private SourceType m_sourceType;
98 private ClassEntry m_obfSourceClass;
99 private ClassEntry m_obfDestClass;
100 private T m_obfSourceEntry;
101 private T m_obfDestEntry;
102
103 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
104
105 m_classMatches = classMatches;
106 m_memberMatches = fieldMatches;
107 m_sourceDeobfuscator = sourceDeobfuscator;
108 m_destDeobfuscator = destDeobfuscator;
109
110 // init frame
111 m_frame = new JFrame(Constants.Name + " - Member Matcher");
112 final Container pane = m_frame.getContentPane();
113 pane.setLayout(new BorderLayout());
114
115 // init classes side
116 JPanel classesPanel = new JPanel();
117 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
118 classesPanel.setPreferredSize(new Dimension(200, 0));
119 pane.add(classesPanel, BorderLayout.WEST);
120 classesPanel.add(new JLabel("Classes"));
121
122 // init source type radios
123 JPanel sourceTypePanel = new JPanel();
124 classesPanel.add(sourceTypePanel);
125 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
126 ActionListener sourceTypeListener = new ActionListener() {
127 @Override
128 public void actionPerformed(ActionEvent event) {
129 setSourceType(SourceType.valueOf(event.getActionCommand()));
130 }
131 };
132 ButtonGroup sourceTypeButtons = new ButtonGroup();
133 m_sourceTypeButtons = Maps.newHashMap();
134 for (SourceType sourceType : SourceType.values()) {
135 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
136 m_sourceTypeButtons.put(sourceType, button);
137 sourceTypePanel.add(button);
138 }
139
140 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
141 m_sourceClasses.setListener(new ClassSelectionListener() {
142 @Override
143 public void onSelectClass(ClassEntry classEntry) {
144 setSourceClass(classEntry);
145 }
146 });
147 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
148 classesPanel.add(sourceScroller);
149
150 // init readers
151 DefaultSyntaxKit.initKit();
152 m_sourceReader = new CodeReader();
153 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
154 @Override
155 public void onSelect(EntryReference<Entry, Entry> reference) {
156 if (reference != null) {
157 onSelectSource(reference.entry);
158 } else {
159 onSelectSource(null);
160 }
161 }
162 });
163 m_destReader = new CodeReader();
164 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
165 @Override
166 public void onSelect(EntryReference<Entry, Entry> reference) {
167 if (reference != null) {
168 onSelectDest(reference.entry);
169 } else {
170 onSelectDest(null);
171 }
172 }
173 });
174
175 // add key bindings
176 KeyAdapter keyListener = new KeyAdapter() {
177 @Override
178 public void keyPressed(KeyEvent event) {
179 switch (event.getKeyCode()) {
180 case KeyEvent.VK_M:
181 m_matchButton.doClick();
182 break;
183 }
184 }
185 };
186 m_sourceReader.addKeyListener(keyListener);
187 m_destReader.addKeyListener(keyListener);
188
189 // init all the splits
190 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
191 splitRight.setResizeWeight(0.5); // resize 50:50
192 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
193 splitLeft.setResizeWeight(0); // let the right side take all the slack
194 pane.add(splitLeft, BorderLayout.CENTER);
195 splitLeft.resetToPreferredSizes();
196
197 // init bottom panel
198 JPanel bottomPanel = new JPanel();
199 bottomPanel.setLayout(new FlowLayout());
200 pane.add(bottomPanel, BorderLayout.SOUTH);
201
202 m_matchButton = new JButton();
203 m_unmatchableButton = new JButton();
204
205 m_sourceLabel = new JLabel();
206 bottomPanel.add(m_sourceLabel);
207 bottomPanel.add(m_matchButton);
208 bottomPanel.add(m_unmatchableButton);
209 m_destLabel = new JLabel();
210 bottomPanel.add(m_destLabel);
211
212 // show the frame
213 pane.doLayout();
214 m_frame.setSize(1024, 576);
215 m_frame.setMinimumSize(new Dimension(640, 480));
216 m_frame.setVisible(true);
217 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
218
219 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
220 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
221
222 // init state
223 m_saveListener = null;
224 m_obfSourceClass = null;
225 m_obfDestClass = null;
226 m_obfSourceEntry = null;
227 m_obfDestEntry = null;
228 setSourceType(SourceType.getDefault());
229 updateButtons();
230 }
231
232 protected void setSourceType(SourceType val) {
233 m_sourceType = val;
234 updateSourceClasses();
235 }
236
237 public void setSaveListener(SaveListener<T> val) {
238 m_saveListener = val;
239 }
240
241 private void updateSourceClasses() {
242
243 String selectedPackage = m_sourceClasses.getSelectedPackage();
244
245 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
246 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
247 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
248 }
249 m_sourceClasses.setClasses(deobfClassEntries);
250
251 if (selectedPackage != null) {
252 m_sourceClasses.expandPackage(selectedPackage);
253 }
254
255 for (SourceType sourceType : SourceType.values()) {
256 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
257 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
258 ));
259 }
260 }
261
262 protected void setSourceClass(ClassEntry sourceClass) {
263
264 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
265 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
266 if (m_obfDestClass == null) {
267 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
268 }
269
270 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
271 @Override
272 public void run() {
273 updateSourceHighlights();
274 }
275 });
276 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
277 @Override
278 public void run() {
279 updateDestHighlights();
280 }
281 });
282 }
283
284 protected void updateSourceHighlights() {
285 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
286 }
287
288 protected void updateDestHighlights() {
289 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
290 }
291
292 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
293 reader.clearHighlights();
294 SourceIndex index = reader.getSourceIndex();
295
296 // matched fields
297 for (T obfT : obfMatchedEntries) {
298 T deobfT = deobfuscator.deobfuscateEntry(obfT);
299 Token token = index.getDeclarationToken(deobfT);
300 if (token != null) {
301 reader.setHighlightedToken(token, m_matchedHighlightPainter);
302 }
303 }
304
305 // unmatched fields
306 for (T obfT : obfUnmatchedEntries) {
307 T deobfT = deobfuscator.deobfuscateEntry(obfT);
308 Token token = index.getDeclarationToken(deobfT);
309 if (token != null) {
310 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
311 }
312 }
313 }
314
315 private boolean isSelectionMatched() {
316 return m_obfSourceEntry != null && m_obfDestEntry != null
317 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
318 }
319
320 protected void onSelectSource(Entry source) {
321
322 // start with no selection
323 if (isSelectionMatched()) {
324 setDest(null);
325 }
326 setSource(null);
327
328 // then look for a valid source selection
329 if (source != null) {
330
331 // this looks really scary, but it's actually ok
332 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
333 // and MemberMatches.hasSource() will only pass entries that actually match T
334 @SuppressWarnings("unchecked")
335 T sourceEntry = (T) source;
336
337 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
338 if (m_memberMatches.hasSource(obfSourceEntry)) {
339 setSource(obfSourceEntry);
340
341 // look for a matched dest too
342 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
343 if (obfDestEntry != null) {
344 setDest(obfDestEntry);
345 }
346 }
347 }
348
349 updateButtons();
350 }
351
352 protected void onSelectDest(Entry dest) {
353
354 // start with no selection
355 if (isSelectionMatched()) {
356 setSource(null);
357 }
358 setDest(null);
359
360 // then look for a valid dest selection
361 if (dest != null) {
362
363 // this looks really scary, but it's actually ok
364 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
365 // and MemberMatches.hasSource() will only pass entries that actually match T
366 @SuppressWarnings("unchecked")
367 T destEntry = (T) dest;
368
369 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
370 if (m_memberMatches.hasDest(obfDestEntry)) {
371 setDest(obfDestEntry);
372
373 // look for a matched source too
374 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
375 if (obfSourceEntry != null) {
376 setSource(obfSourceEntry);
377 }
378 }
379 }
380
381 updateButtons();
382 }
383
384 private void setSource(T obfEntry) {
385 if (obfEntry == null) {
386 m_obfSourceEntry = obfEntry;
387 m_sourceLabel.setText("");
388 } else {
389 m_obfSourceEntry = obfEntry;
390 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
391 }
392 }
393
394 private void setDest(T obfEntry) {
395 if (obfEntry == null) {
396 m_obfDestEntry = obfEntry;
397 m_destLabel.setText("");
398 } else {
399 m_obfDestEntry = obfEntry;
400 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
401 }
402 }
403
404 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
405 // show obfuscated and deobfuscated names, but no types/signatures
406 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
407 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
408 }
409
410 private void updateButtons() {
411
412 GuiTricks.deactivateButton(m_matchButton);
413 GuiTricks.deactivateButton(m_unmatchableButton);
414
415 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
416 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
417 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
418 @Override
419 public void actionPerformed(ActionEvent event) {
420 unmatch();
421 }
422 });
423 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
424 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
425 @Override
426 public void actionPerformed(ActionEvent event) {
427 match();
428 }
429 });
430 }
431 } else if (m_obfSourceEntry != null) {
432 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
433 @Override
434 public void actionPerformed(ActionEvent event) {
435 unmatchable();
436 }
437 });
438 }
439 }
440
441 protected void match() {
442
443 // update the field matches
444 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
445 save();
446
447 // update the ui
448 onSelectSource(null);
449 onSelectDest(null);
450 updateSourceHighlights();
451 updateDestHighlights();
452 updateSourceClasses();
453 }
454
455 protected void unmatch() {
456
457 // update the field matches
458 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
459 save();
460
461 // update the ui
462 onSelectSource(null);
463 onSelectDest(null);
464 updateSourceHighlights();
465 updateDestHighlights();
466 updateSourceClasses();
467 }
468
469 protected void unmatchable() {
470
471 // update the field matches
472 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
473 save();
474
475 // update the ui
476 onSelectSource(null);
477 onSelectDest(null);
478 updateSourceHighlights();
479 updateDestHighlights();
480 updateSourceClasses();
481 }
482
483 private void save() {
484 if (m_saveListener != null) {
485 m_saveListener.save(m_memberMatches);
486 }
487 }
488}