summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/gui/MatchingGui.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/cuchaz/enigma/gui/MatchingGui.java')
-rw-r--r--src/cuchaz/enigma/gui/MatchingGui.java413
1 files changed, 316 insertions, 97 deletions
diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java
index 53c767a..e9dff16 100644
--- a/src/cuchaz/enigma/gui/MatchingGui.java
+++ b/src/cuchaz/enigma/gui/MatchingGui.java
@@ -1,133 +1,352 @@
1package cuchaz.enigma.gui; 1package cuchaz.enigma.gui;
2 2
3import java.awt.BorderLayout;
4import java.awt.Container;
5import java.awt.Dimension;
6import java.awt.FlowLayout;
7import java.awt.event.ActionEvent;
8import java.awt.event.ActionListener;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.List;
13
14import javax.swing.BoxLayout;
15import javax.swing.ButtonGroup;
16import javax.swing.JButton;
17import javax.swing.JEditorPane;
18import javax.swing.JFrame;
19import javax.swing.JLabel;
20import javax.swing.JPanel;
21import javax.swing.JRadioButton;
22import javax.swing.JScrollPane;
23import javax.swing.JSplitPane;
24import javax.swing.WindowConstants;
25
26import com.beust.jcommander.internal.Lists;
27import com.google.common.collect.ArrayListMultimap;
28import com.google.common.collect.Multimap;
29
30import cuchaz.enigma.Constants;
3import cuchaz.enigma.Deobfuscator; 31import cuchaz.enigma.Deobfuscator;
32import cuchaz.enigma.convert.ClassIdentifier;
33import cuchaz.enigma.convert.ClassIdentity;
34import cuchaz.enigma.convert.ClassMatch;
35import cuchaz.enigma.convert.ClassNamer;
4import cuchaz.enigma.convert.Matches; 36import cuchaz.enigma.convert.Matches;
37import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
38import cuchaz.enigma.mapping.ClassEntry;
39import de.sciss.syntaxpane.DefaultSyntaxKit;
5 40
6 41
7public class MatchingGui { 42public class MatchingGui {
8 43
9 public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { 44 private static enum SourceType {
10 // TODO Auto-generated constructor stub 45 Matched {
46
47 @Override
48 public Collection<ClassEntry> getSourceClasses(Matches matches) {
49 return matches.getUniqueMatches().keySet();
50 }
51 },
52 Unmatched {
53
54 @Override
55 public Collection<ClassEntry> getSourceClasses(Matches matches) {
56 return matches.getUnmatchedSourceClasses();
57 }
58 },
59 Ambiguous {
60
61 @Override
62 public Collection<ClassEntry> getSourceClasses(Matches matches) {
63 return matches.getAmbiguouslyMatchedSourceClasses();
64 }
65 };
66
67 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
68 JRadioButton button = new JRadioButton(name(), this == getDefault());
69 button.setActionCommand(name());
70 button.addActionListener(listener);
71 group.add(button);
72 return button;
73 }
74
75 public abstract Collection<ClassEntry> getSourceClasses(Matches matches);
76
77 public static SourceType getDefault() {
78 return values()[0];
79 }
11 } 80 }
12 81
82 // controls
83 private JFrame m_frame;
84 private ClassSelector m_sourceClasses;
85 private ClassSelector m_destClasses;
86 private JEditorPane m_sourceReader;
87 private JEditorPane m_destReader;
88 private JLabel m_sourceClassLabel;
89 private JLabel m_destClassLabel;
90 private JButton m_matchButton;
91
92 private Matches m_matches;
93 private Deobfuscator m_sourceDeobfuscator;
94 private Deobfuscator m_destDeobfuscator;
95 private ClassEntry m_sourceClass;
96 private ClassEntry m_destClass;
13 97
14 /* TODO: see if we can use any of this here 98 public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
15 public static doTheThings() {
16 99
17 // get all the obf class names used in the mappings 100 m_matches = matches;
18 Set<ClassEntry> usedClasses = Sets.newHashSet(); 101 m_sourceDeobfuscator = sourceDeobfuscator;
19 for (String className : mappings.getAllObfClassNames()) { 102 m_destDeobfuscator = destDeobfuscator;
20 usedClasses.add(new ClassEntry(className)); 103
21 } 104 // init frame
22 System.out.println(String.format("Mappings reference %d/%d classes", 105 m_frame = new JFrame(Constants.Name);
23 usedClasses.size(), sourceIndex.getObfClassEntries().size() 106 final Container pane = m_frame.getContentPane();
24 )); 107 pane.setLayout(new BorderLayout());
25 108
26 // get the used matches 109 // init source side
27 Collection<ClassMatch> matches = matching.matches(); 110 JPanel sourcePanel = new JPanel();
28 Matches usedMatches = new Matches(); 111 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
29 for (ClassMatch match : matching.matches()) { 112 sourcePanel.setPreferredSize(new Dimension(200, 0));
30 if (!match.intersectSourceClasses(usedClasses).isEmpty()) { 113 pane.add(sourcePanel, BorderLayout.WEST);
31 usedMatches.add(match); 114 sourcePanel.add(new JLabel("Source Classes"));
115
116 // init source type radios
117 JPanel sourceTypePanel = new JPanel();
118 sourcePanel.add(sourceTypePanel);
119 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
120 ActionListener sourceTypeListener = new ActionListener() {
121 @Override
122 public void actionPerformed(ActionEvent event) {
123 setSourceType(SourceType.valueOf(event.getActionCommand()));
32 } 124 }
125 };
126 ButtonGroup sourceTypeButtons = new ButtonGroup();
127 for (SourceType sourceType : SourceType.values()) {
128 sourceTypePanel.add(sourceType.newRadio(sourceTypeListener, sourceTypeButtons));
33 } 129 }
34 System.out.println(String.format("Mappings reference %d/%d match groups", 130
35 usedMatches.size(), matches.size() 131 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
36 )); 132 m_sourceClasses.setListener(new ClassSelectionListener() {
37 133 @Override
38 // see what the used classes map to 134 public void onSelectClass(ClassEntry classEntry) {
39 BiMap<ClassEntry,ClassEntry> uniqueUsedMatches = HashBiMap.create(); 135 setSourceClass(classEntry);
40 Map<ClassEntry,ClassMatch> ambiguousUsedMatches = Maps.newHashMap();
41 Set<ClassEntry> unmatchedUsedClasses = Sets.newHashSet();
42 for (ClassMatch match : matching.matches()) {
43 Set<ClassEntry> matchUsedClasses = match.intersectSourceClasses(usedClasses);
44 if (matchUsedClasses.isEmpty()) {
45 continue;
46 } 136 }
47 137 });
48 usedMatches.add(match); 138 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
139 sourcePanel.add(sourceScroller);
140
141 // init dest side
142 JPanel destPanel = new JPanel();
143 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
144 destPanel.setPreferredSize(new Dimension(200, 0));
145 pane.add(destPanel, BorderLayout.WEST);
146 destPanel.add(new JLabel("Destination Classes"));
147
148 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
149 m_destClasses.setListener(new ClassSelectionListener() {
150 @Override
151 public void onSelectClass(ClassEntry classEntry) {
152 setDestClass(classEntry);
153 }
154 });
155 JScrollPane destScroller = new JScrollPane(m_destClasses);
156 destPanel.add(destScroller);
157
158 // init source panels
159 DefaultSyntaxKit.initKit();
160 m_sourceReader = new JEditorPane();
161 m_sourceReader.setEditable(false);
162 m_sourceReader.setContentType("text/java");
163 m_destReader = new JEditorPane();
164 m_destReader.setEditable(false);
165 m_destReader.setContentType("text/java");
166
167 // init all the splits
168 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
169 splitLeft.setResizeWeight(0); // let the right side take all the slack
170 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
171 splitRight.setResizeWeight(1); // let the left side take all the slack
172 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
173 splitCenter.setResizeWeight(0.5); // resize 50:50
174 pane.add(splitCenter, BorderLayout.CENTER);
175 splitCenter.resetToPreferredSizes();
176
177 // init bottom panel
178 JPanel bottomPanel = new JPanel();
179 bottomPanel.setLayout(new FlowLayout());
180
181 m_sourceClassLabel = new JLabel();
182 m_sourceClassLabel.setPreferredSize(new Dimension(300, 0));
183 m_destClassLabel = new JLabel();
184 m_destClassLabel.setPreferredSize(new Dimension(300, 0));
185
186 m_matchButton = new JButton();
187 m_matchButton.addActionListener(new ActionListener() {
188 @Override
189 public void actionPerformed(ActionEvent event) {
190 onMatchClick();
191 }
192 });
193 m_matchButton.setPreferredSize(new Dimension(140, 24));
194
195 bottomPanel.add(m_sourceClassLabel);
196 bottomPanel.add(m_matchButton);
197 bottomPanel.add(m_destClassLabel);
198 pane.add(bottomPanel, BorderLayout.SOUTH);
199
200 // show the frame
201 pane.doLayout();
202 m_frame.setSize(1024, 576);
203 m_frame.setMinimumSize(new Dimension(640, 480));
204 m_frame.setVisible(true);
205 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
206
207 // init state
208 setSourceType(SourceType.getDefault());
209 updateMatchButton();
210 }
49 211
50 // classify the match 212 protected void setSourceType(SourceType val) {
51 if (!match.isMatched()) { 213 // show the source classes
52 // unmatched 214 m_sourceClasses.setClasses(deobfuscateClasses(val.getSourceClasses(m_matches), m_sourceDeobfuscator));
53 unmatchedUsedClasses.addAll(matchUsedClasses); 215 }
216
217 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
218 List<ClassEntry> out = Lists.newArrayList();
219 for (ClassEntry entry : in) {
220 out.add(deobfuscator.deobfuscateEntry(entry));
221 }
222 return out;
223 }
224
225 protected void setSourceClass(ClassEntry classEntry) {
226
227 // update the current source class
228 m_sourceClass = classEntry;
229 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
230
231 if (m_sourceClass != null) {
232
233 // show the dest class(es)
234 ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
235 assert(match != null);
236 if (match.destClasses.isEmpty()) {
237 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
54 } else { 238 } else {
55 if (match.isAmbiguous()) { 239 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
56 // ambiguously matched
57 for (ClassEntry matchUsedClass : matchUsedClasses) {
58 ambiguousUsedMatches.put(matchUsedClass, match);
59 }
60 } else {
61 // uniquely matched
62 uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest());
63 }
64 } 240 }
241 m_destClasses.expandRow(0);
65 } 242 }
66 243
67 // get unmatched dest classes 244 setDestClass(null);
68 Set<ClassEntry> unmatchedDestClasses = Sets.newHashSet(); 245 readSource(m_sourceClass, m_sourceDeobfuscator, m_sourceReader);
69 for (ClassMatch match : matching.matches()) { 246
70 if (!match.isMatched()) { 247 updateMatchButton();
71 unmatchedDestClasses.addAll(match.destClasses); 248 }
72 } 249
250 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
251
252 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
253
254 // set up identifiers
255 ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches());
256 ClassIdentifier sourceIdentifier = new ClassIdentifier(
257 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
258 namer.getSourceNamer(), true
259 );
260 ClassIdentifier destIdentifier = new ClassIdentifier(
261 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
262 namer.getDestNamer(), true
263 );
264
265 // rank all the unmatched dest classes against the source class
266 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
267 Multimap<Float,ClassEntry> scoredDestClasses = ArrayListMultimap.create();
268 for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) {
269 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
270 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
271 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
272 scoredDestClasses.put(score, unmatchedDestClass);
73 } 273 }
74 274
75 // warn about the ambiguous used matches 275 // sort by scores
76 if (ambiguousUsedMatches.size() > 0) { 276 List<Float> scores = new ArrayList<Float>(scoredDestClasses.keySet());
77 System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size())); 277 Collections.sort(scores, Collections.reverseOrder());
78 List<ClassMatch> ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values())); 278
79 Collections.sort(ambiguousMatchesList, new Comparator<ClassMatch>() { 279 // collect the scored classes in order
80 @Override 280 List<ClassEntry> scoredClasses = Lists.newArrayList();
81 public int compare(ClassMatch a, ClassMatch b) { 281 for (float score : scores) {
82 String aName = a.sourceClasses.iterator().next().getName(); 282 for (ClassEntry classEntry : scoredDestClasses.get(score)) {
83 String bName = b.sourceClasses.iterator().next().getName(); 283 scoredClasses.add(new DecoratedClassEntry(classEntry, String.format("%.0f%% ", score)));
84 return aName.compareTo(bName); 284 if (scoredClasses.size() > 10) {
285 return scoredClasses;
85 } 286 }
86 });
87 for (ClassMatch match : ambiguousMatchesList) {
88 System.out.println("Ambiguous matching:");
89 System.out.println("\tSource: " + getClassNames(match.sourceClasses));
90 System.out.println("\tDest: " + getClassNames(match.destClasses));
91 } 287 }
92 } 288 }
289 return scoredClasses;
290 }
291
292 protected void setDestClass(ClassEntry classEntry) {
93 293
94 // warn about unmatched used classes 294 // update the current source class
95 for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) { 295 m_destClass = classEntry;
96 System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry()); 296 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
97 297
98 // rank all the unmatched dest classes against the used class 298 readSource(m_destClass, m_destDeobfuscator, m_destReader);
99 ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass); 299
100 Multimap<Integer,ClassEntry> scoredDestClasses = ArrayListMultimap.create(); 300 updateMatchButton();
101 for (ClassEntry unmatchedDestClass : unmatchedDestClasses) { 301 }
102 ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass); 302
103 scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass); 303 protected void readSource(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) {
104 }
105
106 List<Integer> scores = new ArrayList<Integer>(scoredDestClasses.keySet());
107 Collections.sort(scores, Collections.reverseOrder());
108 printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses);
109 }
110 304
111 // bail if there were unmatched classes 305 if (classEntry == null) {
112 if (!unmatchedUsedClasses.isEmpty()) { 306 reader.setText(null);
113 throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!"); 307 return;
114 } 308 }
309
310 reader.setText("(decompiling...)");
311
312 // run decompiler in a separate thread to keep ui responsive
313 new Thread() {
314 @Override
315 public void run() {
316
317 // get the outermost class
318 ClassEntry obfClassEntry = deobfuscator.obfuscateEntry(classEntry);
319 List<ClassEntry> classChain = deobfuscator.getJarIndex().getObfClassChain(obfClassEntry);
320 ClassEntry obfOutermostClassEntry = classChain.get(0);
321
322 // decompile it
323 reader.setText(deobfuscator.getSource(deobfuscator.getSourceTree(obfOutermostClassEntry.getName())));
324 }
325 }.start();
115 } 326 }
116 327
117 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassEntry> scoredMatches) { 328 private void updateMatchButton() {
118 int numScoredMatchesShown = 0; 329
119 for (int score : scores) { 330 boolean twoSelected = m_sourceClass != null && m_destClass != null;
120 for (ClassEntry classEntry : scoredMatches.get(score)) { 331 boolean isMatched = twoSelected && m_matches.getUniqueMatches().containsKey(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
121 System.out.println(String.format("\tScore: %3d %3.0f%% %s", 332
122 score, 100.0 * score / maxScore, classEntry.getName() 333 m_matchButton.setEnabled(twoSelected);
123 )); 334 if (twoSelected) {
124 if (numScoredMatchesShown++ > 10) { 335 if (isMatched) {
125 return; 336 m_matchButton.setText("Unmatch");
126 } 337 } else {
338 m_matchButton.setText("Match");
127 } 339 }
340 } else {
341 m_matchButton.setText("");
128 } 342 }
129 } 343 }
130 344
345 protected void onMatchClick() {
346 // TODO
347 }
348
349 /*
131 private static List<String> getClassNames(Collection<ClassEntry> classes) { 350 private static List<String> getClassNames(Collection<ClassEntry> classes) {
132 List<String> out = Lists.newArrayList(); 351 List<String> out = Lists.newArrayList();
133 for (ClassEntry c : classes) { 352 for (ClassEntry c : classes) {