jabyengine/src/Tools/psxcdgen_ex/src/encoder/psx.rs

487 lines
20 KiB
Rust

use super::{*, SectorWriter, {CDDesc, Error}};
use super::super::types::{helper::{DirectoryRecordMember, PathTableMember}, layout::Layout, *};
use builder::SubModeBuilder;
use cdtypes::types::{cdstring::{AString, DString}, date::*, dir_record::*, helper::{round_bytes_mode2_form1, sector_count_mode2_form1}, lsb_msb::*, path_table::*, pvd as cd_pvd, sector::{AudioSample, Mode2Form1}};
use tool_helper::{BufferedInputFile, format_if_error, open_input_file_buffered, print_warning};
use std::io::{Read, Seek, SeekFrom};
const ROOT_DIR_NAME:&'static str = "\x00";
const SYSTEM_AREA_SECTOR_COUNT:usize = 16;
const PVD_SECTOR_COUNT:usize = 2;
pub fn calculate_psx_length_for(element: &Layout) -> LengthInfo {
match element {
Layout::SystemArea(_) => LengthInfo{bytes: None, sectors: SYSTEM_AREA_SECTOR_COUNT},
Layout::PVD(_) => LengthInfo{bytes: None, sectors: PVD_SECTOR_COUNT},
Layout::PathTables(root, _) => {
let path_table_size = PathTable::calculate_size_for(root.clone());
LengthInfo{bytes: Some(path_table_size), sectors: sector_count_mode2_form1(path_table_size)}
},
Layout::Directory(dir) => {
let dir = dir.borrow();
let properties = dir.properties.borrow_mut();
let size_bytes = properties.get_padded_size();
return LengthInfo{bytes: Some(size_bytes), sectors: sector_count_mode2_form1(size_bytes)};
},
Layout::File(file) => {
let file = file.borrow();
let fake_size = file.properties.get_padded_size();
return LengthInfo{bytes: Some(fake_size), sectors: sector_count_mode2_form1(fake_size)};
}
}
}
pub fn calculate_psx_lbas(cd_desc: &mut CDDesc) {
let mut cur_lba = 0;
let track_offset = LBA::FIRST_TRACK_OFFSET;
cd_desc.vol_sector_count = 0;
CDDesc::for_each_dir_mut(cd_desc.root.clone(), &|dir| {dir.update_content_size();});
for element in cd_desc.get_memory_layout() {
fn update_lba(properties: &mut Properties, cur_lba: usize, track_offset: usize, content_sector_size: usize) -> usize {
properties.lba.overwrite(cur_lba, track_offset);
cur_lba + content_sector_size
}
let element_size_info = calculate_psx_length_for(&element);
match element {
Layout::SystemArea(system_area) => {
let mut system_area = system_area.borrow_mut();
system_area.lba.overwrite(cur_lba, track_offset);
cur_lba += element_size_info.sectors;
},
Layout::PVD(pvd) => {
let mut pvd = pvd.borrow_mut();
pvd.lba.overwrite(cur_lba, track_offset);
cur_lba += element_size_info.sectors;
},
Layout::PathTables(_, path_table) => {
let mut path_table = path_table.borrow_mut();
path_table.lba.overwrite(cur_lba, track_offset);
path_table.size_bytes = element_size_info.bytes.unwrap_or(0);
cur_lba += element_size_info.sectors*4;
},
Layout::Directory(dir) => {
let dir = dir.borrow_mut();
let mut properties = dir.properties.borrow_mut();
cd_desc.vol_sector_count += element_size_info.sectors;
if properties.is_hidden {
properties.lba.overwrite(0, 0);
}
else {
cur_lba = update_lba(&mut properties, cur_lba, track_offset, element_size_info.sectors);
}
},
Layout::File(file) => {
let mut file = file.borrow_mut();
cd_desc.vol_sector_count += element_size_info.sectors;
cur_lba = update_lba(&mut file.properties, cur_lba, track_offset, element_size_info.sectors);
}
}
}
}
pub fn encode_psx_image(cd_desc: &CDDesc, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
let vol_sector_count = cd_desc.vol_sector_count;
for element in cd_desc.get_memory_layout() {
match element {
Layout::SystemArea(system_area) => process_system_area(&mut system_area.borrow_mut(), sec_writer)?,
Layout::PVD(pvd) => process_pvd(&pvd.borrow(), cd_desc.path_table.clone(), cd_desc.root.clone(), vol_sector_count, sec_writer)?,
Layout::PathTables(root, path_table) => process_path_table(&path_table.borrow(), root, sec_writer)?,
Layout::Directory(dir) => process_directory_record(&dir.borrow(), sec_writer)?,
Layout::File(file) => process_file(&file.borrow(), sec_writer)?,
}
}
process_end_dummy_section(cd_desc.end_dummy_padding, sec_writer)?;
process_cd_da(&cd_desc.cd_da_tracks, sec_writer)
}
fn process_system_area(system_area: &SystemArea, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
fn write_license_file(sec_writer: &mut dyn SectorWriter, mut license_file: BufferedInputFile) -> Result<(), Error> {
fn write_data_zeros(sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
for _ in 0..4 {
sec_writer.write_cd_xa_data(builder::create_xa_data_zero())?;
}
Ok(())
}
fn write_license_string(sec_writer: &mut dyn SectorWriter, license_file: &mut BufferedInputFile) -> Result<(), Error> {
const LICENSE_STRING_START:u64 = 0x2488;
if license_file.get_ref().metadata()?.len() < LICENSE_STRING_START {
return Err(Error::from_str("License file to short to contain license string. Is this is a valid license file?"));
}
let mut license_string_buffer = [0u8; Mode2Form1::DATA_SIZE];
license_file.seek(SeekFrom::Start(LICENSE_STRING_START))?;
license_file.read(&mut license_string_buffer)?;
let sub_mode = SubModeBuilder::new_mode1().create();
let sector = builder::create_xa_data_for_raw(sub_mode, &license_string_buffer);
sec_writer.write_cd_xa_data(sector)?;
Ok(())
}
fn write_license_logo(sec_writer: &mut dyn SectorWriter, license_file: &mut BufferedInputFile) -> Result<(), Error> {
const LICENSE_LOGO_START:u64 = 0x2DA8;
license_file.seek(SeekFrom::Start(LICENSE_LOGO_START))?;
for _ in 0..7 {
const LICENSE_SECTOR_REST:i64 = 0x120;
if license_file.get_ref().metadata()?.len() < license_file.stream_position()? + LICENSE_SECTOR_REST as u64 {
return Err(Error::from_str("License file to short to contain license logo. Is this is a valid license file?"));
}
let mut license_logo_buffer = [0u8; Mode2Form1::DATA_SIZE];
license_file.read(&mut license_logo_buffer)?;
license_file.seek(SeekFrom::Current(LICENSE_SECTOR_REST))?;
let sub_mode = SubModeBuilder::new_mode1().create();
let sector = builder::create_xa_data_for_raw(sub_mode, &license_logo_buffer);
sec_writer.write_cd_xa_data(sector)?;
}
Ok(())
}
fn write_audio_zeros(sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
for _ in 0..4 {
let sector = {
let mut sector = builder::create_xa_audio_zero();
sector.sub_header.sub_mode.clear_audio();
sector
};
sec_writer.write_cd_xa_audio(sector)?;
}
Ok(())
}
format_if_error!(write_data_zeros(sec_writer), "Writing license data zeros failed with: {error_text}")?;
format_if_error!(write_license_string(sec_writer, &mut license_file), "Writing license string from file failed with: {error_text}")?;
format_if_error!(write_license_logo(sec_writer, &mut license_file), "Writing license logo from file failed with: {error_text}")?;
format_if_error!(write_audio_zeros(sec_writer), "Writing license audio zeros failed with: {error_text}")?;
Ok(())
}
let system_area_lba = system_area.lba.get_track_relative();
if system_area_lba != 0 {
return Err(Error::from_text(format!("System Area required to start at sector 0 of Track - found LBA: {}", system_area_lba)));
}
if let Some(license_path) = &system_area.license_file_path {
let license_file = format_if_error!(open_input_file_buffered(license_path), "Loading license file from {} failed with: {error_text}", license_path.to_string_lossy())?;
write_license_file(sec_writer, license_file)
}
else {
// No license specified - filling it with zeros
print_warning("WARNING: No license file provided. Some emulators (like No$PSX) will not boot this CD.".to_owned());
for _ in 0..SYSTEM_AREA_SECTOR_COUNT {
sec_writer.write_cd_xa_data(builder::create_xa_data_zero())?;
}
Ok(())
}
}
fn process_pvd(pvd: &PrimaryVolumeDescriptor, path_table: SharedPtr<PathTable>, root_dir: SharedPtr<Directory>, vol_sector_count: usize, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
const PLAYSATATION_STR:&'static str = "PLAYSTATION";
let path_table = validate_and_unwrap_path_table(&path_table)?;
let root_dir = root_dir.borrow();
let pvd_lba = pvd.lba.get_track_relative();
if pvd_lba != 16 {
return Err(Error::from_text(format!("PVD required to start at sector 16 of Track - found LBA: {}", pvd_lba)));
}
let mut cd_pvd = cd_pvd::PrimaryVolumeDescriptor::new();
let now = Date::now();
//Config pvd here
cd_pvd.system_id = AString::from_str(PLAYSATATION_STR)?;
cd_pvd.volume_id = DString::from_str("PSX")?;
cd_pvd.vol_space_size.write(vol_sector_count as u32);
//Set PathTable values
cd_pvd.path_table_size.write(path_table.size_bytes as u32);
cd_pvd.path_table_1.write(path_table.get_track_rel_lba_for(1, sector_count_mode2_form1) as u32);
cd_pvd.path_table_2.write(path_table.get_track_rel_lba_for(2, sector_count_mode2_form1) as u32);
cd_pvd.path_table_3.write(path_table.get_track_rel_lba_for(3, sector_count_mode2_form1) as u32);
cd_pvd.path_table_4.write(path_table.get_track_rel_lba_for(4, sector_count_mode2_form1) as u32);
//Set Root Directory Record
write_dir_record(&mut cd_pvd.root_dir_record, &DirectoryRecordMember::new_dir(ROOT_DIR_NAME.to_owned(), &root_dir.properties.borrow()), false)?;
//Set other stuff
cd_pvd.publisher_id = AString::from_str(pvd.publisher.as_str())?;
cd_pvd.data_preparer = AString::from_str("JABYENGINE PSXCDGEN_EX")?;
cd_pvd.app_id = AString::from_str(PLAYSATATION_STR)?;
cd_pvd.vol_create_time = now;
cd_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 PVD and VDT
sec_writer.write_cd_xa_data(builder::create_xa_data_for(SubModeBuilder::new_mode1().set_eor().create(), &cd_pvd))?;
sec_writer.write_cd_xa_data(builder::create_xa_data_for(SubModeBuilder::new_mode1().set_eor().set_eof().create(), &cd_pvd::VolumeDescriptorTerminator::new()))?;
Ok(())
}
fn process_path_table(path_table: &PathTable, root_dir: SharedPtr<Directory>, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
macro_rules! write_path_table_twice {
($table:ident) => {
for sector in $table.clone() {
sec_writer.write_cd_xa_data(sector)?;
}
for sector in $table {
sec_writer.write_cd_xa_data(sector)?;
}
};
}
let mut bytes_used = 0;
let mut path_table_raw_l = vec![0u8; path_table.size_bytes];
let mut path_table_raw_b = vec![0u8; path_table.size_bytes];
validate_path_table(path_table)?;
let path_table = PathTable::collect_member(root_dir);
for entry in path_table {
bytes_used += unsafe{update_path_table_entry(std::mem::transmute::<&mut u8, &mut PathTableL>(&mut path_table_raw_l[bytes_used]), std::mem::transmute::<&mut u8, &mut PathTableB>(&mut path_table_raw_b[bytes_used]), entry)?};
}
let path_table_l = builder::create_xa_data_for_vec(None, &path_table_raw_l);
let path_table_b = builder::create_xa_data_for_vec(None, &path_table_raw_b);
write_path_table_twice!(path_table_l);
write_path_table_twice!(path_table_b);
Ok(())
}
fn process_directory_record(dir: &Directory, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
if dir.properties.borrow().is_hidden {
return Ok(());
}
let properties = dir.properties.borrow();
if !properties.is_size_valid() {
return Err(create_wrong_padding_error("Dir", dir.name.to_string(), properties.get_padded_size(), properties.get_real_size()));
}
let mut dir_record = vec![0u8; properties.get_real_size()];
let dir_length = dir_record.len();
let mut raw_data = &mut dir_record[0..dir_length];
for member in dir.collect_member() {
let raw_data_len = raw_data.len();
let bytes_written = write_dir_record(raw_data, &member, true)?;
raw_data = &mut raw_data[bytes_written..raw_data_len];
}
let dir_record_sectors = builder::create_xa_data_for_vec(None, &dir_record);
let dir_record_sector_count = dir_record_sectors.len();
if dir_record_sector_count > 1 {
return Err(Error::from_text(format!("Directory Record for {} spans {} sectors but PSX doesn't support more then one sector", dir.name.as_str().unwrap_or("<No name>"), dir_record_sector_count)));
}
for sector in dir_record_sectors {
sec_writer.write_cd_xa_data(sector)?;
}
let extended_sector_count = sector_count_mode2_form1(dir.properties.borrow().get_padded_size()) - dir_record_sector_count;
for _ in 0..extended_sector_count {
sec_writer.write_cd_xa_data(builder::create_xa_data_zero())?;
}
Ok(())
}
fn process_file(file: &File, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
if !file.properties.is_size_valid() {
return Err(create_wrong_padding_error("File", file.name.to_string(), file.properties.get_padded_size(), file.properties.get_real_size()));
}
let content_sectors = {
match &file.content {
FileType::Regular(raw) => builder::create_xa_data_for_vec(None, raw),
FileType::Main(_, _) => {
return Err(Error::from_str("Trying to encode an unprocssed main file"));
},
FileType::Overlay(_, _) => {
return Err(Error::from_str("Trying to encode an unprocessed overlay file"));
},
}
};
let content_sector_count = content_sectors.len();
for sector in content_sectors {
sec_writer.write_cd_xa_data(sector)?;
}
let extended_sector_count = sector_count_mode2_form1(file.properties.get_padded_size()) - content_sector_count;
for _ in 0..extended_sector_count {
sec_writer.write_cd_xa_data(builder::create_xa_data_zero())?;
}
Ok(())
}
fn process_end_dummy_section(padding: usize, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
for _ in 0..sector_count_mode2_form1(padding) {
sec_writer.write_cd_xa_data(builder::create_xa_data_zero())?;
}
Ok(())
}
fn process_cd_da(cd_da_tracks: &Vec<Vec<AudioSample>>, sec_writer: &mut dyn SectorWriter) -> Result<(), Error> {
for cd_da_track in cd_da_tracks {
sec_writer.cd_da_start()?;
for audio in builder::create_audio_for_vec(cd_da_track) {
sec_writer.write_audio(audio)?;
}
}
Ok(())
}
fn validate_path_table(path_table: &PathTable) -> Result<(), Error> {
if path_table.size_bytes > Mode2Form1::DATA_SIZE {
Err(Error::from_text(format!("Path Tables are not allowed to be bigger then {} bytes - Path Table has {} bytes", Mode2Form1::DATA_SIZE, path_table.size_bytes)))
}
else {
Ok(())
}
}
fn validate_and_unwrap_path_table(path_table: &SharedPtr<PathTable>) -> Result<std::cell::Ref<PathTable>, Error> {
let path_table = path_table.borrow();
validate_path_table(&path_table)?;
Ok(path_table)
}
fn update_path_table_entry(path_table_l: &mut PathTableL, path_table_b: &mut PathTableB, entry: PathTableMember) -> Result<usize, Error> {
let name_len = entry.name.len();
if name_len > 8 {
return Err(Error::from_text(format!("Directory name can not exceed size of 8 characters but folder {} has {} characters", entry.name, name_len)));
}
unsafe{
let name = entry.name.as_str();
path_table_l.new(name);
path_table_b.new(name);
}
path_table_l.directory_logical_block.write(entry.track_rel_lba as u32);
path_table_b.directory_logical_block.write(entry.track_rel_lba as u32);
path_table_l.parent_table_id.write(entry.parent_table_id as u16);
path_table_b.parent_table_id.write(entry.parent_table_id as u16);
Ok(path_table_l.get_size())
}
fn create_dir_record_raw<'a>(dst: &'a mut [u8], name: &str, track_rel_lba: u32, size_bytes: u32, system_use: Option<CDXASystemUse>) -> Result<&'a mut DirectoryRecord, Error> {
let has_system_use = system_use.is_some();
let bytes_needed = DirectoryRecord::calculate_size_for(name, has_system_use);
if dst.len() < bytes_needed {
return Err(Error::from_text(format!("DirectoryRecord for entry {} needs {} bytes but {} bytes were provided", name, bytes_needed, dst.len())));
}
unsafe {
let dir_record = std::mem::transmute::<&mut u8, &mut DirectoryRecord>(&mut dst[0]);
dir_record.new(name, has_system_use);
dir_record.data_block_number.write(track_rel_lba);
dir_record.data_size.write(size_bytes);
dir_record.time_stamp = SmallDate::now();
if let Some(system_use) = system_use {
if let Some(record_system_use) = dir_record.get_cdxa_system_use_mut() {
*record_system_use = system_use;
}
}
Ok(dir_record)
}
}
fn write_dir_record(dir_record: &mut [u8], dir_member: &DirectoryRecordMember, has_system_use: bool) -> Result<usize, Error> {
match dir_member {
DirectoryRecordMember::Directory{name, track_rel_lba, real_size} => {
let system_use = {
if has_system_use {
let mut system_use = CDXASystemUse::default();
system_use.file_attribute.set_mode2();
system_use.file_attribute.set_directory();
Some(system_use)
}
else {
None
}
};
let dir_record = create_dir_record_raw(dir_record, name.as_str(), *track_rel_lba as u32, round_bytes_mode2_form1(*real_size as usize) as u32, system_use)?;
dir_record.set_directory();
return Ok(dir_record.length[0] as usize);
},
DirectoryRecordMember::File{name, track_rel_lba, real_size} => {
let system_use = {
if has_system_use {
let mut system_use = CDXASystemUse::default();
system_use.file_attribute.set_mode2();
Some(system_use)
}
else {
None
}
};
let dir_record = create_dir_record_raw(dir_record, name.as_str(), *track_rel_lba as u32, *real_size as u32, system_use)?;
dir_record.set_file();
return Ok(dir_record.length[0] as usize);
}
}
}
fn create_wrong_padding_error(for_who: &str, name: String, padded_size: usize, real_size: usize) -> Error {
Error::from_text(format!("Encoding-Error for {} {}: Padded size ({}) is smaller then the original size ({}).", for_who, name, padded_size, real_size))
}