diff --git a/Cargo.lock b/Cargo.lock index 8c87cf6..435a51f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,17 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -333,6 +344,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1063,6 +1083,7 @@ dependencies = [ name = "wt_ext_cli" version = "0.5.7" dependencies = [ + "atty", "clap", "color-eyre", "const_format", diff --git a/Cargo.toml b/Cargo.toml index da8b15d..ca19856 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ serde = { version = "^1.0", features = ["serde_derive"] } const_format = { version = "0.2.31", default-features = false, features = ["fmt"] } zip = { version = "2.1.6", features = ["deflate"], default-features = false} wt_version = { git = "https://github.com/Warthunder-Open-Source-Foundation/wt_version.git" } +atty = "0.2.14" # Config for 'cargo dist' [workspace.metadata.dist] diff --git a/src/cli/unpack_raw_blk.rs b/src/cli/unpack_raw_blk.rs index 3077170..3d903b0 100644 --- a/src/cli/unpack_raw_blk.rs +++ b/src/cli/unpack_raw_blk.rs @@ -1,4 +1,4 @@ -use clap::{Arg, Command, ValueHint}; +use clap::{Arg, ArgAction::SetTrue, Command, ValueHint}; pub fn unpack_raw_blk() -> Command { Command::new("unpack_raw_blk") @@ -10,7 +10,8 @@ pub fn unpack_raw_blk() -> Command { .long("input_dir") .help("Folder containing blk files, sub-folders will be recursively searched") .required(true) - .value_hint(ValueHint::FilePath), + .value_hint(ValueHint::FilePath) + .conflicts_with("stdin"), ) .arg( // Not providing this argument means the input folder name will be used, with a `_u` suffix @@ -18,7 +19,20 @@ pub fn unpack_raw_blk() -> Command { .short('o') .long("output_dir") .help("Target folder that will be created to contain new files") - .value_hint(ValueHint::FilePath), + .value_hint(ValueHint::FilePath) + .conflicts_with("stdout"), + ) + .arg( + Arg::new("stdout") + .long("stdout") + .help("writes to stdout instead of a file") + .action(SetTrue), + ) + .arg( + Arg::new("stdin") + .long("stdin") + .help("reads from stdin instead of a file") + .action(SetTrue), ) .arg( Arg::new("format") diff --git a/src/subcommands/unpack_raw_blk.rs b/src/subcommands/unpack_raw_blk.rs index 28e4a4b..ba3c6ab 100644 --- a/src/subcommands/unpack_raw_blk.rs +++ b/src/subcommands/unpack_raw_blk.rs @@ -1,86 +1,55 @@ use std::{ fs, fs::OpenOptions, - io::Write, + io, + io::{Read, Write}, path::{Path, PathBuf}, str::FromStr, }; +use atty::Stream; use clap::ArgMatches; use color_eyre::eyre::{bail, ContextCompat, Result}; use wt_blk::{blk, blk::file::FileType}; -use crate::error::CliError; - // This is the entry-point pub fn unpack_raw_blk(args: &ArgMatches) -> Result<()> { - let input = args - .get_one::("Input directory") - .ok_or(CliError::RequiredFlagMissing)?; - let format = args .get_one::("format") .context("Invalid format specified or missing")?; - let input = Path::new(input); - if input.is_dir() { - bail!("Directories as input are not implemented yet"); - } - - let mut read = fs::read(input)?; + let mut input_path = None; + let mut read = get_input(&args, &mut input_path)?; let zstd_dict = None; let nm = None; - + let bye_bye = |format| { + bail!("{format} is not implemented yet. If you need it, poke me with an issue at: https://github.com/Warthunder-Open-Source-Foundation/wt_ext_cli/issues") + }; match FileType::from_byte(read[0])? { FileType::BBF => {}, FileType::FAT => {}, FileType::FAT_ZSTD => {}, FileType::SLIM => { - bail!("External name-map is not implemented yet"); + bye_bye("External name-map")?; }, FileType::SLIM_ZSTD => { - bail!("External name-map is not implemented yet"); + bye_bye("External name-map")?; }, FileType::SLIM_ZST_DICT => { - bail!("ZSTD dictionary is not implemented yet"); + bye_bye("ZSTD dictionary")?; }, } let mut parsed = blk::unpack_blk(&mut read, zstd_dict, nm)?; - let mut output_folder = match () { - _ if let Some(path) = args.get_one::("Output directory") => { - let buf = PathBuf::from_str(path)?; - if buf.is_absolute() { - buf - } else { - let exec_dir = std::env::current_dir()?; - exec_dir.join(buf) - } - }, - _ => input.to_owned(), - }; - - // output_folder.push(input.file_name().unwrap().to_string_lossy().to_string()); match format.as_str() { "Json" => { - output_folder.set_extension("json"); - let mut file = OpenOptions::new() - .write(true) - .create(true) - .open(output_folder)?; - parsed.merge_fields(); - file.write_all(&parsed.as_serde_json()?)?; + write_output(args, parsed.as_serde_json()?, input_path, format)?; }, "BlkText" => { - output_folder.set_extension("blkx"); - let mut file = OpenOptions::new() - .write(true) - .create(true) - .open(output_folder)?; - file.write_all(parsed.as_blk_text()?.as_bytes())?; + write_output(args, parsed.as_blk_text()?.into_bytes(), input_path, format)?; }, _ => { panic!("Unrecognized format: {format}") @@ -89,3 +58,80 @@ pub fn unpack_raw_blk(args: &ArgMatches) -> Result<()> { Ok(()) } + +pub fn get_input(args: &ArgMatches, input_path: &mut Option) -> Result> { + if let Some(p) = args.get_one::("Input directory") { + let input = Path::new(p); + if input.is_dir() { + bail!("Directories as input are not implemented yet"); + } + *input_path = Some(input.to_path_buf()); + return Ok(fs::read(p)?); + } + + if Some(&true) == args.get_one::("stdin") { + if atty::is(Stream::Stdin) { + bail!("Stdin is not connected!"); + } + + let mut buf = vec![]; + io::stdin().read_to_end(&mut buf)?; + return Ok(buf); + } + + bail!("No input passed") +} + +pub fn write_output( + args: &ArgMatches, + buf: Vec, + input: Option, + format: &str, +) -> Result<()> { + if args.contains_id("Output directory") { + let mut output_folder = match () { + _ if let Some(path) = args.get_one::("Output directory") => { + let buf = PathBuf::from_str(path)?; + if buf.is_absolute() { + buf + } else { + let exec_dir = std::env::current_dir()?; + exec_dir.join(buf) + } + }, + _ => input + .context("No output path was specified, and the input was not a file")? + .to_owned(), + }; + + match format { + "Json" => { + output_folder.set_extension("json"); + let mut file = OpenOptions::new() + .write(true) + .create(true) + .open(output_folder)?; + file.write_all(&buf)?; + }, + "BlkText" => { + output_folder.set_extension("blkx"); + let mut file = OpenOptions::new() + .write(true) + .create(true) + .open(output_folder)?; + file.write_all(&buf)?; + }, + _ => { + panic!("Unrecognized format: {format}") + }, + } + return Ok(()); + } + + if Some(&true) == args.get_one::("stdout") { + io::stdout().write_all(&buf)?; + return Ok(()); + } + + bail!("No output location passed. Pass an explicit output such as a file or stdout") +} diff --git a/src/subcommands/unpack_vromf.rs b/src/subcommands/unpack_vromf.rs index aca167f..e4fd38c 100644 --- a/src/subcommands/unpack_vromf.rs +++ b/src/subcommands/unpack_vromf.rs @@ -20,7 +20,7 @@ use color_eyre::{ use tracing::info; use wt_blk::{ blk::util::maybe_blk, - vromf::{BlkOutputFormat, VromfUnpacker, File as BlkFile}, + vromf::{BlkOutputFormat, File as BlkFile, VromfUnpacker}, }; use zip::{write::SimpleFileOptions, CompressionMethod}; diff --git a/src/subcommands/vromf_version.rs b/src/subcommands/vromf_version.rs index 3da280a..a59ab97 100644 --- a/src/subcommands/vromf_version.rs +++ b/src/subcommands/vromf_version.rs @@ -13,10 +13,8 @@ pub fn vromf_version(args: &ArgMatches) -> color_eyre::Result<()> { let parsed_input_dir = PathBuf::from_str(&input_dir).or(Err(CliError::InvalidPath))?; let versions: Vec<_> = if parsed_input_dir.is_file() { - let unpacker = VromfUnpacker::from_file( - &File::new(parsed_input_dir.clone()).unwrap(), - true, - )?; + let unpacker = + VromfUnpacker::from_file(&File::new(parsed_input_dir.clone()).unwrap(), true)?; vec![( parsed_input_dir .file_name()