Rename fconv to pxfconv

This commit is contained in:
2024-10-10 22:11:39 +02:00
parent 6478a4c000
commit e229115c19
22 changed files with 18 additions and 18 deletions

View File

@@ -0,0 +1,15 @@
[package]
name = "psxfileconv"
version = "0.5.0"
edition = "2021"
[profile.release]
panic = "abort"
[dependencies]
clap = {version = "4.4.11", features = ["derive"]}
image = "0.24.7"
hound = "3.5.1"
paste = "1.0.14"
png = "0.17.10"
tool_helper = {path = "../tool_helper"}

View File

@@ -0,0 +1,13 @@
include ../Common.mk
ARTIFACT = psxfileconv
.PHONY: $(WINDOWS_ARTIFACT) $(UNIX_ARTIFACT)
$(WINDOWS_ARTIFACT):
$(call cargo_windows_default)
$(UNIX_ARTIFACT):
$(call cargo_unix_default)
all-windows: $(WINDOWS_ARTIFACT)
all: $(UNIX_ARTIFACT)

View File

@@ -0,0 +1,44 @@
pub mod my_vag;
pub mod vag;
pub mod xa;
use std::{env, path::PathBuf, process::Command};
use tool_helper::Error;
fn run_psxavenc<I, S>(input: PathBuf, output: PathBuf, args: I) -> Result<(), Error>
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>, {
let psxavenc = get_psxavenc_path()?;
let result = Command::new(psxavenc).args(args).arg(input.to_string_lossy().as_ref()).arg(output.to_string_lossy().as_ref()).output()?;
let stderr = tool_helper::vec_helper::to_string(result.stderr)?;
let stdout = tool_helper::vec_helper::to_string(result.stdout)?;
if !result.status.success() {
return Err(Error::from_text(format!("psxavenc returned: {}. {}", result.status, stderr)));
}
if !stdout.is_empty() {
println!("{}", stdout);
}
Ok(())
}
fn get_psxavenc_path() -> Result<PathBuf, Error> {
let tool_path = {
let mut my_path = env::current_exe()?;
my_path.pop();
my_path.push("extern");
my_path.push("psxavenc");
my_path
};
if !tool_path.exists() {
return Err(Error::from_str("Could not locate psxavenc. Make sure it is installed under \"<Jaby Engine>/bin/extern\""));
}
Ok(tool_path)
}

View File

@@ -0,0 +1,35 @@
pub mod types;
use std::io::Write;
use tool_helper::{Error, Input};
use types::MonoADPCMIterator;
pub fn convert(input: Input, _output: &mut dyn Write) -> Result<(), Error> {
let mut wav_file = hound::WavReader::new(input)?;
let wav_header = wav_file.spec();
validate(&wav_header)?;
let mut sample_count = 0;
for _adpcm_samples in MonoADPCMIterator::create(wav_file.samples::<i16>()) {
sample_count += 1;
}
println!("Parsed {} vag samples", sample_count);
Err(Error::not_implemented("my vag convert"))
}
fn validate(wav_header: &hound::WavSpec) -> Result<(), Error> {
if wav_header.sample_format != hound::SampleFormat::Int {
return Err(Error::from_str("VAG: Only integer samples are supported as input."));
}
if wav_header.bits_per_sample != 16 {
return Err(Error::from_str("VAG: Only 16bits samples are currently supported as input."));
}
if wav_header.channels != 1 {
return Err(Error::from_str("VAG: Only mono samples are currently supported"));
}
Ok(())
}

View File

@@ -0,0 +1,45 @@
use tool_helper::Error;
pub struct VAGADPCM {
data: [u32; 4]
}
impl VAGADPCM {
const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;
pub fn create() -> VAGADPCM {
VAGADPCM{data: [0; 4]}
}
}
pub struct MonoADPCMIterator<I: std::iter::Iterator<Item=Result<i16, hound::Error>>> {
iter: I
}
impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> MonoADPCMIterator<I>{
pub fn create(iter: I) -> MonoADPCMIterator<I> {
MonoADPCMIterator{iter}
}
}
impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> std::iter::Iterator for MonoADPCMIterator<I> {
type Item = Result<[i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM], Error>;
fn next(&mut self) -> Option<Self::Item> {
const STREAM_GONE_ERROR: &'static str = "Reading ADPCM sample failed";
if let Some(next_sample) = self.iter.next() {
let Ok(next_sample) = next_sample else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};
let mut sample = [0;VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM];
sample[0] = next_sample;
for idx in 1..VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM {
let Ok(next_sample) = self.iter.next().unwrap_or(Ok(0)) else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};
sample[idx] = next_sample;
}
return Some(Ok(sample));
}
None
}
}

View File

@@ -0,0 +1,22 @@
use clap::Args;
use std::path::PathBuf;
use tool_helper::Error;
#[derive(Args)]
pub struct Arguments {
frequency: Option<u32>
}
pub fn convert(args: Arguments, input: PathBuf, output: PathBuf) -> Result<(), Error> {
let mut cmd_args = vec!["-t", "vag"];
let frequency_str;
if let Some(frequency) = args.frequency {
frequency_str = frequency.to_string().to_owned();
cmd_args.push("-f");
cmd_args.push(frequency_str.as_ref());
}
super::run_psxavenc(input, output, cmd_args)
}

View File

@@ -0,0 +1,55 @@
use clap::{Args, ValueEnum};
use std::{path::PathBuf, str};
use tool_helper::Error;
#[derive(Args)]
pub struct Arguments {
#[clap(value_enum, value_parser, default_value_t=Quality::High)]
quality: Quality,
#[clap(value_enum, value_parser, default_value_t=Sample::Stereo)]
sample: Sample
}
#[derive(Copy, Clone, ValueEnum)]
pub enum Quality {
Low,
High
}
impl Quality {
fn get_frequency(&self) -> u64 {
match self {
Self::Low => 18900,
Self::High => 37800,
}
}
}
#[derive(Copy, Clone, ValueEnum)]
pub enum Sample {
Mono,
Stereo
}
impl Sample {
fn get_channel(&self) -> u64 {
match self {
Self::Mono => 1,
Self::Stereo => 2,
}
}
}
pub fn convert(args: Arguments, input: PathBuf, output: PathBuf) -> Result<(), Error> {
let quality = args.quality;
let sample = args.sample;
super::run_psxavenc(input, output, [
"-t", "xacd",
"-f", quality.get_frequency().to_string().as_ref(),
"-b", "4",
"-c", sample.get_channel().to_string().as_ref(),
"-F", "0",
"-C", "0",
])
}

View File

@@ -0,0 +1 @@
pub mod reduced_tim;

View File

@@ -0,0 +1,141 @@
use tool_helper::Error;
use super::types::{Color, PSXImageConverter};
pub enum OutputType {
FourBit,
EightBit,
}
enum IndexPerByte {
OneIndex,
TwoIndices,
}
pub struct IndexedImage {
pub palette: Vec<Color>,
raw_data: std::vec::IntoIter<u8>,
index_byte: IndexPerByte,
next_func: fn(&mut Self) -> Option<Color>,
width: u16,
height: u16,
}
impl IndexedImage {
pub fn new<R: std::io::Read>(mut reader: png::Reader<R>, output_type: OutputType) -> Result<IndexedImage, Error> {
let action_name = "Creating IndexedImage";
let mut buffer = vec![0; reader.output_buffer_size()];
match reader.next_frame(&mut buffer) {
Ok(info) => {
let width = info.width as u16;
let height = info.height as u16;
let index_byte = {
match info.bit_depth {
png::BitDepth::Four => IndexPerByte::TwoIndices,
png::BitDepth::Eight => IndexPerByte::OneIndex,
_ => {
return Err(Error::from_text(format!("{}: Only 4 and 8bit color depth are supported", action_name)));
}
}
};
if info.color_type != png::ColorType::Indexed {
return Err(Error::from_text(format!("{}: PNG file must be indexed", action_name)));
}
else {
Ok(IndexedImage{palette: Self::make_palette(reader.info()), raw_data: buffer.into_iter(), index_byte, next_func: {
match output_type {
OutputType::FourBit => Self::next_four_bit,
OutputType::EightBit => Self::next_eight_bit,
}
}, width, height})
}
}
Err(err) => {
Err(Error::from_text(format!("{}: {}", action_name, err.to_string())))
}
}
}
fn make_palette(png_info: &png::Info) -> Vec<Color> {
fn make_color(iter: &mut dyn std::iter::Iterator<Item = &u8>) -> Option<Color> {
let r = iter.next()?;
let g = iter.next()?;
let b = iter.next()?;
Some(Color::non_transparent(*r, *g, *b))
}
let mut new_palette = Vec::new();
if let Some(palette) = &png_info.palette {
let mut iter = palette.iter();
while let Some(color) = make_color(&mut iter) {
new_palette.push(color);
}
}
new_palette
}
fn split_into_two_indices(index: u8) -> (u8, u8) {
(index >> 4, index & 0xF)
}
fn next_four_bit(&mut self) -> Option<Color> {
let (pixel0, pixel1, pixel2, pixel3) = {
match self.index_byte {
IndexPerByte::OneIndex => {
(self.raw_data.next()?, self.raw_data.next()?, self.raw_data.next()?, self.raw_data.next()?)
}
IndexPerByte::TwoIndices => {
let (pixel0, pixel1) = Self::split_into_two_indices(self.raw_data.next()?);
let (pixel2, pixel3) = Self::split_into_two_indices(self.raw_data.next()?);
(pixel0, pixel1, pixel2, pixel3)
},
}
};
Some(Color::new_4bit_entry(pixel0, pixel1, pixel2, pixel3))
}
fn next_eight_bit(&mut self) -> Option<Color> {
let (pixel0, pixel1) = {
match self.index_byte {
IndexPerByte::OneIndex => {
(self.raw_data.next()?, self.raw_data.next()?)
},
IndexPerByte::TwoIndices => {
Self::split_into_two_indices(self.raw_data.next()?)
}
}
};
Some(Color::new_8bit_entry(pixel0, pixel1))
}
}
impl PSXImageConverter for IndexedImage {
fn width(&self) -> u16 {
self.width
}
fn height(&self) -> u16 {
self.height
}
fn get_palette(&self) -> Option<&Vec<Color>> {
Some(&self.palette)
}
}
impl std::iter::Iterator for IndexedImage {
type Item = Color;
fn next(&mut self) -> Option<Self::Item> {
(self.next_func)(self)
}
}

View File

@@ -0,0 +1,92 @@
use super::types::{Color, PSXImageConverter};
pub struct RgbImage {
raw_data: std::vec::IntoIter<u8>,
width: u16,
height: u16,
}
impl RgbImage {
pub fn new(image: image::RgbImage) -> RgbImage {
let width = image.width() as u16;
let height = image.height() as u16;
let raw_data = image.into_raw().into_iter();
RgbImage{raw_data, width, height}
}
}
impl PSXImageConverter for RgbImage {
fn width(&self) -> u16 {
self.width
}
fn height(&self) -> u16 {
self.height
}
fn get_palette(&self) -> Option<&Vec<Color>> {
None
}
}
impl std::iter::Iterator for RgbImage {
type Item = Color;
fn next(&mut self) -> Option<Self::Item> {
let r = self.raw_data.next()?;
let g = self.raw_data.next()?;
let b = self.raw_data.next()?;
Some(Color::non_transparent(r, g, b))
}
}
pub struct RgbaImage {
raw_data: std::vec::IntoIter<u8>,
width: u16,
height: u16,
}
impl RgbaImage {
pub fn new(image: image::RgbaImage) -> RgbaImage {
let width = image.width() as u16;
let height = image.height() as u16;
let raw_data = image.into_raw().into_iter();
RgbaImage{raw_data, width, height}
}
}
impl PSXImageConverter for RgbaImage {
fn width(&self) -> u16 {
self.width
}
fn height(&self) -> u16 {
self.height
}
fn get_palette(&self) -> Option<&Vec<Color>> {
None
}
}
impl std::iter::Iterator for RgbaImage {
type Item = Color;
fn next(&mut self) -> Option<Self::Item> {
let r = self.raw_data.next()?;
let g = self.raw_data.next()?;
let b = self.raw_data.next()?;
let a = self.raw_data.next()?;
Some(
match a {
0x0 => Color::transparent(),
0xFF => Color::non_transparent(r, g, b),
_ => Color::semi_transparent(r, g, b),
}
)
}
}

View File

@@ -0,0 +1,180 @@
use clap::{Args, ValueEnum};
use image::{DynamicImage, io::Reader as ImageReader};
use color_clut::IndexedImage;
use color_full16::{RgbaImage, RgbImage};
use std::io::{Cursor, Write};
use tool_helper::{Error, Input};
use types::{Header, Color as PSXColor, PSXImageConverter};
mod types;
mod color_clut;
mod color_full16;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum ColorType{
Clut4,
Clut8,
Full16,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum ClutAlignment {
None,
Linear,
Block
}
#[derive(Args)]
pub struct Arguments {
#[clap(value_enum, value_parser)]
color_depth: ColorType,
#[clap(value_enum, value_parser, default_value_t=ClutAlignment::None)]
clut_align: ClutAlignment,
#[clap(long="semi-trans", default_value_t=false)]
semi_transparent: bool,
#[clap(long="color-trans", default_value_t=false)]
transparent_palette: bool
}
fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> IndexedImage {
if semi_transparent {
for color in image.palette.iter_mut() {
*color = PSXColor::semi_transparent(color.get_red(), color.get_green(), color.get_blue());
}
}
if transparent_palette {
if clut_align == ClutAlignment::Block {
for color in image.palette.iter_mut().step_by(16) {
*color = PSXColor::transparent();
}
}
else {
if let Some(first_color) = image.palette.get_mut(0) {
*first_color = PSXColor::transparent();
}
}
}
image
}
fn encode<T: PSXImageConverter>(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)}));
}
let width = image.width();
let height = image.height();
match color_depth {
ColorType::Clut4 => {
if width & 3 == 0 {
Ok((width/4, height))
}
else {
return_error(4, 4, width, height)
}
},
ColorType::Clut8 => {
if width & 1 == 0 {
Ok((width/2, height))
}
else {
return_error(8, 2, width, height)
}
},
ColorType::Full16 => {
Ok((width, height))
}
}
}?;
let palette = image.get_palette();
let (pal_width, pal_height) = {
if let Some(palette) = &palette {
let pal_length_adjusted = {
let pal_length = palette.len();
if pal_length <= 16 {
16u16
}
else {
256u16
}
};
match clut_align {
ClutAlignment::None |
ClutAlignment::Linear => (pal_length_adjusted, 1u16),
ClutAlignment::Block => (16u16, pal_length_adjusted/16u16),
}
}
else {
(0u16, 0u16)
}
};
let header = Header::encode(width, height, pal_width, pal_height).ok_or(Error::from_callback(|| {format!("Image size (width: {}, height: {}) needs to be even", width, height)}))?;
tool_helper::raw::write_raw(output, &header)?;
if let Some(palette) = palette {
let mut color_count = pal_width*pal_height;
for color in palette {
tool_helper::raw::write_raw(output, color)?;
color_count -= 1;
}
while color_count > 0 {
tool_helper::raw::write_raw(output, &PSXColor::black())?;
color_count -= 1;
}
}
for color in image {
tool_helper::raw::write_raw(output, &color)?;
}
Ok(())
}
fn convert_full16(input: Input, output: &mut dyn Write) -> Result<(), Error> {
match ImageReader::new(Cursor::new(tool_helper::input_to_vec(input)?)).with_guessed_format()?.decode() {
Ok(image) => {
match image {
DynamicImage::ImageRgb8(image) => encode(RgbImage::new(image), ColorType::Full16, ClutAlignment::None, output),
DynamicImage::ImageRgba8(image) => encode(RgbaImage::new(image), ColorType::Full16, ClutAlignment::None, output),
_ => Err(Error::from_str("Only RGB and RGBA images are supported for 16bit encoding"))
}
},
Err(error) => Err(Error::from_error(error))
}
}
fn convert_palette_based(input: Input, output: &mut dyn Write, color_type: ColorType, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> Result<(), Error> {
match png::Decoder::new(input).read_info() {
Ok(reader) => {
let output_type = {
match color_type {
ColorType::Clut4 => color_clut::OutputType::FourBit,
ColorType::Clut8 => color_clut::OutputType::EightBit,
_ => return Err(Error::from_str("ColorType not supported"))
}
};
encode(modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), color_type, clut_align, output)
},
Err(error) => Err(Error::from_error(error))
}
}
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
match args.color_depth {
ColorType::Full16 => convert_full16(input, output),
_ => convert_palette_based(input, output, args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette),
}
}

View File

@@ -0,0 +1,162 @@
use tool_helper::{*, bits::BitRange, raw::RawConversion};
#[repr(packed(1))]
#[allow(dead_code)]
pub struct Color {
value: u16
}
#[repr(packed(1))]
#[allow(dead_code)]
pub struct Header {
value: u32
}
macro_rules! set_member_value {
($dst:expr, $color:ident, $shift:expr, $bits:ident) => {
paste::item! {
bits::[< set_value_ $bits >]($dst, ($color >> $shift) as $bits, &Self::[< $color:upper _BIT_RANGE >])
}
};
}
macro_rules! make_member_getter_setter {
($color:ident, $shift:expr, $bits:ident) => {
paste::item! {
pub fn [< set_ $color >](&mut self, $color: u8) {
self.value = set_member_value!(self.value, $color, $shift, $bits);
}
pub fn [< get_ $color >](&self) -> u8 {
(bits::[< get_value_ $bits >](self.value, &Self::[< $color:upper _BIT_RANGE >]) as u8) << $shift
}
}
};
}
#[allow(dead_code)]
impl Color {
const COLOR_SHIFT: u16 = 3;
const STP_BIT_RANGE: BitRange = BitRange::from_to(15, 15);
const RED_BIT_RANGE: BitRange = BitRange::from_to(0, 4);
const GREEN_BIT_RANGE: BitRange = BitRange::from_to(5, 9);
const BLUE_BIT_RANGE: BitRange = BitRange::from_to(10, 14);
const PIXEL3_4_BIT_RANGE: BitRange = BitRange::from_to(12, 15);
const PIXEL2_4_BIT_RANGE: BitRange = BitRange::from_to(8, 11);
const PIXEL1_4_BIT_RANGE: BitRange = BitRange::from_to(4, 7);
const PIXEL0_4_BIT_RANGE: BitRange = BitRange::from_to(0, 3);
const PIXEL1_8_BIT_RANGE: BitRange = BitRange::from_to(8, 15);
const PIXEL0_8_BIT_RANGE: BitRange = BitRange::from_to(0, 7);
pub const fn transparent() -> Color {
Color::new(0, 0, 0, 0)
}
pub const fn non_transparent(red: u8, green: u8, blue: u8) -> Color {
if red == 0 && green == 0 && blue == 0 {
Color::black()
}
else {
Color::new(0, red, green, blue)
}
}
pub const fn semi_transparent(mut red: u8, mut green: u8, mut blue: u8) -> Color {
if red == 0 {
red = 1;
}
if green == 0 {
green = 1;
}
if blue == 0 {
blue = 1;
}
Color::new(1, red, green, blue)
}
pub const fn black() -> Color {
Color::new(1, 0, 0, 0)
}
pub const fn new_4bit_entry(pixel0_4: u8, pixel1_4: u8, pixel2_4: u8, pixel3_4: u8) -> Color {
let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0,
pixel0_4, 0, u16),
pixel1_4, 0, u16),
pixel2_4, 0, u16),
pixel3_4, 0, u16);
Color{value}
}
pub const fn new_8bit_entry(pixel0_8: u8, pixel1_8: u8) -> Color {
let value = set_member_value!(set_member_value!(0,
pixel0_8, 0, u16),
pixel1_8, 0, u16);
Color{value}
}
const fn new(stp: u8, red: u8, green: u8, blue: u8) -> Color {
let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0,
stp, 0, u16),
red, Self::COLOR_SHIFT, u16),
green, Self::COLOR_SHIFT, u16),
blue, Self::COLOR_SHIFT, u16);
Color{value}
}
make_member_getter_setter!(stp, 0, u16);
make_member_getter_setter!(red, Self::COLOR_SHIFT, u16);
make_member_getter_setter!(green, Self::COLOR_SHIFT, u16);
make_member_getter_setter!(blue, Self::COLOR_SHIFT, u16);
}
impl RawConversion<2> for Color {
fn convert_to_raw(&self) -> [u8; 2] {
self.value.to_le_bytes()
}
}
#[allow(dead_code)]
impl Header {
const TEX_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(0, 8);
const TEX_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(9, 16);
const CLUT_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(17, 22);
const CLUT_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(23, 31);
pub fn encode(tex_width: u16, tex_height: u16, clut_width: u16, clut_height: u16) -> Option<Header> {
if tex_width & 1 == 1 || tex_height & 1 == 1 {
None
}
else {
let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0,
tex_width, 1, u32),
tex_height, 1, u32),
clut_width, 4, u32),
clut_height, 0, u32);
Some(Header{value})
}
}
}
impl RawConversion<4> for Header {
fn convert_to_raw(&self) -> [u8; 4] {
self.value.to_le_bytes()
}
}
pub trait PSXImageConverter: std::iter::Iterator<Item = Color> {
fn width(&self) -> u16;
fn height(&self) -> u16;
fn get_palette(&self) -> Option<&Vec<Color>>;
}

View File

@@ -0,0 +1,3 @@
pub mod audio;
pub mod images;
pub mod nothing;

View File

@@ -0,0 +1,135 @@
use clap::{Parser, Subcommand};
use psxfileconv::{audio::{self, *}, images::*, nothing};
use std::path::PathBuf;
use tool_helper::{exit_with_error, print_warning, Error};
#[derive(Parser)]
#[clap(about = "Converts files to various JabyEngine related file formats", long_about = None)]
struct CommandLine {
#[clap(long="lz4", default_value_t=false)]
compress_lz4: bool,
#[clap(short='o')]
output_file: Option<PathBuf>,
#[clap(value_parser)]
input_file: Option<PathBuf>,
#[clap(subcommand)]
sub_command: SubCommands,
}
#[derive(Subcommand)]
enum SubCommands {
// === Internal Commands ===
Nothing,
SimpleTIM(reduced_tim::Arguments),
MyVAG,
// === External Commands ===
VAG(vag::Arguments),
XA(xa::Arguments),
}
impl SubCommands {
pub fn is_external_command(&self) -> bool {
match self {
SubCommands::Nothing => false,
SubCommands::SimpleTIM(_) => false,
SubCommands::MyVAG => false,
SubCommands::VAG(_) => true,
SubCommands::XA(_) => true
}
}
}
fn run_internal_conversion(cmd: CommandLine) -> Result<(), Error> {
let mut input = tool_helper::open_input(&cmd.input_file)?;
let mut buffer = Vec::<u8>::new();
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
}
else {
&mut output_file as &mut dyn std::io::Write
}
};
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),
SubCommands::MyVAG => audio::my_vag::convert(input, dst_buffer),
_ => Err(Error::from_str("External functions can not be called for internal conversion"))
}
};
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
if cmd.compress_lz4 {
let buffer = tool_helper::compress::psx_default::lz4(&buffer)?;
output_file.write(&buffer)?;
}
Ok(())
}
fn run_external_conversion(cmd: CommandLine) -> Result<(), Error> {
let input_file = cmd.input_file.ok_or(Error::from_str("Input has to be a file"))?;
let output_file = cmd.output_file.ok_or(Error::from_str("Output has to be a file"))?;
let is_xa = matches!(cmd.sub_command, SubCommands::XA(_));
match cmd.sub_command {
SubCommands::VAG(args) => vag::convert(args, input_file, output_file.clone()),
SubCommands::XA(args) => xa::convert(args, input_file, output_file.clone()),
_ => Err(Error::from_str("Internal functions can not be called for external conversion"))
}?;
if cmd.compress_lz4 {
if is_xa {
print_warning(format!("File {} is of type XA and can not be compressed", output_file.to_string_lossy()));
}
else {
let compressed_file = tool_helper::compress::psx_default::lz4(&tool_helper::read_file(&output_file)?)?;
return tool_helper::write_file(&output_file, compressed_file);
}
}
Ok(())
}
fn run_main(cmd: CommandLine) -> Result<(), Error> {
if cmd.sub_command.is_external_command() {
run_external_conversion(cmd)
}
else {
run_internal_conversion(cmd)
}
}
fn main() {
match CommandLine::try_parse() {
Ok(cmd) => {
if let Err(error) = run_main(cmd) {
exit_with_error(error);
}
},
Err(error) => println!("{}", error)
}
}

View File

@@ -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(())
}