rpgmv_tool/command/
decrypt.rs

1use crate::util::try_metadata;
2use anyhow::Context;
3use anyhow::anyhow;
4use anyhow::bail;
5use anyhow::ensure;
6use clap::Parser;
7use glob::glob;
8use std::fs::File;
9use std::io::BufReader;
10use std::io::Write;
11use std::path::Path;
12use std::path::PathBuf;
13
14#[derive(Debug, Parser)]
15#[command(about = "Decrypt a file")]
16pub struct Options {
17    #[arg(long = "input", short = 'i', help = "A file to decrypt")]
18    pub input: Vec<PathBuf>,
19
20    #[arg(long = "glob-input", help = "A glob of input files to decrypt")]
21    pub glob_input: Vec<String>,
22
23    #[arg(long = "output", short = 'o', help = "The output folder")]
24    pub output: PathBuf,
25}
26
27/// Interface inspired by mv.
28/// See: https://man7.org/linux/man-pages/man1/mv.1p.html
29pub fn exec(options: Options) -> anyhow::Result<()> {
30    let mut inputs = options.input;
31    for input in options.glob_input {
32        let iter = glob(&input)?;
33        for input in iter {
34            let input = input?;
35
36            inputs.push(input);
37        }
38    }
39
40    ensure!(!inputs.is_empty(), "need at least 1 input");
41
42    let output_metadata = try_metadata(&options.output)
43        .with_context(|| format!("failed to stat \"{}\"", options.output.display()))?;
44
45    // If the output is a directory, use the vector impl.
46    match output_metadata {
47        Some(metadata) if metadata.is_dir() => {
48            return exec_vector(&inputs, &options.output);
49        }
50        Some(_) | None => {}
51    }
52
53    if inputs.len() == 1 {
54        let input = &inputs[0];
55
56        // For file destinations or non-existent destinations
57        // We filter out directory outputs earlier.
58        exec_scalar(input, &options.output)
59    } else {
60        // We can't use the scalar impl since there must be more than 1 input.
61        // We can't use the vector impl since the target output is either a file or does not exist.
62        // Assume the user wanted to use the vector impl for error, since there is more than 1 input.
63        Err(anyhow!(
64            "\"{}\" is not a directory or does not exist",
65            options.output.display()
66        ))
67    }
68}
69
70fn exec_scalar(input: &Path, output: &Path) -> anyhow::Result<()> {
71    decrypt_single_file(input, output)
72}
73
74fn exec_vector(inputs: &[PathBuf], output: &Path) -> anyhow::Result<()> {
75    for input in inputs.iter() {
76        let input_file_name = input
77            .file_name()
78            .with_context(|| format!("failed to get file name from \"{}\"", &input.display()))?;
79
80        let output = {
81            let mut path = output.join(input_file_name);
82            path.set_extension("png");
83            path
84        };
85
86        decrypt_single_file(input, &output)?;
87    }
88
89    Ok(())
90}
91
92fn decrypt_single_file(input: &Path, output: &Path) -> anyhow::Result<()> {
93    let output_metadata =
94        try_metadata(output).with_context(|| format!("failed to stat \"{}\"", output.display()))?;
95
96    if output_metadata.is_some() {
97        bail!(
98            "output path \"{}\" exists, refusing to overwrite",
99            output.display()
100        );
101    }
102
103    let file =
104        File::open(input).with_context(|| format!("failed to open \"{}\"", input.display()))?;
105
106    let file = BufReader::new(file);
107    let mut reader = rpgmvp::Reader::new(file);
108    reader.read_header().context("invalid header")?;
109    let key = reader.extract_key().context("failed to extract key")?;
110    let key_hex = base16ct::lower::encode_string(&key);
111    println!("Key for \"{}\": {}", input.display(), key_hex);
112
113    let output_tmp = output.with_added_extension("tmp");
114    let mut writer = File::create(&output_tmp)
115        .with_context(|| format!("failed to open \"{}\"", output_tmp.display()))?;
116    std::io::copy(&mut reader, &mut writer)?;
117    writer.flush()?;
118    writer.sync_all()?;
119    std::fs::rename(&output_tmp, output)?;
120
121    Ok(())
122}