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; for (passwords_checked, password) in
106 chunk.into_iter().enumerate()
107 {
108 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 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}