/*******************************************************************************
* 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 com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.strobel.decompiler.languages.java.ast.CompilationUnit;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.jar.JarFile;
import cuchaz.enigma.Deobfuscator;
import cuchaz.enigma.analysis.*;
import cuchaz.enigma.mapping.*;
public class GuiController {
private Deobfuscator deobfuscator;
private Gui gui;
private SourceIndex index;
private ClassEntry currentObfClass;
private boolean isDirty;
private Deque> referenceStack;
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;
}
public void openJar(final JarFile jar) throws IOException {
this.gui.onStartOpenJar();
this.deobfuscator = new Deobfuscator(jar);
this.gui.onFinishOpenJar(this.deobfuscator.getJarName());
refreshClasses();
}
public void closeJar() {
this.deobfuscator = null;
this.gui.onCloseJar();
}
public void openOldMappings(File file) throws IOException, MappingParseException {
FileReader in = new FileReader(file);
this.deobfuscator.setMappings(new MappingsReaderOld().read(in));
in.close();
this.isDirty = false;
this.gui.setMappingsFile(file);
refreshClasses();
refreshCurrentClass();
}
public void openMappings(File file) throws IOException, MappingParseException {
this.deobfuscator.setMappings(new MappingsReader().read(file));
this.isDirty = false;
this.gui.setMappingsFile(file);
refreshClasses();
refreshCurrentClass();
}
public void saveMappings(File file) throws IOException {
new MappingsWriter().write(file, this.deobfuscator.getMappings());
this.isDirty = false;
}
public void closeMappings() {
this.deobfuscator.setMappings(null);
this.gui.setMappingsFile(null);
refreshClasses();
refreshCurrentClass();
}
public void exportSource(final File dirOut) {
ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress));
}
public void exportJar(final File fileOut) {
ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress));
}
public Token getToken(int pos) {
if (this.index == null) {
return null;
}
return this.index.getReferenceToken(pos);
}
public EntryReference getDeobfReference(Token token) {
if (this.index == null) {
return null;
}
return this.index.getDeobfReference(token);
}
public ReadableToken getReadableToken(Token token) {
if (this.index == null) {
return null;
}
return new ReadableToken(
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 entryIsInJar(Entry deobfEntry) {
return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry));
}
public boolean referenceIsRenameable(EntryReference deobfReference) {
return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference));
}
public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry);
ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), 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);
}
public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry);
MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), 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);
if (rootNodes.isEmpty()) {
return null;
}
if (rootNodes.size() > 1) {
System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
}
return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
}
public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry);
FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry);
rootNode.load(this.deobfuscator.getJarIndex(), true);
return rootNode;
}
public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry);
BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry);
rootNode.load(this.deobfuscator.getJarIndex(), true);
return rootNode;
}
public void rename(EntryReference deobfReference, String newName) {
EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference);
this.deobfuscator.rename(obfReference.getNameableEntry(), newName);
this.isDirty = true;
refreshClasses();
refreshCurrentClass(obfReference);
}
public void removeMapping(EntryReference deobfReference) {
EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference);
this.deobfuscator.removeMapping(obfReference.getNameableEntry());
this.isDirty = true;
refreshClasses();
refreshCurrentClass(obfReference);
}
public void markAsDeobfuscated(EntryReference deobfReference) {
EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference);
this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
this.isDirty = true;
refreshClasses();
refreshCurrentClass(obfReference);
}
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) {
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();
if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
}
if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) {
// deobfuscate the class, then navigate to the reference
this.currentObfClass = obfClassEntry;
deobfuscate(this.currentObfClass, obfReference);
} else {
showReference(obfReference);
}
}
private void showReference(EntryReference obfReference) {
EntryReference deobfReference = this.deobfuscator.deobfuscateReference(obfReference);
Collection tokens = this.index.getReferenceTokens(deobfReference);
if (tokens.isEmpty()) {
// DEBUG
System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass));
} else {
this.gui.showTokens(tokens);
}
}
public void savePreviousReference(EntryReference deobfReference) {
this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference));
}
public void openPreviousReference() {
if (hasPreviousLocation()) {
openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop()));
}
}
public boolean hasPreviousLocation() {
return !this.referenceStack.isEmpty();
}
private void refreshClasses() {
List obfClasses = Lists.newArrayList();
List deobfClasses = Lists.newArrayList();
this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
this.gui.setObfClasses(obfClasses);
this.gui.setDeobfClasses(deobfClasses);
}
private void refreshCurrentClass() {
refreshCurrentClass(null);
}
private void refreshCurrentClass(EntryReference obfReference) {
if (this.currentObfClass != null) {
deobfuscate(this.currentObfClass, obfReference);
}
}
private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) {
this.gui.setSource("(deobfuscating...)");
// run the deobfuscator in a separate thread so we don't block the GUI event queue
new Thread() {
@Override
public void run() {
// decompile,deobfuscate the bytecode
CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName());
if (sourceTree == null) {
// decompilation of this class is not supported
gui.setSource("Unable to find class: " + classEntry);
return;
}
String source = deobfuscator.getSource(sourceTree);
index = deobfuscator.getSourceIndex(sourceTree, source);
gui.setSource(index.getSource());
if (obfReference != null) {
showReference(obfReference);
}
// set the highlighted tokens
List obfuscatedTokens = Lists.newArrayList();
List deobfuscatedTokens = Lists.newArrayList();
List otherTokens = Lists.newArrayList();
for (Token token : index.referenceTokens()) {
EntryReference reference = index.getDeobfReference(token);
if (referenceIsRenameable(reference)) {
if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
deobfuscatedTokens.add(token);
} else {
obfuscatedTokens.add(token);
}
} else {
otherTokens.add(token);
}
}
gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
}
}.start();
}
}