feat: exfat content extraction (#1)

Co-authored-by: jujuforce <jujuforce@gmail.com>
Co-authored-by: beerpsi <beerpsi@noreply.gitea.tendokyu.moe>
Reviewed-on: https://gitea.tendokyu.moe/beerpsi/fsdecrypt/pulls/1
Co-authored-by: jujuforce <jujuforce@noreply.gitea.tendokyu.moe>
Co-committed-by: jujuforce <jujuforce@noreply.gitea.tendokyu.moe>
This commit is contained in:
jujuforce
2025-07-16 15:49:11 +00:00
committed by beerpsi
co-authored by jujuforce beerpsi
parent 252ea9c797
commit 5d1e0ca908
3 changed files with 297 additions and 8 deletions
Generated
+240 -1
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aes"
@@ -19,6 +19,21 @@ version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
dependencies = [
"bytemuck",
]
[[package]]
name = "block-padding"
version = "0.3.3"
@@ -28,6 +43,26 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bytemuck"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cbc"
version = "0.1.2"
@@ -43,6 +78,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "checked_num"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deb5a345c406bf67f62bd14f284c9cb041ce5107725c22e665a163c871a4fb7b"
dependencies = [
"num-traits",
]
[[package]]
name = "cipher"
version = "0.4.4"
@@ -94,12 +138,130 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "endify"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "412c0c5048548a61a2ed94233a7305d2dfa4d1d3acc69039da77efa84e51df21"
dependencies = [
"endify-derive",
]
[[package]]
name = "endify-derive"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a204d4aa704d0a74ee3bebccc65f24bca0cdef2588f08eaafda467cd4d3c8a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "enumeric"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f2f9bd649bd8c884aa518a798861fd111699dfc7d7dcc193bc2b658c8b9188d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "exfat-fs"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbdfa8ecf36ab2341032974d3644a56f1e40c9da3b643118492cf97f357f3c5"
dependencies = [
"bitflags",
"bytemuck",
"checked_num",
"derive_builder",
"endify",
"enumeric",
"thiserror",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fsdecrypt"
version = "0.1.1"
@@ -108,6 +270,7 @@ dependencies = [
"anyhow",
"cbc",
"crc32fast",
"exfat-fs",
"hex-literal",
"indicatif",
]
@@ -128,6 +291,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indicatif"
version = "0.17.8"
@@ -172,6 +341,15 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "number_prefix"
version = "0.4.0"
@@ -184,12 +362,73 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-width"
version = "0.1.13"
+1
View File
@@ -10,3 +10,4 @@ cbc = "0.1.2"
crc32fast = "1.4.2"
hex-literal = "0.4.1"
indicatif = "0.17.8"
exfat-fs = "0.1.3"
+56 -7
View File
@@ -1,9 +1,11 @@
use std::{
fs::File,
fs::{create_dir_all, File},
io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write},
path::Path,
};
use exfat_fs::dir::{entry::fs::FsElement, Root};
use aes::{
cipher::{block_padding::NoPadding, BlockDecryptMut, InnerIvInit, KeyInit, KeyIvInit},
Aes128Dec,
@@ -21,6 +23,43 @@ mod crypto;
const PAGE_SIZE: u64 = 4096;
fn extract_exfat_contents(exfat_path: &Path) -> Result<()> {
println!("Extracting contents of {}", exfat_path.display());
let file = File::open(exfat_path)?;
let mut root = Root::open(file)?;
// Create output directory with same name as exfat file (without extension)
let output_dir = exfat_path.with_extension("");
create_dir_all(&output_dir)?;
extract_fs_elements(root.items(), &output_dir)?;
Ok(())
}
fn extract_fs_elements(elements: &mut [FsElement<File>], output_dir: &Path) -> Result<()> {
for element in elements {
match element {
FsElement::F(ref mut file) => {
let dest_path = output_dir.join(file.name());
let mut dest = File::create(dest_path)?;
std::io::copy(file, &mut dest)?;
}
FsElement::D(directory) => {
let dest_path = output_dir.join(directory.name());
create_dir_all(&dest_path)?;
let mut children = directory.open()?;
extract_fs_elements(&mut children, &dest_path)?;
}
}
}
Ok(())
}
fn main() -> Result<()> {
let args = std::env::args().collect::<Vec<String>>();
@@ -83,11 +122,7 @@ fn main() -> Result<()> {
let data_offset = bootid.header_block_count * bootid.block_size;
let key = keys.key;
let iv = if bootid.use_custom_iv {
None
} else {
keys.iv
};
let iv = if bootid.use_custom_iv { None } else { keys.iv };
let iv = match iv {
Some(iv) => iv,
None => {
@@ -160,7 +195,7 @@ fn main() -> Result<()> {
.template("{prefix} [{bar:20!.bright.yellow/dim.white}] {bytes:>8} [{elapsed}<{eta}, {bytes_per_sec}]")?
);
pb.set_prefix(output_filename);
pb.set_prefix(output_filename.clone());
reader.seek(SeekFrom::Start(data_offset))?;
for _ in 0..output_size / PAGE_SIZE {
@@ -184,6 +219,20 @@ fn main() -> Result<()> {
writer.flush()?;
pb.finish();
// Extract exfat contents if this is an exfat file
if bootid.container_type == ContainerType::OPTION
&& output_path.extension().unwrap_or_default() == "exfat"
{
if let Err(e) = extract_exfat_contents(&output_path) {
println!("WARNING: Failed to extract exfat contents: {e:#?}");
} else {
println!("Extracted exfat contents: {:?}", output_path);
println!("Deleting exfat file: {:?}", output_path);
std::fs::remove_file(output_path)?;
}
}
page.clear();
page_iv.fill(0);
bootid_bytes.fill(0);