dmg_cracker/
lib.rs

1mod cli;
2pub mod dmg;
3pub mod passwords;
4
5use crate::dmg::Dmg;
6use crate::passwords::read_password_list;
7use clap::Parser;
8use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
9use rayon::prelude::*;
10use std::env;
11use std::sync::{Arc, RwLock};
12
13pub fn run() -> Result<(), Box<dyn std::error::Error>> {
14    let args = cli::Args::parse();
15
16    let passwords =
17        read_password_list(&args.password_list_path).map_err(|e| {
18            eprintln!(
19                "❌ Error reading password list from '{}': {}",
20                args.password_list_path, e
21            );
22            eprintln!("💡 Please check:");
23            eprintln!("   - File exists and is readable");
24            eprintln!("   - File format is correct (.txt or .csv)");
25            eprintln!("   - You have permission to read the file");
26            e
27        })?;
28
29    if passwords.is_empty() {
30        eprintln!("❌ Error: Password list is empty");
31        eprintln!("💡 Please ensure your password file contains at least one password");
32        return Err("Password list is empty".into());
33    }
34
35    let mut password_list = passwords;
36    if args.randomize {
37        use rand::seq::SliceRandom;
38        use rand::thread_rng;
39        password_list.shuffle(&mut thread_rng());
40        println!("🔀 Randomized password order");
41    }
42
43    println!(
44        "🔒 Attempting {} passwords with {} threads...",
45        password_list.len(),
46        args.thread_count
47    );
48    let found_password = attempt_passwords_in_parallel(
49        &password_list,
50        &args.dmg_path,
51        &args.thread_count,
52    );
53    match found_password {
54        None => {
55            println!("No password was found");
56        }
57        Some(password) => {
58            println!("Password found: {password}");
59        }
60    }
61
62    Ok(())
63}
64
65fn attempt_passwords_in_parallel(
66    passwords: &[String],
67    dmg_path: &String,
68    thread_count: &usize,
69) -> Option<String> {
70    let password_vec_size = passwords.len();
71    let mp = MultiProgress::new();
72    let sty = ProgressStyle::with_template(
73        "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} ({eta}) ({per_sec})",
74    )
75    .unwrap()
76    .progress_chars("##-");
77
78    let shared_mp = Arc::new(&mp);
79    let shared_dmg_path = Arc::new(dmg_path);
80    let shared_password_found = Arc::new(RwLock::new(String::new()));
81
82    let chunk_size = if *thread_count == 0 || password_vec_size == 0 {
83        1
84    } else {
85        std::cmp::max(1, password_vec_size / *thread_count)
86    };
87
88    let chunks = passwords
89        .chunks(chunk_size)
90        .map(|chunk| chunk.to_vec())
91        .collect::<Vec<_>>();
92
93    env::set_var("RAYON_NUM_THREADS", format!("{thread_count}"));
94    chunks.into_par_iter().for_each(|chunk| {
95        let pb =
96            Arc::clone(&shared_mp).add(ProgressBar::new(chunk.len() as u64));
97        pb.set_style(sty.clone());
98        let password_found = Arc::clone(&shared_password_found);
99
100        rayon::scope(|s| {
101            s.spawn(|_| {
102                let dmg = Dmg::new(Arc::clone(&shared_dmg_path).as_ref());
103                const CHECK_FREQUENCY: usize = 10; // Check shared state every 10 passwords
104
105                for (passwords_checked, password) in
106                    chunk.into_iter().enumerate()
107                {
108                    // Check if another thread has found the password (less frequently)
109                    if passwords_checked % CHECK_FREQUENCY == 0 {
110                        let password_found_read =
111                            password_found.read().unwrap();
112                        if !password_found_read.is_empty() {
113                            return;
114                        }
115                        drop(password_found_read);
116                    }
117
118                    // check password and broadcast to other threads if found
119                    let success = dmg.attempt_password(&password);
120                    if success {
121                        let mut pw_found_guard =
122                            password_found.write().unwrap();
123                        *pw_found_guard = password;
124                        return;
125                    };
126
127                    pb.inc(1);
128                }
129            });
130        });
131    });
132
133    let found_password = shared_password_found.read().unwrap();
134    match found_password.is_empty() {
135        true => None,
136        false => Some(found_password.clone()),
137    }
138}