diff options
| author | 2017-03-08 08:17:04 +0100 | |
|---|---|---|
| committer | 2017-03-08 08:17:04 +0100 | |
| commit | 6e464ea251cab63c776ece0b2a356f1498ffa294 (patch) | |
| tree | 5ed30c03f5ac4cd2d6877874f5ede576049954f7 /src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java | |
| parent | Drop unix case style and implement hashCode when equals is overrided (diff) | |
| download | enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.tar.gz enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.tar.xz enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.zip | |
Follow Fabric guidelines
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java | 990 |
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 | |||
| 11 | package cuchaz.enigma.gui; | 12 | package cuchaz.enigma.gui; |
| 12 | 13 | ||
| 13 | import com.google.common.collect.BiMap; | 14 | import com.google.common.collect.BiMap; |
| @@ -31,508 +32,505 @@ import java.util.Collection; | |||
| 31 | import java.util.List; | 32 | import java.util.List; |
| 32 | import java.util.Map; | 33 | import java.util.Map; |
| 33 | 34 | ||
| 34 | |||
| 35 | public class ClassMatchingGui { | 35 | public 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 | } |