use anyhow::Context;
use clap::Parser;
use explode::explode;
use freenukum::data_dir;
use std::fs::{create_dir_all, File};
use std::io::ErrorKind;
use std::io::{Read, Write};
use std::iter::Iterator;
use std::ops::Shl;
use std::path::{Path, PathBuf};
use zip::read::ZipArchive;
trait Skip {
fn skip(&mut self, count: usize) -> std::io::Result<()>;
}
impl<R: Read> Skip for R {
fn skip(&mut self, count: usize) -> std::io::Result<()> {
for _ in 0..count {
let mut buffer = [0; 1];
self.read_exact(&mut buffer)?;
}
Ok(())
}
}
#[derive(Parser, Debug)]
struct InstallFromShrArguments {
input: PathBuf,
}
#[derive(Parser, Debug)]
struct InstallFromZipArguments {
input: PathBuf,
}
#[derive(Parser, Debug)]
enum Command {
InstallFromShr(InstallFromShrArguments),
InstallFromZip(InstallFromZipArguments),
}
#[derive(Parser, Debug)]
struct Arguments {
#[clap(subcommand)]
command: Command,
}
fn read_file_entry<R: Read>(
reader: &mut R,
) -> Result<Option<(String, Vec<u8>)>, anyhow::Error> {
let mut filename = [0; 16];
match reader.read_exact(&mut filename) {
Ok(_) => {}
Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e.into()),
}
let mut filename = std::str::from_utf8(
filename
.split(|c| *c == 0)
.next()
.context("Couldn't extract filename")?,
)?
.to_string();
filename.make_ascii_lowercase();
reader.skip(120)?;
let compressed_size: usize = {
let mut b = [0; 4];
reader.read_exact(&mut b)?;
(b[3] as usize).shl(24)
| (b[2] as usize).shl(16)
| (b[1] as usize).shl(8)
| (b[0] as usize).shl(0)
};
reader.skip(28)?;
let mut buffer = Vec::new();
buffer.resize_with(compressed_size, Default::default);
reader.read_exact(&mut buffer)?;
Ok(Some((filename, explode(&buffer)?)))
}
fn extract_shr_data_to_dir<R: Read>(
reader: &mut R,
directory: &Path,
) -> Result<(), anyhow::Error> {
reader.skip(58)?;
create_dir_all(directory)?;
while let Some((filename, data)) = read_file_entry(reader)? {
println!("Extracting {}", filename);
let path = directory.join(filename);
let mut file = File::create(path)?;
file.write_all(&data)?;
}
Ok(())
}
fn extract_shr_file_to_dir(
file: &Path,
directory: &Path,
) -> Result<(), anyhow::Error> {
println!(
"Extracting data from {:?} to directory {:?}",
file, directory
);
let mut file = File::open(file)?;
extract_shr_data_to_dir(&mut file, directory)
}
fn extract_zip_nested_shr_file_to_dir(
file: &Path,
directory: &Path,
) -> Result<(), anyhow::Error> {
println!(
"Extracting data from {:?} to directory {:?}",
file, directory
);
let file = File::open(file)?;
let mut zip = ZipArchive::new(file)?;
let filename = "DN1SW20.SHR";
let mut zip_file = zip
.by_name(filename)
.context(format!("Couldn't extract file {:?}", filename))?;
extract_shr_data_to_dir(&mut zip_file, directory)
}
fn main() -> Result<(), anyhow::Error> {
let arguments = Arguments::parse();
let data_path = data_dir().join("data").join("original");
match arguments.command {
Command::InstallFromShr(args) => {
extract_shr_file_to_dir(&args.input, &data_path).unwrap();
}
Command::InstallFromZip(args) => {
extract_zip_nested_shr_file_to_dir(&args.input, &data_path)
.unwrap();
}
}
Ok(())
}