ps3dec/
lib.rs

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(&regions, 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}