use std::path::{Path, PathBuf};
use std::process::Command;
use crate::error::{Error, Result};
use crate::util::io::check_free_space;
pub async fn install_ida(
installer_path: &Path,
install_dir: &Path,
accept_eula: bool,
) -> Result<PathBuf> {
if !installer_path.exists() {
return Err(Error::FileNotFound(installer_path.to_path_buf()));
}
std::fs::create_dir_all(install_dir)?;
let installer_size = std::fs::metadata(installer_path)?.len();
check_free_space(install_dir, installer_size * 2)?;
let ext = installer_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
match ext {
"zip" if cfg!(target_os = "macos") => install_ida_mac(installer_path, install_dir).await,
"run" if cfg!(target_os = "linux") => {
install_ida_unix(installer_path, install_dir, accept_eula).await
}
"exe" if cfg!(target_os = "windows") => {
install_ida_windows(installer_path, install_dir, accept_eula).await
}
_ => Err(Error::IdaInstallFailed(format!(
"Unsupported installer format: {ext}"
))),
}
}
async fn install_ida_mac(zip_path: &Path, install_dir: &Path) -> Result<PathBuf> {
let status = Command::new("ditto")
.args(["-xk", &zip_path.to_string_lossy(), &install_dir.to_string_lossy()])
.status()?;
if !status.success() {
return Err(Error::IdaInstallFailed("ditto extraction failed".into()));
}
for entry in std::fs::read_dir(install_dir)?.flatten() {
let name = entry.file_name();
if name.to_string_lossy().ends_with(".app") {
return Ok(entry.path());
}
}
Err(Error::IdaInstallFailed(
"No .app bundle found after extraction".into(),
))
}
async fn install_ida_unix(
run_path: &Path,
install_dir: &Path,
accept_eula: bool,
) -> Result<PathBuf> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(run_path)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(run_path, perms)?;
}
let mut cmd = Command::new(run_path.as_os_str());
cmd.arg("--unattendedmodeui")
.arg("none")
.arg("--mode")
.arg("unattended")
.arg("--prefix")
.arg(install_dir.as_os_str());
if accept_eula {
cmd.arg("--installpassword").arg("");
}
let status = cmd.status()?;
if !status.success() {
return Err(Error::IdaInstallFailed(format!(
"Installer exited with code {}",
status.code().unwrap_or(-1)
)));
}
Ok(install_dir.to_path_buf())
}
async fn install_ida_windows(
exe_path: &Path,
install_dir: &Path,
accept_eula: bool,
) -> Result<PathBuf> {
let mut cmd = Command::new(exe_path.as_os_str());
cmd.arg("--unattendedmodeui")
.arg("none")
.arg("--mode")
.arg("unattended")
.arg("--prefix")
.arg(install_dir.as_os_str());
if accept_eula {
cmd.arg("--installpassword").arg("");
}
let status = cmd.status()?;
if !status.success() {
return Err(Error::IdaInstallFailed(format!(
"Installer exited with code {}",
status.code().unwrap_or(-1)
)));
}
Ok(install_dir.to_path_buf())
}
pub fn detect_ida_version(install_dir: &Path) -> Option<String> {
let sdk_file = install_dir.join("python").join("ida_pro.py");
if sdk_file.exists()
&& let Ok(content) = std::fs::read_to_string(&sdk_file) {
for line in content.lines().take(10) {
if let Some(rest) = line.strip_prefix("IDA SDK v") {
let version = rest.trim().trim_matches('"');
if !version.is_empty() {
return Some(version.to_owned());
}
}
}
}
install_dir
.file_name()
.and_then(|n| n.to_str())
.and_then(|name| {
let re = regex::Regex::new(r"(\d+\.\d+(?:\.\d+)?)").ok()?;
re.find(name).map(|m| m.as_str().to_owned())
})
}