use crate::create::cd::CreateCdOptions;
use crate::create::common::{
parse_cd_descriptor, DEFAULT_CD_CODECS, DEFAULT_HD_CODECS, DEFAULT_RAW_CODECS,
};
use crate::create::dvd::CreateDvdOptions;
use crate::create::hd::CreateHdOptions;
use crate::create::raw::CreateRawOptions;
use crate::error::DiscError;
use crate::hd::{guess_chs, HD_TEMPLATES};
use std::fs::File;
use std::path::{Path, PathBuf};
pub fn format_with_commas(n: u64) -> String {
let s = n.to_string();
let bytes = s.as_bytes();
let len = bytes.len();
let mut res = String::with_capacity(len + len / 3);
for (i, &b) in bytes.iter().enumerate() {
if i > 0 && (len - i).is_multiple_of(3) {
res.push(',');
}
res.push(b as char);
}
res
}
pub fn msf_string_from_frames(frames: u32) -> String {
let seconds = frames / 75;
let rem_frames = frames % 75;
let minutes = seconds / 60;
let rem_seconds = seconds % 60;
format!("{:02}:{:02}:{:02}", minutes, rem_seconds, rem_frames)
}
pub fn codec_name(codec: u32) -> &'static str {
match &codec.to_be_bytes() {
b"zlib" => "Deflate",
b"zstd" => "Zstandard",
b"lzma" => "LZMA",
b"huff" => "Huffman",
b"flac" => "FLAC",
b"cdzl" => "CD Deflate",
b"cdzs" => "CD Zstandard",
b"cdlz" => "CD LZMA",
b"cdfl" => "CD FLAC",
b"avhu" => "A/V Huffman",
_ => "unknown",
}
}
pub fn format_codecs(codecs: &[u32; 4]) -> String {
if codecs[0] == 0 {
return "none".to_string();
}
let mut parts = Vec::new();
for &c in codecs {
if c == 0 {
break;
}
let fourcc = match std::str::from_utf8(&c.to_be_bytes()) {
Ok(s) => s.to_string(),
Err(_) => format!("{:#010x}", c),
};
let name = codec_name(c);
parts.push(format!("{} ({})", fourcc, name));
}
parts.join(", ")
}
#[derive(Debug, Clone)]
pub struct CdCreateInfo {
pub output_path: PathBuf,
pub parent_path: Option<PathBuf>,
pub input_path: PathBuf,
pub num_tracks: usize,
pub total_frames: u32,
pub padded_sectors: u32,
pub logical_bytes: u64,
pub hunk_size: u32,
pub codecs: [u32; 4],
}
#[derive(Debug, Clone)]
pub struct DvdCreateInfo {
pub output_path: PathBuf,
pub parent_path: Option<PathBuf>,
pub input_path: PathBuf,
pub input_start: u64,
pub input_length: u64,
pub input_total_size: u64,
pub logical_bytes: u64,
pub hunk_size: u32,
pub codecs: [u32; 4],
}
#[derive(Debug, Clone)]
pub struct HdCreateInfo {
pub output_path: PathBuf,
pub parent_path: Option<PathBuf>,
pub input_path: Option<PathBuf>,
pub input_start: u64,
pub input_length: u64,
pub input_total_size: u64,
pub template_name: Option<String>,
pub cylinders: u32,
pub heads: u32,
pub sectors: u32,
pub sector_size: u32,
pub hunk_size: u32,
pub logical_bytes: u64,
pub codecs: [u32; 4],
}
#[derive(Debug, Clone)]
pub struct RawCreateInfo {
pub output_path: PathBuf,
pub parent_path: Option<PathBuf>,
pub input_path: PathBuf,
pub input_start: u64,
pub input_length: u64,
pub input_total_size: u64,
pub logical_bytes: u64,
pub hunk_size: u32,
pub unit_size: u32,
pub codecs: [u32; 4],
}
pub fn inspect_create_cd(
input_path: &Path,
output_path: &Path,
options: &CreateCdOptions,
) -> Result<CdCreateInfo, DiscError> {
let toc = parse_cd_descriptor(input_path)?;
let mut origtotalsectors = 0u32;
let mut totalsectors = 0u32;
for track in &toc.tracks {
let padded = track.frames.div_ceil(4);
let extra_frames = padded * 4 - track.frames;
origtotalsectors += track.frames;
totalsectors += track.frames + extra_frames;
}
let unit_size = 2448u32;
let hunk_size = options.hunk_size.unwrap_or(8 * 2448);
let logical_bytes = totalsectors as u64 * unit_size as u64;
let codecs = options.codecs.unwrap_or(DEFAULT_CD_CODECS);
Ok(CdCreateInfo {
output_path: output_path.to_path_buf(),
parent_path: options.parent_path.clone(),
input_path: input_path.to_path_buf(),
num_tracks: toc.tracks.len(),
total_frames: origtotalsectors,
padded_sectors: totalsectors,
logical_bytes,
hunk_size,
codecs,
})
}
pub fn inspect_create_dvd(
input_path: &Path,
output_path: &Path,
options: &CreateDvdOptions,
) -> Result<DvdCreateInfo, DiscError> {
let input_file = File::open(input_path)?;
let total_size = input_file.metadata()?.len();
let unit_size = 2048u32;
let hunk_size = options.hunk_size.unwrap_or(4096);
let start = match (options.start_byte, options.start_hunk) {
(Some(b), _) => b,
(None, Some(h)) => h * hunk_size as u64,
(None, None) => 0,
};
let length = match (options.length_bytes, options.length_hunks) {
(Some(b), _) => b,
(None, Some(h)) => h * hunk_size as u64,
(None, None) => total_size.saturating_sub(start),
};
let end = start
.checked_add(length)
.ok_or_else(|| DiscError::InvalidMetadata("specified range overflows u64".into()))?;
if end > total_size {
return Err(DiscError::InvalidMetadata(
"specified range exceeds input file size".into(),
));
}
if !length.is_multiple_of(unit_size as u64) {
return Err(DiscError::InvalidMetadata(format!(
"data length {} is not divisible by DVD sector size 2048",
length
)));
}
let codecs = options.codecs.unwrap_or(DEFAULT_HD_CODECS);
Ok(DvdCreateInfo {
output_path: output_path.to_path_buf(),
parent_path: options.parent_path.clone(),
input_path: input_path.to_path_buf(),
input_start: start,
input_length: length,
input_total_size: total_size,
logical_bytes: length,
hunk_size,
codecs,
})
}
pub fn inspect_create_hd(
input_path: Option<&Path>,
output_path: &Path,
options: &CreateHdOptions,
) -> Result<HdCreateInfo, DiscError> {
let mut sector_size = options.sector_size.unwrap_or(512);
if sector_size == 0 {
return Err(DiscError::InvalidMetadata(
"sector size must be non-zero".into(),
));
}
let mut cylinders = 0u32;
let mut heads = 0u32;
let mut sectors = 0u32;
let mut template_name = None;
if let Some(t_idx) = options.template {
if t_idx >= HD_TEMPLATES.len() {
return Err(DiscError::InvalidMetadata(format!(
"invalid hard disk template index {}",
t_idx
)));
}
let t = &HD_TEMPLATES[t_idx];
cylinders = t.cylinders;
heads = t.heads;
sectors = t.sectors;
sector_size = t.sector_size;
template_name = Some(format!("{} {}", t.manufacturer, t.model));
} else if let Some((c, h, s)) = options.chs {
cylinders = c;
heads = h;
sectors = s;
}
if let Some(ref id_path) = options.ident_path {
let ident_data = std::fs::read(id_path)?;
if ident_data.len() < 14 {
return Err(DiscError::InvalidMetadata(
"ident file too short (< 14 bytes)".into(),
));
}
if cylinders == 0 {
let c = u16::from_le_bytes([ident_data[2], ident_data[3]]) as u32;
let h = u16::from_le_bytes([ident_data[6], ident_data[7]]) as u32;
let s = u16::from_le_bytes([ident_data[12], ident_data[13]]) as u32;
if (c as u64 * h as u64 * s as u64) < 16_514_064 {
cylinders = c;
heads = h;
sectors = s;
}
}
}
let hunk_size = options
.hunk_size
.unwrap_or_else(|| ((4096 / sector_size) * sector_size).max(sector_size));
let (input_start, input_length, total_file_size, logical_bytes) =
if let Some(in_path) = input_path {
let file = File::open(in_path)?;
let total_file_size = file.metadata()?.len();
let start = match (options.start_byte, options.start_hunk) {
(Some(b), _) => b,
(None, Some(h)) => h * hunk_size as u64,
(None, None) => 0,
};
let length = match (options.length_bytes, options.length_hunks) {
(Some(b), _) => b,
(None, Some(h)) => h * hunk_size as u64,
(None, None) => total_file_size.saturating_sub(start),
};
let end = start.checked_add(length).ok_or_else(|| {
DiscError::InvalidMetadata("specified range overflows u64".into())
})?;
if end > total_file_size {
return Err(DiscError::InvalidMetadata(
"specified range exceeds input file size".into(),
));
}
if cylinders == 0 {
let geo = guess_chs(length, sector_size)?;
cylinders = geo.cylinders;
heads = geo.heads;
sectors = geo.sectors;
}
let totalsectors = cylinders as u64 * heads as u64 * sectors as u64;
let expected_bytes = totalsectors * sector_size as u64;
(start, length, total_file_size, expected_bytes)
} else {
let capacity = if cylinders != 0 {
(cylinders as u64 * heads as u64 * sectors as u64) * sector_size as u64
} else if let Some(sz) = options.size {
let geo = guess_chs(sz, sector_size)?;
cylinders = geo.cylinders;
heads = geo.heads;
sectors = geo.sectors;
(cylinders as u64 * heads as u64 * sectors as u64) * sector_size as u64
} else {
return Err(DiscError::InvalidMetadata(
"length or CHS geometry must be specified for blank hard disk".into(),
));
};
(0, capacity, capacity, capacity)
};
let codecs = if input_path.is_none() {
[0, 0, 0, 0]
} else {
options.codecs.unwrap_or(DEFAULT_HD_CODECS)
};
Ok(HdCreateInfo {
output_path: output_path.to_path_buf(),
parent_path: options.parent_path.clone(),
input_path: input_path.map(|p| p.to_path_buf()),
input_start,
input_length,
input_total_size: total_file_size,
template_name,
cylinders,
heads,
sectors,
sector_size,
hunk_size,
logical_bytes,
codecs,
})
}
pub fn inspect_create_raw(
input_path: &Path,
output_path: &Path,
options: &CreateRawOptions,
) -> Result<RawCreateInfo, DiscError> {
let input_file = File::open(input_path)?;
let total_size = input_file.metadata()?.len();
let unit_size = options.unit_size;
if unit_size == 0 {
return Err(DiscError::InvalidMetadata(
"unit size must be non-zero".into(),
));
}
let hunk_size = options
.hunk_size
.unwrap_or_else(|| ((4096 / unit_size) * unit_size).max(unit_size));
let start = match (options.start_byte, options.start_hunk) {
(Some(b), _) => b,
(None, Some(h)) => h * hunk_size as u64,
(None, None) => 0,
};
let length = match (options.length_bytes, options.length_hunks) {
(Some(b), _) => b,
(None, Some(h)) => h * hunk_size as u64,
(None, None) => total_size.saturating_sub(start),
};
let end = start
.checked_add(length)
.ok_or_else(|| DiscError::InvalidMetadata("specified range overflows u64".into()))?;
if end > total_size {
return Err(DiscError::InvalidMetadata(
"specified range exceeds input file size".into(),
));
}
if !length.is_multiple_of(unit_size as u64) {
return Err(DiscError::InvalidMetadata(format!(
"data length {} is not divisible by unit size {}",
length, unit_size
)));
}
let codecs = options.codecs.unwrap_or(DEFAULT_RAW_CODECS);
Ok(RawCreateInfo {
output_path: output_path.to_path_buf(),
parent_path: options.parent_path.clone(),
input_path: input_path.to_path_buf(),
input_start: start,
input_length: length,
input_total_size: total_size,
logical_bytes: length,
hunk_size,
unit_size,
codecs,
})
}