rpgmv_tool/command/
decrypt.rs1use 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
27pub 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 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 exec_scalar(input, &options.output)
59 } else {
60 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}