psxcdgen removed
This commit is contained in:
parent
2e51e2605b
commit
76ca9b5dd1
|
@ -77,7 +77,7 @@
|
||||||
{
|
{
|
||||||
"id": "project",
|
"id": "project",
|
||||||
"type": "pickString",
|
"type": "pickString",
|
||||||
"options": ["cdtypes", "cpp_out", "jaby_engine_fconv", "mkoverlay", "psxreadmap", "psxcdgen", "psxcdgen_ex", "psxcdread", "tool_helper", "wslpath"],
|
"options": ["cdtypes", "cpp_out", "jaby_engine_fconv", "mkoverlay", "psxreadmap", "psxcdgen_ex", "psxcdread", "tool_helper", "wslpath"],
|
||||||
"description": "project to build"
|
"description": "project to build"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "psxcdgen"
|
|
||||||
version = "0.5.5"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
panic = "abort"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cdtypes = {path = "../cdtypes"}
|
|
||||||
clap = {version = "*", features = ["derive"]}
|
|
||||||
fast-xml = "*"
|
|
||||||
paste = "*"
|
|
||||||
wav = "*"
|
|
|
@ -1,42 +0,0 @@
|
||||||
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)),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,465 +0,0 @@
|
||||||
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}))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
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)?))
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
|
@ -1,571 +0,0 @@
|
||||||
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, PathTableB},
|
|
||||||
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);
|
|
||||||
raw_pvd.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];
|
|
||||||
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_table_id.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 PathTableB>(&mut data_l[idx])};
|
|
||||||
|
|
||||||
new_value.directory_logical_block.write(org_value.directory_logical_block.read());
|
|
||||||
new_value.parent_table_id.write(org_value.parent_table_id.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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,179 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
pub mod iso_writer;
|
|
||||||
pub mod xml_reader;
|
|
||||||
pub mod file_helper;
|
|
||||||
|
|
||||||
pub use xml_reader::parse_mkpsxiso;
|
|
|
@ -1,105 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,333 +0,0 @@
|
||||||
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 = false;
|
|
||||||
}
|
|
||||||
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 = false;
|
|
||||||
}
|
|
||||||
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(())
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
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())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue