mirror of
https://gitea.tendokyu.moe/beerpsi/fsdecrypt.git
synced 2026-09-28 01:37:59 +03:00
feat: also extract exfat without saving intermediary exfat image
This commit is contained in:
Generated
+1
-1
@@ -507,7 +507,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fsdecrypt"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"anyhow",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "fsdecrypt"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
+28
-1
@@ -1,4 +1,4 @@
|
||||
use std::fmt::Display;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use hex_literal::hex;
|
||||
|
||||
@@ -75,3 +75,30 @@ pub struct BootId {
|
||||
// We don't need the entire bootID, so keep size down by not including the string table.
|
||||
// pub strings: [u8; 10156],
|
||||
}
|
||||
|
||||
impl Debug for BootId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BootId")
|
||||
.field("crc32", &self.crc32)
|
||||
.field("length", &self.length)
|
||||
.field("signature", &self.signature)
|
||||
.field("unk1", &self.unk1)
|
||||
.field("container_type", &self.container_type)
|
||||
.field("sequence_number", &self.sequence_number)
|
||||
.field("use_custom_iv", &self.use_custom_iv)
|
||||
.field("game_id", &self.game_id)
|
||||
.field("target_timestamp", &self.target_timestamp)
|
||||
//.field("target_version", &self.target_version)
|
||||
.field("block_count", &self.block_count)
|
||||
.field("block_size", &self.block_size)
|
||||
.field("header_block_count", &self.header_block_count)
|
||||
.field("unk2", &self.unk2)
|
||||
.field("os_id", &self.os_id)
|
||||
.field("os_generation", &self.os_generation)
|
||||
.field("source_timestamp", &self.source_timestamp)
|
||||
.field("source_version", &self.source_version)
|
||||
.field("os_version", &self.os_version)
|
||||
.field("padding", &self.padding)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
+39
-22
@@ -50,27 +50,46 @@ fn extract_exfat_contents(exfat_path: &Path) -> Result<()> {
|
||||
// Create output directory with same name as exfat file (without extension)
|
||||
let output_dir = exfat_path.with_extension("");
|
||||
|
||||
let pb = ProgressBar::new_spinner()
|
||||
.with_style(ProgressStyle::default_bar().template("{prefix} {spinner}")?);
|
||||
pb.set_prefix(format!(
|
||||
"Extracting contents of {} to {}",
|
||||
exfat_path.file_name().unwrap().display(),
|
||||
output_dir.display(),
|
||||
));
|
||||
pb.enable_steady_tick(Duration::from_millis(100));
|
||||
|
||||
let file = File::open(exfat_path)?;
|
||||
let file = FscryptDecryptor::new(File::open(exfat_path)?).map_err(|e| anyhow!(e))?;
|
||||
let mut root = Root::open(file)?;
|
||||
|
||||
let pb = ProgressBar::new(calculate_exfat_size(root.items())?)
|
||||
.with_style(
|
||||
ProgressStyle::default_bar().template(
|
||||
"{prefix} [{bar:20!.bright.yellow/dim.white}] {bytes:>8} [{elapsed}<{eta}, {bytes_per_sec}]",
|
||||
)?
|
||||
);
|
||||
pb.set_prefix(format!(
|
||||
"Extracting {}",
|
||||
exfat_path.file_name().unwrap().display(),
|
||||
));
|
||||
|
||||
create_dir_all(&output_dir)?;
|
||||
extract_exfat_elements(root.items(), &output_dir)?;
|
||||
extract_exfat_elements(root.items(), &output_dir, &pb)?;
|
||||
|
||||
pb.finish();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_exfat_elements(elements: &mut [FsElement<File>], output_dir: &Path) -> Result<()> {
|
||||
fn calculate_exfat_size(elements: &[FsElement<FscryptDecryptor<File>>]) -> Result<u64> {
|
||||
let mut total = 0;
|
||||
|
||||
for element in elements {
|
||||
match element {
|
||||
FsElement::F(ref file) => total += file.len(),
|
||||
FsElement::D(directory) => total += calculate_exfat_size(&directory.open()?)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn extract_exfat_elements(
|
||||
elements: &mut [FsElement<FscryptDecryptor<File>>],
|
||||
output_dir: &Path,
|
||||
pb: &ProgressBar,
|
||||
) -> Result<()> {
|
||||
for element in elements {
|
||||
match element {
|
||||
FsElement::F(ref mut file) => {
|
||||
@@ -90,13 +109,15 @@ fn extract_exfat_elements(elements: &mut [FsElement<File>], output_dir: &Path) -
|
||||
let mut writer = BufWriter::with_capacity(256 * 1024, &mut dest);
|
||||
|
||||
std::io::copy(file, &mut writer)?;
|
||||
writer.flush()?;
|
||||
pb.inc(file.len());
|
||||
}
|
||||
FsElement::D(directory) => {
|
||||
let dest_path = output_dir.join(directory.name());
|
||||
create_dir_all(&dest_path)?;
|
||||
|
||||
let mut children = directory.open()?;
|
||||
extract_exfat_elements(&mut children, &dest_path)?;
|
||||
extract_exfat_elements(&mut children, &dest_path, &pb)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,7 +229,7 @@ fn main() -> Result<()> {
|
||||
|
||||
// Can't directly extract options since we can't impl exfat_fs::dir::ReadOffset
|
||||
// for our wrapper decryptor
|
||||
if cli.no_extract || file.bootid.container_type == ContainerType::OPTION {
|
||||
if cli.no_extract {
|
||||
let mut output_file = File::create(&output_path)?;
|
||||
|
||||
output_file.set_len(file.len())?;
|
||||
@@ -236,9 +257,9 @@ fn main() -> Result<()> {
|
||||
|
||||
pb.inc(length as u64);
|
||||
}
|
||||
}
|
||||
|
||||
if !cli.no_extract {
|
||||
output_file.flush()?;
|
||||
} else {
|
||||
match bootid.container_type {
|
||||
ContainerType::OS | ContainerType::APP => {
|
||||
match extract_internal_vhd(&path, bootid.sequence_number) {
|
||||
@@ -248,12 +269,8 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
ContainerType::OPTION => match extract_exfat_contents(&output_path) {
|
||||
Ok(_) => {
|
||||
println!("Deleting exFAT image: {}", output_path.display());
|
||||
|
||||
std::fs::remove_file(output_path)?;
|
||||
}
|
||||
ContainerType::OPTION => match extract_exfat_contents(&path) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("WARNING: Failed to extract exfat contents: {e:#?}");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, Read, Seek, SeekFrom},
|
||||
str::Utf8Error,
|
||||
};
|
||||
@@ -8,6 +9,7 @@ use aes::cipher::{
|
||||
BlockDecryptMut, InvalidLength,
|
||||
};
|
||||
use cipher::KeyIvInit;
|
||||
use exfat_fs::disk::ReadOffset;
|
||||
|
||||
use crate::{
|
||||
bootid::{BootId, ContainerType, BOOTID_IV, BOOTID_KEY},
|
||||
@@ -44,6 +46,7 @@ pub enum DecryptError {
|
||||
}
|
||||
|
||||
/// Decryptor for an fscrypt container.
|
||||
#[derive(Debug)]
|
||||
pub struct FscryptDecryptor<R> {
|
||||
/// The fscrypt file.
|
||||
input: R,
|
||||
@@ -289,3 +292,37 @@ impl<R: Read + Seek> Seek for FscryptDecryptor<R> {
|
||||
Ok(target_pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadOffset for FscryptDecryptor<File> {
|
||||
type Err = std::io::Error;
|
||||
|
||||
fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<usize, Self::Err> {
|
||||
let start = self.bootid.header_block_count * self.bootid.block_size;
|
||||
let target_index = offset / PAGE_SIZE;
|
||||
let target_offset = offset % PAGE_SIZE;
|
||||
let mut page = [0u8; 4096];
|
||||
let mut page_iv = [0u8; 16];
|
||||
let page_offset = target_index * PAGE_SIZE;
|
||||
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::FileExt::read_at(&self.input, &mut page, start + page_offset)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
std::os::windows::fs::FileExt::seek_read(&self.input, &mut page, start + page_offset)?;
|
||||
|
||||
calculate_page_iv(page_offset, &self.iv, &mut page_iv);
|
||||
|
||||
let page_cipher = Aes128CbcDec::new_from_slices(&self.key, &page_iv).unwrap();
|
||||
|
||||
page_cipher
|
||||
.decrypt_padded_mut::<NoPadding>(&mut page)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "failed to decrypt"))?;
|
||||
|
||||
let to_read = std::cmp::min(buffer.len(), (PAGE_SIZE - target_offset) as usize);
|
||||
|
||||
buffer[..to_read]
|
||||
.copy_from_slice(&page[target_offset as usize..target_offset as usize + to_read]);
|
||||
|
||||
Ok(to_read)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user