From 00fcd0550fcdda621c2e4662f6ddd55ce673b931 Mon Sep 17 00:00:00 2001 From: Gegy Date: Thu, 24 Jan 2019 14:48:32 +0200 Subject: [WIP] Mapping rework (#91) * Move packages * Mapping & entry refactor: first pass * Fix deobf -> obf tree remapping * Resolve various issues * Give all entries the potential for parents and treat inner classes as children * Deobf UI tree elements * Tests pass * Sort mapping output * Fix delta tracking * Index separation and first pass for #97 * Keep track of remapped jar index * Fix child entries not being remapped * Drop non-root entries * Track dropped mappings * Fix enigma mapping ordering * EntryTreeNode interface * Small tweaks * Naive full index remap on rename * Entries can resolve to more than one root entry * Support alternative resolution strategies * Bridge method resolution * Tests pass * Fix mappings being used where there are none * Fix methods with different descriptors being considered unique. closes #89 --- src/main/java/cuchaz/enigma/gui/ClassSelector.java | 5 +- src/main/java/cuchaz/enigma/gui/CodeReader.java | 57 +----- src/main/java/cuchaz/enigma/gui/Gui.java | 80 ++++---- src/main/java/cuchaz/enigma/gui/GuiController.java | 209 +++++++++++---------- src/main/java/cuchaz/enigma/gui/GuiTricks.java | 42 ----- .../java/cuchaz/enigma/gui/ScoredClassEntry.java | 44 ----- .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 4 +- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 45 ++--- .../enigma/gui/node/ClassSelectorClassNode.java | 2 +- .../java/cuchaz/enigma/gui/panels/PanelObf.java | 6 +- 10 files changed, 172 insertions(+), 322 deletions(-) delete mode 100644 src/main/java/cuchaz/enigma/gui/GuiTricks.java delete mode 100644 src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java (limited to 'src/main/java/cuchaz/enigma/gui') diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java index 8863386..c3b7288 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -17,14 +17,13 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import cuchaz.enigma.gui.node.ClassSelectorClassNode; import cuchaz.enigma.gui.node.ClassSelectorPackageNode; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.throwables.IllegalNameException; import javax.swing.*; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.tree.*; -import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.*; @@ -32,7 +31,7 @@ import java.util.List; public class ClassSelector extends JTree { - public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); + public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); private DefaultMutableTreeNode rootNodes; private ClassSelectionListener selectionListener; private RenameSelectionListener renameSelectionListener; diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java index 137c730..0810043 100644 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -16,9 +16,8 @@ import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; import javax.swing.*; @@ -33,7 +32,6 @@ public class CodeReader extends JEditorPane { private static final long serialVersionUID = 3673180950485748810L; private static final Object lock = new Object(); - private SelectionHighlightPainter selectionHighlightPainter; private SourceIndex sourceIndex; private SelectionListener selectionListener; @@ -58,8 +56,6 @@ public class CodeReader extends JEditorPane { } } }); - - selectionHighlightPainter = new SelectionHighlightPainter(); } // HACKHACK: someday we can update the main GUI to use this code reader @@ -144,7 +140,7 @@ public class CodeReader extends JEditorPane { // decompile it - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getName()); String source = deobfuscator.getSource(sourceTree); setCode(source); sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); @@ -155,52 +151,7 @@ public class CodeReader extends JEditorPane { }).start(); } - public void navigateToClassDeclaration(ClassEntry classEntry) { - - // navigate to the class declaration - Token token = sourceIndex.getDeclarationToken(classEntry); - if (token == null) { - // couldn't find the class declaration token, might be an anonymous class - // look for any declaration in that class instead - for (Entry entry : sourceIndex.declarations()) { - if (entry.getOwnerClassEntry().equals(classEntry)) { - token = sourceIndex.getDeclarationToken(entry); - break; - } - } - } - - if (token != null) { - navigateToToken(token); - } else { - // couldn't find anything =( - System.out.println("Unable to find declaration in source for " + classEntry); - } - } - - public void navigateToToken(final Token token) { - navigateToToken(this, token, selectionHighlightPainter); - } - - public void setHighlightedTokens(Iterable tokens, HighlightPainter painter) { - for (Token token : tokens) { - setHighlightedToken(token, painter); - } - } - - public void setHighlightedToken(Token token, HighlightPainter painter) { - try { - getHighlighter().addHighlight(token.start, token.end, painter); - } catch (BadLocationException ex) { - throw new IllegalArgumentException(ex); - } - } - - public void clearHighlights() { - getHighlighter().removeAllHighlights(); - } - public interface SelectionListener { - void onSelect(EntryReference reference); + void onSelect(EntryReference, Entry> reference); } } diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 53500aa..d119735 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -29,9 +29,9 @@ import cuchaz.enigma.gui.panels.PanelDeobf; import cuchaz.enigma.gui.panels.PanelEditor; import cuchaz.enigma.gui.panels.PanelIdentifier; import cuchaz.enigma.gui.panels.PanelObf; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.mapping.AccessModifier; +import cuchaz.enigma.translation.representation.entry.*; import cuchaz.enigma.utils.Utils; import de.sciss.syntaxpane.DefaultSyntaxKit; @@ -44,8 +44,8 @@ import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.*; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.*; import java.util.List; import java.util.function.Function; @@ -58,7 +58,7 @@ public class Gui { private final MenuBar menuBar; // state - public EntryReference reference; + public EntryReference, Entry> reference; public FileDialog jarFileChooser; public FileDialog tinyMappingsFileChooser; public JFileChooser enigmaMappingsFileChooser; @@ -222,7 +222,7 @@ public class Gui { Object node = path.getLastPathComponent(); if (node instanceof ReferenceTreeNode) { - ReferenceTreeNode referenceNode = ((ReferenceTreeNode) node); + ReferenceTreeNode, Entry> referenceNode = ((ReferenceTreeNode, Entry>) node); if (referenceNode.getReference() != null) { navigateTo(referenceNode.getReference()); } else { @@ -250,10 +250,10 @@ public class Gui { tokens.setPreferredSize(new Dimension(0, 200)); tokens.setMinimumSize(new Dimension(0, 200)); JSplitPane callPanel = new JSplitPane( - JSplitPane.VERTICAL_SPLIT, - true, - new JScrollPane(callsTree), - new JScrollPane(tokens) + JSplitPane.VERTICAL_SPLIT, + true, + new JScrollPane(callsTree), + new JScrollPane(tokens) ); callPanel.setResizeWeight(1); // let the top side take all the slack callPanel.resetToPreferredSizes(); @@ -368,9 +368,9 @@ public class Gui { this.deobfPanel.deobfClasses.setClasses(deobfClasses); } - public void setMappingsFile(File file) { - this.enigmaMappingsFileChooser.setSelectedFile(file); - this.menuBar.saveMappingsMenu.setEnabled(file != null); + public void setMappingsFile(Path path) { + this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); + this.menuBar.saveMappingsMenu.setEnabled(path != null); } public void setSource(String source) { @@ -427,7 +427,7 @@ public class Gui { } } - private void showReference(EntryReference reference) { + private void showReference(EntryReference, Entry> reference) { if (reference == null) { infoPanel.clearReference(); return; @@ -453,29 +453,29 @@ public class Gui { private void showLocalVariableEntry(LocalVariableEntry entry) { addNameValue(infoPanel, "Variable", entry.getName()); - addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getOwnerEntry().getName()); + addNameValue(infoPanel, "Class", entry.getContainingClass().getFullName()); + addNameValue(infoPanel, "Method", entry.getParent().getName()); addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); } private void showClassEntry(ClassEntry entry) { - addNameValue(infoPanel, "Class", entry.getName()); + addNameValue(infoPanel, "Class", entry.getFullName()); addModifierComboBox(infoPanel, "Modifier", entry); } private void showFieldEntry(FieldEntry entry) { addNameValue(infoPanel, "Field", entry.getName()); - addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Class", entry.getParent().getFullName()); addNameValue(infoPanel, "TypeDescriptor", entry.getDesc().toString()); addModifierComboBox(infoPanel, "Modifier", entry); } private void showMethodEntry(MethodEntry entry) { if (entry.isConstructor()) { - addNameValue(infoPanel, "Constructor", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Constructor", entry.getParent().getFullName()); } else { addNameValue(infoPanel, "Method", entry.getName()); - addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Class", entry.getParent().getFullName()); } addNameValue(infoPanel, "MethodDescriptor", entry.getDesc().toString()); addModifierComboBox(infoPanel, "Modifier", entry); @@ -494,7 +494,7 @@ public class Gui { container.add(panel); } - private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { + private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { if (!getController().entryIsInJar(entry)) return null; JPanel panel = new JPanel(); @@ -502,7 +502,7 @@ public class Gui { JLabel label = new JLabel(name + ":", JLabel.RIGHT); label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); panel.add(label); - JComboBox combo = new JComboBox<>(Mappings.EntryModifier.values()); + JComboBox combo = new JComboBox<>(AccessModifier.values()); ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); @@ -520,11 +520,13 @@ public class Gui { boolean isToken = token != null; reference = this.controller.getDeobfReference(token); - boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; - boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; - boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry && !((MethodEntry) reference.entry).isConstructor(); - boolean isConstructorEntry = isToken && reference.entry instanceof MethodEntry && ((MethodEntry) reference.entry).isConstructor(); - boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); + + Entry referenceEntry = reference != null ? reference.entry : null; + boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; + boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; + boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); + boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); + boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry); boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); if (isToken) { @@ -542,14 +544,14 @@ public class Gui { this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); - if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { + if (isToken && this.controller.entryHasDeobfuscatedName(referenceEntry)) { this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); } else { this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); } } - public void navigateTo(Entry entry) { + public void navigateTo(Entry entry) { if (!this.controller.entryIsInJar(entry)) { // entry is not in the jar. Ignore it return; @@ -560,7 +562,7 @@ public class Gui { this.controller.openDeclaration(entry); } - private void navigateTo(EntryReference reference) { + private void navigateTo(EntryReference, Entry> reference) { if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { return; } @@ -613,7 +615,7 @@ public class Gui { String newName = text.getText(); if (saveName && newName != null && !newName.isEmpty()) { try { - this.controller.rename(reference, newName); + this.controller.rename(reference, newName, true); } catch (IllegalNameException ex) { text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); text.setToolTipText(ex.getReason()); @@ -737,13 +739,13 @@ public class Gui { public void showDiscardDiag(Function callback, String... options) { int response = JOptionPane.showOptionDialog(this.frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, null, options, options[2]); + JOptionPane.QUESTION_MESSAGE, null, options, options[2]); callback.apply(response); } public void saveMapping() throws IOException { if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) - this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile()); + this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); } public void close() { @@ -782,7 +784,7 @@ public class Gui { DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); - this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount()); + this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); childNode.setUserObject(dataChild); } node.setUserObject(data); @@ -791,19 +793,19 @@ public class Gui { } // class rename else if (data instanceof ClassEntry) - this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); + this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); } - public void moveClassTree(EntryReference deobfReference, String newName) { - String oldEntry = deobfReference.entry.getOwnerClassEntry().getPackageName(); + public void moveClassTree(EntryReference, Entry> deobfReference, String newName) { + String oldEntry = deobfReference.entry.getContainingClass().getPackageName(); String newEntry = new ClassEntry(newName).getPackageName(); moveClassTree(deobfReference, newName, oldEntry == null, - newEntry == null); + newEntry == null); } // TODO: getExpansionState will *not* actually update itself based on name changes! - public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) { - ClassEntry oldEntry = deobfReference.entry.getOwnerClassEntry(); + public void moveClassTree(EntryReference, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) { + ClassEntry oldEntry = deobfReference.entry.getContainingClass(); ClassEntry newEntry = new ClassEntry(newName); // Ob -> deob diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 69aefe5..06cb33e 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -20,19 +20,25 @@ import cuchaz.enigma.analysis.*; import cuchaz.enigma.api.EnigmaPlugin; import cuchaz.enigma.config.Config; import cuchaz.enigma.gui.dialog.ProgressDialog; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.Entry; -import cuchaz.enigma.mapping.entry.FieldEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.ReadableToken; +import javax.annotation.Nullable; import java.awt.event.ItemEvent; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.*; import java.util.jar.JarFile; +import java.util.stream.Collectors; public class GuiController { @@ -40,27 +46,26 @@ public class GuiController { private Gui gui; private SourceIndex index; private ClassEntry currentObfClass; - private boolean isDirty; - private Deque> referenceStack; + private Deque, Entry>> referenceStack; + + private Path loadedMappingPath; + private MappingFormat loadedMappingFormat; public GuiController(Gui gui) { this.gui = gui; this.deobfuscator = null; this.index = null; this.currentObfClass = null; - this.isDirty = false; this.referenceStack = Queues.newArrayDeque(); } public boolean isDirty() { - return this.isDirty; + return deobfuscator.getMapper().isDirty(); } public void openJar(final JarFile jar) throws IOException { this.gui.onStartOpenJar("Loading JAR..."); - this.deobfuscator = new Deobfuscator(jar, (msg) -> { - this.gui.onStartOpenJar(msg); - }); + this.deobfuscator = new Deobfuscator(jar, this.gui::onStartOpenJar); this.gui.onFinishOpenJar(jar.getName()); refreshClasses(); } @@ -70,43 +75,37 @@ public class GuiController { this.gui.onCloseJar(); } - public void openEnigmaMappings(File file) throws IOException, MappingParseException { - this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file)); - this.isDirty = false; - this.gui.setMappingsFile(file); + public void openMappings(MappingFormat format, Path path) throws IOException, MappingParseException { + EntryTree mappings = format.read(path); + deobfuscator.setMappings(mappings); + + gui.setMappingsFile(path); + loadedMappingFormat = format; + refreshClasses(); refreshCurrentClass(); } - public void openTinyMappings(File file) throws IOException, MappingParseException { - this.deobfuscator.setMappings(new MappingsTinyReader().read(file)); - this.isDirty = false; - this.gui.setMappingsFile(file); - refreshClasses(); - refreshCurrentClass(); + public void saveMappings(Path path) { + saveMappings(loadedMappingFormat, path); } - public void saveMappings(File file) throws IOException { - Mappings mappings = this.deobfuscator.getMappings(); - switch (mappings.getOriginMappingFormat()) { - case SRG_FILE: - saveSRGMappings(file); - break; - default: - saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat()); - break; - } + public void saveMappings(MappingFormat format, Path path) { + EntryRemapper mapper = deobfuscator.getMapper(); - } + MappingDelta delta = mapper.takeMappingDelta(); + boolean saveAll = !path.equals(loadedMappingPath); - public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { - this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); - this.isDirty = false; - } + ProgressDialog.runInThread(this.gui.getFrame(), progress -> { + if (saveAll) { + format.write(mapper.getObfToDeobf(), path, progress); + } else { + format.write(mapper.getObfToDeobf(), delta, path, progress); + } + }); - public void saveSRGMappings(File file) throws IOException { - this.deobfuscator.getMappings().saveSRGMappings(file); - this.isDirty = false; + loadedMappingFormat = format; + loadedMappingPath = path; } public void closeMappings() { @@ -116,11 +115,6 @@ public class GuiController { refreshCurrentClass(); } - public void rebuildMethodNames() { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress)); - this.isDirty = true; - } - public void exportSource(final File dirOut) { ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); } @@ -136,7 +130,8 @@ public class GuiController { return this.index.getReferenceToken(pos); } - public EntryReference getDeobfReference(Token token) { + @Nullable + public EntryReference, Entry> getDeobfReference(Token token) { if (this.index == null) { return null; } @@ -148,44 +143,52 @@ public class GuiController { return null; } return new ReadableToken( - this.index.getLineNumber(token.start), - this.index.getColumnNumber(token.start), - this.index.getColumnNumber(token.end) + this.index.getLineNumber(token.start), + this.index.getColumnNumber(token.start), + this.index.getColumnNumber(token.end) ); } - public boolean entryHasDeobfuscatedName(Entry deobfEntry) { - return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry)); + public boolean entryHasDeobfuscatedName(Entry deobfEntry) { + EntryResolver resolver = this.deobfuscator.getMapper().getDeobfResolver(); + Entry resolvedEntry = resolver.resolveFirstEntry(deobfEntry, ResolutionStrategy.RESOLVE_ROOT); + return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.getMapper().obfuscate(resolvedEntry)); } - public boolean entryIsInJar(Entry deobfEntry) { - return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); + public boolean entryIsInJar(Entry deobfEntry) { + if (deobfEntry == null) return false; + return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.getMapper().obfuscate(deobfEntry)); } - public boolean referenceIsRenameable(EntryReference deobfReference) { - return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference)); + public boolean referenceIsRenameable(EntryReference, Entry> deobfReference) { + if (deobfReference == null) return false; + return this.deobfuscator.isRenameable(this.deobfuscator.getMapper().obfuscate(deobfReference)); } public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); + ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, obfClassEntry); return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); } public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); + ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, obfClassEntry); } public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); + MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, obfMethodEntry); return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); } public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); + MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + List rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, obfMethodEntry); if (rootNodes.isEmpty()) { return null; } @@ -196,34 +199,32 @@ public class GuiController { } public ClassReferenceTreeNode getClassReferences(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); + ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator(); + ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, obfClassEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { - FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfFieldEntry); + FieldEntry obfFieldEntry = this.deobfuscator.getMapper().obfuscate(deobfFieldEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, obfFieldEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry, boolean recursive) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); + MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, obfMethodEntry); rootNode.load(this.deobfuscator.getJarIndex(), true, recursive); return rootNode; } - public void rename(EntryReference deobfReference, String newName) { - rename(deobfReference, newName, true, true); - } - - public void rename(EntryReference deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache); - this.isDirty = true; + public void rename(EntryReference, Entry> deobfReference, String newName, boolean refreshClassTree) { + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); + this.deobfuscator.rename(obfReference.getNameableEntry(), newName); if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) this.gui.moveClassTree(deobfReference, newName); @@ -231,39 +232,37 @@ public class GuiController { } - public void removeMapping(EntryReference deobfReference) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + public void removeMapping(EntryReference, Entry> deobfReference) { + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); this.deobfuscator.removeMapping(obfReference.getNameableEntry()); - this.isDirty = true; if (deobfReference.entry instanceof ClassEntry) this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); refreshCurrentClass(obfReference); } - public void markAsDeobfuscated(EntryReference deobfReference) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + public void markAsDeobfuscated(EntryReference, Entry> deobfReference) { + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); - this.isDirty = true; if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); refreshCurrentClass(obfReference); } - public void openDeclaration(Entry deobfEntry) { + public void openDeclaration(Entry deobfEntry) { if (deobfEntry == null) { throw new IllegalArgumentException("Entry cannot be null!"); } openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); } - public void openReference(EntryReference deobfReference) { + public void openReference(EntryReference, Entry> deobfReference) { if (deobfReference == null) { throw new IllegalArgumentException("Reference cannot be null!"); } // get the reference target class - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); + ClassEntry obfClassEntry = obfReference.getLocationClassEntry(); if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); } @@ -276,24 +275,30 @@ public class GuiController { } } - private void showReference(EntryReference obfReference) { - EntryReference deobfReference = this.deobfuscator.deobfuscateReference(obfReference); - Collection tokens = this.index.getReferenceTokens(deobfReference); + private void showReference(EntryReference, Entry> obfReference) { + EntryRemapper mapper = this.deobfuscator.getMapper(); + + Collection tokens = mapper.getObfResolver().resolveReference(obfReference, ResolutionStrategy.RESOLVE_ROOT) + .stream() + .map(mapper::deobfuscate) + .flatMap(reference -> index.getReferenceTokens(reference).stream()) + .collect(Collectors.toList()); + if (tokens.isEmpty()) { // DEBUG - System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); + System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentObfClass)); } else { this.gui.showTokens(tokens); } } - public void savePreviousReference(EntryReference deobfReference) { - this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); + public void savePreviousReference(EntryReference, Entry> deobfReference) { + this.referenceStack.push(this.deobfuscator.getMapper().obfuscate(deobfReference)); } public void openPreviousReference() { if (hasPreviousLocation()) { - openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); + openReference(this.deobfuscator.getMapper().deobfuscate(this.referenceStack.pop())); } } @@ -313,13 +318,13 @@ public class GuiController { refreshCurrentClass(null); } - private void refreshCurrentClass(EntryReference obfReference) { + private void refreshCurrentClass(EntryReference, Entry> obfReference) { if (this.currentObfClass != null) { deobfuscate(this.currentObfClass, obfReference); } } - private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) { + private void deobfuscate(final ClassEntry classEntry, final EntryReference, Entry> obfReference) { this.gui.setSource("(deobfuscating...)"); @@ -327,7 +332,7 @@ public class GuiController { new Thread(() -> { // decompile,deobfuscate the bytecode - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getFullName()); if (sourceTree == null) { // decompilation of this class is not supported gui.setSource("Unable to find class: " + classEntry); @@ -349,17 +354,18 @@ public class GuiController { boolean remapped = false; for (Token inToken : index.referenceTokens()) { - EntryReference reference = index.getDeobfReference(inToken); Token token = inToken.move(offset); + EntryReference, Entry> reference = index.getDeobfReference(inToken); if (referenceIsRenameable(reference)) { boolean added = false; if (!entryHasDeobfuscatedName(reference.getNameableEntry())) { - Entry obfEntry = deobfuscator.obfuscateEntry(reference.getNameableEntry()); + Entry obfEntry = deobfuscator.getMapper().obfuscate(reference.getNameableEntry()); if (obfEntry instanceof FieldEntry) { for (EnigmaPlugin plugin : deobfuscator.getPlugins()) { - String proposal = plugin.proposeFieldName(obfEntry.getClassName(), obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString()); + String owner = obfEntry.getContainingClass().getFullName(); + String proposal = plugin.proposeFieldName(owner, obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString()); if (proposal != null) { proposedTokens.add(token); offset += token.getRenameOffset(proposal); @@ -411,8 +417,7 @@ public class GuiController { public void modifierChange(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem()); - this.isDirty = true; + deobfuscator.changeModifier(gui.reference.entry, (AccessModifier) event.getItem()); refreshCurrentClass(); } } diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java deleted file mode 100644 index 9208455..0000000 --- a/src/main/java/cuchaz/enigma/gui/GuiTricks.java +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionListener; - -public class GuiTricks { - - public static JLabel unboldLabel(JLabel label) { - Font font = label.getFont(); - label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); - return label; - } - - public static void deactivateButton(JButton button) { - button.setEnabled(false); - button.setText(""); - for (ActionListener listener : button.getActionListeners()) { - button.removeActionListener(listener); - } - } - - public static void activateButton(JButton button, String text, ActionListener newListener) { - button.setText(text); - button.setEnabled(true); - for (ActionListener listener : button.getActionListeners()) { - button.removeActionListener(listener); - } - button.addActionListener(newListener); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java deleted file mode 100644 index 34ec26e..0000000 --- a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import cuchaz.enigma.mapping.entry.ClassEntry; - -public class ScoredClassEntry extends ClassEntry { - - private static final long serialVersionUID = -8798725308554217105L; - - private float score; - - public ScoredClassEntry(ClassEntry other, float score) { - super(other); - this.score = score; - } - - public float getScore() { - return score; - } - - @Override - public int hashCode() { - return Float.hashCode(score) + super.hashCode(); - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other); - } - - public boolean equals(ScoredClassEntry other) { - return other != null && Float.compare(score, other.score) == 0; - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java index 5f04833..84fe7c8 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java @@ -12,7 +12,7 @@ package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; -import cuchaz.enigma.Deobfuscator.ProgressListener; +import cuchaz.enigma.ProgressListener; import cuchaz.enigma.utils.Utils; import javax.swing.*; @@ -81,7 +81,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable { } @Override - public void onProgress(int numDone, String message) { + public void step(int numDone, String message) { this.labelText.setText(message); this.progress.setValue(numDone); diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 609aecb..f4f0277 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -5,6 +5,7 @@ import cuchaz.enigma.config.Themes; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.gui.dialog.AboutDialog; import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; import javax.swing.*; import java.awt.event.InputEvent; @@ -23,7 +24,6 @@ public class MenuBar extends JMenuBar { public final JMenuItem saveMappingEnigmaDirectoryMenu; public final JMenuItem saveMappingsSrgMenu; public final JMenuItem closeMappingsMenu; - public final JMenuItem rebuildMethodNamesMenu; public final JMenuItem exportSourceMenu; public final JMenuItem exportJarMenu; private final Gui gui; @@ -68,7 +68,9 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { try { - this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile(); + MappingFormat format = selectedFile.isDirectory() ? MappingFormat.ENIGMA_DIRECTORY : MappingFormat.ENIGMA_FILE; + this.gui.getController().openMappings(format, selectedFile.toPath()); } catch (IOException ex) { throw new Error(ex); } catch (MappingParseException ex) { @@ -85,7 +87,7 @@ public class MenuBar extends JMenuBar { File file = new File(this.gui.tinyMappingsFileChooser.getDirectory() + File.separator + this.gui.tinyMappingsFileChooser.getFile()); if (file.exists()) { try { - this.gui.getController().openTinyMappings(file); + this.gui.getController().openMappings(MappingFormat.TINY_FILE, file.toPath()); } catch (IOException ex) { throw new Error(ex); } catch (MappingParseException ex) { @@ -99,11 +101,7 @@ public class MenuBar extends JMenuBar { JMenuItem item = new JMenuItem("Save Mappings"); menu.add(item); item.addActionListener(event -> { - try { - this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - } catch (IOException ex) { - throw new Error(ex); - } + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); }); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); this.saveMappingsMenu = item; @@ -116,12 +114,8 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { // TODO: Use a specific file chooser for it if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } + this.gui.getController().saveMappings(MappingFormat.ENIGMA_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.saveMappingsMenu.setEnabled(true); } }); this.saveMappingEnigmaFileMenu = item; @@ -132,12 +126,8 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { // TODO: Use a specific file chooser for it if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } + this.gui.getController().saveMappings(MappingFormat.ENIGMA_DIRECTORY, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.saveMappingsMenu.setEnabled(true); } }); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); @@ -149,12 +139,8 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { // TODO: Use a specific file chooser for it if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } + this.gui.getController().saveMappings(MappingFormat.SRG_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.saveMappingsMenu.setEnabled(true); } }); this.saveMappingsSrgMenu = item; @@ -183,13 +169,6 @@ public class MenuBar extends JMenuBar { this.closeMappingsMenu = item; } menu.addSeparator(); - { - JMenuItem item = new JMenuItem("Rebuild Method Names"); - menu.add(item); - item.addActionListener(event -> this.gui.getController().rebuildMethodNames()); - this.rebuildMethodNamesMenu = item; - } - menu.addSeparator(); { JMenuItem item = new JMenuItem("Export Source..."); menu.add(item); diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java index a965a8f..bf6b178 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java @@ -11,7 +11,7 @@ package cuchaz.enigma.gui.node; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import javax.swing.tree.DefaultMutableTreeNode; diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java index 9eb8f8f..ccdc9f8 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java @@ -2,7 +2,7 @@ package cuchaz.enigma.gui.panels; import cuchaz.enigma.gui.ClassSelector; import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import javax.swing.*; import java.awt.*; @@ -17,8 +17,8 @@ public class PanelObf extends JPanel { this.gui = gui; Comparator obfClassComparator = (a, b) -> { - String aname = a.getName(); - String bname = b.getName(); + String aname = a.getFullName(); + String bname = b.getFullName(); if (aname.length() != bname.length()) { return aname.length() - bname.length(); } -- cgit v1.2.3