spoon.decompiler.ProcyonDecompiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spoon-decompiler Show documentation
Show all versions of spoon-decompiler Show documentation
Extension of spoon for bytecode through decompilation
The newest version!
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.decompiler;
import com.strobel.assembler.InputTypeLoader;
import com.strobel.assembler.metadata.CompositeTypeLoader;
import com.strobel.assembler.metadata.DeobfuscationUtilities;
import com.strobel.assembler.metadata.IMetadataResolver;
import com.strobel.assembler.metadata.ITypeLoader;
import com.strobel.assembler.metadata.JarTypeLoader;
import com.strobel.assembler.metadata.MetadataParser;
import com.strobel.assembler.metadata.MetadataSystem;
import com.strobel.assembler.metadata.TypeDefinition;
import com.strobel.assembler.metadata.TypeReference;
import com.strobel.core.StringUtilities;
import com.strobel.decompiler.DecompilationOptions;
import com.strobel.decompiler.DecompilerSettings;
import com.strobel.decompiler.PlainTextOutput;
import com.strobel.decompiler.languages.BytecodeLanguage;
import com.strobel.io.PathHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import spoon.support.Experimental;
@Experimental
public class ProcyonDecompiler implements Decompiler {
@Override
public void decompile(String inputPath, String outputPath, String[] classpath) {
try {
if (inputPath.endsWith(".jar")) {
decompileJar(inputPath, outputPath);
} else if (inputPath.endsWith(".class")) {
decompileClass(inputPath, outputPath);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void decompileClass(String path, String outputDir) throws Exception {
final File tempClass = new File(path);
DecompilerSettings settings = DecompilerSettings.javaDefaults();
MetadataSystem metadataSystem = new MetadataSystem(new InputTypeLoader());
TypeReference type = metadataSystem.lookupType(tempClass
.getCanonicalPath());
DecompilationOptions decompilationOptions = new DecompilationOptions();
decompilationOptions.setSettings(DecompilerSettings.javaDefaults());
decompilationOptions.setFullDecompilation(true);
TypeDefinition resolvedType = null;
if (type == null || ((resolvedType = type.resolve()) == null)) {
throw new Exception("Unable to resolve type.");
}
final Writer writer = createWriter(resolvedType, settings, outputDir);
final boolean writeToFile = writer instanceof FileOutputWriter;
final PlainTextOutput output;
output = new PlainTextOutput(writer);
output.setUnicodeOutputEnabled(settings.isUnicodeOutputEnabled());
if (settings.getLanguage() instanceof BytecodeLanguage) {
output.setIndentToken(" ");
}
if (writeToFile) {
System.out.printf("Decompiling %s...\n", resolvedType.getFullName());
}
settings.getLanguage().decompileType(resolvedType, output, decompilationOptions);
writer.flush();
if (writeToFile) {
writer.close();
}
}
private void decompileJar(String jarFilePath, String outputDir) throws IOException {
DecompilationOptions decompilationOptions = new DecompilationOptions();
decompilationOptions.setSettings(DecompilerSettings.javaDefaults());
decompilationOptions.setFullDecompilation(true);
final File jarFile = new File(jarFilePath);
if (!jarFile.exists()) {
throw new FileNotFoundException("File not found: " + jarFilePath);
}
final DecompilerSettings settings = decompilationOptions.getSettings();
settings.setTypeLoader(new InputTypeLoader());
settings.setExcludeNestedTypes(false);
final JarFile jar = new JarFile(jarFile);
final Enumeration entries = jar.entries();
final boolean oldShowSyntheticMembers = settings.getShowSyntheticMembers();
final ITypeLoader oldTypeLoader = settings.getTypeLoader();
settings.setShowSyntheticMembers(false);
settings.setTypeLoader(new CompositeTypeLoader(new JarTypeLoader(jar), oldTypeLoader));
try {
MetadataSystem metadataSystem = new NoRetryMetadataSystem(settings.getTypeLoader());
int classesDecompiled = 0;
while (entries.hasMoreElements()) {
final JarEntry entry = entries.nextElement();
final String name = entry.getName();
if (!name.endsWith(".class")) {
continue;
}
final String internalName = StringUtilities.removeRight(name, ".class");
try {
decompileType(metadataSystem, internalName, decompilationOptions, outputDir);
if (++classesDecompiled % 100 == 0) {
metadataSystem = new NoRetryMetadataSystem(settings.getTypeLoader());
}
} catch (final Throwable t) {
t.printStackTrace();
}
}
} finally {
settings.setShowSyntheticMembers(oldShowSyntheticMembers);
settings.setTypeLoader(oldTypeLoader);
}
}
private void decompileType(MetadataSystem metadataSystem, String typeName, DecompilationOptions options, String outputDir) throws IOException {
final TypeReference type;
final DecompilerSettings settings = options.getSettings();
if (typeName.length() == 1) {
//
// Hack to get around classes whose descriptors clash with primitive types.
//
final MetadataParser parser = new MetadataParser(IMetadataResolver.EMPTY);
final TypeReference reference = parser.parseTypeDescriptor(typeName);
type = metadataSystem.resolve(reference);
} else {
type = metadataSystem.lookupType(typeName);
}
final TypeDefinition resolvedType;
if (type == null || (resolvedType = type.resolve()) == null) {
System.err.printf("!!! ERROR: Failed to load class %s.\n", typeName);
return;
}
DeobfuscationUtilities.processType(resolvedType);
if (resolvedType.isNested() || resolvedType.isAnonymous() || resolvedType.isSynthetic()) {
return;
}
final Writer writer = createWriter(resolvedType, settings, outputDir);
final boolean writeToFile = writer instanceof FileOutputWriter;
final PlainTextOutput output;
output = new PlainTextOutput(writer);
output.setUnicodeOutputEnabled(settings.isUnicodeOutputEnabled());
if (settings.getLanguage() instanceof BytecodeLanguage) {
output.setIndentToken(" ");
}
if (writeToFile) {
System.out.printf("Decompiling %s...\n", typeName);
}
settings.getLanguage().decompileType(resolvedType, output, options);
writer.flush();
if (writeToFile) {
writer.close();
}
}
private Writer createWriter(TypeDefinition type, DecompilerSettings settings, String outputDirectory) throws IOException {
if (StringUtilities.isNullOrWhitespace(outputDirectory)) {
return new OutputStreamWriter(
System.out,
settings.isUnicodeOutputEnabled() ? Charset.forName("UTF-8")
: Charset.defaultCharset()
);
}
String outputPath;
String fileName = type.getName() + settings.getLanguage().getFileExtension();
String packageName = type.getPackageName();
if (StringUtilities.isNullOrWhitespace(packageName)) {
outputPath = PathHelper.combine(outputDirectory, fileName);
} else {
outputPath = PathHelper.combine(
outputDirectory,
packageName.replace('.', PathHelper.DirectorySeparator),
fileName
);
}
File outputFile = new File(outputPath);
File parentFile = outputFile.getParentFile();
if (parentFile != null && !parentFile.exists() && !parentFile.mkdirs()) {
throw new IllegalStateException(String.format("Could not create output directory for file \"%s\".", outputPath));
}
if (!outputFile.exists() && !outputFile.createNewFile()) {
throw new IllegalStateException(String.format("Could not create output file \"%s\".", outputPath));
}
return new FileOutputWriter(outputFile, settings);
}
final class FileOutputWriter extends OutputStreamWriter {
private final File file;
FileOutputWriter(final File file, final DecompilerSettings settings) throws IOException {
super(
new FileOutputStream(file),
settings.isUnicodeOutputEnabled() ? Charset.forName("UTF-8")
: Charset.defaultCharset()
);
this.file = file;
}
}
final class NoRetryMetadataSystem extends MetadataSystem {
private final Set _failedTypes = new HashSet<>();
NoRetryMetadataSystem(final ITypeLoader typeLoader) {
super(typeLoader);
}
@Override
protected TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) {
if (_failedTypes.contains(descriptor)) {
return null;
}
final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive);
if (result == null) {
_failedTypes.add(descriptor);
}
return result;
}
}
}