use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::emu::Emu;
const SEVENZIP_BINS: [&str; 3] = ["7z", "7zz", "7za"];
const INSTALL_IMAGES: [&str; 2] = ["sources/install.wim", "sources/install.esd"];
impl Emu {
pub fn set_maps_from_iso(&mut self, iso_path: &str) -> Result<(), Box<dyn Error>> {
let iso = Path::new(iso_path);
if !iso.is_file() {
return Err(format!("ISO not found: {}", iso_path).into());
}
let sevenzip = find_sevenzip().ok_or(
"the `7z` tool is required for --iso but was not found in PATH \
(install p7zip, e.g. `pacman -S p7zip` or `apt install p7zip-full`)",
)?;
let (arch_sub, win_dir) = if self.cfg.arch.is_x64() {
("x86_64", "System32")
} else if self.cfg.arch.is_x86() {
("x86", "SysWOW64")
} else if self.cfg.arch.is_aarch64() {
("aarch64", "System32")
} else {
return Err("--iso only supports x86, x86_64 and aarch64 Windows".into());
};
let default_folder = self.ensure_default_maps(arch_sub)?;
let dest = PathBuf::from(format!("maps/iso/{}/", arch_sub));
let iso_len = iso.metadata()?.len();
if iso_cache_is_fresh(&dest, iso_path, iso_len) {
log::trace!("--iso: reusing cached system32 at {}", dest.display());
self.set_maps_folder(dest.to_str().unwrap());
return Ok(());
}
log::trace!(
"--iso: building {} maps from {} (this can take a moment)",
arch_sub,
iso_path
);
clone_dir(&default_folder, &dest)?;
let targets = wanted_dlls(&default_folder)?;
if targets.is_empty() {
return Err(format!("no .dll files found in default maps folder {}", default_folder.display()).into());
}
let tmp = PathBuf::from(format!("maps/iso/.tmp_{}/", arch_sub));
let _ = fs::remove_dir_all(&tmp);
fs::create_dir_all(&tmp)?;
let image = extract_install_image(&sevenzip, iso_path, &tmp)?;
let chosen = pick_image_files(&sevenzip, &image, win_dir, &targets)?;
if chosen.is_empty() {
let _ = fs::remove_dir_all(&tmp);
return Err(format!(
"no {}/{} DLLs found inside the install image of {}",
win_dir, "*.dll", iso_path
)
.into());
}
let extracted_dir = tmp.join("sys");
fs::create_dir_all(&extracted_dir)?;
let copied = extract_and_overlay(&sevenzip, &image, &chosen, &extracted_dir, &dest)?;
let _ = fs::remove_dir_all(&tmp);
write_cache_marker(&dest, iso_path, iso_len)?;
log::trace!(
"--iso: overlaid {} genuine DLLs from {} into {}",
copied,
win_dir,
dest.display()
);
self.set_maps_folder(dest.to_str().unwrap());
Ok(())
}
fn ensure_default_maps(&mut self, arch_sub: &str) -> Result<PathBuf, Box<dyn Error>> {
let candidates = [
format!("maps/windows/{}/", arch_sub),
format!("../../maps/windows/{}/", arch_sub),
];
for c in &candidates {
if Path::new(c).join("kernel32.dll").is_file() {
return Ok(PathBuf::from(c));
}
}
let folder = format!("maps/windows/{}/", arch_sub);
self.set_maps_folder(&folder);
Ok(PathBuf::from(folder))
}
}
fn find_sevenzip() -> Option<String> {
for bin in SEVENZIP_BINS {
if Command::new(bin)
.arg("i")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok()
{
return Some(bin.to_string());
}
}
None
}
fn iso_cache_is_fresh(dest: &Path, iso_path: &str, iso_len: u64) -> bool {
let marker = dest.join(".mwemu-iso");
let Ok(content) = fs::read_to_string(&marker) else {
return false;
};
content.trim() == cache_marker_value(iso_path, iso_len)
&& dest.join("kernel32.dll").is_file()
}
fn cache_marker_value(iso_path: &str, iso_len: u64) -> String {
format!("{}\t{}", iso_path, iso_len)
}
fn write_cache_marker(dest: &Path, iso_path: &str, iso_len: u64) -> Result<(), Box<dyn Error>> {
fs::write(dest.join(".mwemu-iso"), cache_marker_value(iso_path, iso_len))?;
Ok(())
}
fn clone_dir(src: &Path, dst: &Path) -> Result<(), Box<dyn Error>> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
if entry.file_type()?.is_file() {
fs::copy(entry.path(), dst.join(entry.file_name()))?;
}
}
Ok(())
}
fn wanted_dlls(folder: &Path) -> Result<std::collections::HashSet<String>, Box<dyn Error>> {
let mut set = std::collections::HashSet::new();
for entry in fs::read_dir(folder)? {
let name = entry?.file_name().to_string_lossy().to_ascii_lowercase();
if name.ends_with(".dll") {
set.insert(name);
}
}
Ok(set)
}
fn extract_install_image(
sevenzip: &str,
iso_path: &str,
tmp: &Path,
) -> Result<PathBuf, Box<dyn Error>> {
for image in INSTALL_IMAGES {
let out = run_7z(
sevenzip,
&[
"e",
iso_path,
&format!("-o{}", tmp.display()),
image,
"-aoa",
"-y",
],
);
let basename = Path::new(image).file_name().unwrap();
let candidate = tmp.join(basename);
if candidate.is_file() {
return Ok(candidate);
}
let _ = out;
}
Err(format!(
"could not extract {} from {} — is it a Windows installation ISO?",
INSTALL_IMAGES.join(" or "),
iso_path
)
.into())
}
fn pick_image_files(
sevenzip: &str,
image: &Path,
win_dir: &str,
targets: &std::collections::HashSet<String>,
) -> Result<Vec<String>, Box<dyn Error>> {
let listing = run_7z(sevenzip, &["l", "-slt", image.to_str().unwrap()])?;
let needle = format!("/{}/", win_dir).to_ascii_lowercase();
let mut by_image: HashMap<String, Vec<String>> = HashMap::new();
for line in listing.lines() {
let line = line.trim();
let Some(path) = line.strip_prefix("Path = ") else {
continue;
};
let lower = path.replace('\\', "/").to_ascii_lowercase();
if !lower.contains(&needle) {
continue;
}
let Some(file) = lower.rsplit('/').next() else {
continue;
};
if !targets.contains(file) {
continue;
}
let image_idx = lower.split('/').next().unwrap_or("").to_string();
by_image.entry(image_idx).or_default().push(path.to_string());
}
Ok(by_image
.into_values()
.max_by_key(|files| files.len())
.unwrap_or_default())
}
fn extract_and_overlay(
sevenzip: &str,
image: &Path,
files: &[String],
extracted_dir: &Path,
dest: &Path,
) -> Result<usize, Box<dyn Error>> {
let mut args: Vec<String> = vec![
"e".into(),
image.to_string_lossy().into_owned(),
format!("-o{}", extracted_dir.display()),
"-aoa".into(),
"-y".into(),
];
args.extend(files.iter().cloned());
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
run_7z(sevenzip, &arg_refs)?;
let mut copied = 0usize;
for stored in files {
let basename = stored.rsplit(['/', '\\']).next().unwrap_or(stored);
let from = extracted_dir.join(basename);
if from.is_file() {
let to = dest.join(basename.to_ascii_lowercase());
if fs::copy(&from, &to).is_ok() {
copied += 1;
}
}
}
Ok(copied)
}
fn run_7z(sevenzip: &str, args: &[&str]) -> Result<String, Box<dyn Error>> {
let output = Command::new(sevenzip).args(args).output()?;
if !output.status.success() {
return Err(format!(
"`{} {}` failed: {}",
sevenzip,
args.join(" "),
String::from_utf8_lossy(&output.stderr).trim()
)
.into());
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}