1/*
2 * Copyright 2016, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.baksmali;
33
34import com.beust.jcommander.JCommander;
35import com.beust.jcommander.Parameter;
36import com.google.common.base.Strings;
37import com.google.common.collect.Lists;
38import org.jf.dexlib2.DexFileFactory;
39import org.jf.dexlib2.Opcodes;
40import org.jf.dexlib2.dexbacked.DexBackedDexFile;
41import org.jf.util.jcommander.Command;
42import org.jf.util.jcommander.ExtendedParameter;
43
44import javax.annotation.Nonnull;
45import java.io.File;
46import java.io.IOException;
47import java.util.List;
48
49/**
50 * This class implements common functionality for commands that need to load a dex file based on
51 * command line input
52 */
53public abstract class DexInputCommand extends Command {
54
55    @Parameter(names = {"-a", "--api"},
56            description = "The numeric api level of the file being disassembled.")
57    @ExtendedParameter(argumentNames = "api")
58    public int apiLevel = 15;
59
60    @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
61            "files, you can specify the specific entry to use as if the apk/oat file was a directory. " +
62            "e.g. \"app.apk/classes2.dex\". For more information, see \"baksmali help input\".")
63    @ExtendedParameter(argumentNames = "file")
64    protected List<String> inputList = Lists.newArrayList();
65
66    protected File inputFile;
67    protected String inputEntry;
68    protected DexBackedDexFile dexFile;
69
70    public DexInputCommand(@Nonnull List<JCommander> commandAncestors) {
71        super(commandAncestors);
72    }
73
74    /**
75     * Parses a dex file input from the user and loads the given dex file.
76     *
77     * In some cases, the input file can contain multiple dex files. If this is the case, you can refer to a specific
78     * dex file with a slash, followed by the entry name, optionally in quotes.
79     *
80     * If the entry name is enclosed in quotes, then it will strip the first and last quote and look for an entry with
81     * exactly that name. Otherwise, it will perform a partial filename match against the entry to find any candidates.
82     * If there is a single matching candidate, it will be used. Otherwise, an error will be generated.
83     *
84     * For example, to refer to the "/system/framework/framework.jar:classes2.dex" entry within the
85     * "framework/arm/framework.oat" oat file, you could use any of:
86     *
87     * framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
88     * framework/arm/framework.oat/system/framework/framework.jar:classes2.dex
89     * framework/arm/framework.oat/framework/framework.jar:classes2.dex
90     * framework/arm/framework.oat/framework.jar:classes2.dex
91     * framework/arm/framework.oat/classes2.dex
92     *
93     * The last option is the easiest, but only works if the oat file doesn't contain another entry with the
94     * "classes2.dex" name. e.g. "/system/framework/blah.jar:classes2.dex"
95     *
96     * It's technically possible (although unlikely) for an oat file to contain 2 entries like:
97     * /system/framework/framework.jar:classes2.dex
98     * system/framework/framework.jar:classes2.dex
99     *
100     * In this case, the "framework/arm/framework.oat/system/framework/framework.jar:classes2.dex" syntax will generate
101     * an error because both entries match the partial entry name. Instead, you could use the following for the
102     * first and second entry respectively:
103     *
104     * framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
105     * framework/arm/framework.oat/"system/framework/framework.jar:classes2.dex"
106     *
107     * @param input The name of a dex, apk, odex or oat file/entry.
108     */
109    protected void loadDexFile(@Nonnull String input) {
110        File file = new File(input);
111
112        while (file != null && !file.exists()) {
113            file = file.getParentFile();
114        }
115
116        if (file == null || !file.exists() || file.isDirectory()) {
117            System.err.println("Can't find file: " + input);
118            System.exit(1);
119        }
120
121        inputFile = file;
122
123        String dexEntry = null;
124        if (file.getPath().length() < input.length()) {
125            dexEntry = input.substring(file.getPath().length() + 1);
126        }
127
128        if (!Strings.isNullOrEmpty(dexEntry)) {
129            boolean exactMatch = false;
130            if (dexEntry.length() > 2 && dexEntry.charAt(0) == '"' && dexEntry.charAt(dexEntry.length() - 1) == '"') {
131                dexEntry = dexEntry.substring(1, dexEntry.length() - 1);
132                exactMatch = true;
133            }
134
135            inputEntry = dexEntry;
136
137            try {
138                dexFile = DexFileFactory.loadDexEntry(file, dexEntry, exactMatch, Opcodes.forApi(apiLevel));
139            } catch (IOException ex) {
140                throw new RuntimeException(ex);
141            }
142        } else {
143            try {
144                dexFile = DexFileFactory.loadDexFile(file, Opcodes.forApi(apiLevel));
145            } catch (IOException ex) {
146                throw new RuntimeException(ex);
147            }
148        }
149    }
150}
151