use crate::ccd::CcdSheet;
use crate::cue::CueSheet;
use crate::error::DiscError;
use crate::gdi::GdiSheet;
use crate::mds::MdsDisc;
use crate::nrg::NrgDisc;
use crate::toc::CdrdaoToc;
use crate::types::CdToc;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
pub const DEFAULT_RAW_CODECS: [u32; 4] = [
u32::from_be_bytes(*b"lzma"),
u32::from_be_bytes(*b"zlib"),
u32::from_be_bytes(*b"huff"),
u32::from_be_bytes(*b"flac"),
];
pub const DEFAULT_HD_CODECS: [u32; 4] = [
u32::from_be_bytes(*b"lzma"),
u32::from_be_bytes(*b"zlib"),
u32::from_be_bytes(*b"huff"),
u32::from_be_bytes(*b"flac"),
];
pub const DEFAULT_CD_CODECS: [u32; 4] = [
u32::from_be_bytes(*b"cdlz"),
u32::from_be_bytes(*b"cdzl"),
u32::from_be_bytes(*b"cdfl"),
0,
];
pub fn parse_compression_codecs(s: &str) -> Result<[u32; 4], DiscError> {
let clean = s.trim();
if clean == "none" {
return Ok([0, 0, 0, 0]);
}
let mut result = [0u32; 4];
let mut idx = 0;
for part in clean.split(',') {
let trimmed = part.trim();
if trimmed.is_empty() {
continue;
}
if idx >= 4 {
return Err(DiscError::InvalidMetadata(
"at most 4 codecs allowed".into(),
));
}
if trimmed.len() != 4 {
return Err(DiscError::InvalidMetadata(format!(
"invalid compressor '{}': must be 4 characters or 'none'",
trimmed
)));
}
let bytes = trimmed.as_bytes();
let tag = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
result[idx] = tag;
idx += 1;
}
Ok(result)
}
pub fn find_wav_data_chunk(path: &Path) -> Result<(u64, u64), DiscError> {
let mut file = File::open(path)?;
let mut riff_hdr = [0u8; 12];
file.read_exact(&mut riff_hdr)?;
if &riff_hdr[0..4] != b"RIFF" || &riff_hdr[8..12] != b"WAVE" {
return Err(DiscError::InvalidCue(format!(
"file '{}' is not a valid RIFF/WAVE file",
path.display()
)));
}
let file_size = file.metadata()?.len();
let mut cur_pos = 12u64;
while cur_pos + 8 <= file_size {
file.seek(SeekFrom::Start(cur_pos))?;
let mut chunk_hdr = [0u8; 8];
file.read_exact(&mut chunk_hdr)?;
let chunk_tag = &chunk_hdr[0..4];
let chunk_len =
u32::from_le_bytes([chunk_hdr[4], chunk_hdr[5], chunk_hdr[6], chunk_hdr[7]]) as u64;
cur_pos += 8;
if chunk_tag == b"data" {
return Ok((cur_pos, chunk_len));
}
cur_pos += chunk_len;
if !cur_pos.is_multiple_of(2) {
cur_pos += 1;
}
}
Err(DiscError::InvalidCue(format!(
"could not find 'data' chunk in WAV file '{}'",
path.display()
)))
}
pub fn parse_cd_descriptor(input_path: &Path) -> Result<CdToc, DiscError> {
let parent_dir = input_path
.parent()
.unwrap_or_else(|| Path::new("."))
.to_path_buf();
let ext = input_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_ascii_lowercase();
match ext.as_str() {
"cue" => {
let content = std::fs::read_to_string(input_path)?;
CueSheet::parse(&content, Some(&parent_dir))
}
"toc" => {
let content = std::fs::read_to_string(input_path)?;
CdrdaoToc::parse(&content)
}
"gdi" => {
let content = std::fs::read_to_string(input_path)?;
GdiSheet::parse(&content, Some(&parent_dir))
}
"ccd" => {
let content = std::fs::read_to_string(input_path)?;
let sheet = CcdSheet::parse(&content)?;
let mut img_path = input_path.with_extension("img");
if !img_path.is_file() {
let upper = input_path.with_extension("IMG");
if upper.is_file() {
img_path = upper;
} else {
return Err(DiscError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("CloneCD image payload not found: '{}'", img_path.display()),
)));
}
}
let img_file_name = img_path
.file_name()
.and_then(|n| n.to_str())
.map(|s| s.to_string())
.ok_or_else(|| {
DiscError::InvalidCcd("invalid file name for CCD image".to_string())
})?;
sheet.to_toc(Some(&parent_dir), Some(&img_file_name))
}
"mds" => MdsDisc::parse_file(input_path),
"nrg" => NrgDisc::parse_file(input_path),
other => Err(DiscError::InvalidMetadata(format!(
"unsupported CD descriptor format '.{}'",
other
))),
}
}