Fix inconsistent EOL
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxcdgen_ex
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
include ../Common.mk
|
||||
|
||||
ARTIFACT = psxcdgen_ex
|
||||
|
||||
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
|
||||
$(WINDOWS_ARTIFACT):
|
||||
$(call cargo_windows_default)
|
||||
|
||||
$(UNIX_ARTIFACT):
|
||||
$(call cargo_unix_default)
|
||||
|
||||
all-windows: $(WINDOWS_ARTIFACT)
|
||||
all: $(UNIX_ARTIFACT)
|
@@ -1,88 +1,88 @@
|
||||
use super::types::LicenseFile;
|
||||
use super::Error;
|
||||
use std::path::PathBuf;
|
||||
mod xml;
|
||||
|
||||
pub enum LZ4State {
|
||||
None,
|
||||
AlreadyCompressed,
|
||||
Compress,
|
||||
}
|
||||
|
||||
pub struct CDAudioFile {
|
||||
pub file_path: PathBuf,
|
||||
pub align: bool,
|
||||
}
|
||||
|
||||
impl CDAudioFile {
|
||||
pub fn new(file_path: PathBuf, align: bool) -> CDAudioFile {
|
||||
CDAudioFile{file_path, align}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Configuration {
|
||||
pub publisher: Option<String>,
|
||||
pub license_file: LicenseFile,
|
||||
pub backup_license_file: LicenseFile,
|
||||
pub root: Directory,
|
||||
pub cd_audio_files: Vec<CDAudioFile>,
|
||||
pub lead_out_sectors: Option<usize>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
pub fn new() -> Configuration {
|
||||
Configuration{publisher: None, license_file: LicenseFile::None, backup_license_file: LicenseFile::None, root: Directory::new("root", false), cd_audio_files: Vec::new(), lead_out_sectors: None}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommonProperties {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
pub lz4_state: LZ4State,
|
||||
pub padded_size: Option<usize>,
|
||||
}
|
||||
|
||||
pub enum FileKind {
|
||||
Regular,
|
||||
Main(PathBuf),
|
||||
Overlay(PathBuf),
|
||||
XAAudio(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
pub common: CommonProperties,
|
||||
pub path: PathBuf,
|
||||
pub kind: FileKind
|
||||
}
|
||||
pub struct Directory {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
member: Vec<DirMember>,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn new(name: &str, is_hidden: bool) -> Directory {
|
||||
Directory{name: String::from(name), is_hidden, member: Vec::new()}
|
||||
}
|
||||
|
||||
pub fn add_file(&mut self, file: File) {
|
||||
self.member.push(DirMember::File(file));
|
||||
}
|
||||
|
||||
pub fn add_dir(&mut self, dir: Directory) {
|
||||
self.member.push(DirMember::Directory(dir));
|
||||
}
|
||||
|
||||
pub fn into_iter(self) -> std::vec::IntoIter<DirMember> {
|
||||
self.member.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DirMember {
|
||||
File(File),
|
||||
Directory(Directory),
|
||||
}
|
||||
|
||||
pub fn parse_xml(xml: String) -> Result<Configuration, Error> {
|
||||
Ok(xml::parse(xml)?)
|
||||
use super::types::LicenseFile;
|
||||
use super::Error;
|
||||
use std::path::PathBuf;
|
||||
mod xml;
|
||||
|
||||
pub enum LZ4State {
|
||||
None,
|
||||
AlreadyCompressed,
|
||||
Compress,
|
||||
}
|
||||
|
||||
pub struct CDAudioFile {
|
||||
pub file_path: PathBuf,
|
||||
pub align: bool,
|
||||
}
|
||||
|
||||
impl CDAudioFile {
|
||||
pub fn new(file_path: PathBuf, align: bool) -> CDAudioFile {
|
||||
CDAudioFile{file_path, align}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Configuration {
|
||||
pub publisher: Option<String>,
|
||||
pub license_file: LicenseFile,
|
||||
pub backup_license_file: LicenseFile,
|
||||
pub root: Directory,
|
||||
pub cd_audio_files: Vec<CDAudioFile>,
|
||||
pub lead_out_sectors: Option<usize>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
pub fn new() -> Configuration {
|
||||
Configuration{publisher: None, license_file: LicenseFile::None, backup_license_file: LicenseFile::None, root: Directory::new("root", false), cd_audio_files: Vec::new(), lead_out_sectors: None}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommonProperties {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
pub lz4_state: LZ4State,
|
||||
pub padded_size: Option<usize>,
|
||||
}
|
||||
|
||||
pub enum FileKind {
|
||||
Regular,
|
||||
Main(PathBuf),
|
||||
Overlay(PathBuf),
|
||||
XAAudio(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
pub common: CommonProperties,
|
||||
pub path: PathBuf,
|
||||
pub kind: FileKind
|
||||
}
|
||||
pub struct Directory {
|
||||
pub name: String,
|
||||
pub is_hidden: bool,
|
||||
member: Vec<DirMember>,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn new(name: &str, is_hidden: bool) -> Directory {
|
||||
Directory{name: String::from(name), is_hidden, member: Vec::new()}
|
||||
}
|
||||
|
||||
pub fn add_file(&mut self, file: File) {
|
||||
self.member.push(DirMember::File(file));
|
||||
}
|
||||
|
||||
pub fn add_dir(&mut self, dir: Directory) {
|
||||
self.member.push(DirMember::Directory(dir));
|
||||
}
|
||||
|
||||
pub fn into_iter(self) -> std::vec::IntoIter<DirMember> {
|
||||
self.member.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DirMember {
|
||||
File(File),
|
||||
Directory(Directory),
|
||||
}
|
||||
|
||||
pub fn parse_xml(xml: String) -> Result<Configuration, Error> {
|
||||
Ok(xml::parse(xml)?)
|
||||
}
|
@@ -1,315 +1,315 @@
|
||||
use cdtypes::types::time::Time;
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{format_if_error, path_with_env_from, print_warning, string_with_env_from};
|
||||
use crate::{config_reader::Directory, types::LicenseFile};
|
||||
|
||||
use super::{CDAudioFile, CommonProperties, Configuration, Error, File, FileKind, LZ4State};
|
||||
|
||||
mod license_tag {
|
||||
pub const NAME: &'static str = "License";
|
||||
pub mod attribute {
|
||||
pub const ALT_LOGO: &'static str = "alt-logo";
|
||||
pub const ALT_TEXT: &'static str = "alt-text";
|
||||
}
|
||||
}
|
||||
|
||||
mod root_tag {
|
||||
pub const NAME: &'static str = "PSXCD";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod filesystem_tag {
|
||||
pub const NAME: &'static str = "Filesystem";
|
||||
pub mod attribute {
|
||||
pub const LEAD_OUT:&'static str = "lead-out";
|
||||
}
|
||||
}
|
||||
|
||||
mod file_and_directory_attribute {
|
||||
pub const NAME: &'static str = "name";
|
||||
pub const HIDDEN: &'static str = "hidden";
|
||||
pub const PADDED_SIZE: &'static str = "padded_size";
|
||||
pub const LBA_SOURCE: &'static str = "lba_source";
|
||||
pub const LZ4_STATE: &'static str = "lz4";
|
||||
}
|
||||
|
||||
mod interleaved_file_tag {
|
||||
pub const NAME: &'static str = "InterleavedFile";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod cdda_tag {
|
||||
pub const NAME: &'static str = "AudioTrack";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod alt_license_tag {
|
||||
pub const NAME: &'static str = "AltLicense";
|
||||
pub mod attribute {
|
||||
pub const TEXT: &'static str = "text";
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(xml: String) -> Result<Configuration, Error> {
|
||||
let mut config = Configuration::new();
|
||||
let parser = format_if_error!(roxmltree::Document::parse(xml.as_str()), "Parsing XML file failed with: {error_text}")?;
|
||||
let children = parser.root().children();
|
||||
|
||||
for node in children {
|
||||
if node.is_element() && node.tag_name().name() == root_tag::NAME {
|
||||
parse_root(node, &mut config)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn parse_root(root: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
let mut parsed_track = false;
|
||||
|
||||
for node in root.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Description" => parse_description(node, config),
|
||||
filesystem_tag::NAME => {
|
||||
if !parsed_track {
|
||||
parsed_track = true;
|
||||
parse_track(node, config)
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("Found more than 1 filesystem. Multi filesystem discs are not supported")))
|
||||
}
|
||||
},
|
||||
cdda_tag::NAME => parse_cd_audio(node, config),
|
||||
_ => Ok(())
|
||||
}?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_description(description: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_license_path(license: roxmltree::Node) -> (LicenseFile, LicenseFile) {
|
||||
let required_lic_file = LicenseFile::from_authentic_option(path_from_node(&license, license_tag::NAME).ok());
|
||||
let alt_logo = {
|
||||
if let Some(path) = license.attribute(license_tag::attribute::ALT_LOGO) {
|
||||
Some(path_with_env_from(path))
|
||||
}
|
||||
|
||||
else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let alt_text = license.attribute(license_tag::attribute::ALT_TEXT);
|
||||
(required_lic_file, LicenseFile::from_tmd_option(alt_logo, alt_text))
|
||||
}
|
||||
|
||||
fn parse_altlicense_path(license: roxmltree::Node) -> LicenseFile {
|
||||
LicenseFile::from_tmd_option(path_from_node(&license, alt_license_tag::NAME).ok(), license.attribute(alt_license_tag::attribute::TEXT))
|
||||
}
|
||||
|
||||
for node in description.descendants() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Publisher" => config.publisher = Some(String::from(node.text().unwrap_or_default())),
|
||||
license_tag::NAME => (config.license_file, config.backup_license_file) = parse_license_path(node),
|
||||
alt_license_tag::NAME => {
|
||||
if matches!(config.license_file, LicenseFile::None) {
|
||||
config.license_file = parse_altlicense_path(node)
|
||||
}
|
||||
|
||||
else {
|
||||
print_warning("Alternate license ignored because a (alternate) license was already provided".to_owned())
|
||||
}
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_regular_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
let common = read_common_properties(&file, is_hidden, None)?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Regular})
|
||||
}
|
||||
|
||||
fn parse_main_file(file: roxmltree::Node) -> Result<File, Error> {
|
||||
if let Some(lba_path) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
let common = read_common_properties(&file, false, Some(LZ4State::None))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Main(PathBuf::from(lba_path))})
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("The main file should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
parse_regular_file(file, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_overlay_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v They will be compressed automatically
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::AlreadyCompressed))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
let lba_source = {
|
||||
if let Some(lba_source) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
lba_source
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("Overlays should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
""
|
||||
}
|
||||
};
|
||||
Ok(File{common, path, kind: FileKind::Overlay(PathBuf::from(lba_source))})
|
||||
}
|
||||
|
||||
fn parse_interleaved(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v Never compress interleaved files
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::None))?;
|
||||
let channel = {
|
||||
let mut channel = Vec::new();
|
||||
for node in file.children() {
|
||||
if node.is_element() && node.tag_name().name() == "Channel" {
|
||||
channel.push(path_from_node(&node, "Channel")?);
|
||||
}
|
||||
}
|
||||
|
||||
channel
|
||||
};
|
||||
|
||||
Ok(File{common, path: PathBuf::new(), kind: FileKind::XAAudio(channel)})
|
||||
}
|
||||
|
||||
fn parse_file_system(cur_node: roxmltree::Node, root: &mut Directory, mut is_hidden: bool) -> Result<(), Error> {
|
||||
for node in cur_node.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"File" => root.add_file(parse_regular_file(node, is_hidden)?),
|
||||
"Main" => root.add_file(parse_main_file(node)?),
|
||||
"Overlay" => root.add_file(parse_overlay_file(node, is_hidden)?),
|
||||
interleaved_file_tag::NAME => root.add_file(parse_interleaved(node, is_hidden)?),
|
||||
"Directory" => {
|
||||
is_hidden |= parse_boolean_attribute(&node, file_and_directory_attribute::HIDDEN)?;
|
||||
let mut new_dir = Directory::new(node.attribute(file_and_directory_attribute::NAME).unwrap_or_default(), is_hidden);
|
||||
|
||||
parse_file_system(node, &mut new_dir, is_hidden)?;
|
||||
root.add_dir(new_dir);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_lead_out_sectors(track: roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
const MIN_OPTION_NAME:&'static str = "minutes";
|
||||
const SECOND_OPTION_NAME:&'static str = "seconds";
|
||||
const SECTOR_OPTION_NAME:&'static str = "sectors";
|
||||
|
||||
fn parse_split<'a>(who: &'a str, str_opt: Option<&'a str>) -> Result<u8, Error> {
|
||||
match str_opt.unwrap_or("0").parse::<u8>() {
|
||||
Ok(num) => Ok(num),
|
||||
Err(error) => Err(Error::from_text(format!("Failed converting \"{}\" option for \"{}\" attribute \"{}\": {}", who, filesystem_tag::NAME, filesystem_tag::attribute::LEAD_OUT, error)))
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
if let Some(length_str) = track.attribute(filesystem_tag::attribute::LEAD_OUT) {
|
||||
let mut pieces = length_str.split(':');
|
||||
let (mins, seconds, sectors) = (
|
||||
parse_split(MIN_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECOND_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECTOR_OPTION_NAME, pieces.next())?
|
||||
);
|
||||
|
||||
Ok(Some(Time::from_mss(mins, seconds, sectors).as_sectors()))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
config.lead_out_sectors = get_lead_out_sectors(track)?;
|
||||
parse_file_system(track, &mut config.root, false)
|
||||
}
|
||||
|
||||
fn parse_cd_audio(cdda: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
config.cd_audio_files.push(CDAudioFile::new(path_from_node(&cdda, "CD Audio file")?, parse_boolean_attribute(&cdda, "align")?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_common_properties(xml: &roxmltree::Node, is_hidden: bool, force_lz4_state: Option<LZ4State>) -> Result<CommonProperties, Error> {
|
||||
let lz4_state = {
|
||||
if let Some(forced_lz4_state) = force_lz4_state {
|
||||
forced_lz4_state
|
||||
}
|
||||
|
||||
else {
|
||||
if let Some(state_str) = xml.attribute(file_and_directory_attribute::LZ4_STATE) {
|
||||
match state_str.to_lowercase().as_str() {
|
||||
"yes" => LZ4State::Compress,
|
||||
"already" => LZ4State::AlreadyCompressed,
|
||||
_ => LZ4State::None,
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
LZ4State::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CommonProperties{
|
||||
name: string_with_env_from(xml.attribute(file_and_directory_attribute::NAME).unwrap_or_default()),
|
||||
is_hidden: is_hidden | parse_boolean_attribute(&xml, file_and_directory_attribute::HIDDEN)?,
|
||||
lz4_state,
|
||||
padded_size: read_padded_size(&xml)?
|
||||
})
|
||||
}
|
||||
|
||||
fn read_padded_size(xml: &roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
if let Some(padded_attr) = xml.attribute(file_and_directory_attribute::PADDED_SIZE) {
|
||||
let padded_size = format_if_error!(padded_attr.parse::<usize>(), "Failed reading \"{}\" as padded size: {error_text}", padded_attr)?;
|
||||
Ok(Some(padded_size))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_boolean_attribute(xml: &roxmltree::Node, attribute_name: &str) -> Result<bool, Error> {
|
||||
if let Some(bool_str) = xml.attribute(attribute_name) {
|
||||
match bool_str {
|
||||
"true" | "1" => Ok(true),
|
||||
"false" | "0" => Ok(false),
|
||||
_ => {
|
||||
return Err(Error::from_text(format!("Attribute \"{}\" expects either true,false or 1,0 as valid attributes - found {}", attribute_name, bool_str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn path_from_node(node: &roxmltree::Node, name: &str) -> Result<PathBuf, Error> {
|
||||
if let Some(path) = node.text() {
|
||||
Ok(path_with_env_from(path.trim()))
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("No path specified for {}", name)))
|
||||
}
|
||||
use cdtypes::types::time::Time;
|
||||
use std::path::PathBuf;
|
||||
use tool_helper::{format_if_error, path_with_env_from, print_warning, string_with_env_from};
|
||||
use crate::{config_reader::Directory, types::LicenseFile};
|
||||
|
||||
use super::{CDAudioFile, CommonProperties, Configuration, Error, File, FileKind, LZ4State};
|
||||
|
||||
mod license_tag {
|
||||
pub const NAME: &'static str = "License";
|
||||
pub mod attribute {
|
||||
pub const ALT_LOGO: &'static str = "alt-logo";
|
||||
pub const ALT_TEXT: &'static str = "alt-text";
|
||||
}
|
||||
}
|
||||
|
||||
mod root_tag {
|
||||
pub const NAME: &'static str = "PSXCD";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod filesystem_tag {
|
||||
pub const NAME: &'static str = "Filesystem";
|
||||
pub mod attribute {
|
||||
pub const LEAD_OUT:&'static str = "lead-out";
|
||||
}
|
||||
}
|
||||
|
||||
mod file_and_directory_attribute {
|
||||
pub const NAME: &'static str = "name";
|
||||
pub const HIDDEN: &'static str = "hidden";
|
||||
pub const PADDED_SIZE: &'static str = "padded_size";
|
||||
pub const LBA_SOURCE: &'static str = "lba_source";
|
||||
pub const LZ4_STATE: &'static str = "lz4";
|
||||
}
|
||||
|
||||
mod interleaved_file_tag {
|
||||
pub const NAME: &'static str = "InterleavedFile";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod cdda_tag {
|
||||
pub const NAME: &'static str = "AudioTrack";
|
||||
pub mod attribute {}
|
||||
}
|
||||
|
||||
mod alt_license_tag {
|
||||
pub const NAME: &'static str = "AltLicense";
|
||||
pub mod attribute {
|
||||
pub const TEXT: &'static str = "text";
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(xml: String) -> Result<Configuration, Error> {
|
||||
let mut config = Configuration::new();
|
||||
let parser = format_if_error!(roxmltree::Document::parse(xml.as_str()), "Parsing XML file failed with: {error_text}")?;
|
||||
let children = parser.root().children();
|
||||
|
||||
for node in children {
|
||||
if node.is_element() && node.tag_name().name() == root_tag::NAME {
|
||||
parse_root(node, &mut config)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn parse_root(root: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
let mut parsed_track = false;
|
||||
|
||||
for node in root.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Description" => parse_description(node, config),
|
||||
filesystem_tag::NAME => {
|
||||
if !parsed_track {
|
||||
parsed_track = true;
|
||||
parse_track(node, config)
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("Found more than 1 filesystem. Multi filesystem discs are not supported")))
|
||||
}
|
||||
},
|
||||
cdda_tag::NAME => parse_cd_audio(node, config),
|
||||
_ => Ok(())
|
||||
}?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_description(description: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_license_path(license: roxmltree::Node) -> (LicenseFile, LicenseFile) {
|
||||
let required_lic_file = LicenseFile::from_authentic_option(path_from_node(&license, license_tag::NAME).ok());
|
||||
let alt_logo = {
|
||||
if let Some(path) = license.attribute(license_tag::attribute::ALT_LOGO) {
|
||||
Some(path_with_env_from(path))
|
||||
}
|
||||
|
||||
else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let alt_text = license.attribute(license_tag::attribute::ALT_TEXT);
|
||||
(required_lic_file, LicenseFile::from_tmd_option(alt_logo, alt_text))
|
||||
}
|
||||
|
||||
fn parse_altlicense_path(license: roxmltree::Node) -> LicenseFile {
|
||||
LicenseFile::from_tmd_option(path_from_node(&license, alt_license_tag::NAME).ok(), license.attribute(alt_license_tag::attribute::TEXT))
|
||||
}
|
||||
|
||||
for node in description.descendants() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"Publisher" => config.publisher = Some(String::from(node.text().unwrap_or_default())),
|
||||
license_tag::NAME => (config.license_file, config.backup_license_file) = parse_license_path(node),
|
||||
alt_license_tag::NAME => {
|
||||
if matches!(config.license_file, LicenseFile::None) {
|
||||
config.license_file = parse_altlicense_path(node)
|
||||
}
|
||||
|
||||
else {
|
||||
print_warning("Alternate license ignored because a (alternate) license was already provided".to_owned())
|
||||
}
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
fn parse_regular_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
let common = read_common_properties(&file, is_hidden, None)?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Regular})
|
||||
}
|
||||
|
||||
fn parse_main_file(file: roxmltree::Node) -> Result<File, Error> {
|
||||
if let Some(lba_path) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
let common = read_common_properties(&file, false, Some(LZ4State::None))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
|
||||
Ok(File{common, path, kind: FileKind::Main(PathBuf::from(lba_path))})
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("The main file should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
parse_regular_file(file, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_overlay_file(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v They will be compressed automatically
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::AlreadyCompressed))?;
|
||||
let path = path_from_node(&file, &common.name)?;
|
||||
let lba_source = {
|
||||
if let Some(lba_source) = file.attribute(file_and_directory_attribute::LBA_SOURCE) {
|
||||
lba_source
|
||||
}
|
||||
|
||||
else {
|
||||
tool_helper::print_warning(format!("Overlays should always contain the \"{}\" attribute, even when just empty", file_and_directory_attribute::LBA_SOURCE));
|
||||
""
|
||||
}
|
||||
};
|
||||
Ok(File{common, path, kind: FileKind::Overlay(PathBuf::from(lba_source))})
|
||||
}
|
||||
|
||||
fn parse_interleaved(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
|
||||
// v Never compress interleaved files
|
||||
let common = read_common_properties(&file, is_hidden, Some(LZ4State::None))?;
|
||||
let channel = {
|
||||
let mut channel = Vec::new();
|
||||
for node in file.children() {
|
||||
if node.is_element() && node.tag_name().name() == "Channel" {
|
||||
channel.push(path_from_node(&node, "Channel")?);
|
||||
}
|
||||
}
|
||||
|
||||
channel
|
||||
};
|
||||
|
||||
Ok(File{common, path: PathBuf::new(), kind: FileKind::XAAudio(channel)})
|
||||
}
|
||||
|
||||
fn parse_file_system(cur_node: roxmltree::Node, root: &mut Directory, mut is_hidden: bool) -> Result<(), Error> {
|
||||
for node in cur_node.children() {
|
||||
if node.is_element() {
|
||||
match node.tag_name().name() {
|
||||
"File" => root.add_file(parse_regular_file(node, is_hidden)?),
|
||||
"Main" => root.add_file(parse_main_file(node)?),
|
||||
"Overlay" => root.add_file(parse_overlay_file(node, is_hidden)?),
|
||||
interleaved_file_tag::NAME => root.add_file(parse_interleaved(node, is_hidden)?),
|
||||
"Directory" => {
|
||||
is_hidden |= parse_boolean_attribute(&node, file_and_directory_attribute::HIDDEN)?;
|
||||
let mut new_dir = Directory::new(node.attribute(file_and_directory_attribute::NAME).unwrap_or_default(), is_hidden);
|
||||
|
||||
parse_file_system(node, &mut new_dir, is_hidden)?;
|
||||
root.add_dir(new_dir);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_lead_out_sectors(track: roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
const MIN_OPTION_NAME:&'static str = "minutes";
|
||||
const SECOND_OPTION_NAME:&'static str = "seconds";
|
||||
const SECTOR_OPTION_NAME:&'static str = "sectors";
|
||||
|
||||
fn parse_split<'a>(who: &'a str, str_opt: Option<&'a str>) -> Result<u8, Error> {
|
||||
match str_opt.unwrap_or("0").parse::<u8>() {
|
||||
Ok(num) => Ok(num),
|
||||
Err(error) => Err(Error::from_text(format!("Failed converting \"{}\" option for \"{}\" attribute \"{}\": {}", who, filesystem_tag::NAME, filesystem_tag::attribute::LEAD_OUT, error)))
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
if let Some(length_str) = track.attribute(filesystem_tag::attribute::LEAD_OUT) {
|
||||
let mut pieces = length_str.split(':');
|
||||
let (mins, seconds, sectors) = (
|
||||
parse_split(MIN_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECOND_OPTION_NAME, pieces.next())?,
|
||||
parse_split(SECTOR_OPTION_NAME, pieces.next())?
|
||||
);
|
||||
|
||||
Ok(Some(Time::from_mss(mins, seconds, sectors).as_sectors()))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
config.lead_out_sectors = get_lead_out_sectors(track)?;
|
||||
parse_file_system(track, &mut config.root, false)
|
||||
}
|
||||
|
||||
fn parse_cd_audio(cdda: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
|
||||
config.cd_audio_files.push(CDAudioFile::new(path_from_node(&cdda, "CD Audio file")?, parse_boolean_attribute(&cdda, "align")?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_common_properties(xml: &roxmltree::Node, is_hidden: bool, force_lz4_state: Option<LZ4State>) -> Result<CommonProperties, Error> {
|
||||
let lz4_state = {
|
||||
if let Some(forced_lz4_state) = force_lz4_state {
|
||||
forced_lz4_state
|
||||
}
|
||||
|
||||
else {
|
||||
if let Some(state_str) = xml.attribute(file_and_directory_attribute::LZ4_STATE) {
|
||||
match state_str.to_lowercase().as_str() {
|
||||
"yes" => LZ4State::Compress,
|
||||
"already" => LZ4State::AlreadyCompressed,
|
||||
_ => LZ4State::None,
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
LZ4State::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CommonProperties{
|
||||
name: string_with_env_from(xml.attribute(file_and_directory_attribute::NAME).unwrap_or_default()),
|
||||
is_hidden: is_hidden | parse_boolean_attribute(&xml, file_and_directory_attribute::HIDDEN)?,
|
||||
lz4_state,
|
||||
padded_size: read_padded_size(&xml)?
|
||||
})
|
||||
}
|
||||
|
||||
fn read_padded_size(xml: &roxmltree::Node) -> Result<Option<usize>, Error> {
|
||||
if let Some(padded_attr) = xml.attribute(file_and_directory_attribute::PADDED_SIZE) {
|
||||
let padded_size = format_if_error!(padded_attr.parse::<usize>(), "Failed reading \"{}\" as padded size: {error_text}", padded_attr)?;
|
||||
Ok(Some(padded_size))
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_boolean_attribute(xml: &roxmltree::Node, attribute_name: &str) -> Result<bool, Error> {
|
||||
if let Some(bool_str) = xml.attribute(attribute_name) {
|
||||
match bool_str {
|
||||
"true" | "1" => Ok(true),
|
||||
"false" | "0" => Ok(false),
|
||||
_ => {
|
||||
return Err(Error::from_text(format!("Attribute \"{}\" expects either true,false or 1,0 as valid attributes - found {}", attribute_name, bool_str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn path_from_node(node: &roxmltree::Node, name: &str) -> Result<PathBuf, Error> {
|
||||
if let Some(path) = node.text() {
|
||||
Ok(path_with_env_from(path.trim()))
|
||||
}
|
||||
|
||||
else {
|
||||
Err(Error::from_text(format!("No path specified for {}", name)))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,62 +1,62 @@
|
||||
use clap::Parser;
|
||||
use psxcdgen_ex::{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 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<Option<PathBuf>>,
|
||||
|
||||
#[clap(value_parser, help="Input XML path for creating the image")]
|
||||
input_file: PathBuf,
|
||||
}
|
||||
|
||||
fn run_main(cmd_line: CommandLine) -> Result<(), Error> {
|
||||
const PKG_MINIMUM_CONTENT_SIZE:usize = 512*1024;
|
||||
|
||||
let (mut desc, lba_embedded_files) = psxcdgen_ex::process(config_reader::parse_xml(read_file_to_string(&cmd_line.input_file)?)?)?;
|
||||
let file_map = desc.create_file_map();
|
||||
|
||||
psxcdgen_ex::process_files(file_map, lba_embedded_files)?;
|
||||
let content_size = desc.get_content_size();
|
||||
if content_size < PKG_MINIMUM_CONTENT_SIZE {
|
||||
let missing_size = PKG_MINIMUM_CONTENT_SIZE - content_size;
|
||||
desc.end_dummy_padding = missing_size;
|
||||
tool_helper::print_warning(format!("Content size {}b smaller then {}b.\nCD will be padded with {}b to work as a .pkg", content_size, PKG_MINIMUM_CONTENT_SIZE, missing_size));
|
||||
}
|
||||
|
||||
if desc.lead_out_sectors == 0 {
|
||||
tool_helper::print_warning(format!("Consider adding a 2 second lead-out to your track (Add attribute 'lead-out=\"0:2:0\"' to your Track) for supporting the PS3"));
|
||||
}
|
||||
|
||||
write_image(&desc, 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;
|
||||
use psxcdgen_ex::{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 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<Option<PathBuf>>,
|
||||
|
||||
#[clap(value_parser, help="Input XML path for creating the image")]
|
||||
input_file: PathBuf,
|
||||
}
|
||||
|
||||
fn run_main(cmd_line: CommandLine) -> Result<(), Error> {
|
||||
const PKG_MINIMUM_CONTENT_SIZE:usize = 512*1024;
|
||||
|
||||
let (mut desc, lba_embedded_files) = psxcdgen_ex::process(config_reader::parse_xml(read_file_to_string(&cmd_line.input_file)?)?)?;
|
||||
let file_map = desc.create_file_map();
|
||||
|
||||
psxcdgen_ex::process_files(file_map, lba_embedded_files)?;
|
||||
let content_size = desc.get_content_size();
|
||||
if content_size < PKG_MINIMUM_CONTENT_SIZE {
|
||||
let missing_size = PKG_MINIMUM_CONTENT_SIZE - content_size;
|
||||
desc.end_dummy_padding = missing_size;
|
||||
tool_helper::print_warning(format!("Content size {}b smaller then {}b.\nCD will be padded with {}b to work as a .pkg", content_size, PKG_MINIMUM_CONTENT_SIZE, missing_size));
|
||||
}
|
||||
|
||||
if desc.lead_out_sectors == 0 {
|
||||
tool_helper::print_warning(format!("Consider adding a 2 second lead-out to your track (Add attribute 'lead-out=\"0:2:0\"' to your Track) for supporting the PS3"));
|
||||
}
|
||||
|
||||
write_image(&desc, 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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
use crate::types::RawData;
|
||||
|
||||
pub fn skip_to_lba_area(content: &mut RawData) -> &mut [u8] {
|
||||
const PSX_HEADER_SIZE:usize = 2048;
|
||||
|
||||
&mut content[PSX_HEADER_SIZE..]
|
||||
use crate::types::RawData;
|
||||
|
||||
pub fn skip_to_lba_area(content: &mut RawData) -> &mut [u8] {
|
||||
const PSX_HEADER_SIZE:usize = 2048;
|
||||
|
||||
&mut content[PSX_HEADER_SIZE..]
|
||||
}
|
Reference in New Issue
Block a user