1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//! Decompilation module.
//!
//! Handles the extraction, decompression and  decompilation of `_.apks_`

use std::{fs, path::Path, process::Command};

use abxml::apk::Apk;
use colored::Colorize;
use failure::{bail, format_err, Error, ResultExt};

use crate::{get_package_name, print_warning, Config};

/// Decompresses the application using `_Apktool_`.
pub fn decompress<P: AsRef<Path>>(config: &mut Config, package: P) -> Result<(), Error> {
    let path = config
        .dist_folder()
        .join(package.as_ref().file_stem().unwrap());
    if !path.exists() || config.is_force() {
        if path.exists() {
            if config.is_verbose() {
                println!("The application decompression folder existed. But no more!");
            }

            if let Err(e) = fs::remove_dir_all(&path) {
                print_warning(format!(
                    "there was an error when removing the decompression folder: {}",
                    e
                ));
            }
        }
        config.set_force();

        if config.is_verbose() {
            println!();
            println!("Decompressing the application.");
        }

        let mut apk = Apk::from_path(package.as_ref()).context("error loading apk file")?;
        apk.export(&path, true).context(format_err!(
            "could not decompress the apk file. Tried to decompile at: {}",
            path.display()
        ))?;

        if config.is_verbose() {
            println!(
                "{}",
                format!(
                    "The application has been decompressed in {}.",
                    path.display()
                )
                .green()
            );
        } else if !config.is_quiet() {
            println!("Application decompressed.");
        }
    } else if config.is_verbose() {
        println!(
            "Seems that the application has already been decompressed. There is no need to do it \
             again."
        );
    } else {
        println!("Skipping decompression.");
    }

    Ok(())
}

/// Converts `_.dex_` files to `_.jar_` using `_Dex2jar_`.
pub fn dex_to_jar<P: AsRef<Path>>(config: &mut Config, package: P) -> Result<(), Error> {
    let package_name = get_package_name(package.as_ref());
    let classes = config.dist_folder().join(&package_name).join("classes.jar");
    if config.is_force() || !classes.exists() {
        config.set_force();

        // Command to convert .dex to .jar. using dex2jar.
        // "-o path" to specify an output file
        let output = Command::new(config.dex2jar_folder().join(
            if cfg!(target_family = "windows") {
                "d2j-dex2jar.bat"
            } else {
                "d2j-dex2jar.sh"
            },
        ))
        .arg(config.dist_folder().join(&package_name).join("classes.dex"))
        .arg("-f")
        .arg("-o")
        .arg(&classes)
        .output()
        .context(format_err!(
            "there was an error when executing the {} to {} conversion command",
            ".dex".italic(),
            ".jar".italic()
        ))?;

        let stderr = String::from_utf8_lossy(&output.stderr);
        // Here a small hack: seems that dex2jar outputs in stderr even if everything went well,
        // and the status is always success. So the only difference is if we detect the actual
        // exception that was produced. But in some cases it does not return an exception, so we
        // have to check if errors such as "use certain option" occur.
        let mut call_ok = output.status.success() || !stderr.contains("use");
        if stderr.find('\n') != Some(stderr.len() - 1) {
            if stderr.starts_with("Picked up _JAVA_OPTIONS:") {
                call_ok = stderr.lines().count() == 2;
            } else {
                call_ok = false;
            }
        }
        if !call_ok {
            bail!(
                "the {} to {} conversion command returned an error. More info: {}",
                ".dex".italic(),
                ".jar".italic(),
                stderr
            );
        }

        if config.is_verbose() {
            println!(
                "{}",
                format!(
                    "The application {} {} {}",
                    ".jar".italic(),
                    "file has been generated in".green(),
                    format!("{}", classes.display()).green()
                )
                .green()
            );
        } else if !config.is_quiet() {
            println!("Jar file generated.");
        }
    } else if config.is_verbose() {
        println!(
            "Seems that there is already a {} file for the application. There is no need to \
             create it again.",
            ".jar".italic()
        );
    } else {
        println!("Skipping {} file generation.", ".jar".italic());
    }

    Ok(())
}

/// Decompiles the application using `_jd\_cmd_`.
pub fn decompile<P: AsRef<Path>>(config: &mut Config, package: P) -> Result<(), Error> {
    let package_name = get_package_name(package.as_ref());
    let out_path = config.dist_folder().join(&package_name).join("classes");
    if config.is_force() || !out_path.exists() {
        config.set_force();

        // Command to decompile the application using `jd_cmd`.
        // "-od path" to specify an output directory
        let output = Command::new("java")
            .arg("-jar")
            .arg(config.jd_cmd_file())
            .arg(config.dist_folder().join(&package_name).join("classes.jar"))
            .arg("-od")
            .arg(&out_path)
            .output()
            .context("there was an unknown error decompiling the application")?;

        if !output.status.success() {
            bail!(
                "the decompilation command returned an error. More info:\n{}",
                String::from_utf8_lossy(&output.stdout)
            );
        }

        if config.is_verbose() {
            println!(
                "{}",
                "The application has been successfully decompiled!".green()
            );
        } else if !config.is_quiet() {
            println!("Application decompiled.");
        }
    } else if config.is_verbose() {
        println!(
            "Seems that there is already a source folder for the application. There is no need to \
             decompile it again."
        );
    } else {
        println!("Skipping decompilation.");
    }

    Ok(())
}