use genie_drs::{DRSReader, DRSWriter, ReserveDirectoryStrategy};
use std::collections::HashSet;
use std::fs::{create_dir_all, File};
use std::io::{self, stdout, Write};
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt)]
struct Cli {
#[structopt(subcommand)]
command: Command,
}
#[derive(StructOpt)]
enum Command {
#[structopt(name = "list")]
List(List),
#[structopt(name = "get")]
Get(Get),
#[structopt(name = "extract")]
Extract(Extract),
#[structopt(name = "add")]
Add(Add),
}
#[derive(StructOpt)]
struct List {
#[structopt(parse(from_os_str))]
archive: PathBuf,
}
#[derive(StructOpt)]
struct Get {
#[structopt(parse(from_os_str))]
archive: PathBuf,
#[structopt(name = "resource")]
resource_id: u32,
}
#[derive(StructOpt)]
struct Extract {
#[structopt(parse(from_os_str))]
archive: PathBuf,
#[structopt(long, short = "t")]
table: Option<String>,
#[structopt(long, short = "o", parse(from_os_str))]
out: PathBuf,
}
#[derive(Debug, StructOpt)]
struct Add {
#[structopt(parse(from_os_str))]
archive: PathBuf,
#[structopt(long, short = "o", parse(from_os_str))]
output: Option<PathBuf>,
#[structopt(long, short = "t", number_of_values = 1)]
table: Vec<String>,
#[structopt(long, short = "i", number_of_values = 1)]
id: Vec<u32>,
#[structopt(parse(from_os_str), default_value = "-")]
file: Vec<PathBuf>,
}
fn list(args: List) -> anyhow::Result<()> {
let mut file = File::open(args.archive)?;
let drs = DRSReader::new(&mut file)?;
for table in drs.tables() {
for resource in table.resources() {
println!("{}.{}", resource.id, table.resource_ext());
}
}
Ok(())
}
fn get(args: Get) -> anyhow::Result<()> {
let mut file = File::open(args.archive)?;
let drs = DRSReader::new(&mut file)?;
for table in drs.tables() {
match table.get_resource(args.resource_id) {
Some(ref resource) => {
let buf = drs.read_resource(&mut file, table.resource_type, resource.id)?;
stdout().write_all(&buf)?;
return Ok(());
}
None => (),
}
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"Archive does not contain that resource",
)
.into())
}
fn extract(args: Extract) -> anyhow::Result<()> {
let mut file = File::open(args.archive)?;
let drs = DRSReader::new(&mut file)?;
create_dir_all(&args.out)?;
for table in drs.tables() {
let table_ext = table.resource_ext();
if let Some(ref filter_ext) = args.table {
if &table_ext != filter_ext {
continue;
}
}
for resource in table.resources() {
let buf = drs.read_resource(&mut file, table.resource_type, resource.id)?;
let mut outfile =
File::create(args.out.join(format!("{}.{}", resource.id, table_ext)))?;
outfile.write_all(&buf)?;
}
}
Ok(())
}
fn add(args: Add) -> anyhow::Result<()> {
assert_eq!(
args.file.len(),
args.table.len(),
"Must set a --table for every file"
);
assert_eq!(
args.file.len(),
args.id.len(),
"Must set an --id for every file"
);
let mut input = File::open(&args.archive)?;
let drs_read = DRSReader::new(&mut input)?;
let (tables, files) = drs_read.tables().fold((0, 0), |(tables, files), table| {
(tables + 1, files + table.len() as u32)
});
let new_tables = args
.table
.iter()
.fold(HashSet::new(), |mut uniq, table| {
uniq.insert(table);
uniq
})
.len() as u32;
let new_files = args.id.len() as u32;
use std::time::SystemTime;
let suffix = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(d) => format!("{}", d.as_secs()),
_ => "temp".to_string(),
};
let mut temp_out = args.output.as_ref().unwrap_or(&args.archive).clone();
temp_out.set_file_name(format!(
"{}.{}",
temp_out.file_name().unwrap().to_str().unwrap(),
suffix
));
let output = File::create(&temp_out)?;
let mut drs_write = DRSWriter::new(
output,
ReserveDirectoryStrategy::new(tables + new_tables, files + new_files),
)?;
for t in drs_read.tables() {
for r in t.resources() {
let b = drs_read.get_resource_reader(&mut input, t.resource_type, r.id)?;
drs_write.add(t.resource_type, r.id, b)?;
}
}
for (i, path) in args.file.iter().enumerate() {
let mut res_type = [0x20; 4];
let slice = args.table[i].as_bytes();
(&mut res_type[0..slice.len()]).copy_from_slice(slice);
res_type.reverse();
drs_write.add(res_type, args.id[i], File::open(path)?)?;
}
drs_write.flush()?;
std::fs::rename(
temp_out,
if let Some(outfile) = args.output {
outfile
} else {
args.archive
},
)?;
Ok(())
}
fn main() -> anyhow::Result<()> {
let args = Cli::from_args();
match args.command {
Command::List(args) => list(args),
Command::Get(args) => get(args),
Command::Extract(args) => extract(args),
Command::Add(args) => add(args),
}
}