package the.bytecode.club.bytecodeviewer.decompilers;
import org.apache.commons.io.FileUtils;
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler;
import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler;
import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger;
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider;
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
import org.objectweb.asm.tree.ClassNode;
import the.bytecode.club.bytecodeviewer.BytecodeViewer;
import the.bytecode.club.bytecodeviewer.DecompilerSettings;
import the.bytecode.club.bytecodeviewer.JarUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.Manifest;
/***************************************************************************
* Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite *
* Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* 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 *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
/**
* A FernFlower wrapper with all the options (except 2)
*
* @author Konloch
* @author WaterWolf
*/
public class FernFlowerDecompiler extends Decompiler {
public FernFlowerDecompiler() {
for (Settings setting : Settings.values()) {
settings.registerSetting(setting);
}
}
@Override
public String getName() {
return "FernFlower";
}
@Override
public String decompileClassNode(final ClassNode cn, byte[] b) {
try {
if (cn.version < 49) {
b = fixBytes(b);
}
final byte[] bytesToUse = b;
Map<String, Object> options = main(generateMainMethod());
final AtomicReference<String> result = new AtomicReference<String>();
result.set(null);
BaseDecompiler baseDecompiler = new BaseDecompiler(new IBytecodeProvider() {
@Override
public byte[] getBytecode(String s, String s1) throws IOException {
byte[] clone = new byte[bytesToUse.length];
System.arraycopy(bytesToUse, 0, clone, 0, bytesToUse.length);
return clone;
}
}, new IResultSaver() {
@Override
public void saveFolder(String s) {
}
@Override
public void copyFile(String s, String s1, String s2) {
}
@Override
public void saveClassFile(String s, String s1, String s2, String s3, int[] ints) {
result.set(s3);
}
@Override
public void createArchive(String s, String s1, Manifest manifest) {
}
@Override
public void saveDirEntry(String s, String s1, String s2) {
}
@Override
public void copyEntry(String s, String s1, String s2, String s3) {
}
@Override
public void saveClassEntry(String s, String s1, String s2, String s3, String s4) {
}
@Override
public void closeArchive(String s, String s1) {
}
}, options, new PrintStreamLogger(System.out));
baseDecompiler.addSpace(new File(cn.name + ".class"), true);
baseDecompiler.decompileContext();
while (true) {
if (result.get() != null) {
break;
}
}
return result.get();
} catch (Exception e) {
return parseException(e);
}
}
@Override
public void decompileToZip(String zipName) {
try {
Path outputDir = Files.createTempDirectory("fernflower_output");
Path tempJar = Files.createTempFile("fernflower_input", ".jar");
File output = new File(zipName);
JarUtils.saveAsJar(BytecodeViewer.getLoadedBytes(), tempJar.toAbsolutePath().toString());
ConsoleDecompiler decompiler = new ConsoleDecompiler(outputDir.toFile(), main(generateMainMethod()));
decompiler.addSpace(tempJar.toFile(), true);
decompiler.decompileContext();
Files.move(outputDir.toFile().listFiles()[0].toPath(), output.toPath());
Files.delete(tempJar);
FileUtils.deleteDirectory(outputDir.toFile());
} catch (Exception e) {
handleException(e);
}
}
public Map<String, Object> main(String[] args) {
HashMap mapOptions = new HashMap();
boolean isOption = true;
for (int destination = 0; destination < args.length - 1; ++destination) {
String logger = args[destination];
if (isOption && logger.length() > 5 && logger.charAt(0) == 45 && logger.charAt(4) == 61) {
String decompiler = logger.substring(5);
if ("true".equalsIgnoreCase(decompiler)) {
decompiler = "1";
} else if ("false".equalsIgnoreCase(decompiler)) {
decompiler = "0";
}
mapOptions.put(logger.substring(1, 4), decompiler);
} else {
isOption = false;
}
}
return mapOptions;
}
private String[] generateMainMethod() {
String[] result = new String[getSettings().size()];
int index = 0;
for (Settings setting : Settings.values()) {
result[index++] = String.format("-%s=%s", setting.getParam(), getSettings().isSelected(setting) ? "1" : "0");
}
return result;
}
public enum Settings implements DecompilerSettings.Setting {
HIDE_BRIDGE_METHODS("rbr", "Hide Bridge Methods", true),
HIDE_SYNTHETIC_CLASS_MEMBERS("rsy", "Hide Synthetic Class Members"),
DECOMPILE_INNER_CLASSES("din", "Decompile Inner Classes", true),
COLLAPSE_14_CLASS_REFERENCES("dc4", "Collapse 1.4 Class References", true),
DECOMPILE_ASSERTIONS("das", "Decompile Assertions", true),
HIDE_EMPTY_SUPER_INVOCATION("hes", "Hide Empty Super Invocation", true),
HIDE_EMPTY_DEFAULT_CONSTRUCTOR("hec", "Hide Empty Default Constructor", true),
DECOMPILE_GENERIC_SIGNATURES("dgs", "Decompile Generic Signatures"),
ASSUME_RETURN_NOT_THROWING_EXCEPTIONS("ner", "Assume return not throwing exceptions", true),
DECOMPILE_ENUMS("den", "Decompile enumerations", true),
REMOVE_GETCLASS("rgn", "Remove getClass()", true),
OUTPUT_NUMBERIC_LITERALS("lit", "Output numeric literals 'as-is'"),
ENCODE_UNICODE("asc", "Encode non-ASCII as unicode escapes"),
INT_1_AS_BOOLEAN_TRUE("bto", "Assume int 1 is boolean true", true),
ALLOW_NOT_SET_SYNTHETIC("nns", "Allow not set synthetic attribute", true),
NAMELESS_TYPES_AS_OBJECT("uto", "Consider nameless types as java.lang.Object", true),
RECOVER_VARIABLE_NAMES("udv", "Recover variable names", true),
REMOVE_EMPTY_EXCEPTIONS("rer", "Remove empty exceptions", true),
DEINLINE_FINALLY("fdi", "De-inline finally", true),
RENAME_AMBIGIOUS_MEMBERS("ren", "Rename ambigious members"),
REMOVE_INTELLIJ_NOTNULL("inn", "Remove IntelliJ @NotNull", true),
DECOMPILE_LAMBDA_TO_ANONYMOUS("lac", "Decompile lambdas to anonymous classes");
private String name;
private String param;
private boolean on;
Settings(String param, String name) {
this(param, name, false);
}
Settings(String param, String name, boolean on) {
this.name = name;
this.param = param;
this.on = on;
}
public String getText() {
return name;
}
public boolean isDefaultOn() {
return on;
}
public String getParam() {
return param;
}
}
}