136836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com/*
236836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * [The "BSD licence"]
300fc68adf2e39aeb9fed35293f2576bbe729ec4bJesusFreke@JesusFreke.com * Copyright (c) 2010 Ben Gruver (JesusFreke)
436836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * All rights reserved.
536836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com *
636836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * Redistribution and use in source and binary forms, with or without
736836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * modification, are permitted provided that the following conditions
836836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * are met:
936836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * 1. Redistributions of source code must retain the above copyright
1036836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com *    notice, this list of conditions and the following disclaimer.
1136836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * 2. Redistributions in binary form must reproduce the above copyright
1236836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com *    notice, this list of conditions and the following disclaimer in the
1336836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com *    documentation and/or other materials provided with the distribution.
1436836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * 3. The name of the author may not be used to endorse or promote products
1536836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com *    derived from this software without specific prior written permission.
1636836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com *
1736836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1836836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1936836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2036836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2136836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2236836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2336836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2436836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2536836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2636836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2736836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com */
2836836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com
2936836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.compackage org.jf.baksmali;
3036836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com
317e25c35df7786c98bc6fa96958e93146ca73367aBen Gruverimport com.google.common.collect.Lists;
3213705697c23cb2d72bea75d661390be26fea50a4Ben Gruverimport com.google.common.collect.Ordering;
336ef13753e78bb7abc7e7683d5e533c3395d4a9b6JesusFreke@JesusFreke.comimport org.jf.baksmali.Adaptors.ClassDefinition;
348b1508ee58f4918835d8c01483725b508d21be29Ben Gruverimport org.jf.dexlib2.iface.ClassDef;
358b1508ee58f4918835d8c01483725b508d21be29Ben Gruverimport org.jf.dexlib2.iface.DexFile;
36bbf4dbba6127ef96e316060b2b4ec292627a4078JesusFreke@JesusFreke.comimport org.jf.util.ClassFileNameHandler;
374b72225e9d81201838f387171a68a832486903f9JesusFreke@JesusFreke.comimport org.jf.util.IndentingWriter;
3836836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com
3922e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruverimport javax.annotation.Nullable;
40e9ee92dc4c0848146e00d5607eb4baa5750361c8JesusFreke@JesusFreke.comimport java.io.*;
4122e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruverimport java.util.HashSet;
4213705697c23cb2d72bea75d661390be26fea50a4Ben Gruverimport java.util.List;
4322e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruverimport java.util.Set;
447e25c35df7786c98bc6fa96958e93146ca73367aBen Gruverimport java.util.concurrent.*;
4536836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com
465a5eafb818cc18baeef8bdae1940401da3735f25Ben Gruverpublic class Baksmali {
475a5eafb818cc18baeef8bdae1940401da3735f25Ben Gruver    public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) {
4822e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver        return disassembleDexFile(dexFile, outputDir, jobs, options, null);
4922e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver    }
5022e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver
5122e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver    public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options,
5222e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver                                             @Nullable List<String> classes) {
53e9ee92dc4c0848146e00d5607eb4baa5750361c8JesusFreke@JesusFreke.com
54a6e5671a627284347484db96f40a29a45e4e4ed1JesusFreke@JesusFreke.com        //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
55a6e5671a627284347484db96f40a29a45e4e4ed1JesusFreke@JesusFreke.com        //name collisions, then we'll use the same name for each class, if the dex file goes through multiple
56a6e5671a627284347484db96f40a29a45e4e4ed1JesusFreke@JesusFreke.com        //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames
57a6e5671a627284347484db96f40a29a45e4e4ed1JesusFreke@JesusFreke.com        //may still change of course
5813705697c23cb2d72bea75d661390be26fea50a4Ben Gruver        List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
59987cdc3e0e1ee99c104837192ea1f63e4fa5565aBen Gruver
605a5eafb818cc18baeef8bdae1940401da3735f25Ben Gruver        final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali");
61a6e5671a627284347484db96f40a29a45e4e4ed1JesusFreke@JesusFreke.com
625a5eafb818cc18baeef8bdae1940401da3735f25Ben Gruver        ExecutorService executor = Executors.newFixedThreadPool(jobs);
63dea5f8d544040e506b550116bd7874d27436b99bBen Gruver        List<Future<Boolean>> tasks = Lists.newArrayList();
647e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver
6522e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver        Set<String> classSet = null;
6622e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver        if (classes != null) {
6722e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver            classSet = new HashSet<String>(classes);
6822e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver        }
6922e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver
707e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver        for (final ClassDef classDef: classDefs) {
7122e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver            if (classSet != null && !classSet.contains(classDef.getType())) {
7222e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver                continue;
7322e85fc3ff3d85e8a30fc42438ccb942fc5d80dfBen Gruver            }
74dea5f8d544040e506b550116bd7874d27436b99bBen Gruver            tasks.add(executor.submit(new Callable<Boolean>() {
75dea5f8d544040e506b550116bd7874d27436b99bBen Gruver                @Override public Boolean call() throws Exception {
76dea5f8d544040e506b550116bd7874d27436b99bBen Gruver                    return disassembleClass(classDef, fileNameHandler, options);
777e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver                }
787e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver            }));
797e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver        }
807e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver
81dea5f8d544040e506b550116bd7874d27436b99bBen Gruver        boolean errorOccurred = false;
82eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver        try {
83eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver            for (Future<Boolean> task: tasks) {
84eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                while(true) {
85eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                    try {
86eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                        if (!task.get()) {
87eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                            errorOccurred = true;
88eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                        }
89eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                    } catch (InterruptedException ex) {
90eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                        continue;
91eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                    } catch (ExecutionException ex) {
92eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                        throw new RuntimeException(ex);
93dea5f8d544040e506b550116bd7874d27436b99bBen Gruver                    }
94eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver                    break;
957e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver                }
967e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver            }
97eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver        } finally {
98eae0b0edbf3f0feedc289655144c54d27cb2ddccBen Gruver            executor.shutdown();
994b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        }
100dea5f8d544040e506b550116bd7874d27436b99bBen Gruver        return !errorOccurred;
1014b171afedb983fb811990beeec6a15e30a90b455Ben Gruver    }
10281014659d928284a14fafc23bc239e39de836d83JesusFreke@JesusFreke.com
103dea5f8d544040e506b550116bd7874d27436b99bBen Gruver    private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
1045a5eafb818cc18baeef8bdae1940401da3735f25Ben Gruver                                            BaksmaliOptions options) {
1054b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        /**
1064b171afedb983fb811990beeec6a15e30a90b455Ben Gruver         * The path for the disassembly file is based on the package name
1074b171afedb983fb811990beeec6a15e30a90b455Ben Gruver         * The class descriptor will look something like:
1084b171afedb983fb811990beeec6a15e30a90b455Ben Gruver         * Ljava/lang/Object;
1094b171afedb983fb811990beeec6a15e30a90b455Ben Gruver         * Where the there is leading 'L' and a trailing ';', and the parts of the
1104b171afedb983fb811990beeec6a15e30a90b455Ben Gruver         * package name are separated by '/'
1114b171afedb983fb811990beeec6a15e30a90b455Ben Gruver         */
1124b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        String classDescriptor = classDef.getType();
1134b171afedb983fb811990beeec6a15e30a90b455Ben Gruver
1144b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        //validate that the descriptor is formatted like we expect
1154b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        if (classDescriptor.charAt(0) != 'L' ||
1164b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                classDescriptor.charAt(classDescriptor.length()-1) != ';') {
1174b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class");
118dea5f8d544040e506b550116bd7874d27436b99bBen Gruver            return false;
1194b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        }
12081014659d928284a14fafc23bc239e39de836d83JesusFreke@JesusFreke.com
1214b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor);
12236836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com
1234b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        //create and initialize the top level string template
1244b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        ClassDefinition classDefinition = new ClassDefinition(options, classDef);
1256eae34831fee1f116f3a453bdc5e143d68e05e03JesusFreke@JesusFreke.com
1264b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        //write the disassembly
1274b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        Writer writer = null;
1284b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        try
1294b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        {
1304b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            File smaliParent = smaliFile.getParentFile();
1314b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            if (!smaliParent.exists()) {
1324b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                if (!smaliParent.mkdirs()) {
1337e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver                    // check again, it's likely it was created in a different thread
1347e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver                    if (!smaliParent.exists()) {
1357e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver                        System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class");
136dea5f8d544040e506b550116bd7874d27436b99bBen Gruver                        return false;
1377e25c35df7786c98bc6fa96958e93146ca73367aBen Gruver                    }
1384b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                }
13981014659d928284a14fafc23bc239e39de836d83JesusFreke@JesusFreke.com            }
1404b171afedb983fb811990beeec6a15e30a90b455Ben Gruver
1414b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            if (!smaliFile.exists()){
1424b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                if (!smaliFile.createNewFile()) {
1434b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                    System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class");
144dea5f8d544040e506b550116bd7874d27436b99bBen Gruver                    return false;
14581014659d928284a14fafc23bc239e39de836d83JesusFreke@JesusFreke.com                }
146e9ee92dc4c0848146e00d5607eb4baa5750361c8JesusFreke@JesusFreke.com            }
1472371e35aaeaf2ed4d7c571fb3286090eb01b717dJesusFreke@JesusFreke.com
1484b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter(
1494b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                    new FileOutputStream(smaliFile), "UTF8"));
1504b171afedb983fb811990beeec6a15e30a90b455Ben Gruver
1514b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            writer = new IndentingWriter(bufWriter);
1524b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            classDefinition.writeTo((IndentingWriter)writer);
1534b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        } catch (Exception ex) {
15468564258a316e41cb014f98d2777b5944bf37de3Ben Gruver            System.err.println("\n\nError occurred while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class");
1554b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            ex.printStackTrace();
1564b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            // noinspection ResultOfMethodCallIgnored
1574b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            smaliFile.delete();
158dea5f8d544040e506b550116bd7874d27436b99bBen Gruver            return false;
1594b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        }
1604b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        finally
1614b171afedb983fb811990beeec6a15e30a90b455Ben Gruver        {
1624b171afedb983fb811990beeec6a15e30a90b455Ben Gruver            if (writer != null) {
1634b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                try {
1644b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                    writer.close();
1654b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                } catch (Throwable ex) {
16668564258a316e41cb014f98d2777b5944bf37de3Ben Gruver                    System.err.println("\n\nError occurred while closing file " + smaliFile.toString());
1674b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                    ex.printStackTrace();
1684b171afedb983fb811990beeec6a15e30a90b455Ben Gruver                }
1692371e35aaeaf2ed4d7c571fb3286090eb01b717dJesusFreke@JesusFreke.com            }
170e9ee92dc4c0848146e00d5607eb4baa5750361c8JesusFreke@JesusFreke.com        }
171dea5f8d544040e506b550116bd7874d27436b99bBen Gruver        return true;
17278bde01ad4bf31ad44ad7bd0279b07fd2696b53cJesusFreke@JesusFreke.com    }
17336836121d7ecf72050d3ef065b7ab5fa86548319JesusFreke@JesusFreke.com}
174