rpgmv_tool/command/
decrypt.rs1use anyhow::anyhow;
2use anyhow::bail;
3use anyhow::ensure;
4use anyhow::Context;
5use glob::glob;
6use std::fs::File;
7use std::io::BufReader;
8use std::io::Write;
9use std::path::Path;
10use std::path::PathBuf;
11
12#[derive(Debug, argh::FromArgs)]
13#[argh(subcommand, name = "decrypt", description = "decrypt a file")]
14pub struct Options {
15 #[argh(option, long = "input", short = 'i', description = "a file to decrypt")]
16 pub input: Vec<PathBuf>,
17
18 #[argh(
19 option,
20 long = "glob-input",
21 description = "a glob of input files to decrypt"
22 )]
23 pub glob_input: Vec<String>,
24
25 #[argh(
26 option,
27 long = "output",
28 short = 'o',
29 description = "the output folder"
30 )]
31 pub output: PathBuf,
32}
33
34fn try_metadata<P>(path: P) -> std::io::Result<Option<std::fs::Metadata>>
36where
37 P: AsRef<Path>,
38{
39 match std::fs::metadata(path) {
40 Ok(metadata) => Ok(Some(metadata)),
41 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
42 Err(error) => Err(error),
43 }
44}
45
46pub fn exec(options: Options) -> anyhow::Result<()> {
49 let mut inputs = options.input;
50 for input in options.glob_input {
51 let iter = glob(&input)?;
52 for input in iter {
53 let input = input?;
54
55 inputs.push(input);
56 }
57 }
58
59 ensure!(!inputs.is_empty(), "need at least 1 input");
60
61 let output_metadata = try_metadata(&options.output)
62 .with_context(|| format!("failed to stat \"{}\"", options.output.display()))?;
63
64 match output_metadata {
66 Some(metadata) if metadata.is_dir() => {
67 return exec_vector(&inputs, &options.output);
68 }
69 Some(_) | None => {}
70 }
71
72 if inputs.len() == 1 {
73 let input = &inputs[0];
74
75 exec_scalar(input, &options.output)
78 } else {
79 Err(anyhow!(
83 "\"{}\" is not a directory or does not exist",
84 options.output.display()
85 ))
86 }
87}
88
89fn exec_scalar(input: &Path, output: &Path) -> anyhow::Result<()> {
90 decrypt_single_file(input, output)
91}
92
93fn exec_vector(inputs: &[PathBuf], output: &Path) -> anyhow::Result<()> {
94 for input in inputs.iter() {
95 let input_file_name = input
96 .file_name()
97 .with_context(|| format!("failed to get file name from \"{}\"", &input.display()))?;
98
99 let output = {
100 let mut path = output.join(input_file_name);
101 path.set_extension("png");
102 path
103 };
104
105 decrypt_single_file(input, &output)?;
106 }
107
108 Ok(())
109}
110
111fn decrypt_single_file(input: &Path, output: &Path) -> anyhow::Result<()> {
112 let output_metadata =
113 try_metadata(output).with_context(|| format!("failed to stat \"{}\"", output.display()))?;
114
115 if output_metadata.is_some() {
116 bail!(
117 "output path \"{}\" exists, refusing to overwrite",
118 output.display()
119 );
120 }
121
122 let file =
123 File::open(input).with_context(|| format!("failed to open \"{}\"", input.display()))?;
124
125 let file = BufReader::new(file);
126 let mut reader = rpgmvp::Reader::new(file);
127 reader.read_header().context("invalid header")?;
128 let key = reader.extract_key().context("failed to extract key")?;
129 let key_hex = base16ct::lower::encode_string(&key);
130 println!("Key for \"{}\": {}", input.display(), key_hex);
131
132 let output_tmp = nd_util::with_push_extension(output, "tmp");
133 let mut writer = File::create(&output_tmp)
134 .with_context(|| format!("failed to open \"{}\"", output_tmp.display()))?;
135 std::io::copy(&mut reader, &mut writer)?;
136 writer.flush()?;
137 writer.sync_all()?;
138 std::fs::rename(&output_tmp, output)?;
139
140 Ok(())
141}