mirror of
https://github.com/encounter/nod-rs.git
synced 2025-12-10 22:17:40 +00:00
Support decrypted discs & decrypt/encrypt conversion
This commit is contained in:
@@ -30,7 +30,7 @@ indicatif = "0.17"
|
||||
itertools = "0.13"
|
||||
log = "0.4"
|
||||
md-5 = "0.10"
|
||||
nod = { version = "1.2", path = "../nod" }
|
||||
nod = { version = "2.0.0-alpha", path = "../nod" }
|
||||
quick-xml = { version = "0.36", features = ["serialize"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sha1 = "0.10"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use argp::FromArgs;
|
||||
use nod::OpenOptions;
|
||||
|
||||
use crate::util::{redump, shared::convert_and_verify};
|
||||
|
||||
@@ -20,6 +21,12 @@ pub struct Args {
|
||||
#[argp(option, short = 'd')]
|
||||
/// path to DAT file(s) for verification (optional)
|
||||
dat: Vec<PathBuf>,
|
||||
#[argp(switch)]
|
||||
/// decrypt Wii partition data
|
||||
decrypt: bool,
|
||||
#[argp(switch)]
|
||||
/// encrypt Wii partition data
|
||||
encrypt: bool,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> nod::Result<()> {
|
||||
@@ -27,5 +34,15 @@ pub fn run(args: Args) -> nod::Result<()> {
|
||||
println!("Loading dat files...");
|
||||
redump::load_dats(args.dat.iter().map(PathBuf::as_ref))?;
|
||||
}
|
||||
convert_and_verify(&args.file, Some(&args.out), args.md5)
|
||||
let options = OpenOptions {
|
||||
partition_encryption: match (args.decrypt, args.encrypt) {
|
||||
(true, false) => nod::PartitionEncryptionMode::ForceDecrypted,
|
||||
(false, true) => nod::PartitionEncryptionMode::ForceEncrypted,
|
||||
(false, false) => nod::PartitionEncryptionMode::Original,
|
||||
(true, true) => {
|
||||
return Err(nod::Error::Other("Both --decrypt and --encrypt specified".to_string()))
|
||||
}
|
||||
},
|
||||
};
|
||||
convert_and_verify(&args.file, Some(&args.out), args.md5, &options)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{
|
||||
|
||||
use argp::FromArgs;
|
||||
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
|
||||
use nod::{Disc, OpenOptions, Result, ResultContext};
|
||||
use nod::{Disc, OpenOptions, PartitionEncryptionMode, Result, ResultContext};
|
||||
use zerocopy::FromZeros;
|
||||
|
||||
use crate::util::{
|
||||
@@ -165,10 +165,8 @@ struct DiscHashes {
|
||||
}
|
||||
|
||||
fn load_disc(path: &Path, name: &str, full_verify: bool) -> Result<DiscHashes> {
|
||||
let mut disc = Disc::new_with_options(path, &OpenOptions {
|
||||
rebuild_encryption: true,
|
||||
validate_hashes: false,
|
||||
})?;
|
||||
let options = OpenOptions { partition_encryption: PartitionEncryptionMode::Original };
|
||||
let mut disc = Disc::new_with_options(path, &options)?;
|
||||
let disc_size = disc.disc_size();
|
||||
if !full_verify {
|
||||
let meta = disc.meta();
|
||||
|
||||
@@ -9,7 +9,8 @@ use std::{
|
||||
use argp::FromArgs;
|
||||
use itertools::Itertools;
|
||||
use nod::{
|
||||
Disc, Fst, Node, OpenOptions, PartitionBase, PartitionKind, PartitionMeta, ResultContext,
|
||||
Disc, Fst, Node, OpenOptions, PartitionBase, PartitionKind, PartitionMeta, PartitionOptions,
|
||||
ResultContext,
|
||||
};
|
||||
use size::{Base, Size};
|
||||
use zerocopy::IntoBytes;
|
||||
@@ -52,36 +53,39 @@ pub fn run(args: Args) -> nod::Result<()> {
|
||||
} else {
|
||||
output_dir = args.file.with_extension("");
|
||||
}
|
||||
let disc = Disc::new_with_options(&args.file, &OpenOptions {
|
||||
rebuild_encryption: false,
|
||||
validate_hashes: args.validate,
|
||||
})?;
|
||||
let disc = Disc::new_with_options(&args.file, &OpenOptions::default())?;
|
||||
let header = disc.header();
|
||||
let is_wii = header.is_wii();
|
||||
let partition_options = PartitionOptions { validate_hashes: args.validate };
|
||||
if let Some(partition) = args.partition {
|
||||
if partition.eq_ignore_ascii_case("all") {
|
||||
for info in disc.partitions() {
|
||||
let mut out_dir = output_dir.clone();
|
||||
out_dir.push(info.kind.dir_name().as_ref());
|
||||
let mut partition = disc.open_partition(info.index)?;
|
||||
let mut partition =
|
||||
disc.open_partition_with_options(info.index, &partition_options)?;
|
||||
extract_partition(&disc, partition.as_mut(), &out_dir, is_wii, args.quiet)?;
|
||||
}
|
||||
} else if partition.eq_ignore_ascii_case("data") {
|
||||
let mut partition = disc.open_partition_kind(PartitionKind::Data)?;
|
||||
let mut partition =
|
||||
disc.open_partition_kind_with_options(PartitionKind::Data, &partition_options)?;
|
||||
extract_partition(&disc, partition.as_mut(), &output_dir, is_wii, args.quiet)?;
|
||||
} else if partition.eq_ignore_ascii_case("update") {
|
||||
let mut partition = disc.open_partition_kind(PartitionKind::Update)?;
|
||||
let mut partition =
|
||||
disc.open_partition_kind_with_options(PartitionKind::Update, &partition_options)?;
|
||||
extract_partition(&disc, partition.as_mut(), &output_dir, is_wii, args.quiet)?;
|
||||
} else if partition.eq_ignore_ascii_case("channel") {
|
||||
let mut partition = disc.open_partition_kind(PartitionKind::Channel)?;
|
||||
let mut partition =
|
||||
disc.open_partition_kind_with_options(PartitionKind::Channel, &partition_options)?;
|
||||
extract_partition(&disc, partition.as_mut(), &output_dir, is_wii, args.quiet)?;
|
||||
} else {
|
||||
let idx = partition.parse::<usize>().map_err(|_| "Invalid partition index")?;
|
||||
let mut partition = disc.open_partition(idx)?;
|
||||
let mut partition = disc.open_partition_with_options(idx, &partition_options)?;
|
||||
extract_partition(&disc, partition.as_mut(), &output_dir, is_wii, args.quiet)?;
|
||||
}
|
||||
} else {
|
||||
let mut partition = disc.open_partition_kind(PartitionKind::Data)?;
|
||||
let mut partition =
|
||||
disc.open_partition_kind_with_options(PartitionKind::Data, &partition_options)?;
|
||||
extract_partition(&disc, partition.as_mut(), &output_dir, is_wii, args.quiet)?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use argp::FromArgs;
|
||||
use nod::{Disc, OpenOptions, SECTOR_SIZE};
|
||||
use nod::{Disc, SECTOR_SIZE};
|
||||
use size::Size;
|
||||
|
||||
use crate::util::{display, shared::print_header};
|
||||
@@ -24,16 +24,16 @@ pub fn run(args: Args) -> nod::Result<()> {
|
||||
|
||||
fn info_file(path: &Path) -> nod::Result<()> {
|
||||
log::info!("Loading {}", display(path));
|
||||
let disc = Disc::new_with_options(path, &OpenOptions {
|
||||
rebuild_encryption: false,
|
||||
validate_hashes: false,
|
||||
})?;
|
||||
let disc = Disc::new(path)?;
|
||||
let header = disc.header();
|
||||
let meta = disc.meta();
|
||||
print_header(header, &meta);
|
||||
|
||||
if header.is_wii() {
|
||||
for (idx, info) in disc.partitions().iter().enumerate() {
|
||||
let mut partition = disc.open_partition(idx)?;
|
||||
let meta = partition.meta()?;
|
||||
|
||||
println!();
|
||||
println!("Partition {}", idx);
|
||||
println!("\tType: {}", info.kind);
|
||||
@@ -41,43 +41,50 @@ fn info_file(path: &Path) -> nod::Result<()> {
|
||||
println!("\tStart sector: {} (offset {:#X})", info.start_sector, offset);
|
||||
let data_size =
|
||||
(info.data_end_sector - info.data_start_sector) as u64 * SECTOR_SIZE as u64;
|
||||
if info.has_encryption {
|
||||
println!(
|
||||
"\tEncrypted data offset / size: {:#X} / {:#X} ({})",
|
||||
info.data_start_sector as u64 * SECTOR_SIZE as u64,
|
||||
data_size,
|
||||
Size::from_bytes(data_size)
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\tDecrypted data offset / size: {:#X} / {:#X} ({})",
|
||||
offset,
|
||||
data_size,
|
||||
Size::from_bytes(data_size)
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"\tData offset / size: {:#X} / {:#X} ({})",
|
||||
info.data_start_sector as u64 * SECTOR_SIZE as u64,
|
||||
data_size,
|
||||
Size::from_bytes(data_size)
|
||||
);
|
||||
println!(
|
||||
"\tTMD offset / size: {:#X} / {:#X}",
|
||||
"\tTMD offset / size: {:#X} / {:#X}",
|
||||
offset + info.header.tmd_off(),
|
||||
info.header.tmd_size()
|
||||
);
|
||||
if let Some(content_metadata) = meta.content_metadata() {
|
||||
for content in content_metadata {
|
||||
println!(
|
||||
"\t-> Content {:08X} size: {:#X} ({})",
|
||||
content.content_index.get(),
|
||||
content.size.get(),
|
||||
Size::from_bytes(content.size.get()),
|
||||
);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"\tCert offset / size: {:#X} / {:#X}",
|
||||
"\tCert chain offset / size: {:#X} / {:#X}",
|
||||
offset + info.header.cert_chain_off(),
|
||||
info.header.cert_chain_size()
|
||||
);
|
||||
println!(
|
||||
"\tH3 offset / size: {:#X} / {:#X}",
|
||||
"\tH3 table offset / size: {:#X} / {:#X}",
|
||||
offset + info.header.h3_table_off(),
|
||||
info.header.h3_table_size()
|
||||
);
|
||||
|
||||
let mut partition = disc.open_partition(idx)?;
|
||||
let meta = partition.meta()?;
|
||||
let tmd = meta.tmd_header();
|
||||
let title_id_str = if let Some(tmd) = tmd {
|
||||
format!(
|
||||
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
tmd.title_id[0],
|
||||
tmd.title_id[1],
|
||||
tmd.title_id[2],
|
||||
tmd.title_id[3],
|
||||
tmd.title_id[4],
|
||||
tmd.title_id[5],
|
||||
tmd.title_id[6],
|
||||
tmd.title_id[7]
|
||||
)
|
||||
hex::encode_upper(tmd.title_id)
|
||||
} else {
|
||||
"N/A".to_string()
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use argp::FromArgs;
|
||||
use nod::{OpenOptions, PartitionEncryptionMode};
|
||||
|
||||
use crate::util::{redump, shared::convert_and_verify};
|
||||
|
||||
@@ -24,8 +25,9 @@ pub fn run(args: Args) -> nod::Result<()> {
|
||||
println!("Loading dat files...");
|
||||
redump::load_dats(args.dat.iter().map(PathBuf::as_ref))?;
|
||||
}
|
||||
let options = OpenOptions { partition_encryption: PartitionEncryptionMode::Original };
|
||||
for file in &args.file {
|
||||
convert_and_verify(file, None, args.md5)?;
|
||||
convert_and_verify(file, None, args.md5, &options)?;
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -38,20 +38,22 @@ pub fn print_header(header: &DiscHeader, meta: &DiscMeta) {
|
||||
println!("Title: {}", header.game_title_str());
|
||||
println!("Game ID: {}", header.game_id_str());
|
||||
println!("Disc {}, Revision {}", header.disc_num + 1, header.disc_version);
|
||||
if header.no_partition_hashes != 0 {
|
||||
if !header.has_partition_hashes() {
|
||||
println!("[!] Disc has no hashes");
|
||||
}
|
||||
if header.no_partition_encryption != 0 {
|
||||
if !header.has_partition_encryption() {
|
||||
println!("[!] Disc is not encrypted");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_and_verify(in_file: &Path, out_file: Option<&Path>, md5: bool) -> Result<()> {
|
||||
pub fn convert_and_verify(
|
||||
in_file: &Path,
|
||||
out_file: Option<&Path>,
|
||||
md5: bool,
|
||||
options: &OpenOptions,
|
||||
) -> Result<()> {
|
||||
println!("Loading {}", display(in_file));
|
||||
let mut disc = Disc::new_with_options(in_file, &OpenOptions {
|
||||
rebuild_encryption: true,
|
||||
validate_hashes: false,
|
||||
})?;
|
||||
let mut disc = Disc::new_with_options(in_file, options)?;
|
||||
let header = disc.header();
|
||||
let meta = disc.meta();
|
||||
print_header(header, &meta);
|
||||
|
||||
Reference in New Issue
Block a user