diff --git a/src/error.rs b/src/error.rs index d03687f..38c4635 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +//! Error type for NRGrip. + use std::error::Error; use std::fmt; use std::io; diff --git a/src/lib.rs b/src/lib.rs index e909d0e..2189314 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,252 +1,2 @@ -use std::fs::File; -use std::io::{Seek, SeekFrom}; - -mod error; -use error::NrgError; - -mod metadata; -use metadata::NrgMetadata; - -mod cuex; -use cuex::{NrgCuex, NrgCuexTrack}; - -mod daox; -use daox::{NrgDaox, NrgDaoxTrack}; - -mod sinf; -use sinf::{NrgSinf}; - -mod mtyp; -use mtyp::{NrgMtyp}; - -mod readers; -use readers::*; - - -pub fn parse_nrg_metadata(img_name: String) -> Result { - let mut nm = NrgMetadata::new(); - - // Open the image file - let mut fd = try!(File::open(img_name)); - - // Get the file size - nm.file_size = try!(fd.seek(SeekFrom::End(0))); - - // Get the NRG format from the footer - nm.nrg_version = try!(read_nrg_version(&mut fd, nm.file_size)); - if nm.nrg_version != 2 { - // We handle only NRG v2 - return Err(NrgError::NrgFormatV1); - } - - // Read the first chunk offset - nm.chunk_offset = try!(read_u64(&mut fd)); - - // Read all the chunks - try!(fd.seek(SeekFrom::Start(nm.chunk_offset))); - try!(read_nrg_chunks(&mut fd, &mut nm)); - - Ok(nm) -} - - -/// Determines the NRG format of an open NRG image `fd` of file `file_size`. -/// -/// The offset is left after the main chunk ID, therefore the calling function -/// can read the first data chunk's offset (32 bits for NRG v1 or 64 bits for -/// NRG v2) directly without seeking. -fn read_nrg_version(fd: &mut File, file_size: u64) -> Result { - if file_size < 12 { - // Input file too small - return Err(NrgError::NrgFormat); - } - - // In NRG v2, the main footer is on the last 12 bytes - try!(fd.seek(SeekFrom::End(-12))); - let chunk_id = try!(read_nrg_chunk_id(fd)); - if chunk_id == "NER5" { - return Ok(2); // NRG v2 - } - - // In NRG v1, the main footer is on the last 8 bytes; since we just read 4 - // bytes after seeking 12 bytes before the end, the offset is right - let chunk_id = try!(read_nrg_chunk_id(fd)); - if chunk_id == "NERO" { - return Ok(1); // NRG v1 - } - - Err(NrgError::NrgFormat) // Unknown format -} - - -/// Reads all the available NRG chunks. -/// -/// Returns the number of chunks read. -fn read_nrg_chunks(fd: &mut File, nm: &mut NrgMetadata) -> Result { - let mut nread = 0u16; - - loop { - let chunk_id = try!(read_nrg_chunk_id(fd)); - nread += 1; - match chunk_id.as_ref() { - "END!" => break, - "CUEX" => { nm.cuex_chunk = Some(try!(read_nrg_cuex(fd))); }, - "DAOX" => { nm.daox_chunk = Some(try!(read_nrg_daox(fd))); }, - "CDTX" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, - "ETN2" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, - "SINF" => { nm.sinf_chunk = Some(try!(read_nrg_sinf(fd))); }, - "MTYP" => { nm.mtyp_chunk = Some(try!(read_nrg_mtyp(fd))); }, - "DINF" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, - "TOCT" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, - "RELO" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, - _ => { println!("{}", chunk_id); return Err(NrgError::NrgChunkId); }, //fixme - } - } - - Ok(nread) -} - - -/// Reads an NRG chunk ID (i.e. a 4-byte string) from `fd`. -fn read_nrg_chunk_id(fd: &File) -> Result { - read_sized_string(fd, 4) -} - - -fn skip_unhandled_chunk(fd: &mut File, chunk_id: &str) -> Result<(), NrgError> { - let chunk_size = try!(read_u32(fd)); - try!(fd.seek(SeekFrom::Current(chunk_size as i64))); - // fixme: lib should'nt print! - println!("Skipping unhandled chunk: {} ({} bytes)", chunk_id, chunk_size); - Ok(()) -} - - -/// Reads the NRG Cue Sheet chunk (CUEX). -/// -/// The CUEX is constituted of the following data: -/// -/// - 4 B: Chunk size (in bytes): size to be read *after* this chunk size -/// (should be a multiple of 8) -/// -/// - one or more pairs of 8-byte blocks composed of: -/// + 1 B: Mode (values found: 0x01 for audio; 0x21 for non -/// copyright-protected audio; 0x41 for data) -/// + 1 B: Track number (BCD coded; 0xAA for the lead-out area) -/// + 1 B: Index number (probably BCD coded): 0 or 1 -/// + 1 B: Unknown (padding?), always 0 -/// + 4 B: Position in sectors (signed integer value) -/// -/// - one last block like the ones above, for the lead-out area (optional?) -fn read_nrg_cuex(fd: &mut File) -> Result { - let mut chunk = NrgCuex::new(); - chunk.size = try!(read_u32(fd)); - let mut bytes_read = 0; - - // Read all the 8-byte track info - while bytes_read < chunk.size { - chunk.tracks.push(try!(read_nrg_cuex_track(fd))); - bytes_read += 8; - } - - assert_eq!(bytes_read, chunk.size); - - Ok(chunk) -} - - -/// Reads a track from the NRG cue sheet. -fn read_nrg_cuex_track(fd: &mut File) -> Result { - let mut track = NrgCuexTrack::new(); - track.mode = try!(read_u8(fd)); - track.track_number = try!(read_u8(fd)); - track.index_number = try!(read_u8(fd)); - track.padding = try!(read_u8(fd)); - track.position_sectors = try!(read_u32(fd)) as i32; - Ok(track) -} - - -/// Reads the NRG Disc-At-Once Information chunk (DAOX). -/// -/// The DAOX is constituted of the following data: -/// -/// - 4 B: Chunk size (in bytes) -/// - 4 B: Chunk size again, sometimes little endian -/// - 13 B: UPC (text) or null bytes -/// - 1 B: Unknown (padding?), always 0 -/// - 2 B: TOC type -/// - 1 B: First track in the session -/// - 1 B: Last track in the session -/// -/// Followed by one or more groups of 42-byte blocks composed of: -/// - 12 B: ISRC (text) or null bytes -/// - 2 B: Sector size in the image file (bytes) -/// - 2 B: Mode of the data in the image file -/// - 2 B: Unknown (should always be 0x0001) -/// - 8 B: Index0 (Pre-gap) (bytes) -/// - 8 B: Index1 (Start of track) (bytes) -/// - 8 B: End of track + 1 (bytes) -fn read_nrg_daox(fd: &mut File) -> Result { - let mut chunk = NrgDaox::new(); - chunk.size = try!(read_u32(fd)); - let mut bytes_read = 0; - - chunk.size2 = try!(read_u32(fd)); - bytes_read += 4; // 32 bits - - chunk.upc = try!(read_sized_string(fd, 13)); - bytes_read += 13; - - chunk.padding = try!(read_u8(fd)); - bytes_read += 1; - - chunk.toc_type = try!(read_u16(fd)); - bytes_read += 2; - - chunk.first_track = try!(read_u8(fd)); - chunk.last_track = try!(read_u8(fd)); - bytes_read += 2; - - // Read all the 42-byte track info - while bytes_read < chunk.size { - chunk.tracks.push(try!(read_nrg_daox_track(fd))); - bytes_read += 42; - } - - assert_eq!(bytes_read, chunk.size); - - Ok(chunk) -} - - -/// Reads a track from the NRG DAO Information. -fn read_nrg_daox_track(fd: &mut File) -> Result { - let mut track = NrgDaoxTrack::new(); - track.isrc = try!(read_sized_string(fd, 12)); - track.sector_size = try!(read_u16(fd)); - track.data_mode = try!(read_u16(fd)); - track.unknown = try!(read_u16(fd)); - track.index0 = try!(read_u64(fd)); - track.index1 = try!(read_u64(fd)); - track.track_end = try!(read_u64(fd)); - Ok(track) -} - - -/// Reads the NRG Session Information chunk (SINF). -fn read_nrg_sinf(fd: &mut File) -> Result { - let mut chunk = NrgSinf::new(); - chunk.size = try!(read_u32(fd)); - chunk.nb_tracks = try!(read_u32(fd)); - Ok(chunk) -} - - -/// Reads the Media Type (?) chunk (MTYP). -fn read_nrg_mtyp(fd: &mut File) -> Result { - let mut chunk = NrgMtyp::new(); - chunk.size = try!(read_u32(fd)); - chunk.unknown = try!(read_u32(fd)); - Ok(chunk) -} +pub mod metadata; +pub mod error; diff --git a/src/main.rs b/src/main.rs index 6177a0a..1df1f1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ fn main() { exit_usage(&prog_name); } - match nrgrip::parse_nrg_metadata(img_name) { + match nrgrip::metadata::parse_nrg_metadata(img_name) { Err(err) => println!("{}", err.to_string()), Ok(metadata) => println!("\n\ *** Metadata ***\n\ diff --git a/src/cuex.rs b/src/metadata/cuex.rs similarity index 57% rename from src/cuex.rs rename to src/metadata/cuex.rs index bbe359d..3c881bd 100644 --- a/src/cuex.rs +++ b/src/metadata/cuex.rs @@ -1,4 +1,10 @@ +//! NRG CUEX chunk data structure and associated functions. + use std::fmt; +use std::fs::File; + +use ::error::NrgError; +use super::readers::*; #[derive(Debug)] @@ -82,3 +88,48 @@ impl fmt::Display for NrgCuexTrack { self.position_sectors, position_seconds) } } + + +/// Reads the NRG Cue Sheet chunk (CUEX) from `fd`. +/// +/// The CUEX is constituted of the following data: +/// +/// - 4 B: Chunk size (in bytes): size to be read *after* this chunk size +/// (should be a multiple of 8) +/// +/// - one or more pairs of 8-byte blocks composed of: +/// + 1 B: Mode (values found: 0x01 for audio; 0x21 for non +/// copyright-protected audio; 0x41 for data) +/// + 1 B: Track number (BCD coded; 0xAA for the lead-out area) +/// + 1 B: Index number (probably BCD coded): 0 or 1 +/// + 1 B: Unknown (padding?), always 0 +/// + 4 B: Position in sectors (signed integer value) +/// +/// - one last block like the ones above, for the lead-out area (optional?) +pub fn read_nrg_cuex(fd: &mut File) -> Result { + let mut chunk = NrgCuex::new(); + chunk.size = try!(read_u32(fd)); + let mut bytes_read = 0; + + // Read all the 8-byte track info + while bytes_read < chunk.size { + chunk.tracks.push(try!(read_nrg_cuex_track(fd))); + bytes_read += 8; + } + + assert_eq!(bytes_read, chunk.size); + + Ok(chunk) +} + + +/// Reads a track from the NRG cue sheet. +fn read_nrg_cuex_track(fd: &mut File) -> Result { + let mut track = NrgCuexTrack::new(); + track.mode = try!(read_u8(fd)); + track.track_number = try!(read_u8(fd)); + track.index_number = try!(read_u8(fd)); + track.padding = try!(read_u8(fd)); + track.position_sectors = try!(read_u32(fd)) as i32; + Ok(track) +} diff --git a/src/daox.rs b/src/metadata/daox.rs similarity index 59% rename from src/daox.rs rename to src/metadata/daox.rs index d0abf4f..f2a993d 100644 --- a/src/daox.rs +++ b/src/metadata/daox.rs @@ -1,4 +1,10 @@ +//! NRG DAOX chunk data structure and associated functions. + use std::fmt; +use std::fs::File; + +use ::error::NrgError; +use super::readers::*; #[derive(Debug)] @@ -116,3 +122,70 @@ impl fmt::Display for NrgDaoxTrack { self.track_end) } } + + +/// Reads the NRG Disc-At-Once Information chunk (DAOX). +/// +/// The DAOX is constituted of the following data: +/// +/// - 4 B: Chunk size (in bytes) +/// - 4 B: Chunk size again, sometimes little endian +/// - 13 B: UPC (text) or null bytes +/// - 1 B: Unknown (padding?), always 0 +/// - 2 B: TOC type +/// - 1 B: First track in the session +/// - 1 B: Last track in the session +/// +/// Followed by one or more groups of 42-byte blocks composed of: +/// - 12 B: ISRC (text) or null bytes +/// - 2 B: Sector size in the image file (bytes) +/// - 2 B: Mode of the data in the image file +/// - 2 B: Unknown (should always be 0x0001) +/// - 8 B: Index0 (Pre-gap) (bytes) +/// - 8 B: Index1 (Start of track) (bytes) +/// - 8 B: End of track + 1 (bytes) +pub fn read_nrg_daox(fd: &mut File) -> Result { + let mut chunk = NrgDaox::new(); + chunk.size = try!(read_u32(fd)); + let mut bytes_read = 0; + + chunk.size2 = try!(read_u32(fd)); + bytes_read += 4; // 32 bits + + chunk.upc = try!(read_sized_string(fd, 13)); + bytes_read += 13; + + chunk.padding = try!(read_u8(fd)); + bytes_read += 1; + + chunk.toc_type = try!(read_u16(fd)); + bytes_read += 2; + + chunk.first_track = try!(read_u8(fd)); + chunk.last_track = try!(read_u8(fd)); + bytes_read += 2; + + // Read all the 42-byte track info + while bytes_read < chunk.size { + chunk.tracks.push(try!(read_nrg_daox_track(fd))); + bytes_read += 42; + } + + assert_eq!(bytes_read, chunk.size); + + Ok(chunk) +} + + +/// Reads a track from the NRG DAO Information. +fn read_nrg_daox_track(fd: &mut File) -> Result { + let mut track = NrgDaoxTrack::new(); + track.isrc = try!(read_sized_string(fd, 12)); + track.sector_size = try!(read_u16(fd)); + track.data_mode = try!(read_u16(fd)); + track.unknown = try!(read_u16(fd)); + track.index0 = try!(read_u64(fd)); + track.index1 = try!(read_u64(fd)); + track.track_end = try!(read_u64(fd)); + Ok(track) +} diff --git a/src/metadata.rs b/src/metadata/metadata.rs similarity index 90% rename from src/metadata.rs rename to src/metadata/metadata.rs index c3922b0..2d57f37 100644 --- a/src/metadata.rs +++ b/src/metadata/metadata.rs @@ -1,9 +1,11 @@ +//! NRG metadata structure, storing the contents of all of the NRG chunks. + use std::fmt; -use cuex::NrgCuex; -use daox::NrgDaox; -use sinf::NrgSinf; -use mtyp::NrgMtyp; +use super::cuex::NrgCuex; +use super::daox::NrgDaox; +use super::sinf::NrgSinf; +use super::mtyp::NrgMtyp; #[derive(Debug)] diff --git a/src/metadata/mod.rs b/src/metadata/mod.rs new file mode 100644 index 0000000..44b66da --- /dev/null +++ b/src/metadata/mod.rs @@ -0,0 +1,115 @@ +//! Module to read and store the metadata from an NRG image file. + +use std::fs::File; +use std::io::{Seek, SeekFrom}; + +use ::error::NrgError; + +mod metadata; +mod cuex; +mod daox; +mod sinf; +mod mtyp; +mod readers; + +use self::metadata::NrgMetadata; +use self::readers::*; + + +pub fn parse_nrg_metadata(img_name: String) -> Result { + let mut nm = NrgMetadata::new(); + + // Open the image file + let mut fd = try!(File::open(img_name)); + + // Get the file size + nm.file_size = try!(fd.seek(SeekFrom::End(0))); + + // Get the NRG format from the footer + nm.nrg_version = try!(read_nrg_version(&mut fd, nm.file_size)); + if nm.nrg_version != 2 { + // We handle only NRG v2 + return Err(NrgError::NrgFormatV1); + } + + // Read the first chunk offset + nm.chunk_offset = try!(read_u64(&mut fd)); + + // Read all the chunks + try!(fd.seek(SeekFrom::Start(nm.chunk_offset))); + try!(read_nrg_chunks(&mut fd, &mut nm)); + + Ok(nm) +} + + +/// Determines the NRG format of an open NRG image `fd` of file `file_size`. +/// +/// The offset is left after the main chunk ID, therefore the calling function +/// can read the first data chunk's offset (32 bits for NRG v1 or 64 bits for +/// NRG v2) directly without seeking. +fn read_nrg_version(fd: &mut File, file_size: u64) -> Result { + if file_size < 12 { + // Input file too small + return Err(NrgError::NrgFormat); + } + + // In NRG v2, the main footer is on the last 12 bytes + try!(fd.seek(SeekFrom::End(-12))); + let chunk_id = try!(read_nrg_chunk_id(fd)); + if chunk_id == "NER5" { + return Ok(2); // NRG v2 + } + + // In NRG v1, the main footer is on the last 8 bytes; since we just read 4 + // bytes after seeking 12 bytes before the end, the offset is right + let chunk_id = try!(read_nrg_chunk_id(fd)); + if chunk_id == "NERO" { + return Ok(1); // NRG v1 + } + + Err(NrgError::NrgFormat) // Unknown format +} + + +/// Reads all the available NRG chunks. +/// +/// Returns the number of chunks read. +fn read_nrg_chunks(fd: &mut File, nm: &mut NrgMetadata) -> Result { + let mut nread = 0u16; + + loop { + let chunk_id = try!(read_nrg_chunk_id(fd)); + nread += 1; + match chunk_id.as_ref() { + "END!" => break, + "CUEX" => { nm.cuex_chunk = Some(try!(cuex::read_nrg_cuex(fd))); }, + "DAOX" => { nm.daox_chunk = Some(try!(daox::read_nrg_daox(fd))); }, + "CDTX" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, + "ETN2" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, + "SINF" => { nm.sinf_chunk = Some(try!(sinf::read_nrg_sinf(fd))); }, + "MTYP" => { nm.mtyp_chunk = Some(try!(mtyp::read_nrg_mtyp(fd))); }, + "DINF" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, + "TOCT" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, + "RELO" => { try!(skip_unhandled_chunk(fd, &chunk_id)); }, + _ => { println!("{}", chunk_id); return Err(NrgError::NrgChunkId); }, //fixme + } + } + + Ok(nread) +} + + +/// Reads an NRG chunk ID (i.e. a 4-byte string) from `fd`. +fn read_nrg_chunk_id(fd: &File) -> Result { + read_sized_string(fd, 4) +} + + +fn skip_unhandled_chunk(fd: &mut File, chunk_id: &str) -> Result<(), NrgError> { + let chunk_size = try!(read_u32(fd)); + try!(fd.seek(SeekFrom::Current(chunk_size as i64))); + // fixme: lib should'nt print! + println!("Skipping unhandled chunk: {} ({} bytes)", chunk_id, chunk_size); + Ok(()) +} diff --git a/src/mtyp.rs b/src/metadata/mtyp.rs similarity index 59% rename from src/mtyp.rs rename to src/metadata/mtyp.rs index 02f55ef..95994bf 100644 --- a/src/mtyp.rs +++ b/src/metadata/mtyp.rs @@ -1,4 +1,10 @@ +//! NRG MTYP chunk data structure and associated functions. + use std::fmt; +use std::fs::File; + +use ::error::NrgError; +use super::readers::read_u32; #[derive(Debug)] @@ -26,3 +32,12 @@ impl fmt::Display for NrgMtyp { self.unknown) } } + + +/// Reads the Media Type (?) chunk (MTYP). +pub fn read_nrg_mtyp(fd: &mut File) -> Result { + let mut chunk = NrgMtyp::new(); + chunk.size = try!(read_u32(fd)); + chunk.unknown = try!(read_u32(fd)); + Ok(chunk) +} diff --git a/src/readers.rs b/src/metadata/readers.rs similarity index 93% rename from src/readers.rs rename to src/metadata/readers.rs index 9714534..00f7be5 100644 --- a/src/readers.rs +++ b/src/metadata/readers.rs @@ -1,8 +1,10 @@ +//! Miscellaneous functions to read fixed-size data from a file. + use std::fs::File; use std::io::Read; use std::mem; -use error::NrgError; +use ::error::NrgError; /// Reads a String of size `size` from `fd`. diff --git a/src/sinf.rs b/src/metadata/sinf.rs similarity index 60% rename from src/sinf.rs rename to src/metadata/sinf.rs index 072473d..a0b5083 100644 --- a/src/sinf.rs +++ b/src/metadata/sinf.rs @@ -1,4 +1,10 @@ +//! NRG SINF chunk data structure and associated functions. + use std::fmt; +use std::fs::File; + +use ::error::NrgError; +use super::readers::read_u32; #[derive(Debug)] @@ -26,3 +32,12 @@ impl fmt::Display for NrgSinf { self.nb_tracks) } } + + +/// Reads the NRG Session Information chunk (SINF). +pub fn read_nrg_sinf(fd: &mut File) -> Result { + let mut chunk = NrgSinf::new(); + chunk.size = try!(read_u32(fd)); + chunk.nb_tracks = try!(read_u32(fd)); + Ok(chunk) +}