diff --git a/src/main.rs b/src/main.rs index 4b52c68..efbabea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,8 @@ struct InputFile { /// Info about an extracted VHD, used for delta merge. struct ExtractedVhd { vhd_path: PathBuf, + /// Original input .app file path (for resolving sibling files) + input_path: PathBuf, sequence_number: u8, game_id: String, } @@ -367,6 +369,7 @@ fn main() -> Result<()> { std::str::from_utf8(&bootid.game_id)?.trim_end().to_string(); extracted_vhds.push(ExtractedVhd { vhd_path, + input_path: path.clone(), sequence_number: bootid.sequence_number, game_id, }); @@ -406,45 +409,84 @@ fn main() -> Result<()> { continue; } - // If no base was extracted in this run, search the same directory for an existing one - let found_base_path; + // If no base was extracted in this run, search the same directory + let found_base; let base = match base { Some(b) => b, None => { - // Look for a VHD matching "{game_id}_*_0.vhd" in the same directory as the delta let delta_dir = deltas[0] - .vhd_path + .input_path .parent() .unwrap_or_else(|| Path::new(".")); let pattern_prefix = format!("{game_id}_"); - let pattern_suffix = "_0.vhd"; - found_base_path = std::fs::read_dir(delta_dir)? + // First, look for an existing base VHD ({game_id}_*_0.vhd) + let existing_vhd = std::fs::read_dir(delta_dir)? .filter_map(|e| e.ok()) .map(|e| e.path()) .find(|p| { let name = p.file_name().unwrap_or_default().to_string_lossy(); - name.starts_with(&pattern_prefix) && name.ends_with(pattern_suffix) + name.starts_with(&pattern_prefix) && name.ends_with("_0.vhd") }); - match &found_base_path { - Some(path) => { - println!("Found existing base VHD: {}", path.display()); - // Create a temporary ExtractedVhd to reference - &ExtractedVhd { - vhd_path: path.clone(), - sequence_number: 0, - game_id: game_id.clone(), + if let Some(vhd_path) = existing_vhd { + println!("Found existing base VHD: {}", vhd_path.display()); + found_base = ExtractedVhd { + vhd_path, + input_path: PathBuf::new(), + sequence_number: 0, + game_id: game_id.clone(), + }; + &found_base + } else { + // Look for a base .app file ({game_id}_*_0.app) and extract it + let base_app = std::fs::read_dir(delta_dir)? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .find(|p| { + let name = p.file_name().unwrap_or_default().to_string_lossy(); + name.starts_with(&pattern_prefix) && name.ends_with("_0.app") + }); + + match base_app { + Some(app_path) => { + println!( + "Found base APP in same directory, extracting: {}", + app_path.display() + ); + // Read the bootid to get the sequence number + let base_file = FscryptDecryptor::new(File::open(&app_path)?) + .map_err(|e| anyhow!(e))?; + let base_seq = base_file.bootid.sequence_number; + drop(base_file); + + match extract_internal_vhd(&app_path, base_seq) { + Ok(vhd_path) => { + found_base = ExtractedVhd { + vhd_path, + input_path: app_path, + sequence_number: 0, + game_id: game_id.clone(), + }; + &found_base + } + Err(e) => { + println!( + "WARNING: Failed to extract base VHD: {e:#?}" + ); + continue; + } + } + } + None => { + println!( + "WARNING: Delta VHD(s) found for {game_id} but no base (seq=0) found." + ); + println!( + " Place the base .app or .vhd in the same directory." + ); + continue; } - } - None => { - println!( - "WARNING: Delta VHD(s) found for {game_id} but no base (seq=0) VHD found." - ); - println!( - " Provide the base .app file or place the base VHD in the same directory." - ); - continue; } } }