use std::path::PathBuf;
use anyhow::{Context, Result, ensure};
use clap::{ArgGroup, Parser, Subcommand, ValueEnum};
use glob::Pattern;
use poe_data_tools::{
Patch,
fs::{FS, cdn::cdn_base_url},
};
use poe_data_tools_cli::{
VERBOSE,
commands::{
cat::cat_file, dump_art::extract_art, dump_tables_csv::dump_tables, dump_tables_json,
dump_trees::dump_trees, extract::extract_files, list::list_files, translate::translate,
},
};
#[derive(Debug, Clone, ValueEnum)]
enum DumpDatsMode {
Csv,
Json,
}
#[derive(Debug, Subcommand)]
enum Command {
List {
#[clap(default_value = "**")]
#[arg(num_args = 1..)]
globs: Vec<Pattern>,
},
Extract {
output_folder: PathBuf,
#[clap(default_value = "**")]
#[arg(num_args = 1..)]
globs: Vec<Pattern>,
},
Cat {
path: String,
},
DumpTables {
output_folder: PathBuf,
#[arg(long, value_enum, default_value_t = DumpDatsMode::Csv)]
mode: DumpDatsMode,
#[arg(long)]
schema: Option<PathBuf>,
#[clap(default_value = "**/*.datc64")]
#[arg(num_args = 1..)]
globs: Vec<Pattern>,
},
DumpArt {
output_folder: PathBuf,
#[clap(default_value = "**/*.dds")]
#[arg(num_args = 1..)]
globs: Vec<Pattern>,
},
DumpTrees {
output_folder: PathBuf,
#[clap(default_value = "**/*.psg")]
#[arg(num_args = 1..)]
globs: Vec<Pattern>,
},
Translate {
output_folder: PathBuf,
#[clap(default_value = "**/*")]
#[arg(num_args = 1..)]
globs: Vec<Pattern>,
},
}
#[derive(Parser, Debug)]
#[command(
name = "poe_data_tools",
group(
ArgGroup::new("source")
.args(&["steam", "cache_dir", "ggpk"])
.required(false) // At least one is not required, but they are mutually exclusive
.multiple(false) // Only one can be used at a time
)
)]
#[clap(version)]
struct Cli {
#[arg(short, long, required = true)]
patch: Patch,
#[arg(long)]
steam: Option<PathBuf>,
#[arg(long)]
ggpk: Option<PathBuf>,
#[arg(long)]
cache_dir: Option<PathBuf>,
#[arg(short, long, default_value_t = false)]
verbose: bool,
#[command(subcommand)]
command: Command,
}
#[derive(Debug)]
enum Source {
Cdn { cache_dir: PathBuf },
Steam { steam_folder: PathBuf },
Ggpk { ggpk_path: PathBuf },
}
#[derive(Debug)]
struct Args {
patch: Patch,
source: Source,
command: Command,
cache_dir: PathBuf,
verbose: bool,
}
fn parse_args() -> Result<Args> {
let cli = Cli::parse();
let cache_dir = cli
.cache_dir
.unwrap_or_else(|| dirs::cache_dir().unwrap().join("poe_data_tools"));
let source = if let Some(steam_folder) = cli.steam {
ensure!(steam_folder.exists(), "Steam folder doesn't exist");
Source::Steam { steam_folder }
} else if let Some(ggpk_path) = cli.ggpk {
Source::Ggpk { ggpk_path }
} else {
Source::Cdn {
cache_dir: cache_dir.clone(),
}
};
if matches!(source, Source::Steam { .. } | Source::Ggpk { .. }) {
ensure!(
!matches!(cli.patch, Patch::Specific { .. }),
"When using steam or ggpk, specific patch versions are not supported."
);
}
Ok(Args {
patch: cli.patch,
source,
command: cli.command,
cache_dir,
verbose: cli.verbose,
})
}
fn init_logger() {
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or("poe_data_tools=info"),
)
.init();
}
fn main() -> Result<()> {
init_logger();
let args = parse_args()?;
VERBOSE.set(args.verbose).unwrap();
let mut fs = match args.source {
Source::Cdn { cache_dir } => {
let version_string = match &args.patch {
Patch::One => "1",
Patch::Two => "2",
Patch::Specific(v) => v,
};
FS::from_cdn(&cdn_base_url(&cache_dir, version_string)?, &cache_dir)
}
Source::Steam { steam_folder } => FS::from_steam(steam_folder),
Source::Ggpk { ggpk_path } => FS::from_ggpk(&ggpk_path),
}
.context("Failed to initialise file system")?;
match args.command {
Command::List { globs } => list_files(&fs, &globs).context("List command failed")?,
Command::Cat { path } => cat_file(&mut fs, &path).context("Cat command failed")?,
Command::Extract {
globs,
output_folder,
} => extract_files(&mut fs, &globs, &output_folder).context("Extract command filed")?,
Command::DumpTables {
output_folder,
globs,
mode,
schema,
} => match mode {
DumpDatsMode::Csv => dump_tables(
&mut fs,
&globs,
&args.cache_dir,
&output_folder,
&args.patch,
schema.as_ref(),
)
.context("Dump Tables command failed")?,
DumpDatsMode::Json => dump_tables_json::dump_tables(
&mut fs,
&globs,
&args.cache_dir,
&output_folder,
&args.patch,
schema.as_ref(),
)
.context("Dump Tables command failed")?,
},
Command::DumpArt {
output_folder,
globs,
} => extract_art(&mut fs, &globs, &output_folder).context("Dump Art command failed")?,
Command::DumpTrees {
output_folder,
globs,
} => {
dump_trees(
&mut fs,
&globs,
&output_folder,
&args.patch,
&args.cache_dir,
)
.context("Dump Tree command failed")?;
}
Command::Translate {
output_folder,
globs,
} => {
translate(
&mut fs,
&globs,
&args.cache_dir,
&output_folder,
&args.patch,
)
.context("Translate command failed")?;
}
}
Ok(())
}