use std::env;
use std::path::{Path, PathBuf};
use hook_inject_build::{download_devkit, resolve_devkit_platform, resolve_devkit_versions};
const DEFAULT_DEVKIT_VERSION: &str = "17.7.3";
const SUPPORTED_DEVKIT_VERSIONS: &[&str] = &[DEFAULT_DEVKIT_VERSION];
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=native/frida_shim.c");
println!("cargo:rerun-if-changed=native/frida_shim.h");
println!("cargo:rerun-if-env-changed=FRIDA_CORE_DEVKIT_DIR");
println!("cargo:rerun-if-env-changed=HOOK_INJECT_DEVKIT_VERSION");
println!("cargo:rerun-if-env-changed=HOOK_INJECT_DEVKIT_PLATFORM");
println!("cargo:rerun-if-env-changed=CARGO_TARGET_DIR");
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
if let Some(devkit_dir) = env::var_os("FRIDA_CORE_DEVKIT_DIR") {
build_with_devkit(&manifest_dir, &PathBuf::from(devkit_dir));
return;
}
if let Some(devkit_dir) = try_download_devkit(&manifest_dir) {
build_with_devkit(&manifest_dir, &devkit_dir);
return;
}
panic!(
"failed to download a frida-core devkit; set FRIDA_CORE_DEVKIT_DIR or run scripts/build_frida_core_devkit.sh"
);
}
fn try_download_devkit(manifest_dir: &Path) -> Option<PathBuf> {
let (versions, allow_fallback) =
resolve_devkit_versions(DEFAULT_DEVKIT_VERSION, SUPPORTED_DEVKIT_VERSIONS);
let platform = match resolve_devkit_platform() {
Ok(platform) => platform,
Err(err) => {
println!("cargo:warning=devkit platform detection failed: {err}");
return None;
}
};
let target_dir = env::var_os("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| manifest_dir.join("target"));
for (idx, version) in versions.iter().enumerate() {
let devkit_dir = target_dir
.join("frida-devkit")
.join(version)
.join(&platform);
let mut resolved = find_devkit_dir(&devkit_dir);
if resolved.is_none() {
match download_devkit(version, &devkit_dir, Some(&platform)) {
Ok(_) => {}
Err(err) => {
println!("cargo:warning=devkit download failed for {version}: {err}");
if allow_fallback && idx + 1 < versions.len() {
continue;
}
return None;
}
}
resolved = find_devkit_dir(&devkit_dir);
}
if resolved.is_none() {
println!(
"cargo:warning=devkit download succeeded but expected files are missing in {}",
devkit_dir.display()
);
if allow_fallback && idx + 1 < versions.len() {
continue;
}
return None;
}
println!(
"cargo:warning=using frida-core devkit {version} ({platform}) from {}",
devkit_dir.display()
);
return Some(devkit_dir);
}
None
}
fn build_with_devkit(manifest_dir: &Path, devkit_dir: &Path) {
let (lib_dir, lib_name, header_dir, is_static) =
find_devkit_dir(devkit_dir).expect("invalid FRIDA_CORE_DEVKIT_DIR");
emit_devkit_watch(&lib_dir, &header_dir);
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!(
"cargo:rustc-link-lib={}={}",
if is_static { "static" } else { "dylib" },
lib_name
);
link_system_libs(is_static);
cc::Build::new()
.file(manifest_dir.join("native/frida_shim.c"))
.include(header_dir)
.compile("hook_frida_shim");
}
fn emit_devkit_watch(lib_dir: &Path, header_dir: &Path) {
let header = header_dir.join("frida-core.h");
if header.exists() {
println!("cargo:rerun-if-changed={}", header.display());
}
let candidates = [
"libfrida-core.a",
"libfrida-core.so",
"libfrida-core.dylib",
"frida-core.lib",
"frida-core.dll",
];
for candidate in candidates {
let path = lib_dir.join(candidate);
if path.exists() {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
fn link_system_libs(is_static: bool) {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_else(|_| env::consts::OS.to_string());
let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap_or_default();
if target_os == "linux" {
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=resolv");
}
if target_vendor == "apple" {
println!("cargo:rustc-link-lib=bsm");
println!("cargo:rustc-link-lib=resolv");
println!("cargo:rustc-link-lib=pthread");
}
if target_os == "macos" && is_static {
for framework in [
"CoreFoundation",
"Foundation",
"AppKit",
"IOKit",
"Security",
] {
println!("cargo:rustc-link-lib=framework={}", framework);
}
println!("cargo:rustc-link-lib=objc");
}
if target_os == "windows" {
for lib in [
"dnsapi", "iphlpapi", "psapi", "winmm", "ws2_32", "advapi32", "crypt32", "gdi32",
"kernel32", "ole32", "secur32", "shell32", "shlwapi", "user32", "setupapi",
] {
println!("cargo:rustc-link-lib=dylib={lib}");
}
}
}
fn find_devkit_dir(dir: &Path) -> Option<(PathBuf, String, PathBuf, bool)> {
let header = dir.join("frida-core.h");
if !header.exists() {
return None;
}
let candidates = [
("libfrida-core.a", true),
("libfrida-core.so", false),
("libfrida-core.dylib", false),
("frida-core.lib", false),
];
for (name, is_static) in candidates {
if dir.join(name).exists() {
return Some((
dir.to_path_buf(),
"frida-core".to_string(),
dir.to_path_buf(),
is_static,
));
}
}
None
}