use std::path::{Path, PathBuf};
use std::process::Command;
use colored::Colorize;
pub fn decompile_apk(apk_path: &Path, output_dir: &Path, verbose: bool) -> anyhow::Result<PathBuf> {
std::fs::create_dir_all(output_dir)?;
let apk_name = apk_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
println!(
" {} Decompiling {} with jadx...",
"▸".cyan(),
apk_name.bold()
);
let output = Command::new("jadx")
.arg(apk_path)
.arg("-d")
.arg(output_dir)
.arg("--no-debug-info") .arg("--deobf") .arg("--show-bad-code") .output()?;
let exit_code = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if verbose {
if !stdout.is_empty() {
println!(" {} jadx stdout:", "verbose".dimmed());
for line in stdout.lines() {
println!(" {}", line.dimmed());
}
}
if !stderr.is_empty() {
println!(" {} jadx stderr:", "verbose".dimmed());
for line in stderr.lines() {
println!(" {}", line.dimmed());
}
}
println!(" {} jadx exit code: {}", "verbose".dimmed(), exit_code);
}
let file_count = count_files_recursive(output_dir);
if file_count == 0 {
let mut err_msg = format!(
"jadx produced no output files for {}. Exit code: {}",
apk_name, exit_code
);
let error_lines: Vec<&str> = stderr
.lines()
.chain(stdout.lines())
.filter(|l| l.contains("ERROR") || l.contains("error") || l.contains("Exception"))
.collect();
if !error_lines.is_empty() {
err_msg.push_str("\n jadx errors:");
for line in error_lines.iter().take(5) {
err_msg.push_str(&format!("\n {}", line));
}
}
if !java_available() {
err_msg.push_str("\n Note: Java does not appear to be installed (jadx requires it).");
}
anyhow::bail!(err_msg);
}
let error_count = extract_error_count(&stderr, &stdout);
if exit_code != 0 && error_count > 0 {
println!(
" {} jadx completed with {} partial error(s) — {} files decompiled successfully",
"⚠".yellow(),
error_count,
file_count
);
} else {
println!(
" {} Decompiled {} files into {}",
"✓".green(),
file_count,
output_dir.display()
);
}
Ok(output_dir.to_path_buf())
}
fn extract_error_count(stderr: &str, stdout: &str) -> usize {
let combined = format!("{}\n{}", stderr, stdout);
for line in combined.lines() {
if line.contains("finished with errors, count:") {
if let Some(count_str) = line.rsplit("count:").next() {
if let Ok(n) = count_str.trim().parse::<usize>() {
return n;
}
}
}
}
combined.lines().filter(|l| l.contains("ERROR")).count()
}
fn count_files_recursive(dir: &Path) -> usize {
let mut count = 0;
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
count += count_files_recursive(&path);
} else {
count += 1;
}
}
}
count
}
fn java_available() -> bool {
Command::new("which")
.arg("java")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}