270 lines
12 KiB
Rust
270 lines
12 KiB
Rust
pub use tool_helper::Error;
|
|
|
|
pub mod config_reader;
|
|
pub mod encoder;
|
|
pub mod file_writer;
|
|
pub mod types;
|
|
|
|
use crate::types::RawData;
|
|
use cdtypes::types::sector::AudioSample;
|
|
use config_reader::LZ4State;
|
|
use encoder::cd::{calculate_lbas, calculate_length_for};
|
|
use tool_helper::{format_if_error, Output, read_file};
|
|
use types::{layout::Layout, CDDesc, Directory, File, FileType, FileSystemMap, Properties, SharedPtr};
|
|
use std::path::PathBuf;
|
|
|
|
pub type LBAEmbeddedFiles = Vec<SharedPtr<File>>;
|
|
|
|
struct ContentDumpAlignment {
|
|
name: usize,
|
|
lba_pair: usize,
|
|
size: usize,
|
|
ex_size: usize,
|
|
}
|
|
|
|
const DEFAULT_CONTENT_ALIGNMENT:ContentDumpAlignment = ContentDumpAlignment{name: 24, lba_pair: 16, size: 8, ex_size: 8};
|
|
|
|
pub fn process(config: config_reader::Configuration) -> Result<(CDDesc, LBAEmbeddedFiles), Error> {
|
|
let (mut cd_desc, lba_embedded_files) = parse_configuration(config)?;
|
|
|
|
calculate_lbas(&mut cd_desc);
|
|
Ok((cd_desc, lba_embedded_files))
|
|
}
|
|
|
|
pub fn process_files(file_map: FileSystemMap, lba_embedded_files: LBAEmbeddedFiles) -> Result<(), Error> {
|
|
for lba_embedded_file_raw in lba_embedded_files {
|
|
let new_content_info = {
|
|
let mut lba_embedded_file = lba_embedded_file_raw.borrow_mut();
|
|
|
|
match &mut lba_embedded_file.content {
|
|
FileType::Overlay(content, lba_names) => Some(types::overlay::update_content(content, lba_names, &file_map)?),
|
|
FileType::Main(content, lba_names) => Some(types::overlay::update_content_for_main(content, lba_names, &file_map)?),
|
|
_ => None
|
|
}
|
|
};
|
|
|
|
if let Some(new_content) = new_content_info {
|
|
let old_size_info = calculate_length_for(&Layout::File(lba_embedded_file_raw.clone()));
|
|
lba_embedded_file_raw.borrow_mut().make_regular(new_content);
|
|
let new_size_info = calculate_length_for(&Layout::File(lba_embedded_file_raw.clone()));
|
|
|
|
if new_size_info.sectors != old_size_info.sectors {
|
|
let lba_embedded_file = lba_embedded_file_raw.borrow();
|
|
let lba_embedded_file_name = lba_embedded_file.name.to_string();
|
|
|
|
if new_size_info.sectors < old_size_info.sectors {
|
|
return Err(Error::from_text(format!("Failed converting Overlay \"{}\" because new size ({} sectors) is small then {} sectors! (This might be allowed in the future)", lba_embedded_file_name, new_size_info.sectors, old_size_info.sectors)));
|
|
}
|
|
|
|
else {
|
|
return Err(Error::from_text(format!("Failed converting Overlay \"{}\" because new size ({} sectors) is bigger then {} sectors!", lba_embedded_file_name, new_size_info.sectors, old_size_info.sectors)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn dump_content(cd_desc: &CDDesc, mut out: Output) -> Result<(), Error> {
|
|
const NO_NAME:&'static str = "<No Name>";
|
|
const ARROW:&'static str = "|==>";
|
|
const INDENT_STEP:usize = 4;
|
|
|
|
fn get_visible_indicator(is_hidden: bool) -> char {
|
|
if is_hidden {
|
|
'-'
|
|
}
|
|
|
|
else {
|
|
'o'
|
|
}
|
|
}
|
|
|
|
fn create_lba_display_string<T: std::fmt::Display>(rel_lba: T, abs_lba: T) -> String {
|
|
format!("{}({})", rel_lba, abs_lba)
|
|
}
|
|
|
|
fn write_file(out: &mut Output, indent: usize, file_name: String, properties: &Properties, file_rel_lba: usize, file_abs_lba: usize, file_size: usize, file_ex_size: usize) -> Result<(), Error> {
|
|
Ok(writeln!(out, "{:>indent$}File: ({}) {:<name_align$} @{:<lba_pair_align$} ={:<size_align$} >{:<ex_size_align$}", ARROW, get_visible_indicator(properties.is_hidden), file_name, create_lba_display_string(file_rel_lba, file_abs_lba), file_size, file_ex_size,
|
|
indent=indent + ARROW.len(), name_align=DEFAULT_CONTENT_ALIGNMENT.name, lba_pair_align=DEFAULT_CONTENT_ALIGNMENT.lba_pair, size_align=DEFAULT_CONTENT_ALIGNMENT.size, ex_size_align=DEFAULT_CONTENT_ALIGNMENT.ex_size)?)
|
|
}
|
|
|
|
fn write_dir(out: &mut Output, indent: usize, dir_name: &str, properties: &Properties, dir_rel_lba: usize, dir_abs_lba: usize) -> Result<(), Error> {
|
|
macro_rules! LBA_OUT {
|
|
() => {
|
|
"{:<lba_pair_align$}"
|
|
};
|
|
}
|
|
let is_hidden = properties.is_hidden;
|
|
|
|
write!(out, "{:>indent$}Dir: ({}) {:<name_align$} @", ARROW, get_visible_indicator(is_hidden), dir_name,
|
|
indent=indent + ARROW.len(), name_align=DEFAULT_CONTENT_ALIGNMENT.name)?;
|
|
|
|
if is_hidden {
|
|
Ok(writeln!(out, LBA_OUT!(), create_lba_display_string("<None>", "<None>"), lba_pair_align=DEFAULT_CONTENT_ALIGNMENT.lba_pair)?)
|
|
}
|
|
|
|
else {
|
|
Ok(writeln!(out, LBA_OUT!(), create_lba_display_string(dir_rel_lba, dir_abs_lba), lba_pair_align=DEFAULT_CONTENT_ALIGNMENT.lba_pair)?)
|
|
}
|
|
}
|
|
|
|
fn write_intro(out: &mut Output) -> Result<(), Error> {
|
|
writeln!(out, "{:>indent$}Type: ( ) {:<name_align$} @{:<lba_pair_align$} ={:<size_align$} >{:<ex_size_align$}", "", "NAME", create_lba_display_string("LBA", "ABS LBA"), "SIZE", "EXTENDED SIZE",
|
|
indent=ARROW.len(), name_align=DEFAULT_CONTENT_ALIGNMENT.name, lba_pair_align=DEFAULT_CONTENT_ALIGNMENT.lba_pair, size_align=DEFAULT_CONTENT_ALIGNMENT.size, ex_size_align=DEFAULT_CONTENT_ALIGNMENT.ex_size)?;
|
|
Ok(writeln!(out, "")?)
|
|
}
|
|
|
|
fn dump_dir(dir: &Directory, out: &mut Output, indent: usize) -> Result<(), Error> {
|
|
for file in dir.file_iter() {
|
|
let file = file.borrow();
|
|
let file_name = file.name.as_string().unwrap_or(NO_NAME.to_owned());
|
|
let properties = &file.properties;
|
|
let file_rel_lba = file.get_track_rel_lba();
|
|
let file_abs_lba = file.get_absolute_lba();
|
|
let file_size = file.properties.get_real_size();
|
|
let file_ex_size = file.get_extended_size();
|
|
|
|
write_file(out, indent, file_name, properties, file_rel_lba, file_abs_lba, file_size, file_ex_size)?;
|
|
}
|
|
|
|
for dir in dir.dir_iter() {
|
|
let dir = dir.borrow();
|
|
let dir_name = dir.name.as_str().unwrap_or(NO_NAME);
|
|
let properties = dir.properties.borrow();
|
|
let dir_rel_lba = dir.get_track_rel_lba();
|
|
let dir_abs_lba = dir.get_absolute_lba();
|
|
|
|
write_dir(out, indent, dir_name, &properties, dir_rel_lba, dir_abs_lba)?;
|
|
dump_dir(&dir, out, indent + INDENT_STEP)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
format_if_error!(write_intro(&mut out), "Writing content dump intro failed with: {error_text}")?;
|
|
format_if_error!(dump_dir(&cd_desc.root.borrow(), &mut out, 0), "Creating content dump failed with: {error_text}")
|
|
}
|
|
|
|
fn parse_configuration(config: config_reader::Configuration) -> Result<(CDDesc, LBAEmbeddedFiles), Error> {
|
|
fn parse_dir(dst_dir: &mut types::Directory, src_dir: config_reader::Directory, lba_embedded_files: &mut LBAEmbeddedFiles) -> Result<(), Error> {
|
|
for member in src_dir.into_iter() {
|
|
match member {
|
|
config_reader::DirMember::Directory(dir) => {
|
|
let mut new_dir = types::Directory::new(dir.name.as_str())?;
|
|
|
|
new_dir.properties.borrow_mut().is_hidden = dir.is_hidden;
|
|
parse_dir(&mut new_dir, dir, lba_embedded_files)?;
|
|
dst_dir.add_dir(new_dir);
|
|
},
|
|
|
|
config_reader::DirMember::File(file) => {
|
|
let lz4_state = file.common.lz4_state;
|
|
let (mut desc_file, needs_treatment) = {
|
|
fn handle_file_load(file_path: &PathBuf, lz4_state: &LZ4State) -> Result<RawData, Error> {
|
|
let file_content = read_file(file_path)?;
|
|
|
|
if matches!(lz4_state, LZ4State::Compress) {
|
|
tool_helper::compress::psx_default::lz4(&file_content)
|
|
}
|
|
|
|
else {
|
|
Ok(file_content)
|
|
}
|
|
}
|
|
|
|
fn handle_multiple_file_load(file_paths: Vec<PathBuf>, lz4_state: &LZ4State) -> Result<Vec<RawData>, Error> {
|
|
let mut files = Vec::new();
|
|
|
|
for file_path in file_paths {
|
|
files.push(handle_file_load(&file_path, &lz4_state)?);
|
|
}
|
|
Ok(files)
|
|
}
|
|
|
|
match file.kind {
|
|
config_reader::FileKind::Regular => (types::File::new_regular(file.common.name.as_str(), handle_file_load(&file.path, &lz4_state)?)?, false),
|
|
config_reader::FileKind::Main(lba_source) => (types::overlay::load_for_main(file.common.name.as_str(), handle_file_load(&file.path, &lz4_state)?, lba_source)?, true),
|
|
config_reader::FileKind::Overlay(lba_source) => (types::overlay::load_from(file.common.name.as_str(), &file.path, lba_source)?, true),
|
|
config_reader::FileKind::XAAudio(channels) => (types::File::new_xa_audio(file.common.name.as_str(), handle_multiple_file_load(channels, &lz4_state)?)?, false),
|
|
}
|
|
};
|
|
|
|
desc_file.properties.padded_size_bytes = file.common.padded_size;
|
|
desc_file.properties.is_hidden = file.common.is_hidden;
|
|
desc_file.properties.is_lz4 = !matches!(lz4_state, LZ4State::None);
|
|
|
|
let new_file = dst_dir.add_file(desc_file);
|
|
if needs_treatment {
|
|
lba_embedded_files.push(new_file);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_cd_da(cd_da_tracks: &mut Vec<Vec<AudioSample>>, cd_da_files: Vec<PathBuf>) -> Result<(), Error> {
|
|
fn convert_into_16<R:std::io::Read, S:hound::Sample>(samples: hound::WavSamples<R,S>, file_path: &PathBuf) -> Result<Vec<AudioSample>, Error> {
|
|
let mut sample_buffer = 0;
|
|
let mut sample_id = 0;
|
|
let mut cd_samples = Vec::new();
|
|
|
|
for sample in samples {
|
|
match sample {
|
|
Ok(sample) => {
|
|
if sample_id == 0 {
|
|
sample_buffer = sample.as_i16();
|
|
sample_id = 1;
|
|
}
|
|
|
|
else {
|
|
cd_samples.push(AudioSample::new(sample_buffer, sample.as_i16()));
|
|
sample_id = 0;
|
|
}
|
|
},
|
|
Err(err) => {
|
|
return Err(Error::from_text(format!("{}: {}", file_path.to_string_lossy(), err)));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(cd_samples)
|
|
}
|
|
|
|
for cd_da_file in cd_da_files {
|
|
let mut audio_io = hound::WavReader::open(&cd_da_file)?;
|
|
let header = audio_io.spec();
|
|
|
|
if header.sample_format != hound::SampleFormat::Int {
|
|
return Err(Error::from_text(format!("{}: Only integer WAV format (PCM) is supported for Audio tracks", cd_da_file.display())));
|
|
}
|
|
|
|
if header.sample_rate != 44_100 {
|
|
return Err(Error::from_text(format!("{}: Only a sampling rate of 44.1kHz is supported for Audio tracks", cd_da_file.display())));
|
|
}
|
|
|
|
cd_da_tracks.push(convert_into_16(audio_io.samples::<i16>(), &cd_da_file)?);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
let mut cd_desc = CDDesc::new();
|
|
let mut lba_embedded_files = Vec::new();
|
|
|
|
if let Some(publisher) = config.publisher {
|
|
cd_desc.pvd.borrow_mut().set_publisher(publisher);
|
|
}
|
|
|
|
if let Some(license_path) = config.license_path {
|
|
cd_desc.system_area.borrow_mut().license_file_path = Some(license_path);
|
|
}
|
|
|
|
parse_dir(&mut cd_desc.root.borrow_mut(), config.root, &mut lba_embedded_files)?;
|
|
parse_cd_da(&mut cd_desc.cd_da_tracks, config.cd_audio_files)?;
|
|
|
|
cd_desc.lead_out_sectors = config.lead_out_sectors.unwrap_or(0);
|
|
Ok((cd_desc, lba_embedded_files))
|
|
}
|