Ported psxcdgen over
This commit is contained in:
parent
78b937239c
commit
69074600cc
|
@ -0,0 +1,4 @@
|
||||||
|
**/target
|
||||||
|
Input/**
|
||||||
|
Output/**
|
||||||
|
*.lock
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "cdtypes"
|
||||||
|
version = "0.5.5"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "*"
|
||||||
|
paste = "*"
|
||||||
|
chrono = "*"
|
|
@ -0,0 +1,292 @@
|
||||||
|
pub mod reader;
|
||||||
|
pub mod sector;
|
||||||
|
|
||||||
|
use sector::*;
|
||||||
|
use reader::Reader as CDReader;
|
||||||
|
use super::{Error, types as types};
|
||||||
|
use types::{cue::{Specifier, DataType, DataTypeEnd}, lsb_msb::ReadWriteEndian, pvd::PrimaryVolumeDescriptor, sector::{Mode2Form1, SubHeaderForm}};
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
pub struct DirectoryRecordIterator<'a> {
|
||||||
|
parent: &'a CD,
|
||||||
|
cur_sector_id: usize,
|
||||||
|
cur_data: &'a [u8],
|
||||||
|
is_last: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DirectoryRecordIterator<'a> {
|
||||||
|
pub fn new(parent: &CD, sector: usize) -> DirectoryRecordIterator {
|
||||||
|
let mut new_iter = DirectoryRecordIterator{parent: parent, cur_sector_id: sector, cur_data: &[], is_last: true};
|
||||||
|
|
||||||
|
if let Some((data, is_last)) = new_iter.load(sector) {
|
||||||
|
new_iter.cur_data = data;
|
||||||
|
new_iter.is_last = is_last;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_iter
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self, sector_id: usize) -> Option<(&'a [u8], bool)> {
|
||||||
|
if let Some(sector) = self.parent.get_sector(sector_id) {
|
||||||
|
if let Some(sub_header) = sector.get_sub_header() {
|
||||||
|
return Some((sector.get_data(), sub_header.sub_mode.is_eof()));
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::Iterator for DirectoryRecordIterator<'a> {
|
||||||
|
type Item = &'a types::dir_record::DirectoryRecord;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.cur_data.is_empty() {
|
||||||
|
if self.is_last {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
self.cur_sector_id += 1;
|
||||||
|
if let Some((data, is_last)) = self.load(self.cur_sector_id) {
|
||||||
|
self.cur_data = data;
|
||||||
|
self.is_last = is_last;
|
||||||
|
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
let mut data_range = self.cur_data.as_ptr_range();
|
||||||
|
unsafe {
|
||||||
|
if let Some(cur_dir) = (data_range.start as *const types::dir_record::DirectoryRecord).as_ref() {
|
||||||
|
let dir_length = cur_dir.length[0] as usize;
|
||||||
|
if dir_length == 0 {
|
||||||
|
self.cur_data = &[];
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
data_range.start = data_range.start.add(dir_length);
|
||||||
|
|
||||||
|
self.cur_data = std::slice::from_raw_parts(data_range.start, data_range.end.offset_from(data_range.start) as usize);
|
||||||
|
Some(cur_dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PathTableIterator<'a> {
|
||||||
|
path_table: Box<[u8]>,
|
||||||
|
cur_idx: usize,
|
||||||
|
phantom: std::marker::PhantomData<&'a u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PathTableIterator<'a> {
|
||||||
|
pub fn new(cd: &CD, mut sector: usize) -> PathTableIterator {
|
||||||
|
let mut data = Vec::<u8>::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(sector) = cd.get_sector(sector) {
|
||||||
|
if let Some(sub_header) = sector.get_sub_header() {
|
||||||
|
for byte in sector.get_data() {
|
||||||
|
data.push(*byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
if sub_header.sub_mode.is_eof() {
|
||||||
|
return PathTableIterator{path_table: data.into_boxed_slice(), cur_idx: 0usize, phantom: std::marker::PhantomData::default()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return PathTableIterator::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return PathTableIterator::default();
|
||||||
|
}
|
||||||
|
sector += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::Iterator for PathTableIterator<'a> {
|
||||||
|
type Item = &'a types::path_table::PathTableL;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if (self.cur_idx + std::mem::size_of::<Self::Item>()) < self.path_table.len() {
|
||||||
|
unsafe {
|
||||||
|
if let Some(entry) = (self.path_table.as_ptr().add(self.cur_idx) as *const types::path_table::PathTableL).as_ref() {
|
||||||
|
if entry.name_length[0] > 0 {
|
||||||
|
self.cur_idx += entry.get_size();
|
||||||
|
Some(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::default::Default for PathTableIterator<'a> {
|
||||||
|
fn default() -> PathTableIterator<'a> {
|
||||||
|
PathTableIterator{path_table: Box::default(), cur_idx: 0usize, phantom: std::marker::PhantomData::default()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SectorIterator<'a> {
|
||||||
|
parent: &'a CD,
|
||||||
|
cur_sec_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SectorIterator<'a> {
|
||||||
|
pub fn new(cd: &'a CD) -> SectorIterator<'a> {
|
||||||
|
SectorIterator{parent: cd, cur_sec_idx: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::Iterator for SectorIterator<'a> {
|
||||||
|
type Item = &'a Sector;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let value = self.parent.get_sector(self.cur_sec_idx);
|
||||||
|
|
||||||
|
self.cur_sec_idx += 1;
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CD {
|
||||||
|
sectors: Vec<Sector>,
|
||||||
|
track_info: Vec<TrackInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CD {
|
||||||
|
pub fn from_file(file: File, specifier: Option<Vec<Specifier>>) -> Result<CD, Error> {
|
||||||
|
let data_type_ends = {
|
||||||
|
if let Some(specifier) = specifier {
|
||||||
|
DataTypeEnd::parse_cue_specifier(specifier)?
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let reader = CDReader::new(file, &data_type_ends)?;
|
||||||
|
let mut sectors = Vec::new();
|
||||||
|
|
||||||
|
for sector in reader {
|
||||||
|
sectors.push(sector?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CD{sectors, track_info: TrackInfo::new(data_type_ends)})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sector_count(&self) -> usize {
|
||||||
|
self.sectors.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last_sector_idx(&self) -> usize {
|
||||||
|
Self::sector_count(self) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sector(&self, idx: usize) -> Option<&Sector> {
|
||||||
|
self.sectors.get(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_tracks(&self) -> bool {
|
||||||
|
!self.track_info.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_primary_volume_descriptor(&self) -> Result<&PrimaryVolumeDescriptor, Error> {
|
||||||
|
if let Some(pvd_sec) = self.get_sector(16) {
|
||||||
|
if let Sector::CDXAData(pvd_sec) = pvd_sec {
|
||||||
|
unsafe {
|
||||||
|
match pvd_sec.sub_header.get_form() {
|
||||||
|
SubHeaderForm::Form1 => Ok(std::mem::transmute::<&[u8; Mode2Form1::DATA_SIZE], &PrimaryVolumeDescriptor>(&pvd_sec.data)),
|
||||||
|
_ => Err(Error::GenericError("Sector is not CD-XA Data Form 1".to_string()))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("Sector is not CD-XA Data".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("Sector not found".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn directory_record_iter(&self, sector: usize) -> DirectoryRecordIterator {
|
||||||
|
DirectoryRecordIterator::new(self, sector)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_table_iter(&self) -> PathTableIterator {
|
||||||
|
if let Ok(pvd) = self.get_primary_volume_descriptor() {
|
||||||
|
PathTableIterator::new(self, pvd.path_table_1.read() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
PathTableIterator::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sector_iter(&self) -> SectorIterator {
|
||||||
|
SectorIterator::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track_info_iter(&self) -> std::slice::Iter<TrackInfo> {
|
||||||
|
self.track_info.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TrackInfo {
|
||||||
|
pub r#type: DataType,
|
||||||
|
pub start: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackInfo {
|
||||||
|
pub fn new(data_type_ends: Vec<DataTypeEnd>) -> Vec<TrackInfo> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
let mut start = 0;
|
||||||
|
|
||||||
|
for data_type_end in data_type_ends {
|
||||||
|
data.push(TrackInfo{r#type: data_type_end.r#type, start});
|
||||||
|
start = data_type_end.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
use super::sector::Sector;
|
||||||
|
use super::super::{Error, types::{cue::{DataType, DataTypeEnd}, sector::*}};
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
pub struct Reader<'a> {
|
||||||
|
file: std::fs::File,
|
||||||
|
data_type_guide: &'a Vec<DataTypeEnd>,
|
||||||
|
lba: usize,
|
||||||
|
active: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Reader<'a> {
|
||||||
|
pub fn new(file: std::fs::File, data_type_guide: &'a Vec<DataTypeEnd>) -> Result<Reader, Error> {
|
||||||
|
Ok(Reader{
|
||||||
|
file, data_type_guide, lba: 0, active: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_data_type(&mut self) -> Option<DataType> {
|
||||||
|
self.lba += 1;
|
||||||
|
for data_type in self.data_type_guide.iter() {
|
||||||
|
if self.lba < data_type.end {
|
||||||
|
return Some(data_type.r#type.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_audio_sector(&mut self) -> Result<Option<Sector>, Error> {
|
||||||
|
let mut buffer = [0;std::mem::size_of::<Audio>()];
|
||||||
|
let bytes = self.file.read(&mut buffer)?;
|
||||||
|
|
||||||
|
if bytes == 0 {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
unsafe {
|
||||||
|
Ok(Some(Sector::Audio(std::mem::transmute::<[u8; std::mem::size_of::<Audio>()], Audio>(buffer))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_binary_sector(&mut self) -> Result<Option<Sector>, Error> {
|
||||||
|
fn make_ok_some(sector: Sector) -> Result<Option<Sector>, Error> {
|
||||||
|
Ok(Some(sector))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = [0;std::mem::size_of::<Mode2Form1>()];
|
||||||
|
let bytes = self.file.read(&mut buffer)?;
|
||||||
|
|
||||||
|
if bytes == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let sector = &*(buffer.as_ptr() as *const Mode0);
|
||||||
|
let mode = sector.header.get_mode();
|
||||||
|
|
||||||
|
match mode {
|
||||||
|
Ok(HeaderMode::Mode0) => make_ok_some(Sector::Empty(std::mem::transmute::<[u8;std::mem::size_of::<Mode0>()], Mode0>(buffer))),
|
||||||
|
Ok(HeaderMode::Mode1) => make_ok_some(Sector::CDData(std::mem::transmute::<[u8;std::mem::size_of::<Mode1>()], Mode1>(buffer))),
|
||||||
|
Ok(HeaderMode::Mode2) => {
|
||||||
|
let sector = &*(buffer.as_ptr() as *const Mode2);
|
||||||
|
match sector.sub_header.get_form() {
|
||||||
|
SubHeaderForm::Form1 => make_ok_some(Sector::CDXAData(std::mem::transmute::<[u8;std::mem::size_of::<Mode2Form1>()], Mode2Form1>(buffer))),
|
||||||
|
SubHeaderForm::Form2 => make_ok_some(Sector::CDXAAudio(std::mem::transmute::<[u8;std::mem::size_of::<Mode2Form2>()], Mode2Form2>(buffer))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(mode) => {
|
||||||
|
return Err(Error::TypeError(format!("Unkown Mode: {} @LBA: {}", mode, self.lba - 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::Iterator for Reader<'a> {
|
||||||
|
type Item = Result<Sector, Error>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.active {
|
||||||
|
let function = {
|
||||||
|
match self.next_data_type() {
|
||||||
|
Some(DataType::Audio) => Self::process_audio_sector,
|
||||||
|
None |
|
||||||
|
Some(DataType::Mode2_2352) => Self::process_binary_sector,
|
||||||
|
Some(data_type) => return Some(Err(Error::CUEDataTypeNotSupported(data_type)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match function(self) {
|
||||||
|
Ok(sector) => {
|
||||||
|
if let Some(sector) = sector {
|
||||||
|
Some(Ok(sector))
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
self.active = false;
|
||||||
|
return Some(Err(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
use super::super::{types::{error_correction::*, sector::*}};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Sector {
|
||||||
|
Audio(Audio),
|
||||||
|
Empty(Mode0),
|
||||||
|
CDData(Mode1),
|
||||||
|
CDXAData(Mode2Form1),
|
||||||
|
CDXAAudio(Mode2Form2),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sector {
|
||||||
|
pub fn get_header(&self) -> Option<&Header> {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(_) => None,
|
||||||
|
Sector::Empty(sec) => Some(&sec.header),
|
||||||
|
Sector::CDData(sec) => Some(&sec.header),
|
||||||
|
Sector::CDXAData(sec) => Some(&sec.header),
|
||||||
|
Sector::CDXAAudio(sec) => Some(&sec.header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sub_header(&self) -> Option<&SubHeader> {
|
||||||
|
match self {
|
||||||
|
Sector::CDXAData(sec) => Some(&sec.sub_header),
|
||||||
|
Sector::CDXAAudio(sec) => Some(&sec.sub_header),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_error_correction(&self) -> Option<(&EDC, Option<&ECC>)> {
|
||||||
|
match self {
|
||||||
|
Sector::CDData(sector) => Some((§or.edc, Some(§or.ecc))),
|
||||||
|
Sector::CDXAData(sector) => Some((§or.edc, Some(§or.ecc))),
|
||||||
|
Sector::CDXAAudio(sector) => Some((§or.edc, None)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_data_offset_with_size(&self) -> (usize, usize) {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(_) => (0x0, 0x930),
|
||||||
|
Sector::Empty(_) => (0x10, 0x920),
|
||||||
|
Sector::CDData(_) => (0x10, 0x800),
|
||||||
|
Sector::CDXAData(_) => (0x18, 0x800),
|
||||||
|
Sector::CDXAAudio(_) => (0x18, 0x914)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_friendly_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(_) => "Audio",
|
||||||
|
Sector::Empty(_) => "Mode0 (Empty)",
|
||||||
|
Sector::CDData(_) => "Mode1 (Original CDROM)",
|
||||||
|
Sector::CDXAData(_) => "Mode2/Form1 (CD-XA Data)",
|
||||||
|
Sector::CDXAAudio(_) => "Mode2/Form2 (CD-XA Audio)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_data(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(sector) => sector.get_raw_samples(),
|
||||||
|
Sector::Empty(sector) => sector.zero.as_slice(),
|
||||||
|
Sector::CDData(sector) => sector.data.as_slice(),
|
||||||
|
Sector::CDXAData(sector) => sector.data.as_slice(),
|
||||||
|
Sector::CDXAAudio(sector) => sector.data.as_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_edc(&self) -> Option<&EDC> {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(_) => None,
|
||||||
|
Sector::Empty(_) => None,
|
||||||
|
Sector::CDData(sector) => Some(§or.edc),
|
||||||
|
Sector::CDXAData(sector) => Some(§or.edc),
|
||||||
|
Sector::CDXAAudio(sector) => Some(§or.edc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ecc(&self) -> Option<&ECC> {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(_) => None,
|
||||||
|
Sector::Empty(_) => None,
|
||||||
|
Sector::CDData(sector) => Some(§or.ecc),
|
||||||
|
Sector::CDXAData(sector) => Some(§or.ecc),
|
||||||
|
Sector::CDXAAudio(_) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_edc(&mut self) {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(_) => (),
|
||||||
|
Sector::Empty(_) => (),
|
||||||
|
Sector::CDData(sector) => {
|
||||||
|
sector.calculate_edc();
|
||||||
|
},
|
||||||
|
Sector::CDXAData(sector) => {
|
||||||
|
sector.calculate_edc();
|
||||||
|
},
|
||||||
|
Sector::CDXAAudio(sector) => {
|
||||||
|
unsafe {
|
||||||
|
let raw = std::mem::transmute::<&Mode2Form2, &[u8;std::mem::size_of::<Mode2Form2>()]>(sector);
|
||||||
|
sector.edc = EDC::calculate_for(&raw[0x10..=0x92B]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_ecc(&mut self) {
|
||||||
|
match self {
|
||||||
|
Sector::Audio(_) => (),
|
||||||
|
Sector::Empty(_) => (),
|
||||||
|
Sector::CDData(sector) => sector.calculate_ecc(),
|
||||||
|
Sector::CDXAData(sector) => sector.calculate_ecc(),
|
||||||
|
Sector::CDXAAudio(_) => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
pub type ByteVector = Vec<u8>;
|
||||||
|
pub mod cd;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub enum Error {
|
||||||
|
IOError(Option<String>, std::io::Error),
|
||||||
|
TypeError(String),
|
||||||
|
GenericError(String),
|
||||||
|
NoSectorForLBA(usize),
|
||||||
|
IncorrectCUELine(String),
|
||||||
|
CUEDataTypeNotSupported(types::cue::DataType),
|
||||||
|
EOF,
|
||||||
|
TimeToLarge,
|
||||||
|
NotImplemented,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::IOError(file_opt, code) => {
|
||||||
|
if let Some(file) = file_opt {
|
||||||
|
write!(f, "IO-Error for file \"{}\": {}", file, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
write!(f, "IO-Error: {}", code)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error::TypeError(file) => write!(f, "Type error: \"{}\"", file),
|
||||||
|
Error::GenericError(text) => write!(f, "{}", text),
|
||||||
|
Error::NoSectorForLBA(lba) => write!(f, "No sector for lba {}", lba),
|
||||||
|
Error::IncorrectCUELine(line) => write!(f, "Failed parsing \"{}\" as CUE", line),
|
||||||
|
Error::CUEDataTypeNotSupported(cue_type) => write!(f, "CUE type \"{}\" not supported currently", cue_type),
|
||||||
|
Error::EOF => write!(f, "Unexpected end of file"),
|
||||||
|
Error::TimeToLarge => write!(f, "Time exceeded {}min", types::time::Time::MAX_MINUTES),
|
||||||
|
Error::NotImplemented => write!(f, "<not implemented>"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<std::io::Error> for Error {
|
||||||
|
fn from(error: std::io::Error) -> Self {
|
||||||
|
Error::IOError(None, error)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BCDValue {
|
||||||
|
value: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BCDValue {
|
||||||
|
pub fn get_decimal(&self) -> u8 {
|
||||||
|
((self.value >> 4)*10) + (self.value & 0b1111)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<u8> for BCDValue {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
BCDValue{value: (((value/10) << 4) | (value%10))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<BCDValue> for u8 {
|
||||||
|
fn from(bcd: BCDValue) -> Self {
|
||||||
|
bcd.get_decimal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<&BCDValue> for u8 {
|
||||||
|
fn from(bcd: &BCDValue) -> Self {
|
||||||
|
bcd.get_decimal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod bcd_tests {
|
||||||
|
use super::BCDValue;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_one_digit() {
|
||||||
|
assert_eq!(BCDValue::from(2).value, 0x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_two_digit() {
|
||||||
|
assert_eq!(BCDValue::from(22).value, 0x22);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_digit_tou8() {
|
||||||
|
assert_eq!(u8::from(BCDValue::from(2)), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_digit_tou8() {
|
||||||
|
assert_eq!(u8::from(BCDValue::from(22)), 22);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
use super::super::Error;
|
||||||
|
|
||||||
|
pub type AString<const SIZE: usize> = CDString::<AStringValidator, SIZE>;
|
||||||
|
pub type DString<const SIZE: usize> = CDString::<DStringValidator, SIZE>;
|
||||||
|
|
||||||
|
pub type DynAString = CDString<AStringValidator, 0>;
|
||||||
|
pub type DynDString = CDString<DStringValidator, 0>;
|
||||||
|
|
||||||
|
pub trait CDStringValidator {
|
||||||
|
fn is_valid_ascii(value: &[u8]) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AStringValidator {}
|
||||||
|
pub struct DStringValidator {}
|
||||||
|
|
||||||
|
pub struct CDString<F:CDStringValidator, const SIZE: usize> {
|
||||||
|
value: [u8; SIZE],
|
||||||
|
_d: std::marker::PhantomData<F>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CDStringValidator for AStringValidator {
|
||||||
|
fn is_valid_ascii(value: &[u8]) -> Result<(), Error> {
|
||||||
|
for chr in value {
|
||||||
|
let chr = *chr as char;
|
||||||
|
|
||||||
|
if !matches!(chr, '0'..='9' | 'A'..='Z' | ' ' | '!' | '"' | '%' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | '-' | '.' | '/' | ':' | ';' | '<' | '=' | '>' | '?' | '_') {
|
||||||
|
return Err(Error::GenericError(format!("{} not a valid a-character", chr)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CDStringValidator for DStringValidator {
|
||||||
|
fn is_valid_ascii(value: &[u8]) -> Result<(), Error> {
|
||||||
|
for chr in value {
|
||||||
|
let chr = *chr as char;
|
||||||
|
|
||||||
|
if !matches!(chr, '0'..='9' | 'A'..='Z' | '_') {
|
||||||
|
return Err(Error::GenericError(format!("{} not a valid d-character", chr)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F:CDStringValidator, const SIZE: usize> CDString<F, SIZE> {
|
||||||
|
pub fn from_str(string: &str) -> Result<CDString<F, SIZE>, Error> {
|
||||||
|
let mut cd_str = CDString::default();
|
||||||
|
|
||||||
|
CDString::<F, 0>::to_buffer(string, &mut cd_str.value)?;
|
||||||
|
Ok(cd_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_raw(&self) -> &[u8] {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F:CDStringValidator> CDString<F, 0> {
|
||||||
|
pub fn to_buffer(string: &str, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
|
if !string.is_ascii() {
|
||||||
|
return Err(Error::GenericError("String has to be ASCII encoded".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if string.len() > buffer.len() {
|
||||||
|
return Err(Error::GenericError(format!("String has {} characters but buffer can only hold {}", string.len(), buffer.len())));
|
||||||
|
}
|
||||||
|
|
||||||
|
F::is_valid_ascii(string.as_bytes())?;
|
||||||
|
|
||||||
|
let bytes = string.as_bytes();
|
||||||
|
for i in 0..bytes.len() {
|
||||||
|
buffer[i] = bytes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F:CDStringValidator, const SIZE: usize> Default for CDString<F, SIZE> {
|
||||||
|
fn default() -> Self {
|
||||||
|
CDString{value: [' ' as u8; SIZE], _d: std::marker::PhantomData}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
pub mod writer;
|
||||||
|
pub mod read;
|
||||||
|
use super::time::Time;
|
||||||
|
use super::super::Error;
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
pub enum Specifier {
|
||||||
|
File{path: String, format: Format},
|
||||||
|
Track{number: u64, data_type: DataType},
|
||||||
|
Index{number: u64, time: Time},
|
||||||
|
PreGap{time: Time},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Specifier {
|
||||||
|
const FILE_IDENTIFIER:&'static str = "FILE";
|
||||||
|
const TRACK_IDENTIFIER:&'static str = "TRACK";
|
||||||
|
const INDEX_IDENTIFIER:&'static str = "INDEX";
|
||||||
|
const PREGAP_IDENTIFIER:&'static str = "PREGAP";
|
||||||
|
|
||||||
|
pub fn parse_line(string: &str) -> Option<Specifier> {
|
||||||
|
let mut slices = string.split_whitespace();
|
||||||
|
|
||||||
|
match slices.next() {
|
||||||
|
Some(Self::FILE_IDENTIFIER) => Self::parse_file(slices),
|
||||||
|
Some(Self::TRACK_IDENTIFIER) => Self::parse_track(slices),
|
||||||
|
Some(Self::INDEX_IDENTIFIER) => Self::parse_index(slices),
|
||||||
|
Some(Self::PREGAP_IDENTIFIER) => Self::parse_pregap(slices),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_file(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
|
||||||
|
let mut path = slices.next()?.to_owned();
|
||||||
|
|
||||||
|
if !path.starts_with('"') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.remove(0);
|
||||||
|
while !path.ends_with('"') {
|
||||||
|
path = path.add(slices.next()?);
|
||||||
|
}
|
||||||
|
path.pop();
|
||||||
|
|
||||||
|
Some(Specifier::File{path, format: Format::parse(slices.next()?)?})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_track(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
|
||||||
|
let number = slices.next()?;
|
||||||
|
let data_type = slices.next()?;
|
||||||
|
|
||||||
|
Some(Specifier::Track{number: number.parse().ok()?, data_type: DataType::parse(data_type)?})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_index(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
|
||||||
|
let number = slices.next()?;
|
||||||
|
let time = slices.next()?;
|
||||||
|
|
||||||
|
Some(Specifier::Index{number: number.parse().ok()?, time: Time::parse(time)?})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_pregap(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
|
||||||
|
let time = slices.next()?;
|
||||||
|
|
||||||
|
Some(Specifier::PreGap{time: Time::parse(time)?})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Specifier {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::File{path, format} => write!(f, "{} \"{}\" {}", Self::FILE_IDENTIFIER, path, format),
|
||||||
|
Self::Track{number, data_type} => write!(f, "{} {:02} {}", Self::TRACK_IDENTIFIER, number, data_type),
|
||||||
|
Self::Index{number, time} => write!(f, "{} {:02} {}", Self::INDEX_IDENTIFIER, number, time),
|
||||||
|
Self::PreGap{time} => write!(f, "{} {}", Self::PREGAP_IDENTIFIER, time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Format {
|
||||||
|
Binary
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format {
|
||||||
|
const BINARY_IDENTIFIER:&'static str = "BINARY";
|
||||||
|
|
||||||
|
pub fn parse(string: &str) -> Option<Format> {
|
||||||
|
match string {
|
||||||
|
Self::BINARY_IDENTIFIER => Some(Format::Binary),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Format {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Binary => write!(f, "{}", Self::BINARY_IDENTIFIER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum DataType {
|
||||||
|
Audio,
|
||||||
|
Cdg,
|
||||||
|
Mode1_2048,
|
||||||
|
Mode1_2353,
|
||||||
|
Mode2_2336,
|
||||||
|
Mode2_2352,
|
||||||
|
Cdi2336,
|
||||||
|
Cdi2352
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataType {
|
||||||
|
const AUDIO_IDENTIFIER:&'static str = "AUDIO";
|
||||||
|
const CDG_IDENTIFIER:&'static str = "CDG";
|
||||||
|
const MODE1_2048_IDENTIFIER:&'static str = "MODE1/2048";
|
||||||
|
const MODE1_2353_IDENTIFIER:&'static str = "MODE1/2353";
|
||||||
|
const MODE2_2336_IDENTIFIER:&'static str = "MODE2/2336";
|
||||||
|
const MODE2_2352_IDENTIFIER:&'static str = "MODE2/2352";
|
||||||
|
const CDI_2336_IDENTIFIER:&'static str = "CDI/2336";
|
||||||
|
const CDI_2352_IDENTIFIER:&'static str = "CDI/2352";
|
||||||
|
|
||||||
|
pub fn parse(string: &str) -> Option<DataType> {
|
||||||
|
match string {
|
||||||
|
Self::AUDIO_IDENTIFIER => Some(Self::Audio),
|
||||||
|
Self::CDG_IDENTIFIER => Some(Self::Cdg),
|
||||||
|
Self::MODE1_2048_IDENTIFIER => Some(Self::Mode1_2048),
|
||||||
|
Self::MODE1_2353_IDENTIFIER => Some(Self::Mode1_2353),
|
||||||
|
Self::MODE2_2336_IDENTIFIER => Some(Self::Mode2_2336),
|
||||||
|
Self::MODE2_2352_IDENTIFIER => Some(Self::Mode2_2352),
|
||||||
|
Self::CDI_2336_IDENTIFIER => Some(Self::Cdi2336),
|
||||||
|
Self::CDI_2352_IDENTIFIER => Some(Self::Cdi2352),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DataType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Audio => f.pad(Self::AUDIO_IDENTIFIER),
|
||||||
|
Self::Cdg => f.pad(Self::CDG_IDENTIFIER),
|
||||||
|
Self::Mode1_2048 => f.pad(Self::MODE1_2048_IDENTIFIER),
|
||||||
|
Self::Mode1_2353 => f.pad(Self::MODE1_2353_IDENTIFIER),
|
||||||
|
Self::Mode2_2336 => f.pad(Self::MODE2_2336_IDENTIFIER),
|
||||||
|
Self::Mode2_2352 => f.pad(Self::MODE2_2352_IDENTIFIER),
|
||||||
|
Self::Cdi2336 => f.pad(Self::CDI_2336_IDENTIFIER),
|
||||||
|
Self::Cdi2352 => f.pad(Self::CDI_2352_IDENTIFIER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DataTypeEnd {
|
||||||
|
pub r#type: DataType,
|
||||||
|
pub end: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataTypeEnd {
|
||||||
|
fn new_infinite_entry(r#type: DataType) -> DataTypeEnd {
|
||||||
|
DataTypeEnd{r#type, end: usize::MAX}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_infinite_entry(&self) -> bool {
|
||||||
|
self.end == usize::MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_cue_specifier(cue_specifier: Vec<Specifier>) -> Result<Vec<DataTypeEnd>, Error> {
|
||||||
|
fn get_prev_if_infinite(parsed_cue: &mut Vec<DataTypeEnd>) -> Option<&mut DataTypeEnd> {
|
||||||
|
let cur_size = parsed_cue.len();
|
||||||
|
let element = parsed_cue.get_mut(cur_size - 2)?;
|
||||||
|
|
||||||
|
if element.is_infinite_entry() {
|
||||||
|
Some(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parsed_cue = Vec::new();
|
||||||
|
|
||||||
|
for specifier in cue_specifier {
|
||||||
|
match specifier {
|
||||||
|
Specifier::Track{number: _, data_type} => {
|
||||||
|
match data_type {
|
||||||
|
DataType::Audio | DataType::Mode2_2352 => parsed_cue.push(DataTypeEnd::new_infinite_entry(data_type)),
|
||||||
|
_ => return Err(Error::CUEDataTypeNotSupported(data_type)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Specifier::Index{number:_, time} => {
|
||||||
|
if let Some(prev_entry) = get_prev_if_infinite(&mut parsed_cue) {
|
||||||
|
prev_entry.end = time.to_lba();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(parsed_cue)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
use std::{fs::File, io::{BufRead, BufReader}};
|
||||||
|
use super::{{super::super::Error}, *};
|
||||||
|
|
||||||
|
pub fn read(file: File) -> Result<Vec<Specifier>, Error> {
|
||||||
|
let mut specifiers = Vec::new();
|
||||||
|
let file_buffer = BufReader::new(file);
|
||||||
|
|
||||||
|
for line in file_buffer.lines() {
|
||||||
|
let line = line?;
|
||||||
|
if let Some(specifier) = Specifier::parse_line(line.as_ref()) {
|
||||||
|
specifiers.push(specifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::IncorrectCUELine(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(specifiers)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use std::{fs::File, io::Write};
|
||||||
|
use super::{{super::super::Error}, *};
|
||||||
|
|
||||||
|
pub fn write(mut file: File, data: Vec<Specifier>) -> Result<(), Error> {
|
||||||
|
fn write_indent(file: &mut File, specifier: &Specifier) -> Result<(), Error> {
|
||||||
|
let indent = {
|
||||||
|
match specifier {
|
||||||
|
Specifier::File{..} => 0,
|
||||||
|
Specifier::Track{..} => 2,
|
||||||
|
_ => 4,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(file, "{:indent$}", " ")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
for specifier in data {
|
||||||
|
write_indent(&mut file, &specifier)?;
|
||||||
|
|
||||||
|
write!(file, "{}\n", specifier)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,293 @@
|
||||||
|
use chrono::{Duration, FixedOffset, TimeZone as ChronoTimeZone, prelude::*};
|
||||||
|
|
||||||
|
trait DateTimeGetter {
|
||||||
|
fn get_datetime(&self) -> Option<DateTime<FixedOffset>> {
|
||||||
|
|
||||||
|
if self.get_month() == 0 || self.get_day() == 0 {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Some(FixedOffset::east(self.get_timezone_sec()).ymd(self.get_year(), self.get_month(), self.get_day()).and_hms_milli(self.get_hour(), self.get_minute(), self.get_second(), self.get_milli_second()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_year(&self) -> i32;
|
||||||
|
fn get_month(&self) -> u32;
|
||||||
|
fn get_day(&self) -> u32;
|
||||||
|
|
||||||
|
fn get_hour(&self) -> u32;
|
||||||
|
fn get_minute(&self) -> u32;
|
||||||
|
fn get_second(&self) -> u32;
|
||||||
|
fn get_milli_second(&self) -> u32;
|
||||||
|
fn get_timezone_sec(&self) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct Date {
|
||||||
|
year: [u8; 4],
|
||||||
|
month: [u8; 2],
|
||||||
|
day: [u8; 2],
|
||||||
|
hour: [u8; 2],
|
||||||
|
minute: [u8; 2],
|
||||||
|
second: [u8; 2],
|
||||||
|
ten_ms: [u8; 2],
|
||||||
|
pub time_zone: TimeZone
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct SmallDate {
|
||||||
|
year: u8,
|
||||||
|
month: u8,
|
||||||
|
day: u8,
|
||||||
|
hour: u8,
|
||||||
|
minute: u8,
|
||||||
|
second: u8,
|
||||||
|
pub time_zone: TimeZone
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct TimeZone {
|
||||||
|
//Can be between -48 (GMT-12) and 52 (GMT+13)
|
||||||
|
value: i8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for dyn DateTimeGetter {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self.get_datetime() {
|
||||||
|
Some(datetime) => write!(f, "{}", datetime),
|
||||||
|
None => write!(f, "<Empty>"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Date {
|
||||||
|
pub const SECS_PER_HOUR:i32 = 3600;
|
||||||
|
|
||||||
|
pub fn new() -> Date {
|
||||||
|
Date{
|
||||||
|
year: ['0' as u8;4],
|
||||||
|
month: ['0' as u8;2],
|
||||||
|
day: ['0' as u8;2],
|
||||||
|
hour: ['0' as u8;2],
|
||||||
|
minute: ['0' as u8;2],
|
||||||
|
second: ['0' as u8;2],
|
||||||
|
ten_ms: ['0' as u8;2],
|
||||||
|
time_zone: TimeZone::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn now() -> Date {
|
||||||
|
let utc = Local::now();
|
||||||
|
let mut date = Date::new();
|
||||||
|
|
||||||
|
date.set_year(utc.year() as u32);
|
||||||
|
date.set_month(utc.month());
|
||||||
|
date.set_day(utc.day());
|
||||||
|
|
||||||
|
date.set_hour(utc.hour());
|
||||||
|
date.set_minute(utc.minute());
|
||||||
|
date.set_second(utc.second());
|
||||||
|
date.set_milliseconds((utc.timestamp_millis()%1000) as u32);
|
||||||
|
date.time_zone.set_from_date_time(&utc);
|
||||||
|
|
||||||
|
date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_year(&mut self, year: u32) {
|
||||||
|
self.year = Self::set_value(year);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_month(&mut self, month: u32) {
|
||||||
|
self.month = Self::set_value(month);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_day(&mut self, day: u32) {
|
||||||
|
self.day = Self::set_value(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_hour(&mut self, hour: u32) {
|
||||||
|
self.hour = Self::set_value(hour);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_minute(&mut self, minute: u32) {
|
||||||
|
self.minute = Self::set_value(minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_second(&mut self, second: u32) {
|
||||||
|
self.second = Self::set_value(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_milliseconds(&mut self, milliseconds: u32) {
|
||||||
|
self.ten_ms = Self::set_value(milliseconds/10);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_value<const SIZE:usize>(mut number: u32) -> [u8;SIZE] {
|
||||||
|
let mut value = ['0' as u8;SIZE];
|
||||||
|
|
||||||
|
for i in 0..SIZE {
|
||||||
|
value[(SIZE - 1 - i)] = ('0' as u32 + (number%10)) as u8;
|
||||||
|
number /= 10;
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_value<const SIZE:usize>(data: &[u8; SIZE]) -> u32 {
|
||||||
|
let mut number = 0u32;
|
||||||
|
|
||||||
|
for i in (SIZE - 1)..=0 {
|
||||||
|
number += (data[i] - '0' as u8) as u32;
|
||||||
|
number *= 10;
|
||||||
|
}
|
||||||
|
number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Date {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
(self as &dyn DateTimeGetter).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DateTimeGetter for Date {
|
||||||
|
fn get_year(&self) -> i32 {
|
||||||
|
Self::get_value(&self.year) as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_month(&self) -> u32 {
|
||||||
|
Self::get_value(&self.month)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_day(&self) -> u32 {
|
||||||
|
Self::get_value(&self.day)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hour(&self) -> u32 {
|
||||||
|
Self::get_value(&self.hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_minute(&self) -> u32 {
|
||||||
|
Self::get_value(&self.minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_second(&self) -> u32 {
|
||||||
|
Self::get_value(&self.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_milli_second(&self) -> u32 {
|
||||||
|
Self::get_value(&self.ten_ms)*10
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_timezone_sec(&self) -> i32 {
|
||||||
|
self.time_zone.get_gmt_offset()*15*60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SmallDate {
|
||||||
|
pub fn now() -> SmallDate {
|
||||||
|
let utc = Local::now();
|
||||||
|
let mut date = SmallDate::default();
|
||||||
|
|
||||||
|
date.set_year(utc.year() as u32);
|
||||||
|
date.set_month(utc.month());
|
||||||
|
date.set_day(utc.day());
|
||||||
|
|
||||||
|
date.set_hour(utc.hour());
|
||||||
|
date.set_minute(utc.minute());
|
||||||
|
date.set_second(utc.second());
|
||||||
|
date.time_zone.set_from_date_time(&utc);
|
||||||
|
|
||||||
|
date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_year(&mut self, year: u32) {
|
||||||
|
self.year = (year - 1900) as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_month(&mut self, month: u32) {
|
||||||
|
self.month = month as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_day(&mut self, day: u32) {
|
||||||
|
self.day = day as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_hour(&mut self, hour: u32) {
|
||||||
|
self.hour = hour as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_minute(&mut self, minute: u32) {
|
||||||
|
self.minute = minute as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_second(&mut self, second: u32) {
|
||||||
|
self.second = second as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SmallDate {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
(self as &dyn DateTimeGetter).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for SmallDate {
|
||||||
|
fn default() -> SmallDate {
|
||||||
|
SmallDate{year: 0u8, month: 0u8, day: 0u8, hour: 0u8, minute: 0u8, second: 0u8, time_zone: TimeZone::new()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DateTimeGetter for SmallDate {
|
||||||
|
fn get_year(&self) -> i32 {
|
||||||
|
(self.year as i32) + 1900
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_month(&self) -> u32 {
|
||||||
|
self.month as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_day(&self) -> u32 {
|
||||||
|
self.day as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hour(&self) -> u32 {
|
||||||
|
self.hour as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_minute(&self) -> u32 {
|
||||||
|
self.minute as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_second(&self) -> u32 {
|
||||||
|
self.second as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_milli_second(&self) -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_timezone_sec(&self) -> i32 {
|
||||||
|
self.time_zone.get_gmt_offset()*15*60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeZone {
|
||||||
|
pub fn new() -> TimeZone {
|
||||||
|
TimeZone{value: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_from_date_time<Tz: chrono::TimeZone>(&mut self, time: &chrono::DateTime::<Tz>) {
|
||||||
|
const SECS_PER_MINUTE:i32 = 60;
|
||||||
|
const MIN_PER_QUARTER_HOUR:i32 = 15;
|
||||||
|
|
||||||
|
self.value = (time.offset().fix().local_minus_utc()/SECS_PER_MINUTE/MIN_PER_QUARTER_HOUR) as i8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_gmt_offset(&self) -> i32 {
|
||||||
|
self.value as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hour_correction(&self) -> Duration {
|
||||||
|
Duration::hours((self.value/4) as i64)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
use super::{date::SmallDate, helper::{force_convert_ascii_to_str}, lsb_msb::{ReadWriteEndian, BigEndianU16, LittleBigEndianU32, LittleBigEndianU16}};
|
||||||
|
use crate::read_write_bit_getter_setter;
|
||||||
|
use std::concat;
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct DirectoryRecord {
|
||||||
|
pub length: [u8; 1],
|
||||||
|
pub ex_attribute_record_length: [u8; 1],
|
||||||
|
pub data_block_number: LittleBigEndianU32,
|
||||||
|
pub data_size: LittleBigEndianU32,
|
||||||
|
pub time_stamp: SmallDate,
|
||||||
|
pub flags: [u8; 1],
|
||||||
|
pub unit_size: [u8; 1],
|
||||||
|
pub interleave_gap_size: [u8; 1],
|
||||||
|
pub volume_sequence_number: LittleBigEndianU16,
|
||||||
|
pub name_length: [u8; 1],
|
||||||
|
//name: [u8; name_length]
|
||||||
|
//padding: [u8; if name is even this is 0x00 otherwise not present]
|
||||||
|
//system_use: <any system use>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct CDXASystemUse {
|
||||||
|
pub owner_id_group: BigEndianU16,
|
||||||
|
pub owner_id_user: BigEndianU16,
|
||||||
|
pub file_attribute: FileAttribute,
|
||||||
|
pub signature: [u8; 2],
|
||||||
|
pub file_number: [u8; 1],
|
||||||
|
_reserved: [u8; 5],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct FileAttribute {
|
||||||
|
value: BigEndianU16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirectoryRecord {
|
||||||
|
pub unsafe fn new(&mut self, name: &str, has_system_use: bool) {
|
||||||
|
let length = Self::calculate_size_for(name, has_system_use);
|
||||||
|
*self = DirectoryRecord{
|
||||||
|
length: [length as u8],
|
||||||
|
ex_attribute_record_length: [0u8],
|
||||||
|
data_block_number: LittleBigEndianU32::default(),
|
||||||
|
data_size: LittleBigEndianU32::default(),
|
||||||
|
time_stamp: SmallDate::default(),
|
||||||
|
flags: [0u8],
|
||||||
|
unit_size: [0u8],
|
||||||
|
interleave_gap_size: [0u8],
|
||||||
|
volume_sequence_number: LittleBigEndianU16::new(1),
|
||||||
|
name_length: [name.len() as u8],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_name(name);
|
||||||
|
if has_system_use {
|
||||||
|
if let Some(system_use) = self.get_cdxa_system_use_mut() {
|
||||||
|
*system_use = CDXASystemUse::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_system_use_offset(name_length: u8, length: u8) -> Option<(usize, usize)> {
|
||||||
|
let offset = std::mem::size_of::<Self>() + name_length as usize + Self::padding_field_size(name_length as usize);
|
||||||
|
let len_su = length as usize - offset;
|
||||||
|
if len_su == 0 {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Some((offset, len_su))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_system_use_raw(&self) -> Option<&[u8]> {
|
||||||
|
unsafe {
|
||||||
|
let (offset, len_su) = Self::get_system_use_offset(self.name_length[0], self.length[0])?;
|
||||||
|
let raw_system_use:*const u8 = (&self.length as *const u8).add(offset);
|
||||||
|
|
||||||
|
Some(std::slice::from_raw_parts(raw_system_use, len_su))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_system_use_raw_mut(&mut self) -> Option<&mut [u8]> {
|
||||||
|
unsafe {
|
||||||
|
let (offset, len_su) = Self::get_system_use_offset(self.name_length[0], self.length[0])?;
|
||||||
|
let raw_system_use:*mut u8 = (&mut self.length as *mut u8).add(offset);
|
||||||
|
|
||||||
|
Some(std::slice::from_raw_parts_mut(raw_system_use, len_su))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cdxa_system_use(&self) -> Option<&CDXASystemUse> {
|
||||||
|
let raw = self.get_system_use_raw()?;
|
||||||
|
|
||||||
|
if let Ok(raw) = raw.try_into() {
|
||||||
|
unsafe {
|
||||||
|
Some(std::mem::transmute::<&[u8;14], &CDXASystemUse>(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cdxa_system_use_mut(&mut self) -> Option<&mut CDXASystemUse> {
|
||||||
|
let raw = self.get_system_use_raw_mut()?;
|
||||||
|
|
||||||
|
if let Ok(raw) = raw.try_into() {
|
||||||
|
unsafe {
|
||||||
|
Some(std::mem::transmute::<&mut [u8;14], &mut CDXASystemUse>(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_name(&mut self, name: &str) {
|
||||||
|
unsafe {
|
||||||
|
let mut raw_str:*mut u8 = (&mut self.name_length as *mut u8).add(1);
|
||||||
|
for char in name.as_bytes() {
|
||||||
|
*raw_str = *char;
|
||||||
|
raw_str = raw_str.add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
unsafe {
|
||||||
|
let raw_str:*const u8 = (&self.name_length as *const u8).add(1);
|
||||||
|
force_convert_ascii_to_str(std::slice::from_raw_parts(raw_str, self.name_length[0] as usize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_file(&mut self) {
|
||||||
|
self.flags[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_directory(&mut self) {
|
||||||
|
self.flags[0] = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_file(&self) -> bool {
|
||||||
|
self.flags[0] == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_directory(&self) -> bool {
|
||||||
|
self.flags[0] == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_size_for(name: &str, has_system_use: bool) -> usize {
|
||||||
|
std::mem::size_of::<Self>() + name.len() + Self::padding_field_size(name.len()) + {
|
||||||
|
if has_system_use {
|
||||||
|
std::mem::size_of::<CDXASystemUse>()
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
0usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn padding_field_size(length: usize) -> usize {
|
||||||
|
if (length & 0x1) == 0 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DirectoryRecord {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "\tData Block Number: {}\n", self.data_block_number.read_any())?;
|
||||||
|
write!(f, "\tData Size Bytes: {}\n", self.data_size.read_any())?;
|
||||||
|
write!(f, "\tTime Stamp: {}\n", self.time_stamp)?;
|
||||||
|
write!(f, "\tFile Flags: {}\n", self.flags[0])?;
|
||||||
|
write!(f, "\tFile Unit Size: {}\n", self.unit_size[0])?;
|
||||||
|
write!(f, "\tInterleaved Gap Size: {}\n", self.interleave_gap_size[0])?;
|
||||||
|
write!(f, "\tVolume Sequence Number: {}\n", self.volume_sequence_number.read_any())?;
|
||||||
|
write!(f, "\tName: {}\n", self.get_name())?;
|
||||||
|
|
||||||
|
if let Some(system_use) = self.get_cdxa_system_use() {
|
||||||
|
write!(f, "System Use Area:\n{}", system_use)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
write!(f, "\t<No System Use Area>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for CDXASystemUse {
|
||||||
|
fn default() -> CDXASystemUse {
|
||||||
|
CDXASystemUse{
|
||||||
|
owner_id_group: BigEndianU16::new(0),
|
||||||
|
owner_id_user: BigEndianU16::new(0),
|
||||||
|
file_attribute: FileAttribute::default(),
|
||||||
|
signature: ['X' as u8, 'A' as u8],
|
||||||
|
file_number: [0u8],
|
||||||
|
_reserved: [0u8; 5],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for CDXASystemUse {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, concat!(
|
||||||
|
"\t\towner id group: {}\n",
|
||||||
|
"\t\towner id user: {}\n",
|
||||||
|
"\t\tfile attribute:\n{}",
|
||||||
|
"\t\tsignature: {}\n",
|
||||||
|
"\t\tfile number: {}"),
|
||||||
|
self.owner_id_group.read(), self.owner_id_user.read(), self.file_attribute, force_convert_ascii_to_str(&self.signature), self.file_number[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileAttribute {
|
||||||
|
read_write_bit_getter_setter!(value, 0, owner_read);
|
||||||
|
read_write_bit_getter_setter!(value, 2, owner_execute);
|
||||||
|
read_write_bit_getter_setter!(value, 4, group_read);
|
||||||
|
read_write_bit_getter_setter!(value, 6, group_execute);
|
||||||
|
read_write_bit_getter_setter!(value, 8, world_read);
|
||||||
|
read_write_bit_getter_setter!(value, 10, world_execute);
|
||||||
|
read_write_bit_getter_setter!(value, 11, mode2);
|
||||||
|
read_write_bit_getter_setter!(value, 12, mode2_form2);
|
||||||
|
read_write_bit_getter_setter!(value, 13, interleaved);
|
||||||
|
read_write_bit_getter_setter!(value, 14, cdda);
|
||||||
|
read_write_bit_getter_setter!(value, 15, directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for FileAttribute {
|
||||||
|
fn default() -> FileAttribute {
|
||||||
|
let mut att = FileAttribute{value: BigEndianU16::new(0)};
|
||||||
|
|
||||||
|
att.set_owner_read();
|
||||||
|
att.set_owner_execute();
|
||||||
|
att.set_group_read();
|
||||||
|
att.set_group_execute();
|
||||||
|
att.set_world_read();
|
||||||
|
att.set_world_execute();
|
||||||
|
|
||||||
|
att
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for FileAttribute {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "\t\t\towner_read: {}, owner_execute: {}\n", self.is_owner_read(), self.is_owner_execute())?;
|
||||||
|
write!(f, "\t\t\tgroup_read: {}, group_execute: {}\n", self.is_group_read(), self.is_group_execute())?;
|
||||||
|
write!(f, "\t\t\tworld_read: {}, world_execute: {}\n", self.is_world_read(), self.is_world_execute())?;
|
||||||
|
write!(f, "\t\t\tmode2: {}, mode2_form2: {}\n", self.is_mode2(), self.is_mode2_form2())?;
|
||||||
|
write!(f, "\t\t\tinterleaved: {}, CDDA: {}\n", self.is_interleaved(), self.is_cdda())?;
|
||||||
|
write!(f, "\t\t\tdirectory: {}\n", self.is_directory())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
pub mod tables;
|
||||||
|
use super::{sector::SECTOR_SIZE, lsb_msb::{ReadWriteEndian, LittleEndianU32}};
|
||||||
|
|
||||||
|
type RawSector = [u8; SECTOR_SIZE];
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct EDC {
|
||||||
|
data: LittleEndianU32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ECC {
|
||||||
|
data: [u8; 276]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EDC {
|
||||||
|
pub fn calculate_for(data: &[u8]) -> EDC {
|
||||||
|
let mut edc = EDC{data: LittleEndianU32::default()};
|
||||||
|
|
||||||
|
edc.calculate(data);
|
||||||
|
edc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate(&mut self, data: &[u8]) {
|
||||||
|
let mut edc = 0u32;
|
||||||
|
|
||||||
|
for byte in data {
|
||||||
|
edc = edc ^ *byte as u32;
|
||||||
|
edc = (edc >> 8) ^ tables::TABLES.edc[(edc & 0xFF) as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.data.write(edc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EDC {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "0x{:X}", self.data.read())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ECC {
|
||||||
|
pub fn calculate_for<T>(sector: &mut T) {
|
||||||
|
unsafe {
|
||||||
|
let mut raw_sector = std::mem::transmute::<&mut T, &mut [u8; SECTOR_SIZE]>(sector);
|
||||||
|
|
||||||
|
Self::calc_p_parity(&mut raw_sector);
|
||||||
|
Self::calc_q_parity(&mut raw_sector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_parity(sector: &mut RawSector, offset: usize, len: usize, j0: usize, step1: usize, step2: usize) {
|
||||||
|
let mut src = 0xC;
|
||||||
|
let mut dst = 0x81C + offset;
|
||||||
|
let src_max = dst;
|
||||||
|
|
||||||
|
for _ in 0..len {
|
||||||
|
let base = src;
|
||||||
|
let mut x = 0u16;
|
||||||
|
let mut y = 0u16;
|
||||||
|
|
||||||
|
for j in j0..=42 {
|
||||||
|
x ^= tables::TABLES.gf8_product[j][sector[src + 0] as usize];
|
||||||
|
y ^= tables::TABLES.gf8_product[j][sector[src + 1] as usize];
|
||||||
|
|
||||||
|
src += step1;
|
||||||
|
if step1 == 2*44 && src >= src_max {
|
||||||
|
src = src - 2*1118;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sector[dst + 2*len + 0] = (x & 0x00FF) as u8; sector[dst + 0] = (x >> 8) as u8;
|
||||||
|
sector[dst + 2*len + 1] = (y & 0x00FF) as u8; sector[dst + 1] = (y >> 8) as u8;
|
||||||
|
|
||||||
|
dst += 2;
|
||||||
|
src = base + step2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_p_parity(sector: &mut RawSector) {
|
||||||
|
Self::calc_parity(sector, 0, 43, 19, 2*43, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_q_parity(sector: &mut RawSector) {
|
||||||
|
Self::calc_parity(sector, 43*4, 26, 0, 2*44, 2*43);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ECC {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for n in (0..self.data.len()).step_by(4) {
|
||||||
|
let value = unsafe { *std::mem::transmute::<&u8, &u32>(&self.data[n]) };
|
||||||
|
|
||||||
|
if n != 0 && n%(4*4) == 0 {
|
||||||
|
write!(f, "\n")?;
|
||||||
|
}
|
||||||
|
write!(f, "0x{:08X} ", value)?;
|
||||||
|
}
|
||||||
|
std::fmt::Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::PartialEq for ECC {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
for i in 0..std::mem::size_of::<ECC>() {
|
||||||
|
if self.data[i] != other.data[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ECC {
|
||||||
|
fn default() -> Self {
|
||||||
|
ECC{data: [0u8; 276]}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
pub static TABLES:ErrorCorrectionTables = ErrorCorrectionTables::new();
|
||||||
|
|
||||||
|
macro_rules! wrap_gr_eq {
|
||||||
|
($calc:expr, $tresh_hold:expr) => {
|
||||||
|
{
|
||||||
|
let value = $calc;
|
||||||
|
if value >= $tresh_hold {
|
||||||
|
value - $tresh_hold
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type EDC = [u32; 256];
|
||||||
|
type GF8LOG = [u8; 256];
|
||||||
|
type GF8ILOG = [u8; 256];
|
||||||
|
type GF8PRODUCT = [[u16; 256]; 43];
|
||||||
|
|
||||||
|
pub struct ErrorCorrectionTables {
|
||||||
|
pub edc: EDC,
|
||||||
|
pub gf8_log: GF8LOG,
|
||||||
|
pub gf8_ilog: GF8ILOG,
|
||||||
|
pub gf8_product: GF8PRODUCT
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorCorrectionTables {
|
||||||
|
const fn new() -> ErrorCorrectionTables {
|
||||||
|
let edc = Self::make_edc();
|
||||||
|
let (gf8_log, gf8_ilog) = Self::make_gf_logs();
|
||||||
|
let gf8_product = Self::make_gf_product(&gf8_log, &gf8_ilog);
|
||||||
|
|
||||||
|
ErrorCorrectionTables{edc, gf8_log, gf8_ilog, gf8_product}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn make_edc() -> EDC {
|
||||||
|
let mut edc = [0; 256];
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < 256 {
|
||||||
|
let mut x = i as u32;
|
||||||
|
let mut j = 0;
|
||||||
|
|
||||||
|
while j < 8 {
|
||||||
|
let carry = x & 0b1;
|
||||||
|
|
||||||
|
x >>= 1;
|
||||||
|
if carry != 0 {
|
||||||
|
x ^= 0xD8018001;
|
||||||
|
}
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
edc[i] = x;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
edc
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn make_gf_logs() -> (GF8LOG, GF8ILOG) {
|
||||||
|
let mut gf8_log = [0; 256];
|
||||||
|
let mut gf8_ilog = [0; 256];
|
||||||
|
|
||||||
|
gf8_log[0] = 0;
|
||||||
|
gf8_ilog[255] = 0;
|
||||||
|
|
||||||
|
let mut x = 1u8;
|
||||||
|
let mut i = 0u8;
|
||||||
|
|
||||||
|
while i < 255 {
|
||||||
|
gf8_log[x as usize] = i;
|
||||||
|
gf8_ilog[i as usize] = x;
|
||||||
|
|
||||||
|
let carry_8bit = x & 0b1000_0000;
|
||||||
|
x <<= 1;
|
||||||
|
if carry_8bit != 0 {
|
||||||
|
x ^= 0x1D;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(gf8_log, gf8_ilog)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn make_gf_product(gf8_log: &GF8LOG, gf8_ilog: &GF8ILOG) -> GF8PRODUCT {
|
||||||
|
let mut gf8_product: GF8PRODUCT = [[0; 256]; 43];
|
||||||
|
|
||||||
|
let mut j = 0;
|
||||||
|
while j < 43 {
|
||||||
|
let mut xx = gf8_ilog[44 - j];
|
||||||
|
let mut yy = Self::subfunc(xx^1, 0x19, &gf8_log, &gf8_ilog);
|
||||||
|
|
||||||
|
xx = Self::subfunc(xx, 0x1, &gf8_log, &gf8_ilog);
|
||||||
|
xx = Self::subfunc(xx^1, 0x18, &gf8_log, &gf8_ilog);
|
||||||
|
xx = gf8_log[xx as usize];
|
||||||
|
yy = gf8_log[yy as usize];
|
||||||
|
|
||||||
|
gf8_product[j][0] = 0;
|
||||||
|
|
||||||
|
let mut i = 1;
|
||||||
|
while i < 256 {
|
||||||
|
let x = wrap_gr_eq!(xx as u16 + gf8_log[i] as u16, 0xFF) as usize;
|
||||||
|
let y = wrap_gr_eq!(yy as u16 + gf8_log[i] as u16, 0xFF) as usize;
|
||||||
|
|
||||||
|
gf8_product[j][i] = gf8_ilog[x] as u16 + ((gf8_ilog[y] as u16) << 8);
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
gf8_product
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn subfunc(mut a: u8, b: u8, gf8_log: &GF8LOG, gf8_ilog: &GF8ILOG) -> u8 {
|
||||||
|
if a > 0 {
|
||||||
|
let mut tmp_a:i32 = gf8_log[a as usize] as i32 - b as i32;
|
||||||
|
if tmp_a < 0 {
|
||||||
|
tmp_a += 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
a = gf8_ilog[tmp_a as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
a
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
use super::sector;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! read_write_bit_getter_setter {
|
||||||
|
($value:expr, $bit:expr, $name:expr) => {
|
||||||
|
paste::item! {
|
||||||
|
pub fn [< is_ $name >](&self) -> bool {
|
||||||
|
(self.$value.read() & (1 << $bit)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn [< set_ $name >](&mut self) {
|
||||||
|
self.$value.write(self.$value.read() | (1 << $bit));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn [< clear_ $name >](&mut self) {
|
||||||
|
self.$value.write(self.$value.read() & !(1 << $bit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_ascii_to_str(bytes: &[u8]) -> Option<&str> {
|
||||||
|
if let Ok(str) = std::str::from_utf8(bytes) {
|
||||||
|
Some(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_convert_ascii_to_str(bytes: &[u8]) -> &str {
|
||||||
|
if let Some(str) = convert_ascii_to_str(bytes) {
|
||||||
|
str
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
"<invalid UTF8 string>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn sector_count_mode2_form1(data_size: usize) -> usize {
|
||||||
|
multiple_of_round_up(data_size, sector::Mode2Form1::DATA_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn sector_count_mode2_form2(data_size: usize) -> usize {
|
||||||
|
multiple_of_round_up(data_size, sector::Mode2Form2::DATA_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn round_bytes_mode2_form1(data_size: usize) -> usize {
|
||||||
|
multiple_of_round_up(data_size, sector::Mode2Form1::DATA_SIZE)*sector::Mode2Form1::DATA_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn multiple_of_round_up(size: usize, unit: usize) -> usize {
|
||||||
|
(size + (unit - 1))/unit
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
use byteorder::{ByteOrder, BigEndian, LittleEndian};
|
||||||
|
|
||||||
|
pub trait ReadWriteEndian {
|
||||||
|
type UnderlayingType: std::fmt::Display;
|
||||||
|
|
||||||
|
fn read(&self) -> Self::UnderlayingType;
|
||||||
|
fn write(&mut self, value: Self::UnderlayingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait EndianFamilyTypes {
|
||||||
|
type U16: ReadWriteEndian + std::default::Default;
|
||||||
|
type U32: ReadWriteEndian + std::default::Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BigEndianFamily {}
|
||||||
|
pub struct LittleEndianFamily {}
|
||||||
|
|
||||||
|
impl EndianFamilyTypes for BigEndianFamily {
|
||||||
|
type U16 = BigEndianU16;
|
||||||
|
type U32 = BigEndianU32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndianFamilyTypes for LittleEndianFamily {
|
||||||
|
type U16 = LittleEndianU16;
|
||||||
|
type U32 = LittleEndianU32;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! make_lsb_msb {
|
||||||
|
($type_val:ty) => {
|
||||||
|
paste::item! {
|
||||||
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct [< LittleEndian $type_val:upper >] {
|
||||||
|
value: [u8; std::mem::size_of::<$type_val>()]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct [< BigEndian $type_val:upper >] {
|
||||||
|
value: [u8; std::mem::size_of::<$type_val>()]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct [< LittleBigEndian $type_val:upper >] {
|
||||||
|
pub lsb: [< LittleEndian $type_val:upper >],
|
||||||
|
pub msb: [< BigEndian $type_val:upper >],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl [< LittleEndian $type_val:upper >] {
|
||||||
|
pub fn new(value: $type_val) -> [< LittleEndian $type_val:upper >] {
|
||||||
|
let mut new_value = [< LittleEndian $type_val:upper >]::default();
|
||||||
|
|
||||||
|
new_value.write(value);
|
||||||
|
new_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadWriteEndian for [< LittleEndian $type_val:upper >] {
|
||||||
|
type UnderlayingType = $type_val;
|
||||||
|
|
||||||
|
fn read(&self) -> Self::UnderlayingType {
|
||||||
|
LittleEndian::[< read_ $type_val >](&self.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, value: Self::UnderlayingType) {
|
||||||
|
LittleEndian::[< write_ $type_val >](&mut self.value, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl [< BigEndian $type_val:upper >] {
|
||||||
|
pub fn new(value: $type_val) -> [< BigEndian $type_val:upper >] {
|
||||||
|
let mut new_value = [< BigEndian $type_val:upper >]::default();
|
||||||
|
|
||||||
|
new_value.write(value);
|
||||||
|
new_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadWriteEndian for [< BigEndian $type_val:upper >] {
|
||||||
|
type UnderlayingType = $type_val;
|
||||||
|
|
||||||
|
fn read(&self) -> Self::UnderlayingType {
|
||||||
|
BigEndian::[< read_ $type_val >](&self.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, value: Self::UnderlayingType) {
|
||||||
|
BigEndian::[< write_ $type_val >](&mut self.value, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl [< LittleBigEndian $type_val:upper >] {
|
||||||
|
pub fn new(value: $type_val) -> [< LittleBigEndian $type_val:upper >] {
|
||||||
|
let mut new_value = [< LittleBigEndian $type_val:upper >]::default();
|
||||||
|
|
||||||
|
new_value.write(value);
|
||||||
|
new_value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_any(&self) -> $type_val {
|
||||||
|
self.lsb.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, value: $type_val) {
|
||||||
|
self.lsb.write(value);
|
||||||
|
self.msb.write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_lsb_msb!(u16);
|
||||||
|
make_lsb_msb!(i16);
|
||||||
|
make_lsb_msb!(u32);
|
|
@ -0,0 +1,12 @@
|
||||||
|
pub mod bcd;
|
||||||
|
pub mod cue;
|
||||||
|
pub mod cdstring;
|
||||||
|
pub mod date;
|
||||||
|
pub mod dir_record;
|
||||||
|
pub mod error_correction;
|
||||||
|
pub mod helper;
|
||||||
|
pub mod lsb_msb;
|
||||||
|
pub mod path_table;
|
||||||
|
pub mod pvd;
|
||||||
|
pub mod time;
|
||||||
|
pub mod sector;
|
|
@ -0,0 +1,73 @@
|
||||||
|
use super::{helper::{force_convert_ascii_to_str}, lsb_msb::{EndianFamilyTypes, ReadWriteEndian, LittleEndianFamily, BigEndianFamily}};
|
||||||
|
|
||||||
|
pub type PathTableL = PathTableBase<LittleEndianFamily>;
|
||||||
|
pub type PathTableM = PathTableBase<BigEndianFamily>;
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct PathTableBase<EndianFamily: EndianFamilyTypes> {
|
||||||
|
pub name_length: [u8; 1],
|
||||||
|
pub extended_attribute_length: [u8; 1],
|
||||||
|
pub directory_logical_block: EndianFamily::U32,
|
||||||
|
pub parent_logical_block: EndianFamily::U16,
|
||||||
|
//name: DString<name_length>
|
||||||
|
//padding: only if name_length is odd
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EndianFamily: EndianFamilyTypes> PathTableBase<EndianFamily> {
|
||||||
|
pub unsafe fn new(&mut self, name: &str) {
|
||||||
|
*self = PathTableBase{
|
||||||
|
name_length: [name.len() as u8],
|
||||||
|
extended_attribute_length: [0],
|
||||||
|
directory_logical_block: EndianFamily::U32::default(),
|
||||||
|
parent_logical_block: EndianFamily::U16::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_name(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
unsafe {
|
||||||
|
let raw_str:*const u8 = (&self.name_length as *const u8).add(std::mem::size_of::<Self>());
|
||||||
|
force_convert_ascii_to_str(std::slice::from_raw_parts(raw_str, self.name_length[0] as usize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn set_name(&mut self, name: &str) {
|
||||||
|
let mut raw_str:*mut u8 = (&mut self.name_length as *mut u8).add(std::mem::size_of::<Self>());
|
||||||
|
for char in name.bytes() {
|
||||||
|
*raw_str = char;
|
||||||
|
raw_str = raw_str.add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_size(&self) -> usize {
|
||||||
|
let name_length = self.name_length[0] as usize;
|
||||||
|
std::mem::size_of::<PathTableBase<EndianFamily>>() + name_length + Self::padding_value(name_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_size_for(name: &str) -> usize {
|
||||||
|
let name_length = name.len();
|
||||||
|
|
||||||
|
std::mem::size_of::<PathTableBase<EndianFamily>>() + name_length + Self::padding_value(name_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn padding_value(name_length: usize) -> usize {
|
||||||
|
if (name_length & 0x1) != 0 {
|
||||||
|
1usize
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
0usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EndianFamily: EndianFamilyTypes> std::fmt::Display for PathTableBase<EndianFamily> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let (dir_block, parent_block) = unsafe {
|
||||||
|
(std::ptr::addr_of!(self.directory_logical_block).read_unaligned().read(), std::ptr::addr_of!(self.parent_logical_block).read_unaligned().read())
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "\"{}\" @{} ^{}", self.get_name(), dir_block, parent_block)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
use super::{cdstring::{AString, DString}, date::Date, dir_record::DirectoryRecord, lsb_msb::{ReadWriteEndian, BigEndianU32, LittleEndianU32, LittleBigEndianU32, LittleBigEndianU16}, sector::Mode2Form1};
|
||||||
|
use super::helper::force_convert_ascii_to_str as obtain_string;
|
||||||
|
|
||||||
|
const _: () = assert!(std::mem::size_of::<PrimaryVolumeDescriptor>() == Mode2Form1::DATA_SIZE, "PVD wrong data size");
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct PrimaryVolumeDescriptor {
|
||||||
|
pub volume_type: [u8; 1], // 1 for Primary Volume Descriptor
|
||||||
|
pub std_id: [u8; 5], // "CD001"
|
||||||
|
pub version: [u8; 1], // 1 for Standard
|
||||||
|
pub reserved_1: [u8; 1],
|
||||||
|
pub system_id: AString::<32>, // "PLAYSTATION"
|
||||||
|
pub volume_id: DString::<32>, // ???
|
||||||
|
pub reserved_2: [u8; 8],
|
||||||
|
pub vol_space_size: LittleBigEndianU32, // number of logical blocks
|
||||||
|
pub reserved_3: [u8;32],
|
||||||
|
pub vol_set_size: LittleBigEndianU16, // Usually 1
|
||||||
|
pub vol_seq_number: LittleBigEndianU16, // Usually 1
|
||||||
|
pub block_size: LittleBigEndianU16, // Size in bytes of a logical block
|
||||||
|
pub path_table_size: LittleBigEndianU32, // Maximum 0x800 for PSX
|
||||||
|
pub path_table_1: LittleEndianU32, // Start of Table1
|
||||||
|
pub path_table_2: LittleEndianU32, // Start of Table2
|
||||||
|
pub path_table_3: BigEndianU32, // Start of Table3
|
||||||
|
pub path_table_4: BigEndianU32, // Start of Table4
|
||||||
|
pub root_dir_record: [u8; 34], // Root directory record
|
||||||
|
pub volume_set_id: DString::<128>, // String which is usually empty
|
||||||
|
pub publisher_id: AString::<128>, // Company name of publisher
|
||||||
|
pub data_preparer: AString::<128>, //
|
||||||
|
pub app_id: AString::<128>, // "PLAYSTATION"
|
||||||
|
pub copyright_file: [u8; 37], // FILENAME.EXT;VER
|
||||||
|
pub abstract_file: [u8; 37], // Empty
|
||||||
|
pub bibliograph_file: [u8; 37], // Empty
|
||||||
|
pub vol_create_time: Date, // YYYYMMDDHHMMSSFF
|
||||||
|
pub vol_modification_time: Date, // YYYYMMDDHHMMSSFF all zero
|
||||||
|
pub vol_expiration_time: Date, // YYYYMMDDHHMMSSFF all zero
|
||||||
|
pub vol_effective_time: Date, // YYYYMMDDHHMMSSFF all zero
|
||||||
|
pub file_struct_version: [u8; 1], // 1 Standard
|
||||||
|
pub reserved_4: [u8; 1],
|
||||||
|
pub app_use_area: [u8; 141], // zero for PSX and VCD
|
||||||
|
pub cd_xa_id: [u8;8], // "CD-XA001" for PSX and VCD
|
||||||
|
pub cd_xa_flags: [u8;2], // zero for PSX and VCD
|
||||||
|
pub cd_xa_startup_dir: [u8; 8], // zero for PSX and VCD
|
||||||
|
pub cd_xa_reserved: [u8; 8], // zero for PSX and VCD
|
||||||
|
pub app_use_area_2: [u8; 345], // zero for PSX and VCD
|
||||||
|
pub reserved_5: [u8; 653]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
pub struct VolumeDescriptorTerminator {
|
||||||
|
pub volume_type: [u8; 1], //0xFF for Terminator
|
||||||
|
pub std_id: [u8; 5], //"CD0001"
|
||||||
|
pub version: [u8; 1], //0x01 for Standard
|
||||||
|
pub reserved: [u8; 2041] //Zero filled
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimaryVolumeDescriptor {
|
||||||
|
pub fn new() -> PrimaryVolumeDescriptor {
|
||||||
|
PrimaryVolumeDescriptor{
|
||||||
|
volume_type: [1],
|
||||||
|
std_id: ['C' as u8, 'D' as u8, '0' as u8, '0' as u8, '1' as u8],
|
||||||
|
version: [1],
|
||||||
|
reserved_1: [0],
|
||||||
|
system_id: AString::default(),
|
||||||
|
volume_id: DString::default(),
|
||||||
|
reserved_2: [0; 8],
|
||||||
|
vol_space_size: LittleBigEndianU32::default(),
|
||||||
|
reserved_3: [0; 32],
|
||||||
|
vol_set_size: LittleBigEndianU16::new(0x0001),
|
||||||
|
vol_seq_number: LittleBigEndianU16::new(0x0001),
|
||||||
|
block_size: LittleBigEndianU16::new(0x0800),
|
||||||
|
path_table_size: LittleBigEndianU32::default(),
|
||||||
|
path_table_1: LittleEndianU32::default(),
|
||||||
|
path_table_2: LittleEndianU32::default(),
|
||||||
|
path_table_3: BigEndianU32::default(),
|
||||||
|
path_table_4: BigEndianU32::default(),
|
||||||
|
root_dir_record: [0; 34],
|
||||||
|
volume_set_id: DString::default(),
|
||||||
|
publisher_id: AString::default(),
|
||||||
|
data_preparer: AString::default(),
|
||||||
|
app_id: AString::default(),
|
||||||
|
copyright_file: [' ' as u8; 37],
|
||||||
|
abstract_file: [' ' as u8; 37],
|
||||||
|
bibliograph_file: [' ' as u8; 37],
|
||||||
|
vol_create_time: Date::new(),
|
||||||
|
vol_modification_time: Date::new(),
|
||||||
|
vol_expiration_time: Date::new(),
|
||||||
|
vol_effective_time: Date::new(),
|
||||||
|
file_struct_version: [1],
|
||||||
|
reserved_4: [0],
|
||||||
|
app_use_area: [0; 141],
|
||||||
|
cd_xa_id: ['C' as u8, 'D' as u8, '-' as u8, 'X' as u8, 'A' as u8, '0' as u8, '0' as u8, '1' as u8],
|
||||||
|
cd_xa_flags: [0; 2],
|
||||||
|
cd_xa_startup_dir: [0; 8],
|
||||||
|
cd_xa_reserved: [0; 8],
|
||||||
|
app_use_area_2: [0; 345],
|
||||||
|
reserved_5: [0; 653]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_root_dir_record(&self) -> &DirectoryRecord {
|
||||||
|
unsafe {
|
||||||
|
std::mem::transmute::<&[u8; 34], &DirectoryRecord>(&self.root_dir_record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_root_dir_record_mut(&mut self) -> &mut DirectoryRecord {
|
||||||
|
unsafe {
|
||||||
|
std::mem::transmute::<&mut [u8; 34], &mut DirectoryRecord>(&mut self.root_dir_record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for PrimaryVolumeDescriptor {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let root_dir = self.get_root_dir_record();
|
||||||
|
|
||||||
|
write!(f, "Volume Descriptor Type: {}\nStandard Identifier: {}\n", self.volume_type[0], obtain_string(&self.std_id))?;
|
||||||
|
write!(f, "Volume Descriptor Version: {}\nSystem Identifier: {}\n", self.version[0], obtain_string(self.system_id.as_raw()))?;
|
||||||
|
write!(f, "Volume Identifier: {}\nVolume Space Size: {}\n", obtain_string(self.volume_id.as_raw()), self.vol_space_size.read_any())?;
|
||||||
|
write!(f, "Voulme Set Size: {}\nVolume Sequence Number: {}\n", self.vol_set_size.read_any(), self.vol_seq_number.read_any())?;
|
||||||
|
write!(f, "Logical Block Size (Bytes): {}\nPath Table Size (Bytes): {}\n", self.block_size.read_any(), self.path_table_size.read_any())?;
|
||||||
|
write!(f, "Path Table 1: {}\nPath Table 2: {}\n", self.path_table_1.read(), self.path_table_2.read())?;
|
||||||
|
write!(f, "Path Table 3: {}\nPath Table 4: {}\n", self.path_table_3.read(), self.path_table_4.read())?;
|
||||||
|
write!(f, "Root Directory Record: \n{}\nVolume Set Identifier: {}\n", root_dir, obtain_string(self.volume_set_id.as_raw()))?;
|
||||||
|
write!(f, "Publisher Identifier: {}\nData Preparer Identifier: {}\n", obtain_string(self.publisher_id.as_raw()), obtain_string(self.data_preparer.as_raw()))?;
|
||||||
|
write!(f, "Application Identifier: {}\nCopyright Filename: {}\n", obtain_string(self.app_id.as_raw()), obtain_string(&self.copyright_file))?;
|
||||||
|
write!(f, "Abstract Filename: {}\nBibliographic Filename: {}\n", obtain_string(&self.abstract_file), obtain_string(&self.bibliograph_file))?;
|
||||||
|
write!(f, "Volume Creation Time: {}\nVolume Modification Time: {}\n", self.vol_create_time, self.vol_modification_time)?;
|
||||||
|
write!(f, "Volume Expiration Time: {}\nVolume Effective Time: {}\n", self.vol_expiration_time, self.vol_effective_time)?;
|
||||||
|
write!(f, "File Sturcture Version: {}\nCD-XA Identifier: {}\n", self.file_struct_version[0], obtain_string(&self.cd_xa_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VolumeDescriptorTerminator {
|
||||||
|
pub fn new() -> VolumeDescriptorTerminator {
|
||||||
|
VolumeDescriptorTerminator {
|
||||||
|
volume_type: [0xFF],
|
||||||
|
std_id: ['C' as u8, 'D' as u8, '0' as u8, '0' as u8, '1' as u8],
|
||||||
|
version: [0x01],
|
||||||
|
reserved: [0; 2041]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,512 @@
|
||||||
|
use super::{bcd::BCDValue, error_correction::{ECC, EDC}, lsb_msb::LittleEndianI16, time::Time};
|
||||||
|
|
||||||
|
pub const SECTOR_SIZE:usize = 2352;
|
||||||
|
|
||||||
|
const _: () = assert!(std::mem::size_of::<Mode0>() == SECTOR_SIZE, "Mode0 wrong Sector size");
|
||||||
|
const _: () = assert!(std::mem::size_of::<Mode1>() == SECTOR_SIZE, "Mode1 wrong Sector size");
|
||||||
|
const _: () = assert!(std::mem::size_of::<Mode2Form1>() == SECTOR_SIZE, "Mode2From1 wrong Sector size");
|
||||||
|
const _: () = assert!(std::mem::size_of::<Mode2Form2>() == SECTOR_SIZE, "Mode2From2 wrong Sector size");
|
||||||
|
|
||||||
|
macro_rules! bit_getter_setter {
|
||||||
|
($value:expr, $bit:expr, $name:expr) => {
|
||||||
|
paste::item! {
|
||||||
|
pub fn [< set_ $name >](&mut self) {
|
||||||
|
self.$value |= (1 << $bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn [< clear_ $name >](&mut self) {
|
||||||
|
self.$value &= !(1 << $bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn [< is_ $name >](&self) -> bool {
|
||||||
|
(self.$value & (1 << $bit)) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HeaderMode {
|
||||||
|
Mode0 = 0,
|
||||||
|
Mode1 = 1,
|
||||||
|
Mode2 = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SubHeaderForm {
|
||||||
|
Form1,
|
||||||
|
Form2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Sync {
|
||||||
|
value: [u8; 12],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sync {
|
||||||
|
pub fn new() -> Sync {
|
||||||
|
Sync{value: [0x0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Sync {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "< ")?;
|
||||||
|
for byte in self.value {
|
||||||
|
write!(f, "{} ", byte)?;
|
||||||
|
}
|
||||||
|
write!(f, ">")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Header {
|
||||||
|
minute: BCDValue,
|
||||||
|
second: BCDValue,
|
||||||
|
sector: BCDValue,
|
||||||
|
mode: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
pub fn new(mode: u8) -> Header {
|
||||||
|
Header{minute: BCDValue::from(0), second: BCDValue::from(0), sector: BCDValue::from(0), mode}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_time(&mut self, time: Time) {
|
||||||
|
let (min, sec, sector) = time.to_bcd();
|
||||||
|
|
||||||
|
self.minute = min;
|
||||||
|
self.second = sec;
|
||||||
|
self.sector = sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mode(&self) -> Result<HeaderMode, u8> {
|
||||||
|
match self.mode {
|
||||||
|
0 => Ok(HeaderMode::Mode0),
|
||||||
|
1 => Ok(HeaderMode::Mode1),
|
||||||
|
2 => Ok(HeaderMode::Mode2),
|
||||||
|
_ => Err(self.mode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Header {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "min: {}, sec: {}, sector: {}, mode: {}", u8::from(&self.minute), u8::from(&self.second), u8::from(&self.sector), self.mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Header {
|
||||||
|
fn default() -> Self {
|
||||||
|
Header{minute: BCDValue::from(0), second: BCDValue::from(0), sector: BCDValue::from(0), mode: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubHeader {
|
||||||
|
pub file_number: u8,
|
||||||
|
pub channel_number: u8,
|
||||||
|
pub sub_mode: SubMode,
|
||||||
|
pub coding_info: CodingInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubHeader {
|
||||||
|
pub fn default_form1() -> SubHeader {
|
||||||
|
SubHeader{file_number: 0, channel_number: 0, sub_mode: SubMode::default_form1(), coding_info: CodingInfo::default_form1()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_form2() -> SubHeader {
|
||||||
|
SubHeader{file_number: 0, channel_number: 0, sub_mode: SubMode::default_form2(), coding_info: CodingInfo::default_form2()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_form(&self) -> SubHeaderForm {
|
||||||
|
if self.sub_mode.is_form2() {
|
||||||
|
SubHeaderForm::Form2
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
SubHeaderForm::Form1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SubHeader {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "file: {}, channel: {}\nSubmode:\n{}\nCodinginfo:\n\t{}", self.file_number, self.channel_number, self.sub_mode, {
|
||||||
|
if self.sub_mode.is_data() {
|
||||||
|
&"Reserved (0x00)" as &dyn std::fmt::Display
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
&self.coding_info as &dyn std::fmt::Display
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SubHeader {
|
||||||
|
fn default() -> Self {
|
||||||
|
SubHeader{file_number: 0, channel_number: 0, sub_mode: SubMode::default(), coding_info: CodingInfo::default()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubMode {
|
||||||
|
value: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubMode {
|
||||||
|
pub fn default_form1() -> SubMode {
|
||||||
|
let mut sub_mode = SubMode{value: 0};
|
||||||
|
|
||||||
|
sub_mode.set_data();
|
||||||
|
sub_mode.clear_form2();
|
||||||
|
sub_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_form2() -> SubMode {
|
||||||
|
let mut sub_mode = SubMode{value: 0};
|
||||||
|
|
||||||
|
sub_mode.set_audio();
|
||||||
|
sub_mode.set_form2();
|
||||||
|
sub_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_getter_setter!(value, 0, eor);
|
||||||
|
bit_getter_setter!(value, 1, video);
|
||||||
|
bit_getter_setter!(value, 2, audio);
|
||||||
|
bit_getter_setter!(value, 3, data);
|
||||||
|
bit_getter_setter!(value, 4, trigger);
|
||||||
|
bit_getter_setter!(value, 5, form2);
|
||||||
|
bit_getter_setter!(value, 6, real_time);
|
||||||
|
bit_getter_setter!(value, 7, eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SubMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "\tEOR: {}, Video: {}\n\tAudio: {}, Data: {}\n\tTrigger: {}, Form2: {}\n\tRealTime: {}, EOF: {}",
|
||||||
|
self.is_eor(), self.is_video(), self.is_audio(), self.is_data(), self.is_trigger(), self.is_form2(), self.is_real_time(), self.is_eof())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SubMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
SubMode{value: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CodingInfo {
|
||||||
|
value: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CodingInfo {
|
||||||
|
pub fn default_form1() -> CodingInfo {
|
||||||
|
CodingInfo{value: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_form2() -> CodingInfo {
|
||||||
|
CodingInfo{value: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sound_type(&self) -> XAADPCMSound {
|
||||||
|
match self.value & (0b11) {
|
||||||
|
0 => XAADPCMSound::Mono,
|
||||||
|
1 => XAADPCMSound::Stereo,
|
||||||
|
_ => XAADPCMSound::Reserved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sound_type(&mut self, sound_type: XAADPCMSound) {
|
||||||
|
self.value &= !0b11;
|
||||||
|
self.value |= sound_type as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sample_rate(&self) -> XAADPCMSampleRate {
|
||||||
|
match (self.value >> 2) & 0b11 {
|
||||||
|
0 => XAADPCMSampleRate::Freq37800Hz,
|
||||||
|
1 => XAADPCMSampleRate::Freq18900Hz,
|
||||||
|
_ => XAADPCMSampleRate::Reserved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_rate(&mut self, sample_rate: XAADPCMSampleRate) {
|
||||||
|
self.value &= !(0b11 << 2);
|
||||||
|
self.value |= (sample_rate as u8) << 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bits_per_sample(&self) -> XAADPCMBitsPerSample {
|
||||||
|
match (self.value >> 4) & 0b11 {
|
||||||
|
0 => XAADPCMBitsPerSample::Normal,
|
||||||
|
1 => XAADPCMBitsPerSample::High,
|
||||||
|
_ => XAADPCMBitsPerSample::Reserved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_bits_per_sample(&mut self, bits: XAADPCMBitsPerSample) {
|
||||||
|
self.value &= !(0b11 << 4);
|
||||||
|
self.value |= (bits as u8) << 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_emphasis(&self) -> bool {
|
||||||
|
self.value & (1 << 6) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_emphasis(&mut self, has_emphasis: bool) {
|
||||||
|
let emphasis = {
|
||||||
|
if has_emphasis {
|
||||||
|
1 << 6
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.value &= !(1 << 6);
|
||||||
|
self.value |= emphasis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for CodingInfo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Sound: {} Sample Rate: {} Bits per Sample: {}, Emphasis: {}", self.get_sound_type().as_str(), self.get_sample_rate().as_str(), self.get_bits_per_sample().as_str(), self.has_emphasis())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CodingInfo {
|
||||||
|
fn default() -> Self {
|
||||||
|
CodingInfo{value: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct AudioSample {
|
||||||
|
pub left_sample: LittleEndianI16,
|
||||||
|
pub right_sample: LittleEndianI16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioSample {
|
||||||
|
pub fn new(left: i16, right: i16) -> AudioSample {
|
||||||
|
AudioSample{left_sample: LittleEndianI16::new(left), right_sample: LittleEndianI16::new(right)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for AudioSample {
|
||||||
|
fn default() -> AudioSample {
|
||||||
|
AudioSample::new(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Audio {
|
||||||
|
samples: [AudioSample; Self::SAMPLE_SIZE]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Audio {
|
||||||
|
pub const DATA_SIZE:usize = 2352;
|
||||||
|
pub const SAMPLE_SIZE:usize = Self::DATA_SIZE/std::mem::size_of::<AudioSample>();
|
||||||
|
|
||||||
|
pub fn new() -> Audio {
|
||||||
|
Audio{samples: [AudioSample::default(); Self::SAMPLE_SIZE]}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_raw_samples(&self) -> &[u8] {
|
||||||
|
unsafe {
|
||||||
|
std::mem::transmute::<&[AudioSample; 588], &[u8; Self::DATA_SIZE]>(&self.samples)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mode0 {
|
||||||
|
pub sync: Sync,
|
||||||
|
pub header: Header,
|
||||||
|
pub zero: [u8;2336]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode0 {
|
||||||
|
pub fn new() -> Mode0 {
|
||||||
|
Mode0{sync: Sync::new(), header: Header::new(0), zero: [0; 2336]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mode1 {
|
||||||
|
pub sync: Sync,
|
||||||
|
pub header: Header,
|
||||||
|
pub data: [u8; Mode1::DATA_SIZE],
|
||||||
|
pub edc: EDC,
|
||||||
|
pub zero: [u8; 8],
|
||||||
|
pub ecc: ECC
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode1 {
|
||||||
|
pub const DATA_SIZE:usize = 2048;
|
||||||
|
|
||||||
|
pub fn new() -> Mode1 {
|
||||||
|
Mode1{sync: Sync::new(), header: Header::new(1), data: [0; Self::DATA_SIZE], edc: EDC::default(), zero: [0u8; 8], ecc: ECC::default()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize(&mut self) {
|
||||||
|
self.calculate_edc();
|
||||||
|
self.calculate_ecc();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_edc(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let raw = std::mem::transmute::<&Mode1, &[u8;std::mem::size_of::<Mode1>()]>(self);
|
||||||
|
self.edc = EDC::calculate_for(&raw[0..=0x80F]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_ecc(&mut self) {
|
||||||
|
ECC::calculate_for(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Mode2 = Mode2Form1;
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mode2Form1 {
|
||||||
|
pub sync: Sync,
|
||||||
|
pub header: Header,
|
||||||
|
pub sub_header: SubHeader,
|
||||||
|
pub copy_sub_header: SubHeader,
|
||||||
|
pub data: [u8; Mode2Form1::DATA_SIZE],
|
||||||
|
pub edc: EDC,
|
||||||
|
pub ecc: ECC
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode2Form1 {
|
||||||
|
pub const DATA_SIZE:usize = 2048;
|
||||||
|
|
||||||
|
pub fn new() -> Mode2Form1 {
|
||||||
|
let sub_header = SubHeader::default_form1();
|
||||||
|
let copy_sub_header = sub_header.clone();
|
||||||
|
|
||||||
|
Mode2Form1{sync: Sync::new(), header: Header::new(2), sub_header, copy_sub_header, data: [0; Self::DATA_SIZE], edc: EDC::default(), ecc: ECC::default()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_end_of_file(&mut self) {
|
||||||
|
self.sub_header.sub_mode.set_eor();
|
||||||
|
self.sub_header.sub_mode.set_eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize(&mut self) {
|
||||||
|
self.copy_sub_header = self.sub_header.clone();
|
||||||
|
|
||||||
|
self.calculate_edc();
|
||||||
|
self.calculate_ecc();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_edc(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let raw = std::mem::transmute::<&Mode2Form1, &[u8;std::mem::size_of::<Mode2Form1>()]>(self);
|
||||||
|
self.edc = EDC::calculate_for(&raw[0x10..=0x817]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_ecc(&mut self) {
|
||||||
|
let header = std::mem::take(&mut self.header);
|
||||||
|
|
||||||
|
ECC::calculate_for(self);
|
||||||
|
self.header = header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mode2Form2 {
|
||||||
|
pub sync: Sync,
|
||||||
|
pub header: Header,
|
||||||
|
pub sub_header: SubHeader,
|
||||||
|
pub copy_sub_header: SubHeader,
|
||||||
|
pub data: [u8; Mode2Form2::DATA_SIZE],
|
||||||
|
pub edc: EDC,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode2Form2 {
|
||||||
|
pub const DATA_SIZE:usize = 2324;
|
||||||
|
|
||||||
|
pub fn new() -> Mode2Form2 {
|
||||||
|
let sub_header = SubHeader::default_form2();
|
||||||
|
let copy_sub_header = sub_header.clone();
|
||||||
|
|
||||||
|
Mode2Form2{sync: Sync::new(), header: Header::new(2), sub_header, copy_sub_header, data: [0; Self::DATA_SIZE], edc: EDC::default()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_end_of_file(&mut self) {
|
||||||
|
self.sub_header.sub_mode.set_eor();
|
||||||
|
self.sub_header.sub_mode.set_eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize(&mut self) {
|
||||||
|
self.copy_sub_header = self.sub_header.clone();
|
||||||
|
|
||||||
|
self.calculate_edc();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_edc(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let raw = std::mem::transmute::<&Mode2Form2, &[u8;std::mem::size_of::<Mode2Form2>()]>(self);
|
||||||
|
self.edc = EDC::calculate_for(&raw[0x10..=0x92B]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum XAADPCMSound {
|
||||||
|
Mono = 0,
|
||||||
|
Stereo = 1,
|
||||||
|
Reserved = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XAADPCMSound {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Mono => "Mono",
|
||||||
|
Self::Stereo => "Stereo",
|
||||||
|
Self::Reserved => "Reserved"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum XAADPCMBitsPerSample {
|
||||||
|
Normal = 0,
|
||||||
|
High = 1,
|
||||||
|
Reserved = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XAADPCMBitsPerSample {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Normal => "Normal (4bit)",
|
||||||
|
Self::High => "High (8bit)",
|
||||||
|
Self::Reserved => "Reserved"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum XAADPCMSampleRate {
|
||||||
|
Freq37800Hz = 0,
|
||||||
|
Freq18900Hz = 1,
|
||||||
|
Reserved = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XAADPCMSampleRate {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Freq37800Hz => "37800Hz",
|
||||||
|
Self::Freq18900Hz => "18900Hz",
|
||||||
|
Self::Reserved => "Reserved",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
use super::bcd::BCDValue;
|
||||||
|
use super::super::Error;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Time {
|
||||||
|
minute: u8,
|
||||||
|
second: u8,
|
||||||
|
sector: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Time {
|
||||||
|
pub const MAX_SECTORS:usize = 75;
|
||||||
|
pub const MAX_SECONDS:usize = 60;
|
||||||
|
pub const MAX_MINUTES:usize = 74;
|
||||||
|
|
||||||
|
pub fn cue_start() -> Time {
|
||||||
|
Time{minute: 0, second: 0, sector: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cd_start() -> Time {
|
||||||
|
Time{minute: 0, second: 2, sector: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cd_pregap() -> Time {
|
||||||
|
Time{minute: 0, second: 2, sector: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_sectors(&mut self, sector: usize) -> Result<(), Error> {
|
||||||
|
let sector = self.sector as usize + sector;
|
||||||
|
let second = self.second as usize + (sector/Self::MAX_SECTORS);
|
||||||
|
let minute = self.minute as usize + (second/Self::MAX_SECONDS);
|
||||||
|
|
||||||
|
if minute > Self::MAX_MINUTES {
|
||||||
|
Err(Error::TimeToLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
*self = Time{minute: minute as u8, second: (second%Self::MAX_SECONDS) as u8, sector: (sector%Self::MAX_SECTORS) as u8};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(string: &str) -> Option<Time> {
|
||||||
|
let mut slices = string.split(':');
|
||||||
|
let minutes = slices.next()?;
|
||||||
|
let seconds = slices.next()?;
|
||||||
|
let sectors = slices.next()?;
|
||||||
|
|
||||||
|
let mut time = Time{minute: minutes.parse().ok()?, second: seconds.parse().ok()?, sector: sectors.parse().ok()?};
|
||||||
|
time.add_sectors(0).ok()?;
|
||||||
|
|
||||||
|
Some(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_sector(&mut self) -> Result<(), Error> {
|
||||||
|
self.add_sectors(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bcd(&self) -> (BCDValue, BCDValue, BCDValue) {
|
||||||
|
(BCDValue::from(self.minute), BCDValue::from(self.second), BCDValue::from(self.sector))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_lba(&self) -> usize {
|
||||||
|
self.sector as usize + (self.second as usize*Self::MAX_SECTORS) + (self.minute as usize*Self::MAX_SECONDS*Self::MAX_SECTORS)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_lba(lba: usize) -> Time {
|
||||||
|
let sector = (lba%Time::MAX_SECTORS) as u8;
|
||||||
|
let second = ((lba/Time::MAX_SECTORS)%Time::MAX_SECONDS) as u8;
|
||||||
|
let minute = (((lba/Time::MAX_SECTORS)/Time::MAX_SECONDS)%Time::MAX_MINUTES) as u8;
|
||||||
|
|
||||||
|
Time{sector, second, minute}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump(&self) -> String {
|
||||||
|
format!("min: {}, sec: {}, sector: {}", self.minute, self.second, self.sector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<(BCDValue, BCDValue, BCDValue)> for Time {
|
||||||
|
fn from(time: (BCDValue, BCDValue, BCDValue)) -> Self {
|
||||||
|
Time{minute: u8::from(time.0), second: u8::from(time.1), sector: u8::from(time.2)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Time {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:0>2}:{:0>2}:{:0>2}", self.minute, self.second, self.sector)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tasks": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "cargo",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cargo ${input:cmd} --${input:cfg}",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"options":
|
||||||
|
{
|
||||||
|
"cwd": "${workspaceFolder}/${input:project}"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "run psxcdread with",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cargo run --release -- ${input:psxcdread_parameter}",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true,
|
||||||
|
},
|
||||||
|
"options":
|
||||||
|
{
|
||||||
|
"cwd": "${workspaceFolder}/psxcdread"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "run psxcdgen with",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cargo run --release -- Dino.xml",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true,
|
||||||
|
},
|
||||||
|
"options":
|
||||||
|
{
|
||||||
|
"cwd": "${workspaceFolder}/psxcdgen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "cmd",
|
||||||
|
"type": "pickString",
|
||||||
|
"description": "Command to use",
|
||||||
|
"options": ["check", "test", "run", "build", "clean"],
|
||||||
|
"default": "run"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cfg",
|
||||||
|
"type": "pickString",
|
||||||
|
"description": "Configuration for the project",
|
||||||
|
"options": ["debug", "release"],
|
||||||
|
"default": "release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "project",
|
||||||
|
"type": "pickString",
|
||||||
|
"description": "Project to run cargo for",
|
||||||
|
"options": ["psxcdgen", "psxcdread", "cdtypes"],
|
||||||
|
"default": "psxcdgen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "psxcdread_parameter",
|
||||||
|
"type": "pickString",
|
||||||
|
"description": "Parameter for tool",
|
||||||
|
"options": ["../../../iso/JabyEngine.bin", "../../../iso/JabyEngine.cue", "../Output/Test.bin"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"settings": {}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "psxcdgen"
|
||||||
|
version = "0.5.5"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cdtypes = {path = "../cdtypes"}
|
||||||
|
clap = {version = "*", features = ["derive"]}
|
||||||
|
fast-xml = "*"
|
||||||
|
paste = "*"
|
||||||
|
wav = "*"
|
|
@ -0,0 +1,42 @@
|
||||||
|
use cdtypes::Error;
|
||||||
|
use std::{io::Read, fs::OpenOptions, path::PathBuf};
|
||||||
|
|
||||||
|
pub fn open_output_file(path: &str) -> Result<std::fs::File, Error> {
|
||||||
|
Ok(convert_io_error(path, OpenOptions::new().write(true).create(true).truncate(true).open(path))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_file_to_string(path: &str) -> Result<String, Error> {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
if let Ok(_) = convert_io_error(path, OpenOptions::new().read(true).create(false).open(path))?.read_to_string(&mut buffer) {
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(format!("Failed reading file \"{}\" to String", path)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_file_to_u8(path: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
let mut buffer = Vec::default();
|
||||||
|
if let Ok(_) = convert_io_error(path, OpenOptions::new().read(true).create(false).open(path))?.read_to_end(&mut buffer) {
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(format!("Failed reading file \"{}\" to Vec<u8>", path)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_to_string(path: PathBuf) -> Result<String, Error> {
|
||||||
|
match path.into_os_string().into_string() {
|
||||||
|
Ok(str) => Ok(str),
|
||||||
|
Err(_) => Err(Error::GenericError(format!("Couldn't convert OS_String to proper string")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_io_error<T>(path: &str, value: Result<T, std::io::Error>) -> Result<T, Error> {
|
||||||
|
match value {
|
||||||
|
Ok(value) => Ok(value),
|
||||||
|
Err(io_error) => Err(Error::IOError(Some(path.to_string()), io_error)),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,465 @@
|
||||||
|
use cdtypes::{Error, types::{cue::*, helper::{sector_count_mode2_form1, sector_count_mode2_form2, round_bytes_mode2_form1}, dir_record::DirectoryRecord, sector::AudioSample}};
|
||||||
|
pub use super::types::{ProcessedFile, RequestedFile};
|
||||||
|
|
||||||
|
pub trait LBASize {
|
||||||
|
fn set_lba(&mut self, lba: usize);
|
||||||
|
fn set_lba_ret(&mut self, lba: usize) -> Result<usize, Error> {
|
||||||
|
self.set_lba(lba);
|
||||||
|
|
||||||
|
Ok(lba + self.get_length_lba()?)
|
||||||
|
}
|
||||||
|
fn get_lba(&self) -> usize;
|
||||||
|
|
||||||
|
fn is_xa_audio(&self) -> Option<bool>;
|
||||||
|
|
||||||
|
fn get_length(&self) -> usize;
|
||||||
|
fn get_length_lba(&self) -> Result<usize, Error> {
|
||||||
|
self.default_get_length_lba()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Rust doesn't support super calls yet
|
||||||
|
fn default_get_length_lba(&self) -> Result<usize, Error> {
|
||||||
|
let mut length = self.get_length();
|
||||||
|
|
||||||
|
if length == 0 {
|
||||||
|
length = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(is_xa_audio) = self.is_xa_audio() {
|
||||||
|
if is_xa_audio {
|
||||||
|
Ok(sector_count_mode2_form2(length))
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Ok(sector_count_mode2_form1(length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("Failed determining Sector type for LBA length calculation".to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CDDesc {
|
||||||
|
pub licence_path: String,
|
||||||
|
pub output_file: String,
|
||||||
|
pub pvd: PVD,
|
||||||
|
pub tracks: Vec<Track>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CDDesc {
|
||||||
|
pub fn new() -> CDDesc {
|
||||||
|
CDDesc{licence_path: String::new(), output_file: String::new(), pvd: PVD::new(), tracks: Vec::new()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PVD {
|
||||||
|
pub volume_identifier: String,
|
||||||
|
pub publisher: String,
|
||||||
|
pub data_preparer: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PVD {
|
||||||
|
pub fn new() -> PVD {
|
||||||
|
PVD{volume_identifier: String::new(), publisher: String::new(), data_preparer: String::new()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_uppercase(&mut self) {
|
||||||
|
self.volume_identifier = self.volume_identifier.to_uppercase();
|
||||||
|
self.publisher = self.publisher.to_uppercase();
|
||||||
|
self.data_preparer = self.data_preparer.to_uppercase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Track {
|
||||||
|
Data(DirectoryDataType),
|
||||||
|
Audio(Vec<AudioSample>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum DataTrackType {
|
||||||
|
Directory(DirectoryDataType),
|
||||||
|
File(FileDataType)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataTrackType {
|
||||||
|
pub fn get_name(&self) -> &String {
|
||||||
|
match self {
|
||||||
|
DataTrackType::Directory(dir) => &dir.name,
|
||||||
|
DataTrackType::File(file) => &file.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_hidden(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
DataTrackType::Directory(dir) => dir.is_hidden,
|
||||||
|
DataTrackType::File(file) => file.is_hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LBASize for DataTrackType {
|
||||||
|
fn set_lba(&mut self, lba: usize) {
|
||||||
|
match self {
|
||||||
|
DataTrackType::Directory(dir) => dir.set_lba(lba),
|
||||||
|
DataTrackType::File(file) => file.set_lba(lba),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_lba(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
DataTrackType::Directory(dir) => dir.get_lba(),
|
||||||
|
DataTrackType::File(file) => file.get_lba(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_length(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
DataTrackType::Directory(dir) => dir.get_length(),
|
||||||
|
DataTrackType::File(file) => file.get_length(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_xa_audio(&self) -> Option<bool> {
|
||||||
|
match self {
|
||||||
|
DataTrackType::Directory(dir) => dir.is_xa_audio(),
|
||||||
|
DataTrackType::File(file) => file.is_xa_audio(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DirectoryDataType {
|
||||||
|
pub name: String,
|
||||||
|
member: Vec<DataTrackType>,
|
||||||
|
lba: usize,
|
||||||
|
length: usize,
|
||||||
|
pub is_hidden: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirectoryDataType {
|
||||||
|
const CUR_DIR_NAME:&'static str = "\x00";
|
||||||
|
const PARENT_DIR_NAME:&'static str = "\x01";
|
||||||
|
|
||||||
|
pub fn new(name: &str) -> DirectoryDataType {
|
||||||
|
DirectoryDataType{name: name.to_string(), member: Vec::new(), lba: 0usize, length: 0usize, is_hidden: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_pvd_root_dir(lba: usize) -> DirectoryDataType {
|
||||||
|
DirectoryDataType{
|
||||||
|
name: Self::CUR_DIR_NAME.to_string(),
|
||||||
|
member: Vec::new(),
|
||||||
|
lba,
|
||||||
|
length: 34,
|
||||||
|
is_hidden: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_flat_cur_dir(&self) -> DirectoryDataType {
|
||||||
|
self.as_other_dir(Self::CUR_DIR_NAME, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_flat_parent_dir(&self) -> DirectoryDataType {
|
||||||
|
self.as_other_dir(Self::PARENT_DIR_NAME, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_file(&mut self, file: FileDataType) -> bool {
|
||||||
|
match self.get(file.name.as_ref()) {
|
||||||
|
Some(_) => false,
|
||||||
|
None => {
|
||||||
|
self.member.push(DataTrackType::File(file));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_dir(&mut self, dir: DirectoryDataType) -> bool {
|
||||||
|
match self.get(dir.name.as_ref()) {
|
||||||
|
Some(_) => false,
|
||||||
|
None => {
|
||||||
|
self.member.push(DataTrackType::Directory(dir));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort(&mut self) {
|
||||||
|
self.member.sort_by(|a, b|
|
||||||
|
a.get_name().cmp(b.get_name())
|
||||||
|
);
|
||||||
|
|
||||||
|
for member in self.member.iter_mut() {
|
||||||
|
if let DataTrackType::Directory(dir) = member {
|
||||||
|
dir.sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_lengthes(&mut self) {
|
||||||
|
if self.is_hidden {
|
||||||
|
self.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
let mut length = DirectoryRecord::calculate_size_for(Self::CUR_DIR_NAME, true) + DirectoryRecord::calculate_size_for(Self::PARENT_DIR_NAME, true);
|
||||||
|
|
||||||
|
for member in self.member.iter_mut() {
|
||||||
|
match member {
|
||||||
|
DataTrackType::Directory(dir) => {
|
||||||
|
if !dir.is_hidden {
|
||||||
|
length += DirectoryRecord::calculate_size_for(dir.name.as_ref(), true);
|
||||||
|
}
|
||||||
|
dir.calculate_lengthes();
|
||||||
|
}
|
||||||
|
DataTrackType::File(file) => {
|
||||||
|
if !file.is_hidden {
|
||||||
|
length += DirectoryRecord::calculate_size_for(file.name.as_ref(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.length = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_uppercase(&mut self) {
|
||||||
|
if !self.is_hidden {
|
||||||
|
self.name = self.name.to_uppercase();
|
||||||
|
for member in self.member.iter_mut() {
|
||||||
|
match member {
|
||||||
|
DataTrackType::Directory(dir) => {
|
||||||
|
dir.to_uppercase();
|
||||||
|
},
|
||||||
|
DataTrackType::File(file) => {
|
||||||
|
file.to_uppercase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &DataTrackType> {
|
||||||
|
self.member.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut DataTrackType> {
|
||||||
|
self.member.iter_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_file_mut(&mut self) -> impl Iterator<Item = &mut FileDataType> {
|
||||||
|
self.member.iter_mut().filter_map(|member| {
|
||||||
|
match member {
|
||||||
|
DataTrackType::Directory(_) => None,
|
||||||
|
DataTrackType::File(file) => Some(file),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_dir(&self) -> impl Iterator<Item = &DirectoryDataType> {
|
||||||
|
self.member.iter().filter_map(|member| {
|
||||||
|
match member {
|
||||||
|
DataTrackType::Directory(dir) => Some(dir),
|
||||||
|
DataTrackType::File(_) => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_dir_mut(&mut self) -> impl Iterator<Item = &mut DirectoryDataType> {
|
||||||
|
self.member.iter_mut().filter_map(|member| {
|
||||||
|
match member {
|
||||||
|
DataTrackType::Directory(dir) => Some(dir),
|
||||||
|
DataTrackType::File(_) => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, name: &str) -> Option<&DataTrackType> {
|
||||||
|
self.member.iter().find(|entry| {
|
||||||
|
entry.get_name() == name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_other_dir(&self, name: &str, is_flat: bool) -> DirectoryDataType {
|
||||||
|
DirectoryDataType{
|
||||||
|
name: name.to_string(),
|
||||||
|
member: if is_flat {Vec::new()} else {self.member.clone()},
|
||||||
|
lba: self.lba,
|
||||||
|
length: self.length,
|
||||||
|
is_hidden: self.is_hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LBASize for DirectoryDataType {
|
||||||
|
fn set_lba(&mut self, lba: usize) {
|
||||||
|
self.lba = lba;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_lba(&self) -> usize {
|
||||||
|
self.lba
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_length(&self) -> usize {
|
||||||
|
round_bytes_mode2_form1(self.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_xa_audio(&self) -> Option<bool> {
|
||||||
|
Some(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DirectoryDataType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
fn print_self(f: &mut std::fmt::Formatter<'_>, indent: usize, value: &Vec<DataTrackType>) -> std::fmt::Result {
|
||||||
|
for member in value.iter() {
|
||||||
|
match member {
|
||||||
|
DataTrackType::Directory(dir) => {
|
||||||
|
write!(f, "{:indent$}D: {} {{\n", "", dir.name, indent=indent)?;
|
||||||
|
print_self(f, indent + 4, &dir.member)?;
|
||||||
|
write!(f, "{:indent$}}}\n", "", indent=indent)?;
|
||||||
|
}
|
||||||
|
DataTrackType::File(file) => {
|
||||||
|
write!(f, "{:indent$}F: {}\n", "", file.name, indent=indent)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
print_self(f, 0, &self.member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FileDataType {
|
||||||
|
pub name: String,
|
||||||
|
file_path: String,
|
||||||
|
content: FileContent,
|
||||||
|
lba: usize,
|
||||||
|
padded_lba_length: Option<usize>,
|
||||||
|
pub is_hidden: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileDataType {
|
||||||
|
pub fn new(name: &str, content: FileContent) -> FileDataType {
|
||||||
|
FileDataType{name: name.to_string(), file_path: String::new(), content, lba: 0usize, padded_lba_length: None, is_hidden: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_content(&mut self) -> Result<(), Error> {
|
||||||
|
match &mut self.content {
|
||||||
|
FileContent::Request(file) => {
|
||||||
|
let result = std::mem::take(file).process()?;
|
||||||
|
|
||||||
|
self.content = FileContent::Loaded(result.0);
|
||||||
|
self.file_path = result.1;
|
||||||
|
|
||||||
|
if let Some(padded_lba_length) = self.padded_lba_length {
|
||||||
|
let lba_length = LBASize::default_get_length_lba(self)?;
|
||||||
|
if lba_length > padded_lba_length {
|
||||||
|
return Err(Error::GenericError(format!("{} padded lba length of {} exceeds calculated size of {}", self.file_path, padded_lba_length, lba_length)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
FileContent::Loaded(_) => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_uppercase(&mut self) {
|
||||||
|
if !self.is_hidden {
|
||||||
|
self.name = self.name.to_uppercase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_data_ref(&self) -> Result<&ProcessedFile, Error> {
|
||||||
|
match &self.content {
|
||||||
|
FileContent::Request(_) => Err(Error::GenericError("Accessing raw data not possible for reuqested file".to_owned())),
|
||||||
|
FileContent::Loaded(data) => Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_path(&self) -> &String {
|
||||||
|
match &self.content {
|
||||||
|
FileContent::Request(file) => file.get_path(),
|
||||||
|
FileContent::Loaded(_) => &self.file_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lba_padding(&mut self, padded_lba_length: usize) {
|
||||||
|
if padded_lba_length > 0 {
|
||||||
|
self.padded_lba_length = Some(padded_lba_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LBASize for FileDataType {
|
||||||
|
fn set_lba(&mut self, lba: usize) {
|
||||||
|
self.lba = lba;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_lba(&self) -> usize {
|
||||||
|
self.lba
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_length(&self) -> usize {
|
||||||
|
match &self.content {
|
||||||
|
FileContent::Request(_) => 0,
|
||||||
|
FileContent::Loaded(data) => {
|
||||||
|
|
||||||
|
data.len()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_length_lba(&self) -> Result<usize, Error> {
|
||||||
|
if let Some(padded_lba_length) = self.padded_lba_length {
|
||||||
|
Ok(padded_lba_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
LBASize::default_get_length_lba(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_xa_audio(&self) -> Option<bool> {
|
||||||
|
match &self.content {
|
||||||
|
FileContent::Request(_) => None,
|
||||||
|
FileContent::Loaded(content) => Some(content.is_xa_audio()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum FileContent {
|
||||||
|
Request(RequestedFile),
|
||||||
|
Loaded(ProcessedFile),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileContent {
|
||||||
|
pub fn new_raw_data(path: String) -> FileContent {
|
||||||
|
FileContent::Request(RequestedFile::new_raw_data(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_xa_audio(path: String) -> FileContent {
|
||||||
|
FileContent::Request(RequestedFile::new_xa_audio(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CUEDesc {
|
||||||
|
pub output_file: String,
|
||||||
|
pub output_bin_file: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CUEDesc {
|
||||||
|
pub fn new() -> CUEDesc {
|
||||||
|
CUEDesc{output_file: String::new(), output_bin_file: String::new()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deconstruct(self) -> (String, Vec<Specifier>) {
|
||||||
|
(self.output_file, vec!(Specifier::File{path: self.output_bin_file, format: Format::Binary}))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
use cdtypes::Error;
|
||||||
|
use super::cd_desc::LBASize;
|
||||||
|
|
||||||
|
pub type EntryVec = Vec<Entry>;
|
||||||
|
|
||||||
|
pub enum Entry {
|
||||||
|
Directory(DirectoryDataType),
|
||||||
|
File(FileDataType)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DirectoryDataType {
|
||||||
|
pub name: String,
|
||||||
|
pub lba: usize,
|
||||||
|
pub size: usize,
|
||||||
|
pub lba_length: usize,
|
||||||
|
pub entries: EntryVec,
|
||||||
|
pub is_hidden: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirectoryDataType {
|
||||||
|
pub fn new(name: String, lba_size: &dyn LBASize, is_hidden: bool) -> Result<DirectoryDataType, Error> {
|
||||||
|
Ok(DirectoryDataType{name, lba: lba_size.get_lba(), size: lba_size.get_length(), lba_length: lba_size.get_length_lba()?, entries: Vec::new(), is_hidden})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FileDataType {
|
||||||
|
pub name: String,
|
||||||
|
pub file_path: String,
|
||||||
|
pub lba: usize,
|
||||||
|
pub size: usize,
|
||||||
|
pub lba_length: usize,
|
||||||
|
pub is_hidden: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileDataType {
|
||||||
|
pub fn new(name: String, file_path: String, lba_size: &dyn LBASize, is_hidden: bool) -> Result<FileDataType, Error> {
|
||||||
|
Ok(FileDataType{name, file_path, lba: lba_size.get_lba(), size: lba_size.get_length(), lba_length: lba_size.get_length_lba()?, is_hidden})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_dir_entry(entry: DirectoryDataType) -> Entry {
|
||||||
|
Entry::Directory(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_file_entry(name: String, file_path: String, lba_size: &dyn LBASize, is_hidden: bool) -> Result<Entry, Error> {
|
||||||
|
Ok(Entry::File(FileDataType::new(name, file_path, lba_size, is_hidden)?))
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
pub mod types;
|
||||||
|
pub mod psx;
|
||||||
|
pub mod cd_desc;
|
||||||
|
pub mod lba_map;
|
||||||
|
|
||||||
|
use cdtypes::{Error, types::{*, time::Time}};
|
||||||
|
use types::EncodeBehaviour;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
pub fn write_sectors(mut file: std::fs::File, sectors: Vec<types::IntermediateSector>) -> Result<(), Error> {
|
||||||
|
let mut time = Time::cd_start();
|
||||||
|
|
||||||
|
for sector in sectors {
|
||||||
|
file.write(§or.encode(time.clone(), EncodeBehaviour::Mode0AsMode2From1)?)?;
|
||||||
|
time.add_sector()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_cue(file: std::fs::File, cue_specs: Vec<cue::Specifier>) -> Result<(), Error> {
|
||||||
|
cue::writer::write(file, cue_specs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_lba(file: &mut dyn std::io::Write, lba_map: lba_map::EntryVec, bin_path: &str, cue_path: &str) -> Result<(), Error> {
|
||||||
|
macro_rules! write_formatted_line {
|
||||||
|
($file:ident, $type:expr, $name:expr, $length:expr, $lba:expr, $time_code:expr, $bytes:expr, $file_name:expr) => {
|
||||||
|
writeln!($file, "\t{:<TYPE_WIDTH$}{:<NAME_WIDTH$}{:<LENGTH_WIDTH$}{:<LBA_WIDTH$}{:<TIMECODE_WIDTH$}{:<BYTES_WIDTH$}{}", $type, $name, $length, $lba, $time_code, $bytes, $file_name)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const TYPE_WIDTH: usize = 6;
|
||||||
|
const NAME_WIDTH: usize = 17;
|
||||||
|
const LENGTH_WIDTH: usize = 10;
|
||||||
|
const LBA_WIDTH: usize = 10;
|
||||||
|
const TIMECODE_WIDTH: usize = 12;
|
||||||
|
const BYTES_WIDTH: usize = 10;
|
||||||
|
|
||||||
|
fn print_dir(file: &mut dyn std::io::Write, dir: lba_map::DirectoryDataType) -> Result<(), Error> {
|
||||||
|
let is_hidden = dir.is_hidden;
|
||||||
|
|
||||||
|
write_formatted_line!(file, if is_hidden {"!Dir"} else {"Dir"}, dir.name, dir.lba_length, dir.lba, "<none>", dir.size, "")?;
|
||||||
|
for entry in dir.entries {
|
||||||
|
match entry {
|
||||||
|
lba_map::Entry::Directory(dir) => print_dir(file, dir)?,
|
||||||
|
lba_map::Entry::File(file_info) => print_file(file, file_info)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_formatted_line!(file, "End", dir.name, "", "", "", "", "")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_file(file: &mut dyn std::io::Write, file_info: lba_map::FileDataType) -> Result<(), Error> {
|
||||||
|
write_formatted_line!(file, if file_info.is_hidden {"!File"} else {"File"}, file_info.name, file_info.lba_length, file_info.lba, "<none>", file_info.size, file_info.file_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(file, "File LBA log generated by PSXCDGEN v0.5.0\n")?;
|
||||||
|
writeln!(file, "Image bin file: {}", bin_path)?;
|
||||||
|
writeln!(file, "Image cue file: {}", cue_path)?;
|
||||||
|
writeln!(file, "\nFile System:\n\n")?;
|
||||||
|
|
||||||
|
write_formatted_line!(file, "Type", "Name", "Length", "LBA", "Time Code", "Bytes", "File")?;
|
||||||
|
|
||||||
|
for entry in lba_map {
|
||||||
|
match entry {
|
||||||
|
lba_map::Entry::Directory(dir) => print_dir(file, dir)?,
|
||||||
|
lba_map::Entry::File(file_info) => print_file(file, file_info)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
use super::super::super::types::IntermediateSector;
|
||||||
|
use cdtypes::{Error, types::{sector as raw_sector, helper::sector_count_mode2_form1}};
|
||||||
|
|
||||||
|
pub fn copy_data<'a, T, const SIZE:usize>(dst: &mut [T; SIZE], src: &'a [T]) -> &'a [T] {
|
||||||
|
let data_size = {
|
||||||
|
if src.len() > SIZE {
|
||||||
|
SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
src.len()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe{std::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), data_size)};
|
||||||
|
&src[data_size..src.len()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_sectors(sectors: &mut Vec<IntermediateSector>, mut amount: usize) {
|
||||||
|
while amount > 0 {
|
||||||
|
sectors.push(IntermediateSector::Empty);
|
||||||
|
amount -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_sectors_upto(sectors: &mut Vec<IntermediateSector>, amount: usize) {
|
||||||
|
let cur_len = sectors.len();
|
||||||
|
if cur_len < amount {
|
||||||
|
fill_sectors(sectors, amount - cur_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_cdxa_data(sectors: &mut Vec<IntermediateSector>, mut start_idx: usize, mut data: &[u8], mut sub_header: raw_sector::SubHeader) -> Result<(), Error> {
|
||||||
|
let end_idx = start_idx + sector_count_mode2_form1(data.len());
|
||||||
|
|
||||||
|
while start_idx < end_idx {
|
||||||
|
let mut sec_data = [0u8; raw_sector::Mode2Form1::DATA_SIZE];
|
||||||
|
data = copy_data(&mut sec_data, data);
|
||||||
|
|
||||||
|
if start_idx + 1 == end_idx {
|
||||||
|
sub_header.sub_mode.set_eor();
|
||||||
|
sub_header.sub_mode.set_eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sector) = sectors.get_mut(start_idx) {
|
||||||
|
*sector = IntermediateSector::CDXAData(sec_data, sub_header.clone());
|
||||||
|
start_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::NoSectorForLBA(start_idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_cdxa_audio(sectors: &mut Vec<IntermediateSector>, mut start_idx: usize, data: &Vec<(raw_sector::SubHeader, [u8; raw_sector::Mode2Form2::DATA_SIZE])>) -> Result<(), Error> {
|
||||||
|
for data in data {
|
||||||
|
if let Some(sector) = sectors.get_mut(start_idx) {
|
||||||
|
*sector = IntermediateSector::CDXAAudio(data.1, data.0.clone());
|
||||||
|
start_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::NoSectorForLBA(start_idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,570 @@
|
||||||
|
pub mod helper;
|
||||||
|
|
||||||
|
use super::helper::{copy_data, fill_sectors, fill_sectors_upto, write_cdxa_data, write_cdxa_audio};
|
||||||
|
use super::super::{types::{ProcessedFile, builder::*, IntermediateSector}, cd_desc::{DataTrackType, DirectoryDataType, LBASize, PVD as PVDDesc}, lba_map};
|
||||||
|
use cdtypes::{
|
||||||
|
Error,
|
||||||
|
types::{
|
||||||
|
*,
|
||||||
|
pvd::{PrimaryVolumeDescriptor, VolumeDescriptorTerminator},
|
||||||
|
cdstring::{AString, CDStringValidator, DStringValidator, DString},
|
||||||
|
date::{Date, SmallDate},
|
||||||
|
dir_record::DirectoryRecord,
|
||||||
|
helper::sector_count_mode2_form1,
|
||||||
|
lsb_msb::*,
|
||||||
|
path_table::{PathTableL, PathTableM},
|
||||||
|
sector::{AudioSample, Mode2Form1},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
use std::{fs::OpenOptions, io::{Read, Seek, SeekFrom}};
|
||||||
|
|
||||||
|
const PVD_IDX:usize = 16;
|
||||||
|
const VDT_IDX:usize = 16 + 1;
|
||||||
|
|
||||||
|
pub struct PathTableLayout {
|
||||||
|
pub table_1: usize,
|
||||||
|
pub table_2: usize,
|
||||||
|
pub table_3: usize,
|
||||||
|
pub table_4: usize,
|
||||||
|
pub size: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ContentLayout {
|
||||||
|
pub root_lba: usize,
|
||||||
|
pub path_table: PathTableLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cd_header_size() -> usize {
|
||||||
|
VDT_IDX + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_licence(licence_path: String, sectors: &mut Vec<IntermediateSector>) -> Result<(), Error> {
|
||||||
|
fn read_licence_text(file: &mut std::fs::File) -> Result<[u8; Mode2Form1::DATA_SIZE], Error> {
|
||||||
|
let mut data = [0u8; Mode2Form1::DATA_SIZE];
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(0x2488))?;
|
||||||
|
file.read_exact(&mut data)?;
|
||||||
|
|
||||||
|
for i in 0..(data.len()-4) {
|
||||||
|
if data[i..i+4].starts_with(&[b'S', b'o', b'n', b'y']) {
|
||||||
|
data[i] = b'J';
|
||||||
|
data[i+1] = b'a';
|
||||||
|
data[i+2] = b'b';
|
||||||
|
data[i+3] = b'y';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_read_licence_logo(file: &mut std::fs::File) -> Result<(), Error> {
|
||||||
|
file.seek(SeekFrom::Start(0x2DA8))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_licence_logo(file: &mut std::fs::File) -> Result<[u8; Mode2Form1::DATA_SIZE], Error> {
|
||||||
|
let mut data = [0u8; Mode2Form1::DATA_SIZE];
|
||||||
|
|
||||||
|
file.read_exact(&mut data)?;
|
||||||
|
file.seek(SeekFrom::Current(0x120))?;
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut licence_file = OpenOptions::new().read(true).open(licence_path)?;
|
||||||
|
|
||||||
|
for i in 0..=15 {
|
||||||
|
if let Some(sector) = sectors.get_mut(i) {
|
||||||
|
*sector = {
|
||||||
|
match i {
|
||||||
|
0..=3 => EmptySectorBuilder::CDXAData(),
|
||||||
|
4 => {
|
||||||
|
let sector = IntermediateSector::CDXAData(read_licence_text(&mut licence_file)?, SubHeaderBuilder::DataDefault(false));
|
||||||
|
set_read_licence_logo(&mut licence_file)?;
|
||||||
|
|
||||||
|
sector
|
||||||
|
},
|
||||||
|
5..=11 => {
|
||||||
|
IntermediateSector::CDXAData(read_licence_logo(&mut licence_file)?, SubHeaderBuilder::DataDefault(false))
|
||||||
|
},
|
||||||
|
12..=15 => EmptySectorBuilder::CDXAAudio(),
|
||||||
|
_ => {
|
||||||
|
return Err(Error::GenericError("SystemArea can't exceed 16 sectors".to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::GenericError(format!("Couldn't access sector {} of System Area", i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_pvd(mut desc: PVDDesc, sectors: &mut Vec<IntermediateSector>, content_layout: ContentLayout) -> Result<(), Error> {
|
||||||
|
fn write_root_dir_record(raw_pvd: &mut PrimaryVolumeDescriptor, lba: usize) {
|
||||||
|
let root_dir = raw_pvd.get_root_dir_record_mut();
|
||||||
|
|
||||||
|
write_dir_record_entry(&DataTrackType::Directory(DirectoryDataType::new_pvd_root_dir(lba)), root_dir, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_path_table(raw_pvd: &mut PrimaryVolumeDescriptor, layout: PathTableLayout) {
|
||||||
|
raw_pvd.path_table_size.write(layout.size as u32);
|
||||||
|
raw_pvd.path_table_1.write(layout.table_1 as u32);
|
||||||
|
raw_pvd.path_table_2.write(layout.table_2 as u32);
|
||||||
|
raw_pvd.path_table_3.write(layout.table_3 as u32);
|
||||||
|
raw_pvd.path_table_4.write(layout.table_4 as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make sure we have enough space and make all names upper case
|
||||||
|
fill_sectors_upto(sectors, VDT_IDX + 1);
|
||||||
|
desc.to_uppercase();
|
||||||
|
|
||||||
|
let sectors_size = sectors.len();
|
||||||
|
let now_date = Date::now();
|
||||||
|
let mut raw_pvd = PrimaryVolumeDescriptor::new();
|
||||||
|
|
||||||
|
raw_pvd.system_id = AString::from_str("PLAYSTATION")?;
|
||||||
|
if desc.volume_identifier.len() > 8 {
|
||||||
|
println!("Warning: PSX might not support volume identifiers bigger then 8 chars")
|
||||||
|
}
|
||||||
|
raw_pvd.volume_id = DString::from_str(desc.volume_identifier.as_str())?;
|
||||||
|
raw_pvd.publisher_id = AString::from_str(desc.publisher.as_str())?;
|
||||||
|
raw_pvd.data_preparer = AString::from_str(desc.data_preparer.as_str())?;
|
||||||
|
raw_pvd.app_id = AString::from_str("PLAYSTATION")?;
|
||||||
|
raw_pvd.vol_create_time = now_date;
|
||||||
|
raw_pvd.vol_space_size.write((sectors_size - 2) as u32);
|
||||||
|
write_root_dir_record(&mut raw_pvd, content_layout.root_lba);
|
||||||
|
write_path_table(&mut raw_pvd, content_layout.path_table);
|
||||||
|
|
||||||
|
if let Some(sector) = sectors.get_mut(PVD_IDX) {
|
||||||
|
*sector = SectorBuilder::CDXAData(raw_pvd, SubHeaderBuilder::VolumeDescriptorDefault(false));
|
||||||
|
|
||||||
|
if let Some(sector) = sectors.get_mut(VDT_IDX) {
|
||||||
|
|
||||||
|
*sector = SectorBuilder::CDXAData(VolumeDescriptorTerminator::new(), SubHeaderBuilder::VolumeDescriptorDefault(true));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(String::from("Failed padding sectors to VDT")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(String::from("Failed padding sectors to PVD")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_content(mut root_dir: DirectoryDataType, sectors: &mut Vec<IntermediateSector>, lba_map: Option<&mut lba_map::EntryVec>) -> Result<ContentLayout, Error> {
|
||||||
|
let path_table_size = {
|
||||||
|
let lba_size = calculate_path_table_size(&root_dir);
|
||||||
|
if lba_size > Mode2Form1::DATA_SIZE {
|
||||||
|
println!("Warning: Path Table has size of {} bytes but PSX might only support Path Tables of one sector data size", lba_size);
|
||||||
|
}
|
||||||
|
lba_size
|
||||||
|
};
|
||||||
|
let start_idx = sectors.len() + (path_table_size_sector_count(path_table_size)*4);
|
||||||
|
let new_sector_amount = arrange_content(&mut root_dir, start_idx)?;
|
||||||
|
|
||||||
|
if let Some(lba_map) = lba_map {
|
||||||
|
append_lba_for_data_track(lba_map, &root_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
root_dir.sort();
|
||||||
|
let layout = write_path_tables(&root_dir, sectors, path_table_size)?;
|
||||||
|
write_arranged_content(root_dir, sectors, new_sector_amount)?;
|
||||||
|
|
||||||
|
println!("I have {} sectors because I added {} new sectors (Start: {}, PathTableSize: {})", sectors.len(), new_sector_amount, start_idx, path_table_size);
|
||||||
|
Ok(ContentLayout{root_lba: start_idx, path_table: layout})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_audio(pcm: Vec<AudioSample>, sectors: &mut Vec<IntermediateSector>) -> Result<(), Error> {
|
||||||
|
let mut pcm = &pcm[0..];
|
||||||
|
|
||||||
|
while pcm.len() > 0 {
|
||||||
|
let mut pcm_sector = [AudioSample::default(); sector::Audio::SAMPLE_SIZE];
|
||||||
|
|
||||||
|
pcm = copy_data(&mut pcm_sector, pcm);
|
||||||
|
sectors.push(IntermediateSector::Audio(pcm_sector));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decorate_names(root_dir: &mut DirectoryDataType) -> Result<(), Error> {
|
||||||
|
fn add_version_number(name: &mut String) -> Result<(), Error> {
|
||||||
|
if let Some(pos) = name.rfind(';') {
|
||||||
|
let version_str = &name[(pos + 1)..];
|
||||||
|
|
||||||
|
if !version_str.is_empty() {
|
||||||
|
if let Err(_) = version_str.parse::<u16>() {
|
||||||
|
return Err(Error::GenericError(format!("{} does not has a valid version number", name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
name.push('1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
name.push_str(";1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_file_name_extension(string: &String) -> (&str, &str) {
|
||||||
|
let mut name = string.as_ref();
|
||||||
|
let mut ext = "";
|
||||||
|
|
||||||
|
if let Some(dot) = string.rfind('.') {
|
||||||
|
name = &string[0..dot];
|
||||||
|
if let Some(semi) = string.rfind(';') {
|
||||||
|
ext = &string[(dot + 1)..semi];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(name, ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
root_dir.to_uppercase();
|
||||||
|
for entry in root_dir.iter_mut() {
|
||||||
|
match entry {
|
||||||
|
DataTrackType::Directory(dir) => {
|
||||||
|
DStringValidator::is_valid_ascii(dir.name.as_bytes())?;
|
||||||
|
if dir.name.len() > 8 {
|
||||||
|
return Err(Error::GenericError(format!("Directory name {} must not have more then 8 characters", dir)));
|
||||||
|
}
|
||||||
|
decorate_names(dir)?;
|
||||||
|
},
|
||||||
|
DataTrackType::File(file) => {
|
||||||
|
add_version_number(&mut file.name)?;
|
||||||
|
let (name, ext) = get_file_name_extension(&file.name);
|
||||||
|
if ext.is_empty() {
|
||||||
|
return Err(Error::GenericError(format!("Missing file extension for {}", file.name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.len() > 8 {
|
||||||
|
return Err(Error::GenericError(format!("File name {} must not have more then 8 characters", name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ext.len() > 3 {
|
||||||
|
return Err(Error::GenericError(format!("File extension {} for file {} must not have more then 3 characters", ext, name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
DStringValidator::is_valid_ascii(name.as_bytes())?;
|
||||||
|
DStringValidator::is_valid_ascii(ext.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_lba_for_data_track(lba_map: &mut lba_map::EntryVec, root_dir: &DirectoryDataType) -> Result<(), Error> {
|
||||||
|
fn append_dir(dir: &DirectoryDataType, map: &mut Vec<lba_map::Entry>) -> Result<(), Error> {
|
||||||
|
for element in dir.iter() {
|
||||||
|
match element {
|
||||||
|
DataTrackType::Directory(dir) => {
|
||||||
|
let mut dir_entry = lba_map::DirectoryDataType::new(dir.name.clone(), dir, dir.is_hidden)?;
|
||||||
|
append_dir(dir, &mut dir_entry.entries)?;
|
||||||
|
|
||||||
|
map.push(lba_map::new_dir_entry(dir_entry));
|
||||||
|
},
|
||||||
|
DataTrackType::File(file) => map.push(lba_map::new_file_entry(file.name.clone(), file.get_path().clone(), file, file.is_hidden)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
append_dir(root_dir, lba_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arrange_content(root_dir: &mut DirectoryDataType, start_lba: usize) -> Result<usize, Error> {
|
||||||
|
fn arrange_files(dir: &mut DirectoryDataType, mut lba: usize) -> Result<usize, Error> {
|
||||||
|
for file in dir.iter_file_mut() {
|
||||||
|
lba = file.set_lba_ret(lba)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(lba)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arrange_dirs(dir: &mut DirectoryDataType, mut lba: usize) -> Result<usize, Error> {
|
||||||
|
for dir in dir.iter_dir_mut() {
|
||||||
|
if !dir.is_hidden {
|
||||||
|
lba = dir.set_lba_ret(lba)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(lba)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arrange(dir: &mut DirectoryDataType, mut lba: usize) -> Result<usize, Error> {
|
||||||
|
lba = arrange_files(dir, lba)?;
|
||||||
|
lba = arrange_dirs(dir, lba)?;
|
||||||
|
|
||||||
|
for dir in dir.iter_dir_mut() {
|
||||||
|
lba = arrange(dir, lba)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(lba)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _alt_arrange(dir: &mut DirectoryDataType, mut lba: usize) -> Result<usize, Error> {
|
||||||
|
for entry in dir.iter_mut() {
|
||||||
|
match entry {
|
||||||
|
DataTrackType::Directory(dir) => {
|
||||||
|
lba = dir.set_lba_ret(lba)?;
|
||||||
|
lba = _alt_arrange(dir, lba)?;
|
||||||
|
},
|
||||||
|
DataTrackType::File(file) => lba = file.set_lba_ret(lba)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(lba)
|
||||||
|
}
|
||||||
|
|
||||||
|
root_dir.calculate_lengthes();
|
||||||
|
|
||||||
|
//Root is always sorted file first
|
||||||
|
let mut lba = start_lba;
|
||||||
|
|
||||||
|
lba = root_dir.set_lba_ret(lba)?;
|
||||||
|
lba = arrange(root_dir, lba)?;
|
||||||
|
|
||||||
|
Ok(lba - start_lba)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_dir_record_entry(entry: &DataTrackType, dir_rec: &mut DirectoryRecord, has_system_use: bool) {
|
||||||
|
enum XASystemUseType
|
||||||
|
{
|
||||||
|
Directory,
|
||||||
|
DataFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {dir_rec.new(entry.get_name().as_ref(), has_system_use)};
|
||||||
|
|
||||||
|
dir_rec.data_block_number.write(entry.get_lba() as u32);
|
||||||
|
dir_rec.data_size.write(entry.get_length() as u32);
|
||||||
|
dir_rec.time_stamp = SmallDate::now();
|
||||||
|
|
||||||
|
let entry_type = {
|
||||||
|
match entry {
|
||||||
|
DataTrackType::Directory(_) => {
|
||||||
|
dir_rec.set_directory();
|
||||||
|
XASystemUseType::Directory
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTrackType::File(_) => {
|
||||||
|
dir_rec.set_file();
|
||||||
|
XASystemUseType::DataFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cdxa_entry) = dir_rec.get_cdxa_system_use_mut() {
|
||||||
|
match entry_type {
|
||||||
|
XASystemUseType::Directory => {
|
||||||
|
cdxa_entry.file_attribute.set_mode2();
|
||||||
|
cdxa_entry.file_attribute.set_directory();
|
||||||
|
}
|
||||||
|
XASystemUseType::DataFile => {
|
||||||
|
cdxa_entry.file_attribute.set_mode2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_table_size_sector_count(bytes: usize) -> usize {
|
||||||
|
sector_count_mode2_form1(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_path_tables(arranged_root_dir: &DirectoryDataType, sectors: &mut Vec<IntermediateSector>, size: usize) -> Result<PathTableLayout, Error> {
|
||||||
|
type PseudoArray = (Vec<u8>, usize);
|
||||||
|
|
||||||
|
fn push_table_entry(raw_buffer: &mut PseudoArray, name: &str, lba: usize, parent_table_id: usize) -> Result<(), Error> {
|
||||||
|
let entry_size = PathTableL::calculate_size_for(name);
|
||||||
|
if raw_buffer.1 + entry_size > raw_buffer.0.len() {
|
||||||
|
return Err(Error::GenericError(format!("Out of memory for Table Path Entry!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_entry = unsafe{
|
||||||
|
let entry = std::mem::transmute::<&mut u8, &mut PathTableL>(&mut raw_buffer.0[raw_buffer.1]);
|
||||||
|
|
||||||
|
entry.new(name);
|
||||||
|
entry
|
||||||
|
};
|
||||||
|
|
||||||
|
path_entry.directory_logical_block.write(lba as u32);
|
||||||
|
path_entry.parent_logical_block.write(parent_table_id as u16);
|
||||||
|
raw_buffer.1 += entry_size;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_all_entries(raw_buffer: &mut PseudoArray, dir: &DirectoryDataType, mut parent: usize) -> Result<(), Error> {
|
||||||
|
if !dir.is_hidden {
|
||||||
|
for dir in dir.iter_dir() {
|
||||||
|
if !dir.is_hidden {
|
||||||
|
push_table_entry(raw_buffer, dir.name.as_ref(), dir.get_lba(), parent)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for dir in dir.iter_dir() {
|
||||||
|
if !dir.is_hidden {
|
||||||
|
parent += 1;
|
||||||
|
push_all_entries(raw_buffer, dir, parent)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_table(sectors: &mut Vec<IntermediateSector>, mut idx: usize, length_lba: usize, data: &[u8]) -> Result<usize, Error> {
|
||||||
|
for _ in 0..2 {
|
||||||
|
write_cdxa_data(sectors, idx, &data, SubHeaderBuilder::DataDefault(false))?;
|
||||||
|
idx += length_lba;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_table(data_l: &mut [u8]) {
|
||||||
|
let mut idx = 0;
|
||||||
|
while idx < data_l.len() {
|
||||||
|
let org_value = unsafe {std::mem::transmute::<&u8, &PathTableL>(&data_l[idx])};
|
||||||
|
let new_value = unsafe {std::mem::transmute::<&mut u8, &mut PathTableM>(&mut data_l[idx])};
|
||||||
|
|
||||||
|
new_value.directory_logical_block.write(org_value.directory_logical_block.read());
|
||||||
|
new_value.parent_logical_block.write(org_value.parent_logical_block.read());
|
||||||
|
|
||||||
|
idx += new_value.get_size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut raw_buffer_table = (vec![0u8; size], 0);
|
||||||
|
|
||||||
|
push_table_entry(&mut raw_buffer_table, "\x00", arranged_root_dir.get_lba(), 1)?;
|
||||||
|
push_all_entries(&mut raw_buffer_table, arranged_root_dir, 1)?;
|
||||||
|
|
||||||
|
let mut table_idx = sectors.len();
|
||||||
|
let size_lba = path_table_size_sector_count(size);
|
||||||
|
|
||||||
|
let layout = PathTableLayout{table_1: table_idx, table_2: table_idx + size_lba, table_3: table_idx + (2*size_lba), table_4: table_idx + (3*size_lba), size: size};
|
||||||
|
|
||||||
|
fill_sectors(sectors, size_lba*4);
|
||||||
|
table_idx = write_table(sectors, table_idx, size_lba, &raw_buffer_table.0)?;
|
||||||
|
transform_table(&mut raw_buffer_table.0);
|
||||||
|
write_table(sectors, table_idx, size_lba, &raw_buffer_table.0)?;
|
||||||
|
|
||||||
|
Ok(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_arranged_content(arranged_root_dir: DirectoryDataType, sectors: &mut Vec<IntermediateSector>, amount: usize) -> Result<(), Error> {
|
||||||
|
fn write_dir(dir: &DirectoryDataType, parent: &DirectoryDataType, sectors: &mut Vec<IntermediateSector>) -> Result<(), Error> {
|
||||||
|
fn set_sector(data: [u8; Mode2Form1::DATA_SIZE], is_last: bool, lba: usize, sectors: &mut Vec<IntermediateSector>) -> Result<(), Error> {
|
||||||
|
if let Some(sector) = sectors.get_mut(lba) {
|
||||||
|
*sector = IntermediateSector::CDXAData(data, SubHeaderBuilder::DirectoryRecordDefault(is_last));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::NoSectorForLBA(lba))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir.is_hidden {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
let entry_length = dir.get_length_lba()?;
|
||||||
|
let mut sector_buffer = [0u8; Mode2Form1::DATA_SIZE];
|
||||||
|
let mut sector_idx = 0usize;
|
||||||
|
let mut lba = dir.get_lba();
|
||||||
|
let mut write_entry = |entry: &DataTrackType| -> Result<(), Error> {
|
||||||
|
if entry.is_hidden() {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
let name = entry.get_name().as_ref();
|
||||||
|
let size = DirectoryRecord::calculate_size_for(name, true);
|
||||||
|
if sector_idx + size > Mode2Form1::DATA_SIZE {
|
||||||
|
set_sector(sector_buffer, false, lba, sectors)?;
|
||||||
|
|
||||||
|
sector_buffer = [0u8; Mode2Form1::DATA_SIZE];
|
||||||
|
lba += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir_rec = unsafe {std::mem::transmute::<&mut u8, &mut DirectoryRecord>(&mut sector_buffer[sector_idx])};
|
||||||
|
write_dir_record_entry(entry, dir_rec, true);
|
||||||
|
|
||||||
|
sector_idx += size;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if entry_length > 1 {
|
||||||
|
println!("Warning: Directory {} spans {} sectors and might not be supported by PSX", dir.name, entry_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write current and parent dir
|
||||||
|
write_entry(&DataTrackType::Directory(dir.as_flat_cur_dir()))?;
|
||||||
|
write_entry(&DataTrackType::Directory(parent.as_flat_parent_dir()))?;
|
||||||
|
|
||||||
|
for entry in dir.iter() {
|
||||||
|
write_entry(entry)?;
|
||||||
|
}
|
||||||
|
set_sector(sector_buffer, true, lba, sectors)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(parent_dir: &DirectoryDataType, sectors: &mut Vec<IntermediateSector>) -> Result<(), Error> {
|
||||||
|
for entry in parent_dir.iter() {
|
||||||
|
match entry {
|
||||||
|
DataTrackType::Directory(dir) => {
|
||||||
|
write_dir(dir, parent_dir, sectors)?;
|
||||||
|
write(dir, sectors)?;
|
||||||
|
},
|
||||||
|
|
||||||
|
DataTrackType::File(file) => {
|
||||||
|
match file.get_data_ref()? {
|
||||||
|
ProcessedFile::Raw(raw_data) => write_cdxa_data(sectors, file.get_lba(), raw_data.as_ref(), SubHeaderBuilder::DataDefault(false))?,
|
||||||
|
ProcessedFile::XAAudio(raw_data) => write_cdxa_audio(sectors, file.get_lba(), raw_data)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_sectors(sectors, amount);
|
||||||
|
|
||||||
|
write_dir(&arranged_root_dir, &arranged_root_dir, sectors)?;
|
||||||
|
write(&arranged_root_dir, sectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_path_table_size(root_dir: &DirectoryDataType) -> usize {
|
||||||
|
if root_dir.is_hidden {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
let mut size = PathTableL::calculate_size_for("\x00");
|
||||||
|
|
||||||
|
for sub_dir in root_dir.iter_dir() {
|
||||||
|
if !sub_dir.is_hidden {
|
||||||
|
size += PathTableL::calculate_size_for(sub_dir.name.as_ref());
|
||||||
|
size += calculate_path_table_size(sub_dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
use super::{types::IntermediateSector, cd_desc::{CDDesc, DataTrackType, DirectoryDataType, Track}, lba_map};
|
||||||
|
use cdtypes::{types::{*, time::Time}, Error};
|
||||||
|
use encoder::helper;
|
||||||
|
|
||||||
|
pub mod encoder;
|
||||||
|
|
||||||
|
pub fn encode(cd_desc: CDDesc, cue_specs: &mut Vec<cue::Specifier>) -> Result<(Vec<IntermediateSector>, Option<lba_map::EntryVec>), Error> {
|
||||||
|
let header_size = encoder::get_cd_header_size();
|
||||||
|
let mut sectors = Vec::new();
|
||||||
|
let mut content_layout = None;
|
||||||
|
let mut cur_track = 1;
|
||||||
|
let mut cur_time = Time::cue_start();
|
||||||
|
let mut lba_map = Vec::new();
|
||||||
|
|
||||||
|
// Creates enough space for the PVD
|
||||||
|
helper::fill_sectors_upto(&mut sectors, header_size);
|
||||||
|
|
||||||
|
// Write Licence
|
||||||
|
if !cd_desc.licence_path.is_empty() {
|
||||||
|
encoder::write_licence(cd_desc.licence_path, &mut sectors)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for track in cd_desc.tracks {
|
||||||
|
match track {
|
||||||
|
Track::Data(mut dir) => {
|
||||||
|
process_dir(&mut dir)?;
|
||||||
|
// Decorate and verify the names for content creation
|
||||||
|
encoder::decorate_names(&mut dir)?;
|
||||||
|
let new_content_layout = encoder::write_content(dir, &mut sectors, Some(&mut lba_map))?;
|
||||||
|
|
||||||
|
if content_layout.is_none() {
|
||||||
|
content_layout = Some(new_content_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
cue_specs.push(cue::Specifier::Track{number: cur_track, data_type: cue::DataType::Mode2_2352});
|
||||||
|
cue_specs.push(cue::Specifier::Index{number: 1, time: cur_time});
|
||||||
|
}
|
||||||
|
|
||||||
|
Track::Audio(pcm) => {
|
||||||
|
encoder::append_audio(pcm, &mut sectors)?;
|
||||||
|
|
||||||
|
cue_specs.push(cue::Specifier::Track{number: cur_track, data_type: cue::DataType::Audio});
|
||||||
|
cue_specs.push(cue::Specifier::PreGap{time: Time::cd_pregap()});
|
||||||
|
cue_specs.push(cue::Specifier::Index{number: 1, time: cur_time});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_track += 1;
|
||||||
|
cur_time = Time::from_lba(sectors.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(content_layout) = content_layout {
|
||||||
|
// Now write the PVD cause write_content eventaully tells us where to start
|
||||||
|
encoder::write_pvd(cd_desc.pvd, &mut sectors, content_layout)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::GenericError("No Data track found!".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((sectors, Some(lba_map)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_dir(dir: &mut DirectoryDataType) -> Result<(), Error> {
|
||||||
|
for entry in dir.iter_mut() {
|
||||||
|
match entry {
|
||||||
|
DataTrackType::Directory(dir) => process_dir(dir)?,
|
||||||
|
DataTrackType::File(file) => file.process_content()?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
use cdtypes::types::sector::{Mode2Form1, Mode2Form2, SubHeader};
|
||||||
|
use super::IntermediateSector;
|
||||||
|
|
||||||
|
pub struct SectorBuilder{}
|
||||||
|
pub struct SubHeaderBuilder{}
|
||||||
|
pub struct EmptySectorBuilder{}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl SectorBuilder {
|
||||||
|
pub fn CDXAData<T>(data: T, sub_header: SubHeader) -> IntermediateSector {
|
||||||
|
unsafe {
|
||||||
|
let ref_data = std::mem::transmute::<&T, &[u8; Mode2Form1::DATA_SIZE]>(&data);
|
||||||
|
IntermediateSector::CDXAData(*ref_data, sub_header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl SubHeaderBuilder {
|
||||||
|
pub fn VolumeDescriptorDefault(is_eof: bool) -> SubHeader {
|
||||||
|
let mut sub_header = SubHeader::default();
|
||||||
|
|
||||||
|
sub_header.sub_mode.set_eor();
|
||||||
|
sub_header.sub_mode.set_data();
|
||||||
|
|
||||||
|
if is_eof {
|
||||||
|
sub_header.sub_mode.set_eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub_header
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn DirectoryRecordDefault(is_last: bool) -> SubHeader {
|
||||||
|
let mut sub_header = SubHeader::default();
|
||||||
|
|
||||||
|
sub_header.sub_mode.set_data();
|
||||||
|
if is_last {
|
||||||
|
sub_header.sub_mode.set_eor();
|
||||||
|
sub_header.sub_mode.set_eof();
|
||||||
|
}
|
||||||
|
sub_header
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn DataDefault(is_last: bool) -> SubHeader {
|
||||||
|
Self::DirectoryRecordDefault(is_last)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl EmptySectorBuilder {
|
||||||
|
pub fn CDXAData() -> IntermediateSector {
|
||||||
|
IntermediateSector::CDXAData([0u8; Mode2Form1::DATA_SIZE], SubHeaderBuilder::DataDefault(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn CDXAAudio() -> IntermediateSector {
|
||||||
|
let sub_header = {
|
||||||
|
let mut sub_header = SubHeader::default_form2();
|
||||||
|
|
||||||
|
sub_header.sub_mode.clear_audio();
|
||||||
|
sub_header
|
||||||
|
};
|
||||||
|
IntermediateSector::CDXAAudio([0u8; Mode2Form2::DATA_SIZE], sub_header)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
pub mod builder;
|
||||||
|
|
||||||
|
use cdtypes::{Error, types::{error_correction, sector, time::Time}};
|
||||||
|
use builder::SubHeaderBuilder;
|
||||||
|
use super::super::file_helper::read_file_to_u8;
|
||||||
|
|
||||||
|
type ByteSector = [u8; sector::SECTOR_SIZE];
|
||||||
|
|
||||||
|
pub enum EncodeBehaviour {
|
||||||
|
Normal,
|
||||||
|
Mode0AsMode2From1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum IntermediateSector {
|
||||||
|
Empty,
|
||||||
|
Audio([sector::AudioSample; sector::Audio::SAMPLE_SIZE]),
|
||||||
|
CDData([u8; sector::Mode1::DATA_SIZE]),
|
||||||
|
CDXAData([u8;sector::Mode2Form1::DATA_SIZE], sector::SubHeader),
|
||||||
|
CDXAAudio([u8;sector::Mode2Form2::DATA_SIZE], sector::SubHeader),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntermediateSector {
|
||||||
|
pub fn encode(self, time: Time, behaviour: EncodeBehaviour) -> Result<ByteSector, Error> {
|
||||||
|
match self {
|
||||||
|
Self::Empty =>
|
||||||
|
match behaviour {
|
||||||
|
EncodeBehaviour::Normal => Ok(Self::encode_mode0(time)),
|
||||||
|
EncodeBehaviour::Mode0AsMode2From1 => Ok(Self::encode_mode0_as_mode2_form1(time))
|
||||||
|
},
|
||||||
|
Self::Audio(data) => Ok(unsafe {std::mem::transmute::<[sector::AudioSample; sector::Audio::SAMPLE_SIZE], ByteSector>(data)}),
|
||||||
|
Self::CDData(data) => Ok(Self::encode_mode1(time, data)),
|
||||||
|
Self::CDXAData(data, sub_header) => Ok(Self::encode_mode2_form1(time, data, sub_header)),
|
||||||
|
Self::CDXAAudio(data, sub_header) => Ok(Self::encode_mode2_form2(time, data, sub_header)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_mode0(time: Time) -> ByteSector {
|
||||||
|
let mut sector = sector::Mode0::new();
|
||||||
|
|
||||||
|
sector.header.set_time(time);
|
||||||
|
unsafe {
|
||||||
|
std::mem::transmute::<sector::Mode0, ByteSector>(sector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_mode0_as_mode2_form1(time: Time) -> ByteSector {
|
||||||
|
Self::encode_mode2_form1(time, [0u8; sector::Mode1::DATA_SIZE], SubHeaderBuilder::DataDefault(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_mode1(time: Time, data: [u8; sector::Mode1::DATA_SIZE]) -> ByteSector {
|
||||||
|
let mut sector = sector::Mode1::new();
|
||||||
|
|
||||||
|
sector.header.set_time(time);
|
||||||
|
sector.data = data;
|
||||||
|
sector.finalize();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
std::mem::transmute::<sector::Mode1, ByteSector>(sector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_mode2_form1(time: Time, data: [u8; sector::Mode2Form1::DATA_SIZE], sub_header: sector::SubHeader) -> ByteSector {
|
||||||
|
let mut sector = sector::Mode2Form1::new();
|
||||||
|
|
||||||
|
sector.header.set_time(time);
|
||||||
|
sector.data = data;
|
||||||
|
sector.sub_header = sub_header;
|
||||||
|
|
||||||
|
sector.finalize();
|
||||||
|
unsafe {
|
||||||
|
std::mem::transmute::<sector::Mode2Form1, ByteSector>(sector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_mode2_form2(time: Time, data: [u8; sector::Mode2Form2::DATA_SIZE], sub_header: sector::SubHeader) -> ByteSector {
|
||||||
|
let mut sector = sector::Mode2Form2::new();
|
||||||
|
|
||||||
|
sector.header.set_time(time);
|
||||||
|
sector.data = data;
|
||||||
|
sector.sub_header = sub_header;
|
||||||
|
|
||||||
|
sector.finalize();
|
||||||
|
unsafe {
|
||||||
|
std::mem::transmute::<sector::Mode2Form2, ByteSector>(sector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RequestedFile {
|
||||||
|
path: String,
|
||||||
|
data_type: DataType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestedFile {
|
||||||
|
pub fn new_raw_data(file_path: String) -> RequestedFile {
|
||||||
|
Self::new_simple(file_path, DataType::RawData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_xa_audio(file_path: String) -> RequestedFile {
|
||||||
|
Self::new_simple(file_path, DataType::XAAudio)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(self) -> Result<(ProcessedFile, String), Error> {
|
||||||
|
let data = read_file_to_u8(self.path.as_ref())?;
|
||||||
|
|
||||||
|
match self.data_type {
|
||||||
|
DataType::None => Ok((ProcessedFile::Raw(Vec::new()), self.path)),
|
||||||
|
DataType::RawData => Ok((ProcessedFile::Raw(data), self.path)),
|
||||||
|
DataType::XAAudio => Ok((Self::parse_xa_audio(data), self.path)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_path(&self) -> &String {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_simple(file_path: String, data_type: DataType) -> RequestedFile {
|
||||||
|
RequestedFile{path: file_path, data_type}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_xa_audio(data: Vec<u8>) -> ProcessedFile {
|
||||||
|
const XAAUDIOFILE_SIZE:usize = std::mem::size_of::<XAAudioFile>();
|
||||||
|
|
||||||
|
let mut data = &data[..];
|
||||||
|
let mut xa_data = Vec::new();
|
||||||
|
|
||||||
|
while data.len() >= XAAUDIOFILE_SIZE {
|
||||||
|
let content = unsafe {&*(data.as_ptr() as *const XAAudioFile)};
|
||||||
|
|
||||||
|
xa_data.push((content.sub_header.clone(), content.data));
|
||||||
|
data = &data[XAAUDIOFILE_SIZE..];
|
||||||
|
}
|
||||||
|
ProcessedFile::XAAudio(xa_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for RequestedFile {
|
||||||
|
fn default() -> RequestedFile {
|
||||||
|
RequestedFile{path: String::new(), data_type: DataType::None}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ProcessedFile {
|
||||||
|
Raw(Vec<u8>),
|
||||||
|
XAAudio(Vec<(sector::SubHeader, [u8; sector::Mode2Form2::DATA_SIZE])>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessedFile {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Raw(data) => data.len(),
|
||||||
|
Self::XAAudio(data) => (data.len()*sector::Mode2Form2::DATA_SIZE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_xa_audio(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::XAAudio(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum DataType {
|
||||||
|
None,
|
||||||
|
RawData,
|
||||||
|
XAAudio,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
struct XAAudioFile {
|
||||||
|
pub sub_header: sector::SubHeader,
|
||||||
|
pub _sub_header_copy: sector::SubHeader,
|
||||||
|
pub data: [u8; sector::Mode2Form2::DATA_SIZE],
|
||||||
|
pub _edc: error_correction::EDC
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod iso_writer;
|
||||||
|
pub mod xml_reader;
|
||||||
|
pub mod file_helper;
|
||||||
|
|
||||||
|
pub use xml_reader::parse_mkpsxiso;
|
|
@ -0,0 +1,105 @@
|
||||||
|
use psxcdgen::{*, file_helper::{read_file_to_string, open_output_file, path_to_string}, iso_writer::{psx, cd_desc::*}};
|
||||||
|
use cdtypes::Error;
|
||||||
|
use clap::{Parser};
|
||||||
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[clap(about = "Generates a PSX ISO", long_about = None)]
|
||||||
|
struct CommandLine {
|
||||||
|
//Overrides output (not supported)
|
||||||
|
#[clap(short='y')]
|
||||||
|
override_output: bool,
|
||||||
|
|
||||||
|
//Output file for the lba values (not supported)
|
||||||
|
#[clap(long="lba")]
|
||||||
|
lba_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
//Output header file for the lba values (not supported)
|
||||||
|
#[clap(long="lbahead")]
|
||||||
|
lbahead_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
//Disables iso generation (not supported)
|
||||||
|
#[clap(long="noisogen")]
|
||||||
|
no_isogen: bool,
|
||||||
|
|
||||||
|
//The input XML
|
||||||
|
input_path: PathBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TaskError {
|
||||||
|
name: String,
|
||||||
|
error: Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TaskError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} failed with: \"{}\"", self.name, self.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_task<T>(name: &str, result: Result<T, Error>) -> Result<T, TaskError> {
|
||||||
|
match result {
|
||||||
|
Ok(value) => Ok(value),
|
||||||
|
Err(error) => Err(TaskError{name: name.to_owned(), error})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_desc(mut cd_desc: CDDesc, cue_desc: CUEDesc) -> Result<(), TaskError> {
|
||||||
|
let cd_output_file = std::mem::take(&mut cd_desc.output_file);
|
||||||
|
let (cue_path, mut cue_specs) = cue_desc.deconstruct();
|
||||||
|
let file = start_task("Open iso output file", open_output_file(cd_output_file.as_ref()))?;
|
||||||
|
let cue_file = start_task("Open cue output file", open_output_file(cue_path.as_ref()))?;
|
||||||
|
let (encoded_sector, lba_map) = start_task("Encode CD sectors", psx::encode(cd_desc, &mut cue_specs))?;
|
||||||
|
|
||||||
|
start_task("Writing ISO image", iso_writer::write_sectors(file, encoded_sector))?;
|
||||||
|
start_task("Writing CUE file", iso_writer::write_cue(cue_file, cue_specs))?;
|
||||||
|
if let Some(lba_map) = lba_map {
|
||||||
|
start_task("Write LBA file", iso_writer::write_lba(&mut std::io::stdout(), lba_map, cd_output_file.as_ref(), cue_path.as_ref()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_main(arguments: CommandLine) -> Result<(), TaskError> {
|
||||||
|
if !arguments.no_isogen {
|
||||||
|
let input_path = start_task("Opening XML file", path_to_string(arguments.input_path))?;
|
||||||
|
let input_content = start_task("Reading XML file", read_file_to_string(input_path.as_ref()))?;
|
||||||
|
let (cd_desc, cue_desc) = start_task("Parsing XML file like mkpsxiso", xml_reader::parse_mkpsxiso(input_content))?;
|
||||||
|
|
||||||
|
write_desc(cd_desc, cue_desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
//Only needed if running in mkpsxiso mode (Currently) the default
|
||||||
|
fn edit_env_args() -> Vec<String> {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
|
||||||
|
for argument in env::args() {
|
||||||
|
let mut argument = argument.to_owned();
|
||||||
|
|
||||||
|
if argument.len() > 2 && argument.starts_with('-') {
|
||||||
|
argument.insert(0, '-');
|
||||||
|
}
|
||||||
|
args.push(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
args
|
||||||
|
}
|
||||||
|
|
||||||
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
match CommandLine::try_parse_from(edit_env_args().iter()) {
|
||||||
|
Ok(arguments) => {
|
||||||
|
match run_main(arguments) {
|
||||||
|
Ok(()) => println!("Succees!"),
|
||||||
|
Err(error) => println!("{}", error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => println!("{}", error)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,333 @@
|
||||||
|
use cdtypes::{types::sector::AudioSample, Error};
|
||||||
|
use super::{
|
||||||
|
XMLReader,
|
||||||
|
super::iso_writer::cd_desc::{CDDesc, CUEDesc, Track, DirectoryDataType, FileContent, FileDataType}
|
||||||
|
};
|
||||||
|
use fast_xml::events::{attributes::Attributes, Event};
|
||||||
|
|
||||||
|
macro_rules! make_tag_name {
|
||||||
|
($name:expr) => {
|
||||||
|
paste::item! {
|
||||||
|
const [< $name:upper _TAG>]: &'static str = $name;
|
||||||
|
const [< $name:upper _TAG_B>]:&'static [u8] = [< $name:upper _TAG>].as_bytes();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_tag_name!("iso_project");
|
||||||
|
make_tag_name!("track");
|
||||||
|
make_tag_name!("identifiers");
|
||||||
|
make_tag_name!("directory_tree");
|
||||||
|
make_tag_name!("dir");
|
||||||
|
|
||||||
|
struct Desc {
|
||||||
|
cd: CDDesc,
|
||||||
|
cue: CUEDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Desc {
|
||||||
|
fn new() -> Desc {
|
||||||
|
Desc{cd: CDDesc::new(), cue: CUEDesc::new()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(xml: String) -> Result<(CDDesc, CUEDesc), Error> {
|
||||||
|
let mut desc = Desc::new();
|
||||||
|
let mut parser = XMLReader::new(xml.as_ref());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match parser.next()? {
|
||||||
|
Event::Start(tag) => {
|
||||||
|
match tag.name() {
|
||||||
|
ISO_PROJECT_TAG_B => {
|
||||||
|
handle_iso_project_attributes(&mut desc, tag.attributes())?;
|
||||||
|
handle_iso_project(&mut desc, &mut parser)?;
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::Eof => {
|
||||||
|
return Ok((desc.cd, desc.cue));
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_iso_project_attributes(desc: &mut Desc, attributes: Attributes) -> Result<(), Error> {
|
||||||
|
XMLReader::for_each_attribute(attributes, |attribute| {
|
||||||
|
match attribute.key {
|
||||||
|
b"image_name" => {
|
||||||
|
desc.cd.output_file = XMLReader::get_attribute_value_str(&attribute)?.to_string();
|
||||||
|
desc.cue.output_bin_file = desc.cd.output_file.clone();
|
||||||
|
},
|
||||||
|
b"cue_sheet" => {
|
||||||
|
desc.cue.output_file = XMLReader::get_attribute_value_str(&attribute)?.to_string();
|
||||||
|
},
|
||||||
|
_ => XMLReader::print_attribute_not_supported(attribute.key, ISO_PROJECT_TAG),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_iso_project(desc: &mut Desc, parser: &mut XMLReader) -> Result<(), Error> {
|
||||||
|
enum TrackType {
|
||||||
|
None,
|
||||||
|
Data,
|
||||||
|
Audio,
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match parser.next()? {
|
||||||
|
Event::Start(tag) => {
|
||||||
|
match tag.name() {
|
||||||
|
|
||||||
|
TRACK_TAG_B => {
|
||||||
|
let mut track_type = TrackType::None;
|
||||||
|
let mut source = String::default();
|
||||||
|
|
||||||
|
XMLReader::for_each_attribute(tag.attributes(), |attribute| {
|
||||||
|
match attribute.key {
|
||||||
|
b"type" => {
|
||||||
|
let attribute_value = XMLReader::get_attribute_value_str(&attribute)?;
|
||||||
|
match attribute_value {
|
||||||
|
"data" => track_type = TrackType::Data,
|
||||||
|
"audio" => track_type = TrackType::Audio,
|
||||||
|
_ => println!("Unkown track type {}", attribute_value),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
b"source" => {
|
||||||
|
source = XMLReader::get_attribute_value_str(&attribute)?.to_owned();
|
||||||
|
},
|
||||||
|
_ => XMLReader::print_attribute_not_supported(attribute.key, TRACK_TAG)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match track_type {
|
||||||
|
TrackType::None |
|
||||||
|
TrackType::Data => handle_data_track(desc, parser)?,
|
||||||
|
TrackType::Audio => handle_audio_track(desc, source)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::End(tag) => {
|
||||||
|
match tag.name() {
|
||||||
|
ISO_PROJECT_TAG_B => {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::Eof => break,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::EOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_data_track(desc: &mut Desc, parser: &mut XMLReader) -> Result<(), Error> {
|
||||||
|
loop {
|
||||||
|
match parser.next()? {
|
||||||
|
Event::Start(tag) => {
|
||||||
|
match tag.name() {
|
||||||
|
IDENTIFIERS_TAG_B => handle_identifiers_attributes(desc, tag.attributes())?,
|
||||||
|
b"license" => {
|
||||||
|
if let Some(attribute) = XMLReader::find_attribute(tag.attributes(), b"file")? {
|
||||||
|
desc.cd.licence_path = XMLReader::get_attribute_value_str(&attribute)?.to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("No file specified for licence!");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DIRECTORY_TREE_TAG_B => {
|
||||||
|
let mut dir = DirectoryDataType::new("track");
|
||||||
|
handle_directory(&mut dir, parser)?;
|
||||||
|
|
||||||
|
desc.cd.tracks.push(Track::Data(dir));
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::End(tag) => {
|
||||||
|
match tag.name() {
|
||||||
|
TRACK_TAG_B => {
|
||||||
|
// Push track here?
|
||||||
|
return Ok(());
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Eof => break,
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::EOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_identifiers_attributes(desc: &mut Desc, attributes: Attributes) -> Result<(), Error> {
|
||||||
|
XMLReader::for_each_attribute(attributes, |attribute| {
|
||||||
|
match attribute.key {
|
||||||
|
b"system" | b"application" => {
|
||||||
|
let value = XMLReader::get_attribute_value_str(&attribute)?;
|
||||||
|
if value != "PLAYSTATION" {
|
||||||
|
println!("System/Application Identifier \"{}\" will be ignored", value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
b"volume" => {
|
||||||
|
desc.cd.pvd.volume_identifier = XMLReader::get_attribute_value_str(&attribute)?.to_owned();
|
||||||
|
},
|
||||||
|
b"volume_set" | b"copyright" => {
|
||||||
|
println!("{} with \"{}\" ignored", XMLReader::get_attribute_key_str(&attribute)?, XMLReader::get_attribute_value_str(&attribute)?);
|
||||||
|
},
|
||||||
|
b"publisher" => {
|
||||||
|
desc.cd.pvd.publisher = XMLReader::get_attribute_value_str(&attribute)?.to_string();
|
||||||
|
},
|
||||||
|
b"data_preparer" => {
|
||||||
|
desc.cd.pvd.data_preparer = XMLReader::get_attribute_value_str(&attribute)?.to_string();
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
XMLReader::print_attribute_not_supported(attribute.key, IDENTIFIERS_TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_directory(dir: &mut DirectoryDataType, parser: &mut XMLReader) -> Result<(), Error> {
|
||||||
|
enum FileType {
|
||||||
|
None,
|
||||||
|
Data,
|
||||||
|
Xa,
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match parser.next()? {
|
||||||
|
Event::Start(tag) => {
|
||||||
|
match tag.name() {
|
||||||
|
b"file" => {
|
||||||
|
let mut file_type = FileType::None;
|
||||||
|
let mut file_name = String::default();
|
||||||
|
let mut file_source = String::default();
|
||||||
|
|
||||||
|
XMLReader::for_each_attribute(tag.attributes(), |attribute| {
|
||||||
|
match attribute.key {
|
||||||
|
b"name" => {
|
||||||
|
file_name = XMLReader::get_attribute_value_str(&attribute)?.to_string();
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
b"source" => {
|
||||||
|
file_source = XMLReader::get_attribute_value_str(&attribute)?.to_string();
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
b"type" => {
|
||||||
|
if let Ok(r#type) = XMLReader::get_attribute_value_str(&attribute) {
|
||||||
|
if r#type == "data" {
|
||||||
|
file_type = FileType::Data;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else if r#type == "xa" {
|
||||||
|
file_type = FileType::Xa;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("File type: \"{}\" not supported yet", r#type);
|
||||||
|
Err(Error::NotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("Attribute \"type\" needs to be either data or xa".to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match file_type {
|
||||||
|
FileType::None | FileType::Data => {
|
||||||
|
let mut file = FileDataType::new(file_name.as_ref(), FileContent::new_raw_data(file_source));
|
||||||
|
|
||||||
|
if file.name == "SCES_003.90" {
|
||||||
|
file.set_lba_padding(0);
|
||||||
|
file.is_hidden = true;
|
||||||
|
}
|
||||||
|
dir.add_file(file);
|
||||||
|
},
|
||||||
|
FileType::Xa => {
|
||||||
|
dir.add_file(FileDataType::new(file_name.as_ref(), FileContent::new_xa_audio(file_source)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DIR_TAG_B => {
|
||||||
|
if let Some(dir_name) = XMLReader::find_attribute(tag.attributes(), b"name")? {
|
||||||
|
let mut new_dir = DirectoryDataType::new(XMLReader::get_attribute_value_str(&dir_name)?);
|
||||||
|
|
||||||
|
if new_dir.name == "XB" {
|
||||||
|
new_dir.is_hidden = true;
|
||||||
|
}
|
||||||
|
handle_directory(&mut new_dir, parser)?;
|
||||||
|
dir.add_dir(new_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::GenericError("Dir requires name".to_owned()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::End(tag) => {
|
||||||
|
match tag.name() {
|
||||||
|
DIRECTORY_TREE_TAG_B | DIR_TAG_B => {
|
||||||
|
return Ok(());
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Eof => break,
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::EOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_audio_track(desc: &mut Desc, file: String) -> Result<(), Error> {
|
||||||
|
fn convert_into_16(data: wav::bit_depth::BitDepth) -> Result<Vec<AudioSample>, Error> {
|
||||||
|
match data.try_into_sixteen() {
|
||||||
|
Ok(data) => {
|
||||||
|
let mut samples = Vec::new();
|
||||||
|
|
||||||
|
for (left, right) in data.iter().step_by(2).zip(data.iter().skip(1).step_by(2)) {
|
||||||
|
samples.push(AudioSample::new(*left, *right))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(samples)
|
||||||
|
},
|
||||||
|
Err(_) => Err(Error::GenericError("Couldn't convert samples to 16bit".to_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (header, data) = wav::read(&mut std::fs::File::open(file)?)?;
|
||||||
|
|
||||||
|
if header.audio_format != wav::header::WAV_FORMAT_PCM {
|
||||||
|
return Err(Error::GenericError("Only WAV PCM is supported for Audio tracks".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.sampling_rate != 44_100 {
|
||||||
|
return Err(Error::GenericError("Only a sampling rate of 44.1kHz is supported for Audio tracks".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
desc.cd.tracks.push(Track::Audio(convert_into_16(data)?));
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
mod mkpsxiso;
|
||||||
|
use cdtypes::{Error, types::helper::{convert_ascii_to_str, force_convert_ascii_to_str}};
|
||||||
|
use fast_xml::{events::{Event, attributes::{AttrError, Attribute, Attributes}}, Reader};
|
||||||
|
use core::ops::Deref;
|
||||||
|
|
||||||
|
pub use mkpsxiso::parse as parse_mkpsxiso;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct XMLReader<'a> {
|
||||||
|
reader: Reader<&'a [u8]>,
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> XMLReader<'a> {
|
||||||
|
pub fn new(xml: &str) -> XMLReader {
|
||||||
|
let mut reader = Reader::from_str(xml.as_ref());
|
||||||
|
|
||||||
|
reader.trim_text(true);
|
||||||
|
reader.expand_empty_elements(true);
|
||||||
|
reader.check_end_names(true);
|
||||||
|
|
||||||
|
XMLReader{reader, buffer: Vec::new()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Result<Event, Error> {
|
||||||
|
self.buffer.clear();
|
||||||
|
Self::convert_error(self.reader.read_event(&mut self.buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attribute(attribute: Result<Attribute, AttrError>) -> Result<Attribute, Error> {
|
||||||
|
match attribute {
|
||||||
|
Ok(attribute) => Ok(attribute),
|
||||||
|
Err(error) => Err(Error::GenericError(error.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attribute_value_str<'b>(attribute: &'b Attribute) -> Result<&'b str, Error> {
|
||||||
|
if let Some(str) = convert_ascii_to_str(attribute.value.deref()) {
|
||||||
|
Ok(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("Invalid UTF-8 for attribute value".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attribute_key_str<'b>(attribute: &'b Attribute) -> Result<&'b str, Error> {
|
||||||
|
if let Some(str) = convert_ascii_to_str(attribute.key) {
|
||||||
|
Ok(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("Invalid UTF-8 for attribute key".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_attribute<'b>(attributes: Attributes<'b>, name: &[u8]) -> Result<Option<Attribute<'b>>, Error> {
|
||||||
|
for attribute in attributes {
|
||||||
|
match attribute {
|
||||||
|
Ok(attribute) => (
|
||||||
|
if attribute.key == name {
|
||||||
|
return Ok(Some(attribute));
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
return Err(Error::GenericError(error.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn for_each_attribute<'b, F>(attributes: Attributes<'b>, mut callback: F) -> Result<(), Error> where F: FnMut(Attribute) -> Result<(), Error> {
|
||||||
|
for attribute in attributes {
|
||||||
|
callback(Self::get_attribute(attribute)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_attribute_not_supported(name: &[u8], tag_name: &str) {
|
||||||
|
println!("Attribute \"{}\" not yet supported for tag \"{}\"", force_convert_ascii_to_str(name), tag_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_error(result: Result<Event, fast_xml::Error>) -> Result<Event, Error> {
|
||||||
|
match result {
|
||||||
|
Ok(result) => Ok(result),
|
||||||
|
Err(error) => Err(Error::GenericError(error.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "psxcdread"
|
||||||
|
version = "0.5.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cdtypes = {path = "../cdtypes"}
|
|
@ -0,0 +1,137 @@
|
||||||
|
use cdtypes::{Error, cd::{sector::Sector, CD}};
|
||||||
|
|
||||||
|
pub fn check_edc(cd: &CD, sector_id: usize) -> Result<(), Error> {
|
||||||
|
if let Some(sector) = cd.get_sector(sector_id) {
|
||||||
|
if let Some((edc, _)) = sector.get_error_correction() {
|
||||||
|
let mut sector = (*sector).clone();
|
||||||
|
|
||||||
|
sector.calculate_edc();
|
||||||
|
if let Some(calc_edc) = sector.get_edc() {
|
||||||
|
println!("ISO EDC is: {}", edc);
|
||||||
|
println!("Calculated EDC is: {}", calc_edc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("No EDC");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(format!("Sector {} not found", sector_id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_ecc(cd: &CD, sector_id: usize) -> Result<(), Error> {
|
||||||
|
if let Some(sector) = cd.get_sector(sector_id) {
|
||||||
|
if let Some((_, Some(ecc))) = sector.get_error_correction() {
|
||||||
|
let mut sector = (*sector).clone();
|
||||||
|
|
||||||
|
sector.calculate_ecc();
|
||||||
|
if let Some(calc_ecc) = sector.get_ecc() {
|
||||||
|
if ecc == calc_ecc {
|
||||||
|
println!("ECC is same");
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("ECC is different");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("No ECC");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(format!("Sector {} not found", sector_id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump(cd: &CD) -> Result<(), Error> {
|
||||||
|
println!("{}", cd.get_primary_volume_descriptor()?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_root_dir_rec(cd: &CD, detailed: bool) -> Result<(), Error> {
|
||||||
|
let root_dir = cd.get_primary_volume_descriptor()?.get_root_dir_record();
|
||||||
|
|
||||||
|
dump_dir_rec(cd, root_dir.data_block_number.read_any() as usize, detailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_dir_rec(cd: &CD, sector_id: usize, detailed: bool) -> Result<(), Error> {
|
||||||
|
for entry in cd.directory_record_iter(sector_id) {
|
||||||
|
if detailed {
|
||||||
|
println!("{}", entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
let type_name = {
|
||||||
|
if entry.is_file() {
|
||||||
|
"File"
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
"Directory"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("{}: {} @{}", type_name, entry.get_name(), entry.data_block_number.read_any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_path_table(cd: &CD) -> Result<(), Error> {
|
||||||
|
let mut number = 1usize;
|
||||||
|
|
||||||
|
for entry in cd.path_table_iter() {
|
||||||
|
println!("{}.) {}", number, entry);
|
||||||
|
number += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_stats(cd: &CD) -> Result<(), Error> {
|
||||||
|
let mut audio_count = 0;
|
||||||
|
let mut mode0_count = 0;
|
||||||
|
let mut mode1_count = 0;
|
||||||
|
let mut mode2form1_count = 0;
|
||||||
|
let mut mode2form2_count = 0;
|
||||||
|
|
||||||
|
for sector in cd.sector_iter() {
|
||||||
|
match sector {
|
||||||
|
Sector::Audio(_) => audio_count += 1,
|
||||||
|
Sector::Empty(_) => mode0_count += 1,
|
||||||
|
Sector::CDData(_) => mode1_count += 1,
|
||||||
|
Sector::CDXAData(_) => mode2form1_count += 1,
|
||||||
|
Sector::CDXAAudio(_) => mode2form2_count += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Audio: {}\nMode0: {}\nMode1: {}\nMode2Form1: {}\nMode2Form2: {}", audio_count, mode0_count, mode1_count, mode2form1_count, mode2form2_count);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_tracks(cd: &CD) -> Result<(), Error> {
|
||||||
|
if cd.has_tracks() {
|
||||||
|
let mut track_count = 1;
|
||||||
|
|
||||||
|
for track_info in cd.track_info_iter() {
|
||||||
|
println!("Track {:02}: {:<10} @{}", track_count, track_info.r#type, track_info.start);
|
||||||
|
track_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("Track 01");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod complex_dump;
|
|
@ -0,0 +1,379 @@
|
||||||
|
use psxcdread::{*};
|
||||||
|
use cdtypes::{Error, cd::CD, types::{cue, sector::SECTOR_SIZE}};
|
||||||
|
use std::{fs::OpenOptions, io::{stdin, stdout, Write}, path::{Path, PathBuf}};
|
||||||
|
|
||||||
|
fn open_file(path: &Path, write_access: bool) -> Result<std::fs::File, Error> {
|
||||||
|
match OpenOptions::new().read(true).write(write_access).create(write_access).open(path) {
|
||||||
|
Ok(file) => Ok(file),
|
||||||
|
Err(error) => {
|
||||||
|
let path = {
|
||||||
|
if let Some(path) = path.to_str() {
|
||||||
|
path.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
"<Invalid encoded file path>".to_owned()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Err(Error::IOError(Some(path), error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_cue(file: std::fs::File, cue_path: &Path) -> Result<CD, Error> {
|
||||||
|
fn get_bin_path(specifier: &Vec<cue::Specifier>, cue_path: &Path) -> Result<Option<PathBuf>, Error> {
|
||||||
|
for specifier in specifier {
|
||||||
|
match specifier {
|
||||||
|
cue::Specifier::File{path, format} => {
|
||||||
|
if matches!(format, cue::Format::Binary) {
|
||||||
|
let mut bin_path = cue_path.to_path_buf();
|
||||||
|
|
||||||
|
if let Some(_) = bin_path.file_name() {
|
||||||
|
bin_path.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bin_path.push(path);
|
||||||
|
return Ok(Some(bin_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::GenericError("Only binary CUE files are supported".to_owned()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
let specifier = cue::read::read(file)?;
|
||||||
|
if let Some(bin_path) = get_bin_path(&specifier, cue_path)? {
|
||||||
|
Ok(CD::from_file(open_file(bin_path.as_path(), false)?, Some(specifier))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("No binary file specified".to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_cd(path: &str) -> Result<CD, Error> {
|
||||||
|
fn is_cue(path: &Path) -> bool {
|
||||||
|
if let Some(extension) = path.extension() {
|
||||||
|
extension == "cue"
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let path = Path::new(path);
|
||||||
|
let file = open_file(path, false)?;
|
||||||
|
|
||||||
|
if is_cue(&path) {
|
||||||
|
open_cue(file, &path)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Ok(CD::from_file(file, None)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_input(cd: &CD, cur_sec: usize) -> Result<String, std::io::Error> {
|
||||||
|
println!("Current Sector: [{}/{}]", cur_sec, cd.last_sector_idx());
|
||||||
|
stdout().write("> ".as_bytes())?;
|
||||||
|
stdout().flush()?;
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
stdin().read_line(&mut input)?;
|
||||||
|
Ok(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChangeSectorNumMode {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
Absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_number_from_str(number_str: &str) -> Option<usize> {
|
||||||
|
let (number, base) = {
|
||||||
|
if number_str.starts_with("0x") {
|
||||||
|
(number_str.trim_start_matches("0x"), 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
(number_str, 10)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(number) = usize::from_str_radix(number, base) {
|
||||||
|
Some(number)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_sector_number(mode: ChangeSectorNumMode, cd: &CD, mut cur_sec: usize, mut iter: std::str::Split<&str>) -> usize {
|
||||||
|
if let Some(number) = iter.next() {
|
||||||
|
let last_sec = cd.last_sector_idx();
|
||||||
|
|
||||||
|
if let Some(number) = get_number_from_str(number) {
|
||||||
|
let new_sec = {
|
||||||
|
match mode {
|
||||||
|
ChangeSectorNumMode::Increment => cur_sec + number,
|
||||||
|
ChangeSectorNumMode::Decrement => cur_sec - number,
|
||||||
|
ChangeSectorNumMode::Absolute => number
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if new_sec <= last_sec {
|
||||||
|
cur_sec = new_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("{} is higher then last valid index which is {}", new_sec, last_sec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("{} is no valid number", number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_sec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_data(cd: &CD, cur_sec: usize, mut iter: std::str::Split<&str>) -> Result<(), Error> {
|
||||||
|
if let Some(number_str) = iter.next() {
|
||||||
|
let file_name = {
|
||||||
|
let mut file_name = String::new();
|
||||||
|
for str_part in iter {
|
||||||
|
file_name.push_str(str_part);
|
||||||
|
file_name.push(' ');
|
||||||
|
}
|
||||||
|
file_name
|
||||||
|
};
|
||||||
|
|
||||||
|
if !file_name.is_empty() {
|
||||||
|
if let Some(number) = get_number_from_str(number_str) {
|
||||||
|
let mut file = open_file(Path::new(file_name.as_str()), true)?;
|
||||||
|
for i in 0..number {
|
||||||
|
if let Some(sector) = cd.get_sector(cur_sec + i) {
|
||||||
|
file.write(sector.get_data())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return Err(Error::GenericError(format!("Failed writing sector {} - data incomplete", (cur_sec + i))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(format!("{} is not a valid number", number_str)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(format!("No output file specified")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError(format!("Sector count not specified")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump(cd: &CD, cur_sec: usize, dump_ecc: bool) {
|
||||||
|
if let Some(sector) = cd.get_sector(cur_sec) {
|
||||||
|
let header: &dyn std::fmt::Display = {
|
||||||
|
if let Some(header) = sector.get_header() {
|
||||||
|
header
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
&"<No Header>"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let sub_header = sector.get_sub_header();
|
||||||
|
|
||||||
|
println!("{}:", sector.get_friendly_name());
|
||||||
|
println!("Header: {}", header);
|
||||||
|
match sub_header {
|
||||||
|
Some(sub_header) => println!("SubHeader: {}", sub_header),
|
||||||
|
None => println!("SubHeader: <None>")
|
||||||
|
}
|
||||||
|
|
||||||
|
let (data_offset, data_size) = sector.get_data_offset_with_size();
|
||||||
|
let data_start = (cur_sec*SECTOR_SIZE) + data_offset;
|
||||||
|
|
||||||
|
println!("Data: at 0x{:X} - 0x{:x} ({} Bytes)", data_start, (data_start + data_size - 1), data_size);
|
||||||
|
let (edc, ecc) = {
|
||||||
|
if let Some(error_cor) = sector.get_error_correction() {
|
||||||
|
(Some(error_cor.0), error_cor.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("EDC: {}\nECC:\n{}", {
|
||||||
|
if let Some(edc) = &edc {
|
||||||
|
edc
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
&"<none>" as &dyn std::fmt::Display
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
if dump_ecc {
|
||||||
|
if let Some(ecc) = &ecc {
|
||||||
|
ecc
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
&"<none>" as &dyn std::fmt::Display
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
&"<run \"d ecc\">" as &dyn std::fmt::Display
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_complex(cd: &CD, cur_sec: usize, mut iter: std::str::Split<&str>) -> Result<(), Error> {
|
||||||
|
let handle_dir_parms = |iter| {
|
||||||
|
let mut dump_root = false;
|
||||||
|
let mut dump_detail = false;
|
||||||
|
|
||||||
|
for cmd in iter {
|
||||||
|
match cmd {
|
||||||
|
"root" => dump_root = true,
|
||||||
|
"-d" => dump_detail = true,
|
||||||
|
_ => return Err(Error::GenericError(format!("Unkown option {}", cmd)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((dump_root, dump_detail))
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cmd) = iter.next() {
|
||||||
|
match cmd {
|
||||||
|
"pvd" => complex_dump::dump(&cd),
|
||||||
|
"dir" => {
|
||||||
|
let (dump_root, dump_detail) = handle_dir_parms(iter)?;
|
||||||
|
|
||||||
|
if dump_root {
|
||||||
|
complex_dump::dump_root_dir_rec(&cd, dump_detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
complex_dump::dump_dir_rec(&cd, cur_sec, dump_detail)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path" => {
|
||||||
|
complex_dump::dump_path_table(&cd)
|
||||||
|
},
|
||||||
|
"edc" => complex_dump::check_edc(&cd, cur_sec),
|
||||||
|
"ecc" => complex_dump::check_ecc(&cd, cur_sec),
|
||||||
|
"stats" => complex_dump::dump_stats(&cd),
|
||||||
|
"tracks" => complex_dump::dump_tracks(&cd),
|
||||||
|
"?" => {
|
||||||
|
println!("pvd Dump Primary Volume Descriptor");
|
||||||
|
println!("dir [root] [-d] Dumps current sector as Directory Record or the Root Directory Record");
|
||||||
|
println!("path Dumps any PathTable");
|
||||||
|
println!("edc ");
|
||||||
|
println!("ecc ");
|
||||||
|
println!("stats Lists statistics about all sectors");
|
||||||
|
println!("tracks Lists all the tracks of this CD");
|
||||||
|
return Ok(());
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("Unkown command {} - try cplx ?", cmd);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Err(Error::GenericError("No command found".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let file = {
|
||||||
|
match std::env::args().skip(1).next()
|
||||||
|
{
|
||||||
|
Some(file) => file,
|
||||||
|
None => {
|
||||||
|
println!("psxcdread <path fo file>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sec_idx = 0;
|
||||||
|
let cd = {
|
||||||
|
match open_cd(file.as_str()) {
|
||||||
|
Ok(cd) => cd,
|
||||||
|
Err(error) => {
|
||||||
|
println!("Readin CD failed with: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match read_input(&cd, sec_idx) {
|
||||||
|
Ok(input) => {
|
||||||
|
let mut iter = input.trim().split(" ");
|
||||||
|
if let Some(cmd) = iter.next() {
|
||||||
|
match cmd {
|
||||||
|
"" => (),
|
||||||
|
">" => sec_idx = change_sector_number(ChangeSectorNumMode::Increment, &cd, sec_idx, iter),
|
||||||
|
"<" => sec_idx = change_sector_number(ChangeSectorNumMode::Decrement, &cd, sec_idx, iter),
|
||||||
|
"!" => sec_idx = change_sector_number(ChangeSectorNumMode::Absolute, &cd, sec_idx, iter),
|
||||||
|
"d" => dump(&cd, sec_idx, {
|
||||||
|
if let Some(cmd) = iter.next() {
|
||||||
|
cmd == "ecc"
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"w" => {
|
||||||
|
if let Err(txt) = write_data(&cd, sec_idx, iter) {
|
||||||
|
println!("{}", txt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cplx" => {
|
||||||
|
if let Err(txt) = handle_complex(&cd, sec_idx, iter) {
|
||||||
|
println!("{}", txt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"q" => return,
|
||||||
|
"?" => {
|
||||||
|
println!("> <number> move number sectors forward");
|
||||||
|
println!("< <number> move number sectors backward");
|
||||||
|
println!("! <number> move to sector number");
|
||||||
|
println!("d dump current sector");
|
||||||
|
println!("cplx <sub command> dumps extended infos");
|
||||||
|
println!("w <sector count> <file path> writes the data of the sectos to a file");
|
||||||
|
println!("q exit programm");
|
||||||
|
},
|
||||||
|
_ => println!("Unkown CMD: \"{}\"", cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
println!("Reading input failed with: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue