1pub mod autodetect;
2pub mod utils;
3pub mod args;
4use aes::Aes128Dec;
5use aes::cipher::{KeyInit, BlockDecryptMut};
6use aes::cipher::generic_array::{GenericArray, typenum::U16};
7use hex::decode;
8use indicatif::{ProgressBar, ProgressStyle};
9use log::info;
10use rayon::prelude::*;
11use std::fs::{self, File, OpenOptions};
12use std::io::{self, BufReader};
13use std::path::Path;
14use std::sync::Arc;
15use std::time::Instant;
16use utils::{ extract_regions, generate_iv, is_encrypted};
17const SECTOR_SIZE: usize = 2048;
18
19use crate::args::DEFAULT_CHUNK;
20
21
22pub fn decrypt(
23 file_path: String,
24 decryption_key: &str,
25 thread_count: usize,
26 output_dir: Option<String>,
27 output_name: Option<String>,
28 chunk_size: Option<usize>,
29) -> io::Result<()> {
30 info!("Starting decryption process.");
31 let start_time = Instant::now();
32
33 rayon::ThreadPoolBuilder::new()
34 .num_threads(thread_count.max(1))
35 .build_global()
36 .unwrap_or_else(|e| info!("Failed to set thread count, using default: {}", e));
37
38 let key_bytes = decode(decryption_key.trim())
39 .map_err(|e| io::Error::other(e.to_string()))?;
40 if key_bytes.len() != 16 {
41 return Err(io::Error::other(
42 "decryption key must be 16 bytes (32 hex chars)",
43 ));
44 }
45 let key_ga: GenericArray<u8, U16> = GenericArray::clone_from_slice(&key_bytes);
46
47 let input_file = File::open(&file_path)?;
48 let total_size = input_file.metadata()?.len();
49 if total_size % SECTOR_SIZE as u64 != 0 {
50 return Err(io::Error::other(
51 "input size is not a multiple of SECTOR_SIZE",
52 ));
53 }
54 let total_sectors = (total_size / SECTOR_SIZE as u64) as usize;
55
56 info!(
57 "File size: {:.2} MB, Total sectors: {}",
58 total_size as f64 / 1_048_576.0,
59 total_sectors
60 );
61
62 let mut region_reader = BufReader::with_capacity(256 * 1024, File::open(&file_path)?);
63 let regions = Arc::new(extract_regions(&mut region_reader)?);
64 info!("Total regions detected: {}", regions.len());
65
66 let in_file = Arc::new(input_file);
67 let output_file_path = if let Some(dir) = output_dir {
68 let path = Path::new(&file_path);
69 let file_name = if let Some(name) = output_name {
70 format!("{name}.iso")
71 } else {
72 let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("decrypted");
73 format!("{stem}_decrypted.iso")
74 };
75 let output_dir_path = Path::new(&dir);
76 if !output_dir_path.exists() {
77 fs::create_dir_all(output_dir_path)?;
78 }
79 output_dir_path.join(file_name).to_string_lossy().to_string()
80 } else if let Some(name) = output_name {
81 format!("{name}.iso")
82 } else {
83 format!("{file_path}_decrypted.iso")
84 };
85 info!("Output will be written to: {}", output_file_path);
86
87 let out_file = OpenOptions::create(OpenOptions::new().write(true), true).open(&output_file_path)?;
88 out_file.set_len(total_size)?;
89 let out_file = Arc::new(out_file);
90
91 let progress_bar = Arc::new(ProgressBar::new(total_sectors as u64));
92 progress_bar.set_style(
93 ProgressStyle::default_bar()
94 .template("{spinner:.208} {elapsed_precise} |{bar:40.208/236}| {bytes:>8}/{total_bytes:8} ({bytes_per_sec}) ETA {eta_precise}")
95 .unwrap()
96 .progress_chars("█▓▒░"),
97 );
98 let chunk_bytes = chunk_size
99 .map(|mib| mib.saturating_mul(1024 * 1024))
100 .unwrap_or(DEFAULT_CHUNK.unwrap());
101 let chunk_sectors = (chunk_bytes / SECTOR_SIZE).max(1);
102 let tasks: Vec<(usize, usize)> = (0..total_sectors)
103 .step_by(chunk_sectors)
104 .map(|s| (s, (s + chunk_sectors).min(total_sectors)))
105 .collect();
106
107 tasks.into_par_iter().for_each(|(start_sector, end_sector)| {
108 let mut buf = vec![0u8; SECTOR_SIZE * (end_sector - start_sector)];
109 if let Err(e) = utils::read_exact_at(
110 &in_file,
111 &mut buf,
112 (start_sector as u64) * (SECTOR_SIZE as u64),
113 ) {
114 eprintln!("Error reading data: {}", e);
115 return;
116 }
117
118 let mut aes_core = Aes128Dec::new(&key_ga);
119 for (i, sector) in buf.chunks_mut(SECTOR_SIZE).enumerate() {
120 let sector_index = start_sector + i;
121 if !is_encrypted(®ions, sector_index as u64, sector) {
122 continue;
123 }
124
125 let iv_ga = generate_iv(sector_index as u64);
126 let mut prev = [0u8; 16];
127 prev.copy_from_slice(iv_ga.as_slice());
128
129 for block in sector.chunks_exact_mut(16) {
130 let mut cur = [0u8; 16];
131 cur.copy_from_slice(block);
132
133 aes_core.decrypt_block_mut(GenericArray::from_mut_slice(block));
134 for k in 0..16 {
135 block[k] ^= prev[k];
136 }
137 prev = cur;
138 }
139 }
140
141 let off = (start_sector as u64) * (SECTOR_SIZE as u64);
142 if let Err(e) = utils::write_all_at(&out_file, &buf, off) {
143 eprintln!("Error writing data at offset {}: {}", off, e);
144 }
145
146 progress_bar.inc((end_sector - start_sector) as u64);
147 });
148
149 progress_bar.finish_with_message("Decryption completed");
150
151 let elapsed = start_time.elapsed();
152 info!("Decryption completed in {:.2} seconds.", elapsed.as_secs_f64());
153 info!("Data written to {}", output_file_path);
154
155 Ok(())
156}