package the.bytecode.club.bytecodeviewer.decompilers; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipException; import java.util.zip.ZipOutputStream; import org.objectweb.asm.tree.ClassNode; 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.java.JavaFormattingOptions; import com.strobel.assembler.InputTypeLoader; import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ITypeLoader; import com.strobel.assembler.metadata.JarTypeLoader; import com.strobel.assembler.metadata.MetadataSystem; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.assembler.metadata.TypeReference; import the.bytecode.club.bytecodeviewer.BytecodeViewer; import the.bytecode.club.bytecodeviewer.util.EncodeUtils; import the.bytecode.club.bytecodeviewer.util.MiscUtils; /*************************************************************************** * 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 DecompilerSettings getDecompilerSettings() { DecompilerSettings settings = new DecompilerSettings(); settings.setAlwaysGenerateExceptionVariableForCatchBlocks(BytecodeViewer.viewer.chckbxmntmNewCheckItem_6 .isSelected()); settings.setExcludeNestedTypes(BytecodeViewer.viewer.chckbxmntmNewCheckItem_11 .isSelected()); settings.setShowDebugLineNumbers(BytecodeViewer.viewer.chckbxmntmShowDebugLine .isSelected()); settings.setIncludeLineNumbersInBytecode(BytecodeViewer.viewer.chckbxmntmNewCheckItem_3 .isSelected()); settings.setIncludeErrorDiagnostics(BytecodeViewer.viewer.chckbxmntmNewCheckItem_4 .isSelected()); settings.setShowSyntheticMembers(BytecodeViewer.viewer.chckbxmntmNewCheckItem_7 .isSelected()); settings.setSimplifyMemberReferences(BytecodeViewer.viewer.chckbxmntmSimplifyMemberReferences .isSelected()); settings.setMergeVariables(BytecodeViewer.viewer.mnMergeVariables .isSelected()); settings.setForceExplicitTypeArguments(BytecodeViewer.viewer.chckbxmntmNewCheckItem_8 .isSelected()); settings.setForceExplicitImports(BytecodeViewer.viewer.chckbxmntmNewCheckItem_9 .isSelected()); settings.setFlattenSwitchBlocks(BytecodeViewer.viewer.chckbxmntmNewCheckItem_10 .isSelected()); settings.setRetainPointlessSwitches(BytecodeViewer.viewer.chckbxmntmNewCheckItem_2 .isSelected()); settings.setRetainRedundantCasts(BytecodeViewer.viewer.chckbxmntmNewCheckItem_5 .isSelected()); settings.setUnicodeOutputEnabled(BytecodeViewer.viewer.chckbxmntmNewCheckItem_1 .isSelected()); settings.setJavaFormattingOptions(JavaFormattingOptions.createDefault()); return settings; } @Override public String decompileClassNode(ClassNode cn, byte[] b) { String exception = ""; try { String fileStart = BytecodeViewer.tempDirectory + BytecodeViewer.fs + "temp"; final File tempClass = new File(MiscUtils.getUniqueName(fileStart, ".class") + ".class"); try { final FileOutputStream fos = new FileOutputStream(tempClass); fos.write(b); fos.close(); } catch (final IOException e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } DecompilerSettings settings = getDecompilerSettings(); LuytenTypeLoader typeLoader = new LuytenTypeLoader(); MetadataSystem metadataSystem = new MetadataSystem(typeLoader); TypeReference type = metadataSystem.lookupType(tempClass .getCanonicalPath()); DecompilationOptions decompilationOptions = new DecompilationOptions(); decompilationOptions.setSettings(settings); 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); return EncodeUtils.unicodeToString(stringwriter.toString()); } catch (StackOverflowError | Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); e.printStackTrace(); exception = "Bytecode Viewer Version: " + BytecodeViewer.VERSION + BytecodeViewer.nl + BytecodeViewer.nl + sw.toString(); } return "Procyon error! Send the stacktrace to Konloch at http://the.bytecode.club or [email protected]" + BytecodeViewer.nl + BytecodeViewer.nl + "Suggested Fix: Click refresh class, if it fails again try another decompiler." + BytecodeViewer.nl + BytecodeViewer.nl + exception; } @Override public void decompileToZip(String sourceJar, String zipName) { try { doSaveJarDecompiled(new File(sourceJar), new File(zipName)); } catch (StackOverflowError | Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(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(); LuytenTypeLoader typeLoader = new LuytenTypeLoader(); MetadataSystem metadataSystem = new MetadataSystem(typeLoader); ITypeLoader jarLoader = new JarTypeLoader(jfile); typeLoader.getTypeLoaders().add(jarLoader); 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; } } } } } } /** * @author DeathMarine */ public final class LuytenTypeLoader implements ITypeLoader { private final List<ITypeLoader> _typeLoaders; public LuytenTypeLoader() { _typeLoaders = new ArrayList<ITypeLoader>(); _typeLoaders.add(new InputTypeLoader()); } public final List<ITypeLoader> getTypeLoaders() { return _typeLoaders; } @Override public boolean tryLoadType(final String internalName, final Buffer buffer) { for (final ITypeLoader typeLoader : _typeLoaders) { if (typeLoader.tryLoadType(internalName, buffer)) { return true; } buffer.reset(); } return false; } } }