package the.bytecode.club.bytecodeviewer.decompilers; import com.beust.jcommander.JCommander; import com.strobel.assembler.InputTypeLoader; import com.strobel.assembler.metadata.*; import com.strobel.core.StringUtilities; import com.strobel.decompiler.CommandLineOptions; import com.strobel.decompiler.DecompilationOptions; import com.strobel.decompiler.DecompilerSettings; import com.strobel.decompiler.PlainTextOutput; import com.strobel.decompiler.languages.Languages; import org.objectweb.asm.tree.ClassNode; import the.bytecode.club.bytecodeviewer.BytecodeViewer; import the.bytecode.club.bytecodeviewer.JarUtils; import java.io.*; import java.util.Enumeration; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipException; import java.util.zip.ZipOutputStream; /*************************************************************************** * 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/>. * ***************************************************************************/ /** * Procyon Java Decompiler Wrapper * * @author Konloch * @author DeathMarine */ public class ProcyonDecompiler extends Decompiler { public ProcyonDecompiler() { for (Settings setting : Settings.values()) { settings.registerSetting(setting); } } @Override public String getName() { return "Procyon"; } public DecompilerSettings getDecompilerSettings() { CommandLineOptions options = new CommandLineOptions(); JCommander jCommander = new JCommander(options); String[] args = new String[Settings.values().length * 2]; int index = 0; for (the.bytecode.club.bytecodeviewer.DecompilerSettings.Setting setting : Settings.values()) { args[index++] = "--" + setting.getParam(); args[index++] = String.valueOf(getSettings().isSelected(setting)); } jCommander.parse(args); DecompilerSettings settings = new DecompilerSettings(); settings.setFlattenSwitchBlocks(options.getFlattenSwitchBlocks()); settings.setForceExplicitImports(!options.getCollapseImports()); settings.setForceExplicitTypeArguments(options.getForceExplicitTypeArguments()); settings.setRetainRedundantCasts(options.getRetainRedundantCasts()); settings.setShowSyntheticMembers(options.getShowSyntheticMembers()); settings.setExcludeNestedTypes(options.getExcludeNestedTypes()); settings.setOutputDirectory(options.getOutputDirectory()); settings.setIncludeLineNumbersInBytecode(options.getIncludeLineNumbers()); settings.setRetainPointlessSwitches(options.getRetainPointlessSwitches()); settings.setUnicodeOutputEnabled(options.isUnicodeOutputEnabled()); settings.setMergeVariables(options.getMergeVariables()); settings.setShowDebugLineNumbers(options.getShowDebugLineNumbers()); settings.setSimplifyMemberReferences(options.getSimplifyMemberReferences()); settings.setDisableForEachTransforms(options.getDisableForEachTransforms()); settings.setTypeLoader(new InputTypeLoader()); if (options.isRawBytecode()) { settings.setLanguage(Languages.bytecode()); } else if (options.isBytecodeAst()) { settings.setLanguage(options.isUnoptimized() ? Languages.bytecodeAstUnoptimized() : Languages.bytecodeAst()); } return settings; } @Override public String decompileClassNode(final ClassNode cn, byte[] b) { try { if (cn.version < 49) { b = fixBytes(b); } final byte[] bytesToUse = b; final Map<String, byte[]> loadedClasses = BytecodeViewer.getLoadedBytes(); DecompilerSettings settings = getDecompilerSettings(); MetadataSystem metadataSystem = new MetadataSystem(new ITypeLoader() { private InputTypeLoader backLoader = new InputTypeLoader(); @Override public boolean tryLoadType(String s, Buffer buffer) { if (s.equals(cn.name)) { buffer.putByteArray(bytesToUse, 0, bytesToUse.length); buffer.position(0); return true; } else { byte[] toUse = loadedClasses.get(s + ".class"); if (toUse != null) { buffer.putByteArray(toUse, 0, toUse.length); buffer.position(0); return true; } else { return backLoader.tryLoadType(s, buffer); } } } }); TypeReference type = metadataSystem.lookupType(cn.name); 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."); } StringWriter stringwriter = new StringWriter(); settings.getLanguage().decompileType(resolvedType, new PlainTextOutput(stringwriter), decompilationOptions); String decompiledSource = stringwriter.toString(); return decompiledSource; } catch (Throwable e) { return parseException(e); } } @Override public void decompileToZip(String zipName) { File tempZip = new File(BytecodeViewer.tempDir, "temp.jar"); if (tempZip.exists()) tempZip.delete(); JarUtils.saveAsJar(BytecodeViewer.getLoadedBytes(), tempZip.getAbsolutePath()); try { doSaveJarDecompiled(tempZip, new File(zipName)); } catch (Exception e) { handleException(e); } } /** * @author DeathMarine */ private void doSaveJarDecompiled(File inFile, File outFile) throws Exception { try (JarFile jfile = new JarFile(inFile); FileOutputStream dest = new FileOutputStream(outFile); BufferedOutputStream buffDest = new BufferedOutputStream(dest); ZipOutputStream out = new ZipOutputStream(buffDest);) { byte data[] = new byte[1024]; DecompilerSettings settings = getDecompilerSettings(); MetadataSystem metadataSystem = new MetadataSystem(new JarTypeLoader(jfile)); DecompilationOptions decompilationOptions = new DecompilationOptions(); decompilationOptions.setSettings(settings); decompilationOptions.setFullDecompilation(true); Enumeration<JarEntry> ent = jfile.entries(); Set<JarEntry> history = new HashSet<JarEntry>(); while (ent.hasMoreElements()) { JarEntry entry = ent.nextElement(); if (entry.getName().endsWith(".class")) { JarEntry etn = new JarEntry(entry.getName().replace(".class", ".java")); if (history.add(etn)) { out.putNextEntry(etn); try { String internalName = StringUtilities.removeRight(entry.getName(), ".class"); TypeReference type = metadataSystem.lookupType(internalName); TypeDefinition resolvedType = null; if ((type == null) || ((resolvedType = type.resolve()) == null)) { throw new Exception("Unable to resolve type."); } Writer writer = new OutputStreamWriter(out); settings.getLanguage().decompileType(resolvedType, new PlainTextOutput(writer), decompilationOptions); writer.flush(); } finally { out.closeEntry(); } } } else { try { JarEntry etn = new JarEntry(entry.getName()); if (history.add(etn)) continue; history.add(etn); out.putNextEntry(etn); try { InputStream in = jfile.getInputStream(entry); if (in != null) { try { int count; while ((count = in.read(data, 0, 1024)) != -1) { out.write(data, 0, count); } } finally { in.close(); } } } finally { out.closeEntry(); } } catch (ZipException ze) { // some jar-s contain duplicate pom.xml entries: ignore // it if (!ze.getMessage().contains("duplicate")) { throw ze; } } } } } } public enum Settings implements the.bytecode.club.bytecodeviewer.DecompilerSettings.Setting { SHOW_DEBUG_LINE_NUMBERS("debug-line-numbers", "Show Debug Line Numbers"), SIMPLIFY_MEMBER_REFERENCES("simplify-member-references", "Simplify Member References"), MERGE_VARIABLES("merge-variables", "Merge Variables"), UNICODE_OUTPUT("unicode", "Allow Unicode Output"), RETAIN_POINTLESS_SWITCHES("retain-pointless-switches", "Retain pointless switches"), INCLUDE_LINE_NUMBERS_IN_BYTECODE("with-line-numbers", "Include line numbers in bytecode"), RETAIN_REDUNDANT_CASTS("retain-explicit-casts", "Retain redundant casts"), SHOW_SYNTHETIC_MEMBERS("show-synthetic", "Show synthetic members"), FORCE_EXPLICIT_TYPE_ARGS("explicit-type-arguments", "Force explicit type arguments"), FORCE_EXPLICIT_IMPORTS("explicit-imports", "Force explicit imports"), FLATTEN_SWITCH_BLOCKS("flatten-switch-blocks", "Flatten switch blocks"), EXCLUDE_NESTED_TYPES("exclude-nested", "Exclude nested types"); 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; } } }