From e37321de895bd86fea00d91b5df133dc97492a29 Mon Sep 17 00:00:00 2001 From: Jaby Blubb Date: Thu, 12 Oct 2023 14:03:44 +0200 Subject: [PATCH] Make fconv delete faulty files --- src/Tools/cpp_out/src/main.rs | 102 +- src/Tools/jaby_engine_fconv/Cargo.toml | 26 +- .../src/images/reduced_tim/mod.rs | 4 +- src/Tools/jaby_engine_fconv/src/lib.rs | 3 +- src/Tools/jaby_engine_fconv/src/main.rs | 26 +- .../jaby_engine_fconv/src/nothing/mod.rs | 7 + src/Tools/mkoverlay/src/main.rs | 100 +- src/Tools/psxcdgen_ex/src/encoder/psx.rs | 935 +++++++++--------- src/Tools/psxcdgen_ex/src/main.rs | 130 +-- src/Tools/psxreadmap/src/main.rs | 232 ++--- src/Tools/tool_helper/src/lib.rs | 444 ++++----- 11 files changed, 1014 insertions(+), 995 deletions(-) create mode 100644 src/Tools/jaby_engine_fconv/src/nothing/mod.rs diff --git a/src/Tools/cpp_out/src/main.rs b/src/Tools/cpp_out/src/main.rs index 2c21a29f..fb4d1ba9 100644 --- a/src/Tools/cpp_out/src/main.rs +++ b/src/Tools/cpp_out/src/main.rs @@ -1,52 +1,52 @@ -use clap::{Parser}; -use cpp_out::{Configuration, Error, FileType}; -use std::path::PathBuf; -use tool_helper::exit_with_error; - -#[derive(Parser)] -#[clap(about = "Output a file content or stdin to a c++ header/source file", long_about = None)] -struct CommandLine { - #[clap(value_parser)] - input_file: Option, - - #[clap(short='n', long="name")] - data_name: String, - - #[clap(short='o')] - output_file: PathBuf, -} - -fn configurate(cmd: &mut CommandLine) -> Result { - let file_name = tool_helper::get_file_name_from_path_buf(&cmd.output_file, "output")?; - let extension = tool_helper::os_str_to_string(Error::ok_or_new(cmd.output_file.extension(), ||"File extension required for output".to_owned())?, "extension")?.to_ascii_lowercase(); - let file_type = Error::ok_or_new({ - match extension.as_ref() { - "h" => Some(FileType::CHeader), - "hpp" => Some(FileType::CPPHeader), - "c" => Some(FileType::CSource), - "cpp" => Some(FileType::CPPSource), - _ => None - } - }, ||format!("{} unkown file extension", extension))?; - - Ok(Configuration{file_name, data_name: std::mem::take(&mut cmd.data_name), line_feed: cpp_out::LineFeed::Windows, file_type}) -} - -fn run_main(mut cmd: CommandLine) -> Result<(), Error> { - let cfg = configurate(&mut cmd)?; - let input = tool_helper::open_input(cmd.input_file)?; - let output = tool_helper::open_output(Some(cmd.output_file))?; - - return cpp_out::convert(cfg, input, output); -} - -fn main() { - match CommandLine::try_parse() { - Ok(cmd) => { - if let Err(error) = run_main(cmd) { - exit_with_error(error) - } - }, - Err(error) => println!("{}", error) - } +use clap::{Parser}; +use cpp_out::{Configuration, Error, FileType}; +use std::path::PathBuf; +use tool_helper::exit_with_error; + +#[derive(Parser)] +#[clap(about = "Output a file content or stdin to a c++ header/source file", long_about = None)] +struct CommandLine { + #[clap(value_parser)] + input_file: Option, + + #[clap(short='n', long="name")] + data_name: String, + + #[clap(short='o')] + output_file: PathBuf, +} + +fn configurate(cmd: &mut CommandLine) -> Result { + let file_name = tool_helper::get_file_name_from_path_buf(&cmd.output_file, "output")?; + let extension = tool_helper::os_str_to_string(Error::ok_or_new(cmd.output_file.extension(), ||"File extension required for output".to_owned())?, "extension")?.to_ascii_lowercase(); + let file_type = Error::ok_or_new({ + match extension.as_ref() { + "h" => Some(FileType::CHeader), + "hpp" => Some(FileType::CPPHeader), + "c" => Some(FileType::CSource), + "cpp" => Some(FileType::CPPSource), + _ => None + } + }, ||format!("{} unkown file extension", extension))?; + + Ok(Configuration{file_name, data_name: std::mem::take(&mut cmd.data_name), line_feed: cpp_out::LineFeed::Windows, file_type}) +} + +fn run_main(mut cmd: CommandLine) -> Result<(), Error> { + let cfg = configurate(&mut cmd)?; + let input = tool_helper::open_input(cmd.input_file)?; + let output = tool_helper::open_output(&Some(cmd.output_file))?; + + return cpp_out::convert(cfg, input, output); +} + +fn main() { + match CommandLine::try_parse() { + Ok(cmd) => { + if let Err(error) = run_main(cmd) { + exit_with_error(error) + } + }, + Err(error) => println!("{}", error) + } } \ No newline at end of file diff --git a/src/Tools/jaby_engine_fconv/Cargo.toml b/src/Tools/jaby_engine_fconv/Cargo.toml index b2418f82..5d8a41c6 100644 --- a/src/Tools/jaby_engine_fconv/Cargo.toml +++ b/src/Tools/jaby_engine_fconv/Cargo.toml @@ -1,14 +1,14 @@ -[package] -name = "jaby_engine_fconv" -version = "0.1.4" -edition = "2021" - -[profile.release] -panic = "abort" - -[dependencies] -clap = {version = "*", features = ["derive"]} -image = "*" -paste = "*" -png = "*" +[package] +name = "jaby_engine_fconv" +version = "0.1.5" +edition = "2021" + +[profile.release] +panic = "abort" + +[dependencies] +clap = {version = "*", features = ["derive"]} +image = "*" +paste = "*" +png = "*" tool_helper = {path = "../tool_helper"} \ No newline at end of file diff --git a/src/Tools/jaby_engine_fconv/src/images/reduced_tim/mod.rs b/src/Tools/jaby_engine_fconv/src/images/reduced_tim/mod.rs index bcba4409..597d5fee 100644 --- a/src/Tools/jaby_engine_fconv/src/images/reduced_tim/mod.rs +++ b/src/Tools/jaby_engine_fconv/src/images/reduced_tim/mod.rs @@ -64,9 +64,7 @@ fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_trans fn encode(image: T, color_depth: ColorType, clut_align: ClutAlignment, output: &mut dyn Write) -> Result<(), Error> { let (width, height) = { fn return_error(clut_type: u32, div: u32, width: u16, height: u16) -> Result<(u16, u16), Error> { - return Err(Error::from_callback(|| {format!("CLUT {} images require a width divideable by {} (found width: {}/{}={}, height: {}/{}={})", clut_type, div, - width, div, (width as f32/div as f32), - height, div, (height as f32/div as f32))})); + return Err(Error::from_callback(|| {format!("CLUT {} images require a width divideable by {} (found width: {}/{}={}, height: {})", clut_type, div, width, div, (width as f32/div as f32), height)})); } let width = image.width(); diff --git a/src/Tools/jaby_engine_fconv/src/lib.rs b/src/Tools/jaby_engine_fconv/src/lib.rs index 2b040ab6..53066c93 100644 --- a/src/Tools/jaby_engine_fconv/src/lib.rs +++ b/src/Tools/jaby_engine_fconv/src/lib.rs @@ -1 +1,2 @@ -pub mod images; \ No newline at end of file +pub mod images; +pub mod nothing; \ No newline at end of file diff --git a/src/Tools/jaby_engine_fconv/src/main.rs b/src/Tools/jaby_engine_fconv/src/main.rs index 6c43534b..a2006ed1 100644 --- a/src/Tools/jaby_engine_fconv/src/main.rs +++ b/src/Tools/jaby_engine_fconv/src/main.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use jaby_engine_fconv::images::*; +use jaby_engine_fconv::{images::*, nothing}; use std::path::PathBuf; use tool_helper::{Error, exit_with_error}; @@ -28,7 +28,7 @@ enum SubCommands { fn run_main(cmd: CommandLine) -> Result<(), Error> { let mut input = tool_helper::open_input(cmd.input_file)?; let mut buffer = Vec::::new(); - let mut output_file = tool_helper::open_output(cmd.output_file)?; + let mut output_file = tool_helper::open_output(&cmd.output_file)?; let dst_buffer = { if cmd.compress_lz4 { &mut buffer as &mut dyn std::io::Write @@ -39,13 +39,23 @@ fn run_main(cmd: CommandLine) -> Result<(), Error> { } }; - match cmd.sub_command { - SubCommands::Nothing => { - std::io::copy(&mut input, dst_buffer)?; - }, - SubCommands::SimpleTIM(args) => { - reduced_tim::convert(args, input, dst_buffer)?; + let cmd_result: Result<(), Error> = { + match cmd.sub_command { + SubCommands::Nothing => nothing::copy(&mut input, dst_buffer), + SubCommands::SimpleTIM(args) => reduced_tim::convert(args, input, dst_buffer) } + }; + + if let Err(cmd_error) = cmd_result { + if let Some(file_path) = cmd.output_file { + let _result = std::fs::remove_file(file_path); + } + + else { + tool_helper::print_warning("Open stream detected! Incomplete file can not be deleted".to_owned()); + } + + return Err(cmd_error); } // We encoded the file to a temporary buffer and now need to write it diff --git a/src/Tools/jaby_engine_fconv/src/nothing/mod.rs b/src/Tools/jaby_engine_fconv/src/nothing/mod.rs new file mode 100644 index 00000000..390c74fe --- /dev/null +++ b/src/Tools/jaby_engine_fconv/src/nothing/mod.rs @@ -0,0 +1,7 @@ +use std::io::Write; +use tool_helper::{Error, Input}; + +pub fn copy(input: &mut Input, output: &mut dyn Write) -> Result<(), Error> { + std::io::copy(input, output)?; + Ok(()) +} \ No newline at end of file diff --git a/src/Tools/mkoverlay/src/main.rs b/src/Tools/mkoverlay/src/main.rs index a0997b86..f6e769c5 100644 --- a/src/Tools/mkoverlay/src/main.rs +++ b/src/Tools/mkoverlay/src/main.rs @@ -1,51 +1,51 @@ -use clap::Parser; -use mkoverlay::types::OverlayDesc; -use std::path::PathBuf; -use tool_helper::{Error, exit_with_error, format_if_error, open_input, open_output}; - -#[derive(Parser)] -#[clap(about = "Creates a linker script and makefile part for the JabyEngine toolchain", long_about = None)] -struct CommandLine { - #[clap(long="mk-file", help="Output path for the makefile")] - mk_file_output: Option, - - #[clap(long="ld-script", help="Output path for the linker script file")] - ld_file_output: Option, - - #[clap(value_parser, help="Input JSON for creating the files")] - input: Option -} - -fn parse_input(input: Option) -> Result { - if let Some(input) = input { - let input = format_if_error!(open_input(Some(input)), "Opening input file failed with: {error_text}")?; - format_if_error!(mkoverlay::types::json_reader::read_config(input), "Parsing JSON file failed with: {error_text}") - } - - else { - Ok(OverlayDesc::new()) - } -} - -fn run_main(cmd_line: CommandLine) -> Result<(), Error> { - let input = parse_input(cmd_line.input)?; - let mut mk_output = format_if_error!(open_output(cmd_line.mk_file_output), "Opening file for writing makefile failed with: {error_text}")?; - let mut ld_output = format_if_error!(open_output(cmd_line.ld_file_output), "Opening file for writing linkerfile failed with: {error_text}")?; - - format_if_error!(mkoverlay::creator::makefile::write(&mut mk_output, &input), "Writing makefile failed with: {error_text}")?; - format_if_error!(mkoverlay::creator::ldscript::write(&mut ld_output, &input), "Writing ld script failed with: {error_text}") -} - -fn main() { - match CommandLine::try_parse() { - Ok(cmd_line) => { - match run_main(cmd_line) { - Ok(_) => (), - Err(error) => exit_with_error(error) - } - }, - Err(error) => { - println!("{}", error) - } - } +use clap::Parser; +use mkoverlay::types::OverlayDesc; +use std::path::PathBuf; +use tool_helper::{Error, exit_with_error, format_if_error, open_input, open_output}; + +#[derive(Parser)] +#[clap(about = "Creates a linker script and makefile part for the JabyEngine toolchain", long_about = None)] +struct CommandLine { + #[clap(long="mk-file", help="Output path for the makefile")] + mk_file_output: Option, + + #[clap(long="ld-script", help="Output path for the linker script file")] + ld_file_output: Option, + + #[clap(value_parser, help="Input JSON for creating the files")] + input: Option +} + +fn parse_input(input: Option) -> Result { + if let Some(input) = input { + let input = format_if_error!(open_input(Some(input)), "Opening input file failed with: {error_text}")?; + format_if_error!(mkoverlay::types::json_reader::read_config(input), "Parsing JSON file failed with: {error_text}") + } + + else { + Ok(OverlayDesc::new()) + } +} + +fn run_main(cmd_line: CommandLine) -> Result<(), Error> { + let input = parse_input(cmd_line.input)?; + let mut mk_output = format_if_error!(open_output(&cmd_line.mk_file_output), "Opening file for writing makefile failed with: {error_text}")?; + let mut ld_output = format_if_error!(open_output(&cmd_line.ld_file_output), "Opening file for writing linkerfile failed with: {error_text}")?; + + format_if_error!(mkoverlay::creator::makefile::write(&mut mk_output, &input), "Writing makefile failed with: {error_text}")?; + format_if_error!(mkoverlay::creator::ldscript::write(&mut ld_output, &input), "Writing ld script failed with: {error_text}") +} + +fn main() { + match CommandLine::try_parse() { + Ok(cmd_line) => { + match run_main(cmd_line) { + Ok(_) => (), + Err(error) => exit_with_error(error) + } + }, + Err(error) => { + println!("{}", error) + } + } } \ No newline at end of file diff --git a/src/Tools/psxcdgen_ex/src/encoder/psx.rs b/src/Tools/psxcdgen_ex/src/encoder/psx.rs index 3cc2e7e6..a1422263 100644 --- a/src/Tools/psxcdgen_ex/src/encoder/psx.rs +++ b/src/Tools/psxcdgen_ex/src/encoder/psx.rs @@ -1,469 +1,468 @@ -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}, path_table::*, pvd as cd_pvd, lsb_msb::*, sector::Mode2Form1}; -use colored::*; -use tool_helper::{BufferedInputFile, format_if_error, open_input_file_buffered}; -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)?, - } - } - - Ok(()) -} - -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 - eprintln!("{}", "WARNING: No license file provided. Some emulators (like No$PSX) will not boot this CD.".yellow()); - 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, root_dir: SharedPtr, 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, 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(""), 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 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) -> Result, 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 { - 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) -> 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 { - 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)) +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}, path_table::*, pvd as cd_pvd, lsb_msb::*, sector::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)?, + } + } + + Ok(()) +} + +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, root_dir: SharedPtr, 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, 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(""), 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 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) -> Result, 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 { + 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) -> 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 { + 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)) } \ No newline at end of file diff --git a/src/Tools/psxcdgen_ex/src/main.rs b/src/Tools/psxcdgen_ex/src/main.rs index 2ac38219..2acab8f8 100644 --- a/src/Tools/psxcdgen_ex/src/main.rs +++ b/src/Tools/psxcdgen_ex/src/main.rs @@ -1,66 +1,66 @@ -use clap::{Parser, ValueEnum}; -use psxcdgen_ex::{encoder::{EncodingFunctions, psx::{calculate_psx_lbas, calculate_psx_length_for, encode_psx_image}}, file_writer::{ImageType, write_image}, config_reader}; -use std::path::PathBuf; -use tool_helper::{Error, exit_with_error, read_file_to_string}; - -#[derive(Parser)] -#[clap(about = "Creates an ISO image from a description file", long_about = None)] -struct CommandLine { - #[clap(value_enum, value_parser, help="Specifies the system for which to create the disc image")] - system_type: SystemType, - - #[clap(value_enum, value_parser, help="Specifies the disc image type")] - output_type: ImageType, - - #[clap(short='o', help="Output path; Some OUTPUT_TYPE might create additional files with different endings")] - output_file: PathBuf, - - #[clap(long="list", id="Path to file", help="If set will list the CD content to stdout; With path will output list to file")] - list_content: Option>, - - #[clap(value_parser, help="Input XML path for creating the image")] - input_file: PathBuf, -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] -enum SystemType { - Psx -} - -impl SystemType { - pub fn get_encoding_functions(self) -> EncodingFunctions { - match self { - SystemType::Psx => EncodingFunctions{length_calculator: calculate_psx_length_for, lba_calculator: calculate_psx_lbas, encoder: encode_psx_image} - } - } -} - -fn run_main(cmd_line: CommandLine) -> Result<(), Error> { - let encoding_functions = cmd_line.system_type.get_encoding_functions(); - let (desc, lba_embedded_files) = psxcdgen_ex::process(config_reader::parse_xml(read_file_to_string(&cmd_line.input_file)?)?, encoding_functions.lba_calculator)?; - let file_map = desc.create_file_map(); - - psxcdgen_ex::process_files(file_map, lba_embedded_files, encoding_functions.length_calculator)?; - write_image(&desc, encoding_functions.encoder, cmd_line.output_type, cmd_line.output_file)?; - - if let Some(list_content_option) = cmd_line.list_content { - psxcdgen_ex::dump_content(&desc, tool_helper::open_output(list_content_option)?) - } - - else { - Ok(()) - } -} - -fn main() { - match CommandLine::try_parse() { - Ok(cmd_line) => { - if let Err(error) = run_main(cmd_line) { - exit_with_error(error) - } - }, - Err(error) => { - println!("{}", error) - } - } +use clap::{Parser, ValueEnum}; +use psxcdgen_ex::{encoder::{EncodingFunctions, psx::{calculate_psx_lbas, calculate_psx_length_for, encode_psx_image}}, file_writer::{ImageType, write_image}, config_reader}; +use std::path::PathBuf; +use tool_helper::{Error, exit_with_error, read_file_to_string}; + +#[derive(Parser)] +#[clap(about = "Creates an ISO image from a description file", long_about = None)] +struct CommandLine { + #[clap(value_enum, value_parser, help="Specifies the system for which to create the disc image")] + system_type: SystemType, + + #[clap(value_enum, value_parser, help="Specifies the disc image type")] + output_type: ImageType, + + #[clap(short='o', help="Output path; Some OUTPUT_TYPE might create additional files with different endings")] + output_file: PathBuf, + + #[clap(long="list", id="Path to file", help="If set will list the CD content to stdout; With path will output list to file")] + list_content: Option>, + + #[clap(value_parser, help="Input XML path for creating the image")] + input_file: PathBuf, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum SystemType { + Psx +} + +impl SystemType { + pub fn get_encoding_functions(self) -> EncodingFunctions { + match self { + SystemType::Psx => EncodingFunctions{length_calculator: calculate_psx_length_for, lba_calculator: calculate_psx_lbas, encoder: encode_psx_image} + } + } +} + +fn run_main(cmd_line: CommandLine) -> Result<(), Error> { + let encoding_functions = cmd_line.system_type.get_encoding_functions(); + let (desc, lba_embedded_files) = psxcdgen_ex::process(config_reader::parse_xml(read_file_to_string(&cmd_line.input_file)?)?, encoding_functions.lba_calculator)?; + let file_map = desc.create_file_map(); + + psxcdgen_ex::process_files(file_map, lba_embedded_files, encoding_functions.length_calculator)?; + write_image(&desc, encoding_functions.encoder, cmd_line.output_type, cmd_line.output_file)?; + + if let Some(list_content_option) = cmd_line.list_content { + psxcdgen_ex::dump_content(&desc, tool_helper::open_output(&list_content_option)?) + } + + else { + Ok(()) + } +} + +fn main() { + match CommandLine::try_parse() { + Ok(cmd_line) => { + if let Err(error) = run_main(cmd_line) { + exit_with_error(error) + } + }, + Err(error) => { + println!("{}", error) + } + } } \ No newline at end of file diff --git a/src/Tools/psxreadmap/src/main.rs b/src/Tools/psxreadmap/src/main.rs index a3a60ef1..0a3d3f09 100644 --- a/src/Tools/psxreadmap/src/main.rs +++ b/src/Tools/psxreadmap/src/main.rs @@ -1,117 +1,117 @@ -use clap::Parser; -use crossterm::{event::{self, Event as CEvent, KeyboardEnhancementFlags, KeyEventKind, PushKeyboardEnhancementFlags}, execute, terminal}; -use psxreadmap::{ConsoleUI, Event, EventReceiver, Terminal, load_memory_map, UIState}; -use std::{io::{self, Stdout}, path::PathBuf, sync::mpsc, thread, time::{Duration, Instant}}; -use tool_helper::{Error, exit_with_error, open_output}; -use ratatui::backend::CrosstermBackend; - -pub const RUN_DUMP_TOOL_IN_WSL:bool = cfg!(windows); - -#[derive(Parser)] -#[clap(about = "Opens and scans an ELF file to print extended memory information", long_about = None)] -struct CommandLine { - #[clap(value_parser, help="Input ELF file for scannning")] - input: PathBuf, - #[clap(long="wsl", help="Run \"objdump\" in WSL", default_value_t=RUN_DUMP_TOOL_IN_WSL)] - use_wsl: bool, - #[clap(long="raw", default_value_t=false)] - raw_dump: bool, - #[clap(short='o', help="Output a memory map file with running the tool")] - output: Option -} - -pub fn main() { - match CommandLine::try_parse() { - Ok(cmd_line) => { - match run_main(cmd_line) { - Ok(_) => (), - Err(error) => exit_with_error(error) - } - }, - Err(error) => { - println!("{})", error) - } - } -} - -fn run_main(cmd: CommandLine) -> Result<(), Error> { - if cmd.raw_dump { - psxreadmap::get_tool_output(cmd.use_wsl, cmd.input, open_output(cmd.output)?)?; - return Ok(()); - } - - let memory_map = load_memory_map(cmd.use_wsl, cmd.input)?; dump_memory_map(cmd.output, &memory_map)?; - let rx = start_event_loop(); - let terminal = setup_console()?; - let mut console_ui = ConsoleUI::new(rx, terminal); - - console_ui.update_data(memory_map)?; - loop { - match console_ui.update()? { - UIState::Alive => (), - UIState::Render => {console_ui.render()?;} - UIState::Terminated => break - } - } - - console_ui.close() -} - -fn dump_memory_map(output: Option, memory_map: &readmap::types::MemoryMap) -> Result<(), Error> { - if let Some(output) = output { - let output = tool_helper::open_output(Some(output))?; - - readmap::dump::write(output, &memory_map)?; - } - - Ok(()) -} - -fn start_event_loop() -> EventReceiver { - // Set up a mpsc (multiproducer, single consumer) channel to communicate between the input handler and the rendering loop. - let (tx, rx) = mpsc::channel(); - let tick_rate = Duration::from_millis(200); - thread::spawn(move || { - let mut last_tick = Instant::now(); - - tx.send(Event::ForceRender).expect("Send ForceRender command!"); - loop { - let timeout = tick_rate.checked_sub(last_tick.elapsed()).unwrap_or_else(|| Duration::from_secs(0)); - if event::poll(timeout).expect("Event poll working") { - if let CEvent::Key(key) = event::read().expect("Can read key input") { - if key.kind == KeyEventKind::Release { - tx.send(Event::Input(key)).expect("Can send KeyInput"); - } - } - } - - if last_tick.elapsed() >= tick_rate { - if let Ok(_) = tx.send(Event::Tick) { - last_tick = Instant::now(); - } - } - } - }); - - rx -} - -fn setup_console() -> Result { - fn open_stdout() -> Result { - let mut stdout = io::stdout(); - if cfg!(unix) { - execute!(stdout, PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES))?; - } - Ok(stdout) - } - - terminal::enable_raw_mode()?; - - // Setup Crossterm for the Terminal - let stdout = open_stdout()?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - terminal.clear()?; - Ok(terminal) +use clap::Parser; +use crossterm::{event::{self, Event as CEvent, KeyboardEnhancementFlags, KeyEventKind, PushKeyboardEnhancementFlags}, execute, terminal}; +use psxreadmap::{ConsoleUI, Event, EventReceiver, Terminal, load_memory_map, UIState}; +use std::{io::{self, Stdout}, path::PathBuf, sync::mpsc, thread, time::{Duration, Instant}}; +use tool_helper::{Error, exit_with_error, open_output}; +use ratatui::backend::CrosstermBackend; + +pub const RUN_DUMP_TOOL_IN_WSL:bool = cfg!(windows); + +#[derive(Parser)] +#[clap(about = "Opens and scans an ELF file to print extended memory information", long_about = None)] +struct CommandLine { + #[clap(value_parser, help="Input ELF file for scannning")] + input: PathBuf, + #[clap(long="wsl", help="Run \"objdump\" in WSL", default_value_t=RUN_DUMP_TOOL_IN_WSL)] + use_wsl: bool, + #[clap(long="raw", default_value_t=false)] + raw_dump: bool, + #[clap(short='o', help="Output a memory map file with running the tool")] + output: Option +} + +pub fn main() { + match CommandLine::try_parse() { + Ok(cmd_line) => { + match run_main(cmd_line) { + Ok(_) => (), + Err(error) => exit_with_error(error) + } + }, + Err(error) => { + println!("{})", error) + } + } +} + +fn run_main(cmd: CommandLine) -> Result<(), Error> { + if cmd.raw_dump { + psxreadmap::get_tool_output(cmd.use_wsl, cmd.input, open_output(&cmd.output)?)?; + return Ok(()); + } + + let memory_map = load_memory_map(cmd.use_wsl, cmd.input)?; dump_memory_map(cmd.output, &memory_map)?; + let rx = start_event_loop(); + let terminal = setup_console()?; + let mut console_ui = ConsoleUI::new(rx, terminal); + + console_ui.update_data(memory_map)?; + loop { + match console_ui.update()? { + UIState::Alive => (), + UIState::Render => {console_ui.render()?;} + UIState::Terminated => break + } + } + + console_ui.close() +} + +fn dump_memory_map(output: Option, memory_map: &readmap::types::MemoryMap) -> Result<(), Error> { + if let Some(output) = output { + let output = tool_helper::open_output(&Some(output))?; + + readmap::dump::write(output, &memory_map)?; + } + + Ok(()) +} + +fn start_event_loop() -> EventReceiver { + // Set up a mpsc (multiproducer, single consumer) channel to communicate between the input handler and the rendering loop. + let (tx, rx) = mpsc::channel(); + let tick_rate = Duration::from_millis(200); + thread::spawn(move || { + let mut last_tick = Instant::now(); + + tx.send(Event::ForceRender).expect("Send ForceRender command!"); + loop { + let timeout = tick_rate.checked_sub(last_tick.elapsed()).unwrap_or_else(|| Duration::from_secs(0)); + if event::poll(timeout).expect("Event poll working") { + if let CEvent::Key(key) = event::read().expect("Can read key input") { + if key.kind == KeyEventKind::Release { + tx.send(Event::Input(key)).expect("Can send KeyInput"); + } + } + } + + if last_tick.elapsed() >= tick_rate { + if let Ok(_) = tx.send(Event::Tick) { + last_tick = Instant::now(); + } + } + } + }); + + rx +} + +fn setup_console() -> Result { + fn open_stdout() -> Result { + let mut stdout = io::stdout(); + if cfg!(unix) { + execute!(stdout, PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES))?; + } + Ok(stdout) + } + + terminal::enable_raw_mode()?; + + // Setup Crossterm for the Terminal + let stdout = open_stdout()?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + terminal.clear()?; + Ok(terminal) } \ No newline at end of file diff --git a/src/Tools/tool_helper/src/lib.rs b/src/Tools/tool_helper/src/lib.rs index e76d919c..3cba8d99 100644 --- a/src/Tools/tool_helper/src/lib.rs +++ b/src/Tools/tool_helper/src/lib.rs @@ -1,221 +1,225 @@ -use colored::*; -use envmnt::{ExpandOptions, ExpansionType}; -use std::{boxed::Box, io::{BufRead, BufReader, BufWriter, Read, Write}, path::PathBuf}; - -pub mod bits; -pub mod compress; -pub mod raw; - -pub type BufferedInputFile = BufReader; -pub type Output = Box; -pub type Input = Box; - -#[macro_export] -macro_rules! format_if_error { - ($result:expr, $format_text:literal) => { - tool_helper::callback_if_any_error($result, |error_text| { - format!($format_text, error_text=error_text) - }) - }; - ($result:expr, $format_text:literal, $($arg:expr)*) => { - tool_helper::callback_if_any_error($result, |error_text| { - format!($format_text, $($arg),*, error_text=error_text) - }) - }; -} - -#[macro_export] -macro_rules! format_if_error_drop_cause { - ($result:expr, $format_text:literal) => { - tool_helper::callback_if_any_error($result, |error_text| { - format!($format_text) - }) - }; - ($result:expr, $format_text:literal, $($arg:expr)*) => { - tool_helper::callback_if_any_error($result, |error_text| { - format!($format_text, $($arg),*) - }) - }; -} - -pub struct Error { - pub exit_code: i32, - pub text: String, -} - -impl Error { - const DEFAULT_EXITCODE:i32 = -1; - - pub fn from_str(str: &str) -> Error { - Self::from_text(str.to_owned()) - } - - pub fn from_text(text: String) -> Error { - Error{exit_code: Self::DEFAULT_EXITCODE, text} - } - - pub fn from_error(error: T) -> Error where T: std::fmt::Display { - Error::from_text(error.to_string()) - } - - pub fn from_core_error(error: T) -> Error where T: core::fmt::Display { - Error::from_text(error.to_string()) - } - - pub fn from_callback(callback: F) -> Error where F: Fn() -> String { - Error::from_text(callback()) - } - - pub fn not_implemented(function: &str) -> Error { - Error::from_text(format!("{} not implemented yet", function)) - } - - pub fn try_or_new(result: std::result::Result) -> Result where S: std::fmt::Display { - match result { - Ok(value) => Ok(value), - Err(error) => Err(Error{exit_code: Self::DEFAULT_EXITCODE, text: error.to_string()}), - } - } - - pub fn ok_or_new(option: Option, error_text: F) -> Result where F: Fn () -> String{ - Ok(option.ok_or(Error::from_callback(error_text))?) - } - - pub fn print_generic_to_std_err(object: &T) { - eprintln!("{}", format!("ERROR: {}", object).red()); - } - - pub fn print_to_std_err(&self) { - Self::print_generic_to_std_err(self) - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.text) - } -} - -impl std::convert::From for Error { - fn from(error: std::io::Error) -> Self { - Error{exit_code: -1, text: error.to_string()} - } -} - -impl std::convert::From for Error { - fn from(error: cdtypes::Error) -> Self { - Error::from_error(error) - } -} - -impl std::convert::From for Error { - fn from(error: std::convert::Infallible) -> Self { - Error::from_error(error) - } -} - -impl std::convert::From for Error { - fn from(error: std::sync::mpsc::RecvError) -> Self { - Error::from_error(error) - } -} - -pub fn exit_with_error(error: Error) { - error.print_to_std_err(); - std::process::exit(error.exit_code); -} - -pub fn prefix_if_error(prefix: &str, result: Result) -> Result { - match result { - Ok(value) => Ok(value), - Err(mut error) => { - let mut new_text = String::from(prefix); - - new_text.push_str(error.text.as_str()); - error.text = new_text; - Err(error) - }, - } -} - -pub fn callback_if_error String, T>(result: Result, callback: F) -> Result { - match result { - Ok(value) => Ok(value), - Err(mut error) => { - error.text = callback(error.text); - - Err(error) - } - } -} - -pub fn callback_if_any_error String, T, E: std::string::ToString>(result: Result, callback: F) -> Result { - match result { - Ok(value) => Ok(value), - Err(error) => { - Err(Error::from_text(callback(error.to_string()))) - } - } -} - -pub fn path_with_env_from(path: &str) -> PathBuf { - PathBuf::from(envmnt::expand(path, Some(ExpandOptions{expansion_type: Some(ExpansionType::All), default_to_empty: false}))) -} - -pub fn open_output_file(output_path: &PathBuf) -> Result, Error> { - Ok(std::io::BufWriter::new(std::fs::File::create(output_path)?)) -} - -pub fn open_output(output_file: Option) -> Result { - match output_file { - Some(output_path) => Ok(Box::new(open_output_file(&output_path)?)), - None => Ok(Box::new(BufWriter::new(std::io::stdout()))), - } -} - -pub fn open_input(input_file: Option) -> Result { - match input_file { - Some(input_path) => Ok(Box::new(open_input_file_buffered(&input_path)?)), - None => Ok(Box::new(BufReader::new(std::io::stdin()))), - } -} - -pub fn open_input_file_buffered(input_path: &PathBuf) -> Result { - Ok(BufReader::new(std::fs::File::open(input_path)?)) -} - -pub fn os_str_to_string(input: &std::ffi::OsStr, name: &str) -> Result { - Ok(Error::ok_or_new(input.to_str(), ||format!("Converting {} to UTF-8 failed", name))?.to_owned()) -} - -pub fn get_file_name_from_path_buf(input: &PathBuf, name: &str) -> Result { - os_str_to_string(Error::ok_or_new(input.file_name(), ||format!("No {} file name found", name))?, name) -} - -pub fn input_to_vec(input: Input) -> Result, Error> { - let mut data = Vec::new(); - - for byte in input.bytes() { - data.push(byte?); - } - - Ok(data) -} - -pub fn read_file(file_path: &PathBuf) -> Result, Error> { - match std::fs::read(file_path) { - Ok(data) => Ok(data), - Err(error) => create_file_read_error(file_path, error), - } -} - -pub fn read_file_to_string(file_path: &PathBuf) -> Result { - match std::fs::read_to_string(file_path) { - Ok(string) => Ok(string), - Err(error) => create_file_read_error(file_path, error), - } -} - -fn create_file_read_error(file_path: &PathBuf, error: std::io::Error) -> Result { - Err(Error::from_text(format!("Failed reading file {} with error: \"{}\"", file_path.display(), error))) +use colored::*; +use envmnt::{ExpandOptions, ExpansionType}; +use std::{boxed::Box, io::{BufRead, BufReader, BufWriter, Read, Write}, path::PathBuf}; + +pub mod bits; +pub mod compress; +pub mod raw; + +pub type BufferedInputFile = BufReader; +pub type Output = Box; +pub type Input = Box; + +#[macro_export] +macro_rules! format_if_error { + ($result:expr, $format_text:literal) => { + tool_helper::callback_if_any_error($result, |error_text| { + format!($format_text, error_text=error_text) + }) + }; + ($result:expr, $format_text:literal, $($arg:expr)*) => { + tool_helper::callback_if_any_error($result, |error_text| { + format!($format_text, $($arg),*, error_text=error_text) + }) + }; +} + +#[macro_export] +macro_rules! format_if_error_drop_cause { + ($result:expr, $format_text:literal) => { + tool_helper::callback_if_any_error($result, |error_text| { + format!($format_text) + }) + }; + ($result:expr, $format_text:literal, $($arg:expr)*) => { + tool_helper::callback_if_any_error($result, |error_text| { + format!($format_text, $($arg),*) + }) + }; +} + +pub struct Error { + pub exit_code: i32, + pub text: String, +} + +impl Error { + const DEFAULT_EXITCODE:i32 = -1; + + pub fn from_str(str: &str) -> Error { + Self::from_text(str.to_owned()) + } + + pub fn from_text(text: String) -> Error { + Error{exit_code: Self::DEFAULT_EXITCODE, text} + } + + pub fn from_error(error: T) -> Error where T: std::fmt::Display { + Error::from_text(error.to_string()) + } + + pub fn from_core_error(error: T) -> Error where T: core::fmt::Display { + Error::from_text(error.to_string()) + } + + pub fn from_callback(callback: F) -> Error where F: Fn() -> String { + Error::from_text(callback()) + } + + pub fn not_implemented(function: &str) -> Error { + Error::from_text(format!("{} not implemented yet", function)) + } + + pub fn try_or_new(result: std::result::Result) -> Result where S: std::fmt::Display { + match result { + Ok(value) => Ok(value), + Err(error) => Err(Error{exit_code: Self::DEFAULT_EXITCODE, text: error.to_string()}), + } + } + + pub fn ok_or_new(option: Option, error_text: F) -> Result where F: Fn () -> String{ + Ok(option.ok_or(Error::from_callback(error_text))?) + } + + pub fn print_generic_to_std_err(object: &T) { + eprintln!("{}", format!("ERROR: {}", object).red()); + } + + pub fn print_to_std_err(&self) { + Self::print_generic_to_std_err(self) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.text) + } +} + +impl std::convert::From for Error { + fn from(error: std::io::Error) -> Self { + Error{exit_code: -1, text: error.to_string()} + } +} + +impl std::convert::From for Error { + fn from(error: cdtypes::Error) -> Self { + Error::from_error(error) + } +} + +impl std::convert::From for Error { + fn from(error: std::convert::Infallible) -> Self { + Error::from_error(error) + } +} + +impl std::convert::From for Error { + fn from(error: std::sync::mpsc::RecvError) -> Self { + Error::from_error(error) + } +} + +pub fn exit_with_error(error: Error) { + error.print_to_std_err(); + std::process::exit(error.exit_code); +} + +pub fn print_warning(str: String) { + eprintln!("{}", str.yellow()); +} + +pub fn prefix_if_error(prefix: &str, result: Result) -> Result { + match result { + Ok(value) => Ok(value), + Err(mut error) => { + let mut new_text = String::from(prefix); + + new_text.push_str(error.text.as_str()); + error.text = new_text; + Err(error) + }, + } +} + +pub fn callback_if_error String, T>(result: Result, callback: F) -> Result { + match result { + Ok(value) => Ok(value), + Err(mut error) => { + error.text = callback(error.text); + + Err(error) + } + } +} + +pub fn callback_if_any_error String, T, E: std::string::ToString>(result: Result, callback: F) -> Result { + match result { + Ok(value) => Ok(value), + Err(error) => { + Err(Error::from_text(callback(error.to_string()))) + } + } +} + +pub fn path_with_env_from(path: &str) -> PathBuf { + PathBuf::from(envmnt::expand(path, Some(ExpandOptions{expansion_type: Some(ExpansionType::All), default_to_empty: false}))) +} + +pub fn open_output_file(output_path: &PathBuf) -> Result, Error> { + Ok(std::io::BufWriter::new(std::fs::File::create(output_path)?)) +} + +pub fn open_output(output_file: &Option) -> Result { + match output_file { + Some(output_path) => Ok(Box::new(open_output_file(&output_path)?)), + None => Ok(Box::new(BufWriter::new(std::io::stdout()))), + } +} + +pub fn open_input(input_file: Option) -> Result { + match input_file { + Some(input_path) => Ok(Box::new(open_input_file_buffered(&input_path)?)), + None => Ok(Box::new(BufReader::new(std::io::stdin()))), + } +} + +pub fn open_input_file_buffered(input_path: &PathBuf) -> Result { + Ok(BufReader::new(std::fs::File::open(input_path)?)) +} + +pub fn os_str_to_string(input: &std::ffi::OsStr, name: &str) -> Result { + Ok(Error::ok_or_new(input.to_str(), ||format!("Converting {} to UTF-8 failed", name))?.to_owned()) +} + +pub fn get_file_name_from_path_buf(input: &PathBuf, name: &str) -> Result { + os_str_to_string(Error::ok_or_new(input.file_name(), ||format!("No {} file name found", name))?, name) +} + +pub fn input_to_vec(input: Input) -> Result, Error> { + let mut data = Vec::new(); + + for byte in input.bytes() { + data.push(byte?); + } + + Ok(data) +} + +pub fn read_file(file_path: &PathBuf) -> Result, Error> { + match std::fs::read(file_path) { + Ok(data) => Ok(data), + Err(error) => create_file_read_error(file_path, error), + } +} + +pub fn read_file_to_string(file_path: &PathBuf) -> Result { + match std::fs::read_to_string(file_path) { + Ok(string) => Ok(string), + Err(error) => create_file_read_error(file_path, error), + } +} + +fn create_file_read_error(file_path: &PathBuf, error: std::io::Error) -> Result { + Err(Error::from_text(format!("Failed reading file {} with error: \"{}\"", file_path.display(), error))) } \ No newline at end of file