use anyhow::{anyhow, Result};
use clap::{builder::PossibleValuesParser, Parser, Subcommand};
use csv::ReaderBuilder;
use std::path::PathBuf;
use rpfm_lib::games::pfh_file_type::PFHFileType;
use rpfm_lib::games::supported_games::SupportedGames;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub(crate) struct Cli {
#[arg(short, long)]
pub verbose: bool,
#[arg(short, long, value_name = "GAME", value_parser = PossibleValuesParser::new(game_keys()))]
pub game: String,
#[clap(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
AnimPack {
#[clap(subcommand)]
commands: CommandsAnimPack,
},
Dependencies {
#[clap(subcommand)]
commands: CommandsDependencies,
},
Pack {
#[clap(subcommand)]
commands: CommandsPack,
},
Schemas {
#[clap(subcommand)]
commands: CommandsSchemas,
},
PortraitSettings {
#[clap(subcommand)]
commands: CommandsPortraitSettings,
},
}
#[derive(Subcommand)]
pub enum CommandsAnimPack {
List {
#[arg(short, long, required = true, value_name = "PATH")]
pack_path: PathBuf,
},
Create {
#[arg(short, long, required = true, value_name = "PATH")]
pack_path: PathBuf,
},
Add {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = false, num_args = 1.., value_parser = add_file_from_csv, value_name = "FILE_PATH,FOLDER_TO_ADD_TO")]
file_path: Vec<(PathBuf, String)>,
#[arg(short = 'F', long, required = false, num_args = 1.., value_parser = add_folder_from_csv, value_name = "FOLDER_PATH,FOLDER_TO_ADD_TO")]
folder_path: Vec<(PathBuf, String)>,
},
Delete {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = false, num_args = 1.., value_name = "FILE_PATH")]
file_path: Vec<String>,
#[arg(short = 'F', long, required = false, num_args = 1.., value_name = "FOLDER_PATH")]
folder_path: Vec<String>,
},
Extract {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = false, num_args = 1.., value_parser = extract_from_csv, value_name = "FILE_PATH_IN_PACK,FOLDER_TO_EXTRACT_TO")]
file_path: Vec<(String, PathBuf)>,
#[arg(short = 'F', long, required = false, num_args = 1.., value_parser = extract_from_csv, value_name = "FOLDER_PATH_IN_PACK,FOLDER_TO_EXTRACT_TO")]
folder_path: Vec<(String, PathBuf)>,
},
}
#[derive(Subcommand)]
pub enum CommandsDependencies {
Generate {
#[arg(short = 'P', long, required = true, value_name = "PAK2_PATH")]
pak_path: PathBuf,
#[arg(short, long, required = true, value_name = "GAME_PATH")]
game_path: PathBuf,
#[arg(short, long, required = true, value_name = "SCHEMA_PATH")]
schema_path: PathBuf,
#[arg(short, long, required = false, value_name = "ASSEMBLY_KIT_PATH")]
assembly_kit_path: Option<PathBuf>,
}
}
#[derive(Subcommand)]
pub enum CommandsPack {
SetFileType {
#[arg(short, long, required = true, value_name = "PATH")]
pack_path: PathBuf,
#[arg(short, long, required = true, num_args = 1, value_name = "PACK_TYPE", value_parser = parse_pfh_file_type)]
file_type: PFHFileType,
},
List {
#[arg(short, long, required = true, value_name = "PATH")]
pack_path: PathBuf,
},
Create {
#[arg(short, long, required = true, value_name = "PATH")]
pack_path: PathBuf,
},
Add {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = false, value_name = "SCHEMA_PATH")]
tsv_to_binary: Option<PathBuf>,
#[arg(short, long, required = false, num_args = 1.., value_parser = add_file_from_csv, value_name = "FILE_PATH;FOLDER_TO_ADD_TO")]
file_path: Vec<(PathBuf, String)>,
#[arg(short = 'F', long, required = false, num_args = 1.., value_parser = add_folder_from_csv, value_name = "FOLDER_PATH;FOLDER_TO_ADD_TO")]
folder_path: Vec<(PathBuf, String)>,
},
Delete {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = false, num_args = 1.., value_name = "FILE_PATH")]
file_path: Vec<String>,
#[arg(short = 'F', long, required = false, num_args = 1.., value_name = "FOLDER_PATH")]
folder_path: Vec<String>,
},
Extract {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = false, value_name = "SCHEMA_PATH")]
tables_as_tsv: Option<PathBuf>,
#[arg(short, long, required = false, num_args = 1.., value_parser = extract_from_csv, value_name = "FILE_PATH_IN_PACK;FOLDER_TO_EXTRACT_TO")]
file_path: Vec<(String, PathBuf)>,
#[arg(short = 'F', long, required = false, num_args = 1.., value_parser = extract_from_csv, value_name = "FOLDER_PATH_IN_PACK;FOLDER_TO_EXTRACT_TO")]
folder_path: Vec<(String, PathBuf)>,
},
Diagnose {
#[arg(short, long, required = true, value_name = "GAME_PATH")]
game_path: PathBuf,
#[arg(short = 'P', long, required = true, value_name = "PAK2_PATH")]
pak_path: PathBuf,
#[arg(short, long, required = true, value_name = "SCHEMA_PATH")]
schema_path: PathBuf,
#[arg(short, long, required = true, num_args = 1.., value_name = "PACK_PATH")]
pack_path: Vec<PathBuf>,
},
Merge {
#[arg(short = 'p', long, required = true, value_name = "SAVE_PACK_PATH")]
save_pack_path: PathBuf,
#[arg(short = 's', long, required = true, num_args = 1.., value_name = "SOURCE_PACK_PATHS")]
source_pack_paths: Vec<PathBuf>,
},
AddDependencyPack {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = true, value_name = "DEPENDENCY_NAME")]
dependency_pack: String,
},
RemoveDependencyPack {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
#[arg(short, long, required = true, value_name = "DEPENDENCY_NAME")]
dependency_pack: String,
},
RemoveAllDependencies {
#[arg(short, long, required = true, value_name = "PACK_PATH")]
pack_path: PathBuf,
},
}
#[derive(Subcommand)]
pub enum CommandsSchemas {
Update {
#[arg(short, long, required = true, value_name = "SCHEMA_PATH")]
schema_path: PathBuf,
},
ToJson {
#[arg(short, long, required = true, value_name = "SCHEMAS_PATH")]
schemas_path: PathBuf,
}
}
#[derive(Subcommand)]
pub enum CommandsPortraitSettings {
FromJson {
#[arg(short, long, required = true, value_name = "JSON_PATH")]
json_path: PathBuf,
#[arg(short, long, required = true, value_name = "BIN_PATH")]
bin_path: PathBuf,
},
ToJson {
#[arg(short, long, required = true, value_name = "BIN_PATH")]
bin_path: PathBuf,
#[arg(short, long, required = true, value_name = "JSON_PATH")]
json_path: PathBuf,
}
}
fn add_file_from_csv(src: &str) -> Result<(PathBuf, String)> {
let mut reader = ReaderBuilder::new()
.delimiter(b';')
.quoting(true)
.has_headers(false)
.flexible(true)
.from_reader(src.as_bytes());
if let Some(Ok(record)) = reader.records().next() {
if record.len() == 2 {
let source = PathBuf::from(&record[0]);
if !source.is_file() {
return Err(anyhow!("Path {} doesn't belong to a valid file.", &record[0]));
}
let dest = if record[1].ends_with('/') {
record[1].to_owned() + &source.file_name().unwrap().to_string_lossy()
} else {
record[1].to_owned()
};
return Ok((source, dest))
} else if record.len() == 1 {
let source = PathBuf::from(&record[0]);
if !source.is_file() {
return Err(anyhow!("Path {} doesn't belong to a valid file.", &record[0]));
}
let dest = String::new();
return Ok((source, dest))
} else {
return Err(anyhow!("Incorrect CSV input."));
}
}
Ok((PathBuf::new(), String::new()))
}
fn add_folder_from_csv(src: &str) -> Result<(PathBuf, String)> {
let mut reader = ReaderBuilder::new()
.delimiter(b';')
.quoting(true)
.has_headers(false)
.flexible(true)
.from_reader(src.as_bytes());
if let Some(Ok(record)) = reader.records().next() {
if record.len() == 2 {
let source = PathBuf::from(&record[0]);
if !source.is_dir() {
return Err(anyhow!("Path {} doesn't belong to a valid folder.", &record[0]));
}
let dest = record[1].to_owned();
return Ok((source, dest))
} else if record.len() == 1 {
let source = PathBuf::from(&record[0]);
if !source.is_dir() {
return Err(anyhow!("Path {} doesn't belong to a valid folder.", &record[0]));
}
let dest = String::new();
return Ok((source, dest))
} else {
return Err(anyhow!("Incorrect CSV input."));
}
}
Ok((PathBuf::new(), String::new()))
}
fn extract_from_csv(src: &str) -> Result<(String, PathBuf)> {
let mut reader = ReaderBuilder::new()
.delimiter(b';')
.quoting(true)
.has_headers(false)
.flexible(true)
.from_reader(src.as_bytes());
if let Some(Ok(record)) = reader.records().next() {
if record.len() == 2 {
let source = record[0].to_owned();
let dest = PathBuf::from(&record[1]);
return Ok((source, dest))
} else if record.len() == 1 {
let source = record[0].to_owned();
let dest = PathBuf::new();
return Ok((source, dest))
} else {
return Err(anyhow!("Incorrect CSV input."));
}
}
Ok((String::new(), PathBuf::new()))
}
fn game_keys() -> Vec<&'static str> {
let supported_games = SupportedGames::default();
supported_games.game_keys_sorted().to_vec()
}
fn parse_pfh_file_type(src: &str) -> Result<PFHFileType> {
PFHFileType::try_from(src).map_err(From::from)
}