summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui
diff options
context:
space:
mode:
authorGravatar Gegy2019-01-30 21:05:32 +0200
committerGravatar GitHub2019-01-30 21:05:32 +0200
commitba7a354efae7d49833c887cf147ac940c975a1fa (patch)
tree02e14fda81dd5984e24f2df392c57c6e829fc875 /src/main/java/cuchaz/enigma/gui
parentRewrite the Jenkinsfile to use the new declarative pipeline syntax, lets hope... (diff)
downloadenigma-fork-ba7a354efae7d49833c887cf147ac940c975a1fa.tar.gz
enigma-fork-ba7a354efae7d49833c887cf147ac940c975a1fa.tar.xz
enigma-fork-ba7a354efae7d49833c887cf147ac940c975a1fa.zip
Remap sources (#106)
* Source remapping beginnings * Fix navigation to remapped classes * Translate identifier info reference * Remap local variables with default names in source * Caching translator * Fix lack of highlighting for first opened class * Fix unicode variable names * Unicode checker shouldn't be checking just alphanumeric * Fix package tree being built from obf names * Don't index `this` as method call for method::reference * Apply proposed names * Fix source export issues * Replace unicode var names at bytecode level uniquely * Drop imports from editor source * Class selector fixes * Delta keep track of base mappings to enable lookup of old names * Optimize source remapping by remapping source with a StringBuffer instead of copying * Bump version
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java182
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java94
-rw-r--r--src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java129
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java102
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java305
-rw-r--r--src/main/java/cuchaz/enigma/gui/SourceRemapper.java64
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java4
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java8
10 files changed, 503 insertions, 400 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index c3b7288..39d0333 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -17,9 +17,11 @@ import com.google.common.collect.Maps;
17import com.google.common.collect.Multimap; 17import com.google.common.collect.Multimap;
18import cuchaz.enigma.gui.node.ClassSelectorClassNode; 18import cuchaz.enigma.gui.node.ClassSelectorClassNode;
19import cuchaz.enigma.gui.node.ClassSelectorPackageNode; 19import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
20import cuchaz.enigma.translation.representation.entry.ClassEntry;
21import cuchaz.enigma.throwables.IllegalNameException; 20import cuchaz.enigma.throwables.IllegalNameException;
21import cuchaz.enigma.translation.Translator;
22import cuchaz.enigma.translation.representation.entry.ClassEntry;
22 23
24import javax.annotation.Nullable;
23import javax.swing.*; 25import javax.swing.*;
24import javax.swing.event.CellEditorListener; 26import javax.swing.event.CellEditorListener;
25import javax.swing.event.ChangeEvent; 27import javax.swing.event.ChangeEvent;
@@ -27,21 +29,26 @@ import javax.swing.tree.*;
27import java.awt.event.MouseAdapter; 29import java.awt.event.MouseAdapter;
28import java.awt.event.MouseEvent; 30import java.awt.event.MouseEvent;
29import java.util.*; 31import java.util.*;
30import java.util.List;
31 32
32public class ClassSelector extends JTree { 33public class ClassSelector extends JTree {
33 34
34 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); 35 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName);
36
37 private final GuiController controller;
38
35 private DefaultMutableTreeNode rootNodes; 39 private DefaultMutableTreeNode rootNodes;
36 private ClassSelectionListener selectionListener; 40 private ClassSelectionListener selectionListener;
37 private RenameSelectionListener renameSelectionListener; 41 private RenameSelectionListener renameSelectionListener;
38 private Comparator<ClassEntry> comparator; 42 private Comparator<ClassEntry> comparator;
39 43
44 private final Map<ClassEntry, ClassEntry> displayedObfToDeobf = new HashMap<>();
45
40 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) { 46 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) {
41 this.comparator = comparator; 47 this.comparator = comparator;
48 this.controller = gui.getController();
42 49
43 // configure the tree control 50 // configure the tree control
44 setEditable(gui != null); 51 setEditable(true);
45 setRootVisible(false); 52 setRootVisible(false);
46 setShowsRootHandles(false); 53 setShowsRootHandles(false);
47 setModel(null); 54 setModel(null);
@@ -55,66 +62,64 @@ public class ClassSelector extends JTree {
55 TreePath path = getSelectionPath(); 62 TreePath path = getSelectionPath();
56 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { 63 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
57 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); 64 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent();
58 selectionListener.onSelectClass(node.getClassEntry()); 65 selectionListener.onSelectClass(node.getObfEntry());
59 } 66 }
60 } 67 }
61 } 68 }
62 }); 69 });
63 70
64 if (gui != null) { 71 final JTree tree = this;
65 final JTree tree = this;
66 72
67 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, 73 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree,
68 (DefaultTreeCellRenderer) tree.getCellRenderer()) { 74 (DefaultTreeCellRenderer) tree.getCellRenderer()) {
69 @Override 75 @Override
70 public boolean isCellEditable(EventObject event) { 76 public boolean isCellEditable(EventObject event) {
71 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); 77 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event);
72 } 78 }
73 }; 79 };
74 this.setCellEditor(editor); 80 this.setCellEditor(editor);
75 editor.addCellEditorListener(new CellEditorListener() { 81 editor.addCellEditorListener(new CellEditorListener() {
76 @Override 82 @Override
77 public void editingStopped(ChangeEvent e) { 83 public void editingStopped(ChangeEvent e) {
78 String data = editor.getCellEditorValue().toString(); 84 String data = editor.getCellEditorValue().toString();
79 TreePath path = getSelectionPath(); 85 TreePath path = getSelectionPath();
80 86
81 Object realPath = path.getLastPathComponent(); 87 Object realPath = path.getLastPathComponent();
82 if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { 88 if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) {
83 DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; 89 DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath;
84 TreeNode parentNode = node.getParent(); 90 TreeNode parentNode = node.getParent();
85 if (parentNode == null) 91 if (parentNode == null)
86 return; 92 return;
87 boolean allowEdit = true; 93 boolean allowEdit = true;
88 for (int i = 0; i < parentNode.getChildCount(); i++) { 94 for (int i = 0; i < parentNode.getChildCount(); i++) {
89 TreeNode childNode = parentNode.getChildAt(i); 95 TreeNode childNode = parentNode.getChildAt(i);
90 if (childNode != null && childNode.toString().equals(data) && childNode != node) { 96 if (childNode != null && childNode.toString().equals(data) && childNode != node) {
91 allowEdit = false; 97 allowEdit = false;
92 break; 98 break;
93 }
94 } 99 }
95 if (allowEdit && renameSelectionListener != null) {
96 Object prevData = node.getUserObject();
97 Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data;
98 try {
99 renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node);
100 node.setUserObject(objectData); // Make sure that it's modified
101 } catch (IllegalNameException ex) {
102 JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION,
103 JOptionPane.ERROR_MESSAGE, null, new String[] { "Ok" }, "OK");
104 editor.cancelCellEditing();
105 }
106 } else
107 editor.cancelCellEditing();
108 } 100 }
109 101 if (allowEdit && renameSelectionListener != null) {
102 Object prevData = node.getUserObject();
103 Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data;
104 try {
105 renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node);
106 node.setUserObject(objectData); // Make sure that it's modified
107 } catch (IllegalNameException ex) {
108 JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION,
109 JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK");
110 editor.cancelCellEditing();
111 }
112 } else
113 editor.cancelCellEditing();
110 } 114 }
111 115
112 @Override 116 }
113 public void editingCanceled(ChangeEvent e) { 117
114 // NOP 118 @Override
115 } 119 public void editingCanceled(ChangeEvent e) {
116 }); 120 // NOP
117 } 121 }
122 });
118 // init defaults 123 // init defaults
119 this.selectionListener = null; 124 this.selectionListener = null;
120 this.renameSelectionListener = null; 125 this.renameSelectionListener = null;
@@ -142,16 +147,21 @@ public class ClassSelector extends JTree {
142 } 147 }
143 148
144 public void setClasses(Collection<ClassEntry> classEntries) { 149 public void setClasses(Collection<ClassEntry> classEntries) {
150 displayedObfToDeobf.clear();
151
145 List<StateEntry> state = getExpansionState(this); 152 List<StateEntry> state = getExpansionState(this);
146 if (classEntries == null) { 153 if (classEntries == null) {
147 setModel(null); 154 setModel(null);
148 return; 155 return;
149 } 156 }
150 157
158 Translator translator = controller.getDeobfuscator().getMapper().getDeobfuscator();
159
151 // build the package names 160 // build the package names
152 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap(); 161 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
153 for (ClassEntry classEntry : classEntries) { 162 for (ClassEntry obfClass : classEntries) {
154 packages.put(classEntry.getPackageName(), null); 163 ClassEntry deobfClass = translator.translate(obfClass);
164 packages.put(deobfClass.getPackageName(), null);
155 } 165 }
156 166
157 // sort the packages 167 // sort the packages
@@ -191,20 +201,24 @@ public class ClassSelector extends JTree {
191 201
192 // put the classes into packages 202 // put the classes into packages
193 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create(); 203 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create();
194 for (ClassEntry classEntry : classEntries) { 204 for (ClassEntry obfClass : classEntries) {
195 packagedClassEntries.put(classEntry.getPackageName(), classEntry); 205 ClassEntry deobfClass = translator.translate(obfClass);
206 packagedClassEntries.put(deobfClass.getPackageName(), obfClass);
196 } 207 }
197 208
198 // build the class nodes 209 // build the class nodes
199 for (String packageName : packagedClassEntries.keySet()) { 210 for (String packageName : packagedClassEntries.keySet()) {
200 // sort the class entries 211 // sort the class entries
201 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); 212 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
202 classEntriesInPackage.sort(this.comparator); 213 classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2)));
203 214
204 // create the nodes in order 215 // create the nodes in order
205 for (ClassEntry classEntry : classEntriesInPackage) { 216 for (ClassEntry obfClass : classEntriesInPackage) {
217 ClassEntry deobfClass = translator.translate(obfClass);
206 ClassSelectorPackageNode node = packages.get(packageName); 218 ClassSelectorPackageNode node = packages.get(packageName);
207 node.add(new ClassSelectorClassNode(classEntry)); 219 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass);
220 displayedObfToDeobf.put(obfClass, deobfClass);
221 node.add(classNode);
208 } 222 }
209 } 223 }
210 224
@@ -324,7 +338,7 @@ public class ClassSelector extends JTree {
324 } 338 }
325 for (ClassSelectorPackageNode packageNode : packageNodes()) { 339 for (ClassSelectorPackageNode packageNode : packageNodes()) {
326 if (packageNode.getPackageName().equals(packageName)) { 340 if (packageNode.getPackageName().equals(packageName)) {
327 expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode })); 341 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
328 return; 342 return;
329 } 343 }
330 } 344 }
@@ -332,14 +346,13 @@ public class ClassSelector extends JTree {
332 346
333 public void expandAll() { 347 public void expandAll() {
334 for (ClassSelectorPackageNode packageNode : packageNodes()) { 348 for (ClassSelectorPackageNode packageNode : packageNodes()) {
335 expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode })); 349 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
336 } 350 }
337 } 351 }
338 352
339 public ClassEntry getFirstClass() { 353 public ClassEntry getFirstClass() {
340 ClassSelectorPackageNode packageNode = packageNodes().get(0); 354 ClassSelectorPackageNode packageNode = packageNodes().get(0);
341 if (packageNode != null) 355 if (packageNode != null) {
342 {
343 ClassSelectorClassNode classNode = classNodes(packageNode).get(0); 356 ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
344 if (classNode != null) { 357 if (classNode != null) {
345 return classNode.getClassEntry(); 358 return classNode.getClassEntry();
@@ -350,7 +363,7 @@ public class ClassSelector extends JTree {
350 363
351 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { 364 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
352 String packageName = entry.getPackageName(); 365 String packageName = entry.getPackageName();
353 if (packageName == null){ 366 if (packageName == null) {
354 packageName = "(none)"; 367 packageName = "(none)";
355 } 368 }
356 for (ClassSelectorPackageNode packageNode : packageNodes()) { 369 for (ClassSelectorPackageNode packageNode : packageNodes()) {
@@ -361,6 +374,11 @@ public class ClassSelector extends JTree {
361 return null; 374 return null;
362 } 375 }
363 376
377 @Nullable
378 public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) {
379 return displayedObfToDeobf.get(obfEntry);
380 }
381
364 public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) { 382 public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) {
365 ClassSelectorPackageNode packageNode = getPackageNode(entry); 383 ClassSelectorPackageNode packageNode = getPackageNode(entry);
366 384
@@ -402,7 +420,7 @@ public class ClassSelector extends JTree {
402 for (ClassSelectorPackageNode packageNode : packageNodes()) { 420 for (ClassSelectorPackageNode packageNode : packageNodes()) {
403 for (ClassSelectorClassNode classNode : classNodes(packageNode)) { 421 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
404 if (classNode.getClassEntry().equals(classEntry)) { 422 if (classNode.getClassEntry().equals(classEntry)) {
405 setSelectionPath(new TreePath(new Object[] { getModel().getRoot(), packageNode, classNode })); 423 setSelectionPath(new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode}));
406 } 424 }
407 } 425 }
408 } 426 }
@@ -418,6 +436,9 @@ public class ClassSelector extends JTree {
418 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); 436 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i);
419 if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) { 437 if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) {
420 model.removeNodeFromParent(childNode); 438 model.removeNodeFromParent(childNode);
439 if (childNode instanceof ClassSelectorClassNode) {
440 displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry());
441 }
421 break; 442 break;
422 } 443 }
423 } 444 }
@@ -428,13 +449,25 @@ public class ClassSelector extends JTree {
428 ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); 449 ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode);
429 } 450 }
430 451
431 public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) { 452 public void moveClassIn(ClassEntry classEntry) {
432 if (otherSelector == null) 453 removeEntry(classEntry);
433 removeNode(getPackageNode(oldClassEntry), oldClassEntry); 454 insertNode(classEntry);
434 insertNode(getOrCreate(newClassEntry), newClassEntry);
435 } 455 }
436 456
437 public ClassSelectorPackageNode getOrCreate(ClassEntry entry) { 457 public void moveClassOut(ClassEntry classEntry) {
458 removeEntry(classEntry);
459 }
460
461 private void removeEntry(ClassEntry classEntry) {
462 ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry);
463 if (previousDeobf != null) {
464 ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf);
465 removeNode(packageNode, previousDeobf);
466 removeNodeIfEmpty(packageNode);
467 }
468 }
469
470 public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) {
438 DefaultTreeModel model = (DefaultTreeModel) getModel(); 471 DefaultTreeModel model = (DefaultTreeModel) getModel();
439 ClassSelectorPackageNode newPackageNode = getPackageNode(entry); 472 ClassSelectorPackageNode newPackageNode = getPackageNode(entry);
440 if (newPackageNode == null) { 473 if (newPackageNode == null) {
@@ -444,10 +477,15 @@ public class ClassSelector extends JTree {
444 return newPackageNode; 477 return newPackageNode;
445 } 478 }
446 479
447 public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { 480 public void insertNode(ClassEntry obfEntry) {
481 ClassEntry deobfEntry = controller.getDeobfuscator().deobfuscate(obfEntry);
482 ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry);
483
448 DefaultTreeModel model = (DefaultTreeModel) getModel(); 484 DefaultTreeModel model = (DefaultTreeModel) getModel();
449 ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry); 485 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry);
450 model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); 486 model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode));
487
488 displayedObfToDeobf.put(obfEntry, deobfEntry);
451 } 489 }
452 490
453 public void reload() { 491 public void reload() {
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
index 0810043..e119640 100644
--- a/src/main/java/cuchaz/enigma/gui/CodeReader.java
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -11,58 +11,27 @@
11 11
12package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
13 13
14import com.strobel.decompiler.languages.java.ast.CompilationUnit;
15import cuchaz.enigma.Deobfuscator;
16import cuchaz.enigma.analysis.EntryReference;
17import cuchaz.enigma.analysis.SourceIndex;
18import cuchaz.enigma.analysis.Token; 14import cuchaz.enigma.analysis.Token;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.Entry;
21import de.sciss.syntaxpane.DefaultSyntaxKit;
22 15
23import javax.swing.*; 16import javax.swing.*;
24import javax.swing.text.BadLocationException; 17import javax.swing.text.BadLocationException;
18import javax.swing.text.Document;
25import javax.swing.text.Highlighter.HighlightPainter; 19import javax.swing.text.Highlighter.HighlightPainter;
26import java.awt.*; 20import java.awt.*;
27import java.awt.event.ActionEvent; 21import java.awt.event.ActionEvent;
28import java.awt.event.ActionListener; 22import java.awt.event.ActionListener;
29 23
30public class CodeReader extends JEditorPane { 24public class CodeReader extends JEditorPane {
31
32 private static final long serialVersionUID = 3673180950485748810L; 25 private static final long serialVersionUID = 3673180950485748810L;
33 26
34 private static final Object lock = new Object();
35 private SourceIndex sourceIndex;
36 private SelectionListener selectionListener;
37
38 public CodeReader() {
39
40 setEditable(false);
41 setContentType("text/java");
42
43 // turn off token highlighting (it's wrong most of the time anyway...)
44 DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit();
45 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
46
47 // hook events
48 addCaretListener(event ->
49 {
50 if (selectionListener != null && sourceIndex != null) {
51 Token token = sourceIndex.getReferenceToken(event.getDot());
52 if (token != null) {
53 selectionListener.onSelect(sourceIndex.getDeobfReference(token));
54 } else {
55 selectionListener.onSelect(null);
56 }
57 }
58 });
59 }
60
61 // HACKHACK: someday we can update the main GUI to use this code reader 27 // HACKHACK: someday we can update the main GUI to use this code reader
62 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { 28 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
63 29
64 // set the caret position to the token 30 // set the caret position to the token
65 editor.setCaretPosition(token.start); 31 Document document = editor.getDocument();
32 int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength());
33
34 editor.setCaretPosition(clampedPosition);
66 editor.grabFocus(); 35 editor.grabFocus();
67 36
68 try { 37 try {
@@ -101,57 +70,4 @@ public class CodeReader extends JEditorPane {
101 }); 70 });
102 timer.start(); 71 timer.start();
103 } 72 }
104
105 public void setSelectionListener(SelectionListener val) {
106 selectionListener = val;
107 }
108
109 public void setCode(String code) {
110 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
111 synchronized (lock) {
112 setText(code);
113 }
114 }
115
116 public SourceIndex getSourceIndex() {
117 return sourceIndex;
118 }
119
120 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
121 decompileClass(classEntry, deobfuscator, null);
122 }
123
124 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
125 decompileClass(classEntry, deobfuscator, null, callback);
126 }
127
128 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
129
130 if (classEntry == null) {
131 setCode(null);
132 return;
133 }
134
135 setCode("(decompiling...)");
136
137 // run decompilation in a separate thread to keep ui responsive
138 new Thread(() ->
139 {
140
141 // decompile it
142
143 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getName());
144 String source = deobfuscator.getSource(sourceTree);
145 setCode(source);
146 sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
147
148 if (callback != null) {
149 callback.run();
150 }
151 }).start();
152 }
153
154 public interface SelectionListener {
155 void onSelect(EntryReference<Entry<?>, Entry<?>> reference);
156 }
157} 73}
diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
new file mode 100644
index 0000000..03f76c9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
@@ -0,0 +1,129 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.Deobfuscator;
4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.analysis.SourceIndex;
6import cuchaz.enigma.analysis.Token;
7import cuchaz.enigma.api.EnigmaPlugin;
8import cuchaz.enigma.gui.highlight.TokenHighlightType;
9import cuchaz.enigma.translation.LocalNameGenerator;
10import cuchaz.enigma.translation.Translator;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.Entry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry;
16
17import javax.annotation.Nullable;
18import java.util.*;
19
20public class DecompiledClassSource {
21 private final ClassEntry classEntry;
22 private final Deobfuscator deobfuscator;
23
24 private final SourceIndex obfuscatedIndex;
25 private SourceIndex remappedIndex;
26
27 private final Map<TokenHighlightType, Collection<Token>> highlightedTokens = new EnumMap<>(TokenHighlightType.class);
28
29 public DecompiledClassSource(ClassEntry classEntry, Deobfuscator deobfuscator, SourceIndex index) {
30 this.classEntry = classEntry;
31 this.deobfuscator = deobfuscator;
32 this.obfuscatedIndex = index;
33 this.remappedIndex = index;
34 }
35
36 public void remapSource(Translator translator) {
37 highlightedTokens.clear();
38
39 SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens());
40
41 SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(token, movedToken, translator));
42 remappedIndex = obfuscatedIndex.remapTo(remapResult);
43 }
44
45 private String remapToken(Token token, Token movedToken, Translator translator) {
46 EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token);
47
48 if (deobfuscator.isRenamable(reference)) {
49 Entry<?> entry = reference.getNameableEntry();
50 Entry<?> translatedEntry = translator.translate(entry);
51
52 if (isDeobfuscated(entry, translatedEntry)) {
53 highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED);
54 return translatedEntry.getSourceRemapName();
55 } else {
56 String proposedName = proposeName(entry);
57 if (proposedName != null) {
58 highlightToken(movedToken, TokenHighlightType.PROPOSED);
59 return proposedName;
60 }
61
62 highlightToken(movedToken, TokenHighlightType.OBFUSCATED);
63
64 String defaultName = generateDefaultName(translatedEntry);
65 if (defaultName != null) {
66 return defaultName;
67 }
68 }
69 }
70
71 return null;
72 }
73
74 @Nullable
75 private String proposeName(Entry<?> entry) {
76 if (entry instanceof FieldEntry) {
77 for (EnigmaPlugin plugin : deobfuscator.getPlugins()) {
78 String owner = entry.getContainingClass().getFullName();
79 String proposal = plugin.proposeFieldName(owner, entry.getName(), ((FieldEntry) entry).getDesc().toString());
80 if (proposal != null) {
81 return proposal;
82 }
83 }
84 }
85 return null;
86 }
87
88 @Nullable
89 private String generateDefaultName(Entry<?> entry) {
90 if (entry instanceof LocalVariableDefEntry) {
91 LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry;
92
93 int index = localVariable.getIndex();
94 if (localVariable.isArgument()) {
95 List<TypeDescriptor> arguments = localVariable.getParent().getDesc().getArgumentDescs();
96 return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments);
97 } else {
98 return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc());
99 }
100 }
101
102 return null;
103 }
104
105 private boolean isDeobfuscated(Entry<?> entry, Entry<?> translatedEntry) {
106 return !entry.getName().equals(translatedEntry.getName());
107 }
108
109 public ClassEntry getEntry() {
110 return classEntry;
111 }
112
113 public SourceIndex getIndex() {
114 return remappedIndex;
115 }
116
117 public Map<TokenHighlightType, Collection<Token>> getHighlightedTokens() {
118 return highlightedTokens;
119 }
120
121 private void highlightToken(Token token, TokenHighlightType highlightType) {
122 highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token);
123 }
124
125 @Override
126 public String toString() {
127 return remappedIndex.getSource();
128 }
129}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index d119735..a6e20a2 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -24,7 +24,7 @@ import cuchaz.enigma.gui.filechooser.FileChooserAny;
24import cuchaz.enigma.gui.filechooser.FileChooserFolder; 24import cuchaz.enigma.gui.filechooser.FileChooserFolder;
25import cuchaz.enigma.gui.highlight.BoxHighlightPainter; 25import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
26import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; 26import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
27import cuchaz.enigma.gui.node.ClassSelectorPackageNode; 27import cuchaz.enigma.gui.highlight.TokenHighlightType;
28import cuchaz.enigma.gui.panels.PanelDeobf; 28import cuchaz.enigma.gui.panels.PanelDeobf;
29import cuchaz.enigma.gui.panels.PanelEditor; 29import cuchaz.enigma.gui.panels.PanelEditor;
30import cuchaz.enigma.gui.panels.PanelIdentifier; 30import cuchaz.enigma.gui.panels.PanelIdentifier;
@@ -44,10 +44,9 @@ import javax.swing.tree.TreeNode;
44import javax.swing.tree.TreePath; 44import javax.swing.tree.TreePath;
45import java.awt.*; 45import java.awt.*;
46import java.awt.event.*; 46import java.awt.event.*;
47import java.io.IOException;
48import java.nio.file.Path; 47import java.nio.file.Path;
49import java.util.*;
50import java.util.List; 48import java.util.List;
49import java.util.*;
51import java.util.function.Function; 50import java.util.function.Function;
52 51
53public class Gui { 52public class Gui {
@@ -71,7 +70,7 @@ public class Gui {
71 private JPanel classesPanel; 70 private JPanel classesPanel;
72 private JSplitPane splitClasses; 71 private JSplitPane splitClasses;
73 private PanelIdentifier infoPanel; 72 private PanelIdentifier infoPanel;
74 public Map<String, BoxHighlightPainter> boxHighlightPainters; 73 public Map<TokenHighlightType, BoxHighlightPainter> boxHighlightPainters;
75 private SelectionHighlightPainter selectionHighlightPainter; 74 private SelectionHighlightPainter selectionHighlightPainter;
76 private JTree inheritanceTree; 75 private JTree inheritanceTree;
77 private JTree implementationsTree; 76 private JTree implementationsTree;
@@ -320,7 +319,7 @@ public class Gui {
320 this.frame.setTitle(Constants.NAME + " - " + jarName); 319 this.frame.setTitle(Constants.NAME + " - " + jarName);
321 this.classesPanel.removeAll(); 320 this.classesPanel.removeAll();
322 this.classesPanel.add(splitClasses); 321 this.classesPanel.add(splitClasses);
323 setSource(null); 322 setEditorText(null);
324 323
325 // update menu 324 // update menu
326 this.menuBar.closeJarMenu.setEnabled(true); 325 this.menuBar.closeJarMenu.setEnabled(true);
@@ -342,7 +341,7 @@ public class Gui {
342 this.frame.setTitle(Constants.NAME); 341 this.frame.setTitle(Constants.NAME);
343 setObfClasses(null); 342 setObfClasses(null);
344 setDeobfClasses(null); 343 setDeobfClasses(null);
345 setSource(null); 344 setEditorText(null);
346 this.classesPanel.removeAll(); 345 this.classesPanel.removeAll();
347 346
348 // update menu 347 // update menu
@@ -373,11 +372,16 @@ public class Gui {
373 this.menuBar.saveMappingsMenu.setEnabled(path != null); 372 this.menuBar.saveMappingsMenu.setEnabled(path != null);
374 } 373 }
375 374
376 public void setSource(String source) { 375 public void setEditorText(String source) {
377 this.editor.getHighlighter().removeAllHighlights(); 376 this.editor.getHighlighter().removeAllHighlights();
378 this.editor.setText(source); 377 this.editor.setText(source);
379 } 378 }
380 379
380 public void setSource(DecompiledClassSource source) {
381 editor.setText(source.toString());
382 setHighlightedTokens(source.getHighlightedTokens());
383 }
384
381 public void showToken(final Token token) { 385 public void showToken(final Token token) {
382 if (token == null) { 386 if (token == null) {
383 throw new IllegalArgumentException("Token cannot be null!"); 387 throw new IllegalArgumentException("Token cannot be null!");
@@ -401,15 +405,15 @@ public class Gui {
401 showToken(sortedTokens.get(0)); 405 showToken(sortedTokens.get(0));
402 } 406 }
403 407
404 public void setHighlightedTokens(Map<String, Iterable<Token>> tokens) { 408 public void setHighlightedTokens(Map<TokenHighlightType, Collection<Token>> tokens) {
405 // remove any old highlighters 409 // remove any old highlighters
406 this.editor.getHighlighter().removeAllHighlights(); 410 this.editor.getHighlighter().removeAllHighlights();
407 411
408 if (boxHighlightPainters != null) { 412 if (boxHighlightPainters != null) {
409 for (String s : tokens.keySet()) { 413 for (TokenHighlightType type : tokens.keySet()) {
410 BoxHighlightPainter painter = boxHighlightPainters.get(s); 414 BoxHighlightPainter painter = boxHighlightPainters.get(type);
411 if (painter != null) { 415 if (painter != null) {
412 setHighlightedTokens(tokens.get(s), painter); 416 setHighlightedTokens(tokens.get(type), painter);
413 } 417 }
414 } 418 }
415 } 419 }
@@ -435,17 +439,19 @@ public class Gui {
435 439
436 this.reference = reference; 440 this.reference = reference;
437 441
442 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.getDeobfuscator().deobfuscate(reference);
443
438 infoPanel.removeAll(); 444 infoPanel.removeAll();
439 if (reference.entry instanceof ClassEntry) { 445 if (translatedReference.entry instanceof ClassEntry) {
440 showClassEntry((ClassEntry) this.reference.entry); 446 showClassEntry((ClassEntry) translatedReference.entry);
441 } else if (this.reference.entry instanceof FieldEntry) { 447 } else if (translatedReference.entry instanceof FieldEntry) {
442 showFieldEntry((FieldEntry) this.reference.entry); 448 showFieldEntry((FieldEntry) translatedReference.entry);
443 } else if (this.reference.entry instanceof MethodEntry) { 449 } else if (translatedReference.entry instanceof MethodEntry) {
444 showMethodEntry((MethodEntry) this.reference.entry); 450 showMethodEntry((MethodEntry) translatedReference.entry);
445 } else if (this.reference.entry instanceof LocalVariableEntry) { 451 } else if (translatedReference.entry instanceof LocalVariableEntry) {
446 showLocalVariableEntry((LocalVariableEntry) this.reference.entry); 452 showLocalVariableEntry((LocalVariableEntry) translatedReference.entry);
447 } else { 453 } else {
448 throw new Error("Unknown entry desc: " + this.reference.entry.getClass().getName()); 454 throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName());
449 } 455 }
450 456
451 redraw(); 457 redraw();
@@ -519,7 +525,7 @@ public class Gui {
519 Token token = this.controller.getToken(pos); 525 Token token = this.controller.getToken(pos);
520 boolean isToken = token != null; 526 boolean isToken = token != null;
521 527
522 reference = this.controller.getDeobfReference(token); 528 reference = this.controller.getReference(token);
523 529
524 Entry<?> referenceEntry = reference != null ? reference.entry : null; 530 Entry<?> referenceEntry = reference != null ? reference.entry : null;
525 boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; 531 boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry;
@@ -527,7 +533,7 @@ public class Gui {
527 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); 533 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor();
528 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); 534 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor();
529 boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry); 535 boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry);
530 boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); 536 boolean isRenameable = isToken && this.controller.getDeobfuscator().isRenamable(reference);
531 537
532 if (isToken) { 538 if (isToken) {
533 showReference(reference); 539 showReference(reference);
@@ -544,7 +550,7 @@ public class Gui {
544 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); 550 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation());
545 this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); 551 this.popupMenu.toggleMappingMenu.setEnabled(isRenameable);
546 552
547 if (isToken && this.controller.entryHasDeobfuscatedName(referenceEntry)) { 553 if (isToken && this.controller.getDeobfuscator().isRemapped(referenceEntry)) {
548 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); 554 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated");
549 } else { 555 } else {
550 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); 556 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated");
@@ -576,7 +582,10 @@ public class Gui {
576 582
577 // init the text box 583 // init the text box
578 final JTextField text = new JTextField(); 584 final JTextField text = new JTextField();
579 text.setText(reference.getNameableName()); 585
586 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.getDeobfuscator().deobfuscate(reference);
587 text.setText(translatedReference.getNameableName());
588
580 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); 589 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
581 text.addKeyListener(new KeyAdapter() { 590 text.addKeyListener(new KeyAdapter() {
582 @Override 591 @Override
@@ -603,7 +612,7 @@ public class Gui {
603 612
604 int offset = text.getText().lastIndexOf('/') + 1; 613 int offset = text.getText().lastIndexOf('/') + 1;
605 // If it's a class and isn't in the default package, assume that it's deobfuscated. 614 // If it's a class and isn't in the default package, assume that it's deobfuscated.
606 if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) 615 if (translatedReference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0)
607 text.select(offset, text.getText().length()); 616 text.select(offset, text.getText().length());
608 else 617 else
609 text.selectAll(); 618 text.selectAll();
@@ -719,7 +728,7 @@ public class Gui {
719 } 728 }
720 729
721 public void toggleMapping() { 730 public void toggleMapping() {
722 if (this.controller.entryHasDeobfuscatedName(reference.entry)) { 731 if (this.controller.getDeobfuscator().isRemapped(reference.entry)) {
723 this.controller.removeMapping(reference); 732 this.controller.removeMapping(reference);
724 } else { 733 } else {
725 this.controller.markAsDeobfuscated(reference); 734 this.controller.markAsDeobfuscated(reference);
@@ -743,7 +752,7 @@ public class Gui {
743 callback.apply(response); 752 callback.apply(response);
744 } 753 }
745 754
746 public void saveMapping() throws IOException { 755 public void saveMapping() {
747 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) 756 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)
748 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); 757 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath());
749 } 758 }
@@ -757,13 +766,8 @@ public class Gui {
757 // ask to save before closing 766 // ask to save before closing
758 showDiscardDiag((response) -> { 767 showDiscardDiag((response) -> {
759 if (response == JOptionPane.YES_OPTION) { 768 if (response == JOptionPane.YES_OPTION) {
760 try { 769 this.saveMapping();
761 this.saveMapping(); 770 this.frame.dispose();
762 this.frame.dispose();
763
764 } catch (IOException ex) {
765 throw new Error(ex);
766 }
767 } else if (response == JOptionPane.NO_OPTION) 771 } else if (response == JOptionPane.NO_OPTION)
768 this.frame.dispose(); 772 this.frame.dispose();
769 773
@@ -796,47 +800,39 @@ public class Gui {
796 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); 800 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false);
797 } 801 }
798 802
799 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> deobfReference, String newName) { 803 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) {
800 String oldEntry = deobfReference.entry.getContainingClass().getPackageName(); 804 String oldEntry = obfReference.entry.getContainingClass().getPackageName();
801 String newEntry = new ClassEntry(newName).getPackageName(); 805 String newEntry = new ClassEntry(newName).getPackageName();
802 moveClassTree(deobfReference, newName, oldEntry == null, 806 moveClassTree(obfReference, oldEntry == null, newEntry == null);
803 newEntry == null);
804 } 807 }
805 808
806 // TODO: getExpansionState will *not* actually update itself based on name changes! 809 // TODO: getExpansionState will *not* actually update itself based on name changes!
807 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> deobfReference, String newName, boolean isOldOb, boolean isNewOb) { 810 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, boolean isOldOb, boolean isNewOb) {
808 ClassEntry oldEntry = deobfReference.entry.getContainingClass(); 811 ClassEntry classEntry = obfReference.entry.getContainingClass();
809 ClassEntry newEntry = new ClassEntry(newName);
810 812
811 // Ob -> deob 813 // Ob -> deob
812 List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); 814 List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses);
813 List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); 815 List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses);
814 816
815 if (isOldOb && !isNewOb) { 817 if (isOldOb && !isNewOb) {
816 this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses); 818 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
817 ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry); 819 this.obfPanel.obfClasses.moveClassOut(classEntry);
818 this.obfPanel.obfClasses.removeNode(packageNode, oldEntry);
819 this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode);
820 this.deobfPanel.deobfClasses.reload(); 820 this.deobfPanel.deobfClasses.reload();
821 this.obfPanel.obfClasses.reload(); 821 this.obfPanel.obfClasses.reload();
822 } 822 }
823 // Deob -> ob 823 // Deob -> ob
824 else if (isNewOb && !isOldOb) { 824 else if (isNewOb && !isOldOb) {
825 this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses); 825 this.obfPanel.obfClasses.moveClassIn(classEntry);
826 ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry); 826 this.deobfPanel.deobfClasses.moveClassOut(classEntry);
827 this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry);
828 this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode);
829 this.deobfPanel.deobfClasses.reload(); 827 this.deobfPanel.deobfClasses.reload();
830 this.obfPanel.obfClasses.reload(); 828 this.obfPanel.obfClasses.reload();
831 } 829 }
832 // Local move 830 // Local move
833 else if (isOldOb) { 831 else if (isOldOb) {
834 this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null); 832 this.obfPanel.obfClasses.moveClassIn(classEntry);
835 this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry));
836 this.obfPanel.obfClasses.reload(); 833 this.obfPanel.obfClasses.reload();
837 } else { 834 } else {
838 this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null); 835 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
839 this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry));
840 this.deobfPanel.deobfClasses.reload(); 836 this.deobfPanel.deobfClasses.reload();
841 } 837 }
842 838
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
index fd9e7f0..03e1768 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -11,13 +11,13 @@
11 11
12package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
13 13
14import com.google.common.collect.ImmutableMap;
15import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
16import com.google.common.collect.Queues; 15import com.google.common.collect.Queues;
16import com.google.common.util.concurrent.ThreadFactoryBuilder;
17import com.strobel.decompiler.languages.java.ast.CompilationUnit; 17import com.strobel.decompiler.languages.java.ast.CompilationUnit;
18import cuchaz.enigma.Deobfuscator; 18import cuchaz.enigma.Deobfuscator;
19import cuchaz.enigma.SourceProvider;
19import cuchaz.enigma.analysis.*; 20import cuchaz.enigma.analysis.*;
20import cuchaz.enigma.api.EnigmaPlugin;
21import cuchaz.enigma.config.Config; 21import cuchaz.enigma.config.Config;
22import cuchaz.enigma.gui.dialog.ProgressDialog; 22import cuchaz.enigma.gui.dialog.ProgressDialog;
23import cuchaz.enigma.throwables.MappingParseException; 23import cuchaz.enigma.throwables.MappingParseException;
@@ -36,16 +36,20 @@ import java.awt.event.ItemEvent;
36import java.io.File; 36import java.io.File;
37import java.io.IOException; 37import java.io.IOException;
38import java.nio.file.Path; 38import java.nio.file.Path;
39import java.util.*; 39import java.util.Collection;
40import java.util.Deque;
41import java.util.List;
42import java.util.concurrent.ExecutorService;
43import java.util.concurrent.Executors;
40import java.util.jar.JarFile; 44import java.util.jar.JarFile;
41import java.util.stream.Collectors; 45import java.util.stream.Collectors;
42 46
43public class GuiController { 47public class GuiController {
48 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("decompiler-thread").build());
44 49
45 private Deobfuscator deobfuscator; 50 private Deobfuscator deobfuscator;
46 private Gui gui; 51 private Gui gui;
47 private SourceIndex index; 52 private DecompiledClassSource currentSource;
48 private ClassEntry currentObfClass;
49 private Deque<EntryReference<Entry<?>, Entry<?>>> referenceStack; 53 private Deque<EntryReference<Entry<?>, Entry<?>>> referenceStack;
50 54
51 private Path loadedMappingPath; 55 private Path loadedMappingPath;
@@ -54,8 +58,7 @@ public class GuiController {
54 public GuiController(Gui gui) { 58 public GuiController(Gui gui) {
55 this.gui = gui; 59 this.gui = gui;
56 this.deobfuscator = null; 60 this.deobfuscator = null;
57 this.index = null; 61 this.currentSource = null;
58 this.currentObfClass = null;
59 this.referenceStack = Queues.newArrayDeque(); 62 this.referenceStack = Queues.newArrayDeque();
60 } 63 }
61 64
@@ -93,7 +96,7 @@ public class GuiController {
93 public void saveMappings(MappingFormat format, Path path) { 96 public void saveMappings(MappingFormat format, Path path) {
94 EntryRemapper mapper = deobfuscator.getMapper(); 97 EntryRemapper mapper = deobfuscator.getMapper();
95 98
96 MappingDelta delta = mapper.takeMappingDelta(); 99 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta();
97 boolean saveAll = !path.equals(loadedMappingPath); 100 boolean saveAll = !path.equals(loadedMappingPath);
98 101
99 ProgressDialog.runInThread(this.gui.getFrame(), progress -> { 102 ProgressDialog.runInThread(this.gui.getFrame(), progress -> {
@@ -116,189 +119,167 @@ public class GuiController {
116 } 119 }
117 120
118 public void exportSource(final File dirOut) { 121 public void exportSource(final File dirOut) {
119 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); 122 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut.toPath(), progress));
120 } 123 }
121 124
122 public void exportJar(final File fileOut) { 125 public void exportJar(final File fileOut) {
123 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress)); 126 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeTransformedJar(fileOut, progress));
124 } 127 }
125 128
126 public Token getToken(int pos) { 129 public Token getToken(int pos) {
127 if (this.index == null) { 130 if (this.currentSource == null) {
128 return null; 131 return null;
129 } 132 }
130 return this.index.getReferenceToken(pos); 133 return this.currentSource.getIndex().getReferenceToken(pos);
131 } 134 }
132 135
133 @Nullable 136 @Nullable
134 public EntryReference<Entry<?>, Entry<?>> getDeobfReference(Token token) { 137 public EntryReference<Entry<?>, Entry<?>> getReference(Token token) {
135 if (this.index == null) { 138 if (this.currentSource == null) {
136 return null; 139 return null;
137 } 140 }
138 return this.index.getDeobfReference(token); 141 return this.currentSource.getIndex().getReference(token);
139 } 142 }
140 143
141 public ReadableToken getReadableToken(Token token) { 144 public ReadableToken getReadableToken(Token token) {
142 if (this.index == null) { 145 if (this.currentSource == null) {
143 return null; 146 return null;
144 } 147 }
148 SourceIndex index = this.currentSource.getIndex();
145 return new ReadableToken( 149 return new ReadableToken(
146 this.index.getLineNumber(token.start), 150 index.getLineNumber(token.start),
147 this.index.getColumnNumber(token.start), 151 index.getColumnNumber(token.start),
148 this.index.getColumnNumber(token.end) 152 index.getColumnNumber(token.end)
149 ); 153 );
150 } 154 }
151 155
152 public boolean entryHasDeobfuscatedName(Entry<?> deobfEntry) { 156 public boolean entryIsInJar(Entry<?> entry) {
153 EntryResolver resolver = this.deobfuscator.getMapper().getDeobfResolver(); 157 if (entry == null) return false;
154 Entry<?> resolvedEntry = resolver.resolveFirstEntry(deobfEntry, ResolutionStrategy.RESOLVE_ROOT); 158 return this.deobfuscator.isRenamable(entry);
155 return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.getMapper().obfuscate(resolvedEntry));
156 } 159 }
157 160
158 public boolean entryIsInJar(Entry<?> deobfEntry) { 161 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
159 if (deobfEntry == null) return false;
160 return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.getMapper().obfuscate(deobfEntry));
161 }
162
163 public boolean referenceIsRenameable(EntryReference<Entry<?>, Entry<?>> deobfReference) {
164 if (deobfReference == null) return false;
165 return this.deobfuscator.isRenameable(this.deobfuscator.getMapper().obfuscate(deobfReference));
166 }
167
168 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
169 ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
170 Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); 162 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
171 ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, obfClassEntry); 163 ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, entry);
172 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); 164 return ClassInheritanceTreeNode.findNode(rootNode, entry);
173 } 165 }
174 166
175 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { 167 public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
176 ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
177 Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); 168 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
178 return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, obfClassEntry); 169 return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, entry);
179 } 170 }
180 171
181 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { 172 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
182 MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
183 Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); 173 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
184 MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, obfMethodEntry); 174 MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, entry);
185 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); 175 return MethodInheritanceTreeNode.findNode(rootNode, entry);
186 } 176 }
187 177
188 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { 178 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
189 MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
190 Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); 179 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
191 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, obfMethodEntry); 180 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, entry);
192 if (rootNodes.isEmpty()) { 181 if (rootNodes.isEmpty()) {
193 return null; 182 return null;
194 } 183 }
195 if (rootNodes.size() > 1) { 184 if (rootNodes.size() > 1) {
196 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); 185 System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
197 } 186 }
198 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); 187 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
199 } 188 }
200 189
201 public ClassReferenceTreeNode getClassReferences(ClassEntry deobfClassEntry) { 190 public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
202 ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
203 Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator(); 191 Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator();
204 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, obfClassEntry); 192 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
205 rootNode.load(this.deobfuscator.getJarIndex(), true); 193 rootNode.load(this.deobfuscator.getJarIndex(), true);
206 return rootNode; 194 return rootNode;
207 } 195 }
208 196
209 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { 197 public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
210 FieldEntry obfFieldEntry = this.deobfuscator.getMapper().obfuscate(deobfFieldEntry);
211 Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); 198 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
212 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, obfFieldEntry); 199 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
213 rootNode.load(this.deobfuscator.getJarIndex(), true); 200 rootNode.load(this.deobfuscator.getJarIndex(), true);
214 return rootNode; 201 return rootNode;
215 } 202 }
216 203
217 public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry, boolean recursive) { 204 public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
218 MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
219 Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); 205 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
220 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, obfMethodEntry); 206 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
221 rootNode.load(this.deobfuscator.getJarIndex(), true, recursive); 207 rootNode.load(this.deobfuscator.getJarIndex(), true, recursive);
222 return rootNode; 208 return rootNode;
223 } 209 }
224 210
225 public void rename(EntryReference<Entry<?>, Entry<?>> deobfReference, String newName, boolean refreshClassTree) { 211 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) {
226 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); 212 this.deobfuscator.rename(reference.getNameableEntry(), newName);
227 this.deobfuscator.rename(obfReference.getNameableEntry(), newName);
228
229 if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass())
230 this.gui.moveClassTree(deobfReference, newName);
231 refreshCurrentClass(obfReference);
232 213
214 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
215 this.gui.moveClassTree(reference, newName);
216 refreshCurrentClass(reference);
233 } 217 }
234 218
235 public void removeMapping(EntryReference<Entry<?>, Entry<?>> deobfReference) { 219 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
236 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); 220 this.deobfuscator.removeMapping(reference.getNameableEntry());
237 this.deobfuscator.removeMapping(obfReference.getNameableEntry()); 221 if (reference.entry instanceof ClassEntry)
238 if (deobfReference.entry instanceof ClassEntry) 222 this.gui.moveClassTree(reference, false, true);
239 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); 223 refreshCurrentClass(reference);
240 refreshCurrentClass(obfReference);
241 } 224 }
242 225
243 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> deobfReference) { 226 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
244 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); 227 this.deobfuscator.markAsDeobfuscated(reference.getNameableEntry());
245 this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); 228 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
246 if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) 229 this.gui.moveClassTree(reference, true, false);
247 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); 230 refreshCurrentClass(reference);
248 refreshCurrentClass(obfReference);
249 } 231 }
250 232
251 public void openDeclaration(Entry<?> deobfEntry) { 233 public void openDeclaration(Entry<?> entry) {
252 if (deobfEntry == null) { 234 if (entry == null) {
253 throw new IllegalArgumentException("Entry cannot be null!"); 235 throw new IllegalArgumentException("Entry cannot be null!");
254 } 236 }
255 openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); 237 openReference(new EntryReference<>(entry, entry.getName()));
256 } 238 }
257 239
258 public void openReference(EntryReference<Entry<?>, Entry<?>> deobfReference) { 240 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) {
259 if (deobfReference == null) { 241 if (reference == null) {
260 throw new IllegalArgumentException("Reference cannot be null!"); 242 throw new IllegalArgumentException("Reference cannot be null!");
261 } 243 }
262 244
263 // get the reference target class 245 // get the reference target class
264 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); 246 ClassEntry classEntry = reference.getLocationClassEntry();
265 ClassEntry obfClassEntry = obfReference.getLocationClassEntry(); 247 if (!this.deobfuscator.isRenamable(classEntry)) {
266 if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { 248 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!");
267 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
268 } 249 }
269 if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) { 250
251 if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) {
270 // deobfuscate the class, then navigate to the reference 252 // deobfuscate the class, then navigate to the reference
271 this.currentObfClass = obfClassEntry; 253 loadClass(classEntry, () -> showReference(reference));
272 deobfuscate(this.currentObfClass, obfReference);
273 } else { 254 } else {
274 showReference(obfReference); 255 showReference(reference);
275 } 256 }
276 } 257 }
277 258
278 private void showReference(EntryReference<Entry<?>, Entry<?>> obfReference) { 259 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
279 EntryRemapper mapper = this.deobfuscator.getMapper(); 260 EntryRemapper mapper = this.deobfuscator.getMapper();
280 261
281 Collection<Token> tokens = mapper.getObfResolver().resolveReference(obfReference, ResolutionStrategy.RESOLVE_ROOT) 262 SourceIndex index = this.currentSource.getIndex();
263 Collection<Token> tokens = mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_ROOT)
282 .stream() 264 .stream()
283 .map(mapper::deobfuscate) 265 .flatMap(r -> index.getReferenceTokens(r).stream())
284 .flatMap(reference -> index.getReferenceTokens(reference).stream())
285 .collect(Collectors.toList()); 266 .collect(Collectors.toList());
286 267
287 if (tokens.isEmpty()) { 268 if (tokens.isEmpty()) {
288 // DEBUG 269 // DEBUG
289 System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentObfClass)); 270 System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentSource.getEntry()));
290 } else { 271 } else {
291 this.gui.showTokens(tokens); 272 this.gui.showTokens(tokens);
292 } 273 }
293 } 274 }
294 275
295 public void savePreviousReference(EntryReference<Entry<?>, Entry<?>> deobfReference) { 276 public void savePreviousReference(EntryReference<Entry<?>, Entry<?>> reference) {
296 this.referenceStack.push(this.deobfuscator.getMapper().obfuscate(deobfReference)); 277 this.referenceStack.push(reference);
297 } 278 }
298 279
299 public void openPreviousReference() { 280 public void openPreviousReference() {
300 if (hasPreviousLocation()) { 281 if (hasPreviousLocation()) {
301 openReference(this.deobfuscator.getMapper().deobfuscate(this.referenceStack.pop())); 282 openReference(this.referenceStack.pop());
302 } 283 }
303 } 284 }
304 285
@@ -318,97 +299,65 @@ public class GuiController {
318 refreshCurrentClass(null); 299 refreshCurrentClass(null);
319 } 300 }
320 301
321 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> obfReference) { 302 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference) {
322 if (this.currentObfClass != null) { 303 if (currentSource != null) {
323 deobfuscate(this.currentObfClass, obfReference); 304 loadClass(currentSource.getEntry(), () -> {
305 if (reference != null) {
306 showReference(reference);
307 }
308 });
324 } 309 }
325 } 310 }
326 311
327 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry<?>, Entry<?>> obfReference) { 312 private void loadClass(ClassEntry classEntry, Runnable callback) {
313 ClassEntry targetClass = classEntry.getOutermostClass();
328 314
329 this.gui.setSource("(deobfuscating...)"); 315 boolean requiresDecompile = currentSource == null || !currentSource.getEntry().equals(targetClass);
316 if (requiresDecompile) {
317 gui.setEditorText("(decompiling...)");
318 }
330 319
331 // run the deobfuscator in a separate thread so we don't block the GUI event queue 320 DECOMPILER_SERVICE.submit(() -> {
332 new Thread(() -> 321 try {
333 { 322 if (requiresDecompile) {
334 // decompile,deobfuscate the bytecode 323 decompileSource(targetClass, deobfuscator.getObfSourceProvider());
335 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClass().getFullName());
336 if (sourceTree == null) {
337 // decompilation of this class is not supported
338 gui.setSource("Unable to find class: " + classEntry);
339 return;
340 }
341 String source = deobfuscator.getSource(sourceTree);
342 index = deobfuscator.getSourceIndex(sourceTree, source);
343
344 String sourceString = index.getSource();
345
346 // set the highlighted tokens
347 List<Token> obfuscatedTokens = Lists.newArrayList();
348 List<Token> proposedTokens = Lists.newArrayList();
349 List<Token> deobfuscatedTokens = Lists.newArrayList();
350 List<Token> otherTokens = Lists.newArrayList();
351
352 int offset = 0;
353 Map<Token, Token> tokenRemap = new HashMap<>();
354 boolean remapped = false;
355
356 for (Token inToken : index.referenceTokens()) {
357 Token token = inToken.move(offset);
358
359 EntryReference<Entry<?>, Entry<?>> reference = index.getDeobfReference(inToken);
360 if (referenceIsRenameable(reference)) {
361 boolean added = false;
362
363 if (!entryHasDeobfuscatedName(reference.getNameableEntry())) {
364 Entry<?> obfEntry = deobfuscator.getMapper().obfuscate(reference.getNameableEntry());
365 if (obfEntry instanceof FieldEntry) {
366 for (EnigmaPlugin plugin : deobfuscator.getPlugins()) {
367 String owner = obfEntry.getContainingClass().getFullName();
368 String proposal = plugin.proposeFieldName(owner, obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString());
369 if (proposal != null) {
370 proposedTokens.add(token);
371 offset += token.getRenameOffset(proposal);
372 sourceString = token.rename(sourceString, proposal);
373 added = true;
374 remapped = true;
375 break;
376 }
377 }
378 }
379 }
380
381 if (!added) {
382 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
383 deobfuscatedTokens.add(token);
384 } else {
385 obfuscatedTokens.add(token);
386 }
387 }
388 } else {
389 otherTokens.add(token);
390 } 324 }
391 325
392 tokenRemap.put(inToken, token); 326 remapSource(deobfuscator.getMapper().getDeobfuscator());
327 callback.run();
328 } catch (Throwable t) {
329 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName());
330 t.printStackTrace(System.err);
393 } 331 }
332 });
333 }
394 334
395 if (remapped) { 335 private void decompileSource(ClassEntry targetClass, SourceProvider sourceProvider) {
396 index.remap(sourceString, tokenRemap); 336 CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName());
397 } 337 if (sourceTree == null) {
338 gui.setEditorText("Unable to find class: " + targetClass);
339 return;
340 }
398 341
399 gui.setSource(sourceString); 342 DropImportAstTransform.INSTANCE.run(sourceTree);
400 if (obfReference != null) { 343
401 showReference(obfReference); 344 String sourceString = sourceProvider.writeSourceToString(sourceTree);
402 } 345
346 SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true);
347 index.resolveReferences(deobfuscator.getMapper().getObfResolver());
348
349 currentSource = new DecompiledClassSource(targetClass, deobfuscator, index);
350 }
351
352 private void remapSource(Translator translator) {
353 if (currentSource == null) {
354 return;
355 }
356
357 currentSource.remapSource(translator);
403 358
404 gui.setEditorTheme(Config.getInstance().lookAndFeel); 359 gui.setEditorTheme(Config.getInstance().lookAndFeel);
405 gui.setHighlightedTokens(ImmutableMap.of( 360 gui.setSource(currentSource);
406 "obfuscated", obfuscatedTokens,
407 "proposed", proposedTokens,
408 "deobfuscated", deobfuscatedTokens,
409 "other", otherTokens
410 ));
411 }).start();
412 } 361 }
413 362
414 public Deobfuscator getDeobfuscator() { 363 public Deobfuscator getDeobfuscator() {
diff --git a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
new file mode 100644
index 0000000..f38f44e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
@@ -0,0 +1,64 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.analysis.Token;
4
5import java.util.HashMap;
6import java.util.Map;
7
8public class SourceRemapper {
9 private final String source;
10 private final Iterable<Token> tokens;
11
12 public SourceRemapper(String source, Iterable<Token> tokens) {
13 this.source = source;
14 this.tokens = tokens;
15 }
16
17 public Result remap(Remapper remapper) {
18 StringBuffer remappedSource = new StringBuffer(source);
19 Map<Token, Token> remappedTokens = new HashMap<>();
20
21 int accumulatedOffset = 0;
22 for (Token token : tokens) {
23 Token movedToken = token.move(accumulatedOffset);
24
25 String remappedName = remapper.remap(token, movedToken);
26 if (remappedName != null) {
27 accumulatedOffset += movedToken.getRenameOffset(remappedName);
28 movedToken.rename(remappedSource, remappedName);
29 }
30
31 if (!token.equals(movedToken)) {
32 remappedTokens.put(token, movedToken);
33 }
34 }
35
36 return new Result(remappedSource.toString(), remappedTokens);
37 }
38
39 public static class Result {
40 private final String remappedSource;
41 private final Map<Token, Token> remappedTokens;
42
43 Result(String remappedSource, Map<Token, Token> remappedTokens) {
44 this.remappedSource = remappedSource;
45 this.remappedTokens = remappedTokens;
46 }
47
48 public String getSource() {
49 return remappedSource;
50 }
51
52 public Token getRemappedToken(Token token) {
53 return remappedTokens.getOrDefault(token, token);
54 }
55
56 public boolean isEmpty() {
57 return remappedTokens.isEmpty();
58 }
59 }
60
61 public interface Remapper {
62 String remap(Token token, Token movedToken);
63 }
64}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
index f4f0277..dfbfa65 100644
--- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
@@ -152,12 +152,8 @@ public class MenuBar extends JMenuBar {
152 if (this.gui.getController().isDirty()) { 152 if (this.gui.getController().isDirty()) {
153 this.gui.showDiscardDiag((response -> { 153 this.gui.showDiscardDiag((response -> {
154 if (response == JOptionPane.YES_OPTION) { 154 if (response == JOptionPane.YES_OPTION) {
155 try { 155 gui.saveMapping();
156 gui.saveMapping(); 156 this.gui.getController().closeMappings();
157 this.gui.getController().closeMappings();
158 } catch (IOException e) {
159 throw new Error(e);
160 }
161 } else if (response == JOptionPane.NO_OPTION) 157 } else if (response == JOptionPane.NO_OPTION)
162 this.gui.getController().closeMappings(); 158 this.gui.getController().closeMappings();
163 return null; 159 return null;
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
index 10366ce..cef6494 100644
--- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
@@ -34,7 +34,9 @@ public class BoxHighlightPainter implements Highlighter.HighlightPainter {
34 public static Rectangle getBounds(JTextComponent text, int start, int end) { 34 public static Rectangle getBounds(JTextComponent text, int start, int end) {
35 try { 35 try {
36 // determine the bounds of the text 36 // determine the bounds of the text
37 Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); 37 Rectangle startRect = text.modelToView(start);
38 Rectangle endRect = text.modelToView(end);
39 Rectangle bounds = startRect.union(endRect);
38 40
39 // adjust the box so it looks nice 41 // adjust the box so it looks nice
40 bounds.x -= 2; 42 bounds.x -= 2;
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
new file mode 100644
index 0000000..ae23f32
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
@@ -0,0 +1,7 @@
1package cuchaz.enigma.gui.highlight;
2
3public enum TokenHighlightType {
4 OBFUSCATED,
5 DEOBFUSCATED,
6 PROPOSED
7}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
index bf6b178..922f8f2 100644
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
+++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
@@ -17,13 +17,19 @@ import javax.swing.tree.DefaultMutableTreeNode;
17 17
18public class ClassSelectorClassNode extends DefaultMutableTreeNode { 18public class ClassSelectorClassNode extends DefaultMutableTreeNode {
19 19
20 private final ClassEntry obfEntry;
20 private ClassEntry classEntry; 21 private ClassEntry classEntry;
21 22
22 public ClassSelectorClassNode(ClassEntry classEntry) { 23 public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) {
24 this.obfEntry = obfEntry;
23 this.classEntry = classEntry; 25 this.classEntry = classEntry;
24 this.setUserObject(classEntry); 26 this.setUserObject(classEntry);
25 } 27 }
26 28
29 public ClassEntry getObfEntry() {
30 return obfEntry;
31 }
32
27 public ClassEntry getClassEntry() { 33 public ClassEntry getClassEntry() {
28 return this.classEntry; 34 return this.classEntry;
29 } 35 }