rpgmxp_tool/commands/compile_assets/
vx_ace.rs

1use super::generate_map_infos_data;
2use super::generate_ruby_data;
3use super::set_extension_str;
4use super::FileSink;
5use anyhow::bail;
6use anyhow::ensure;
7use anyhow::Context;
8use rpgmvx_ace_types::Script;
9use rpgmvx_ace_types::ScriptList;
10use ruby_marshal::IntoValue;
11use std::collections::BTreeMap;
12use std::fs::File;
13use std::path::Path;
14
15fn generate_scripts_data_vx_ace(path: &Path) -> anyhow::Result<Vec<u8>> {
16    let mut scripts_map = BTreeMap::new();
17
18    for dir_entry in path.read_dir()? {
19        let dir_entry = dir_entry?;
20        let dir_entry_file_type = dir_entry.file_type()?;
21
22        ensure!(dir_entry_file_type.is_file());
23
24        let dir_entry_file_name = dir_entry.file_name();
25        let dir_entry_file_name = dir_entry_file_name
26            .to_str()
27            .context("non-unicode script name")?;
28        let dir_entry_file_stem = dir_entry_file_name
29            .strip_suffix(".rb")
30            .context("script is not an \"rb\" file")?;
31
32        let (script_index, escaped_script_name) = dir_entry_file_stem
33            .split_once('-')
34            .context("invalid script name format")?;
35        let script_index: usize = script_index.parse()?;
36        let unescaped_file_name = crate::util::percent_unescape_file_name(escaped_script_name)?;
37
38        println!("  packing script \"{escaped_script_name}\"");
39
40        let dir_entry_path = dir_entry.path();
41        let script_data = std::fs::read_to_string(dir_entry_path)?;
42
43        let old_entry = scripts_map.insert(
44            script_index,
45            Script {
46                data: script_data,
47                id: i32::try_from(script_index)? + 1,
48                name: unescaped_file_name,
49            },
50        );
51        if old_entry.is_some() {
52            bail!("duplicate scripts for index {script_index}");
53        }
54    }
55
56    // TODO: Consider enforcing that script index ranges cannot have holes and must start at 0.
57    let script_list = ScriptList {
58        scripts: scripts_map.into_values().collect(),
59    };
60
61    let mut arena = ruby_marshal::ValueArena::new();
62    let handle = script_list.into_value(&mut arena)?;
63    arena.replace_root(handle);
64
65    let mut data = Vec::new();
66    ruby_marshal::dump(&mut data, &arena)?;
67
68    Ok(data)
69}
70
71pub fn compile(
72    entry_path: &Path,
73    entry_file_type: std::fs::FileType,
74    relative_path: &Path,
75    relative_path_components: Vec<&str>,
76    file_sink: &mut FileSink,
77) -> anyhow::Result<()> {
78    match relative_path_components.as_slice() {
79        ["Data", "Scripts.rvdata2"] if entry_file_type.is_dir() => {
80            println!("packing \"{}\"", relative_path.display());
81
82            let scripts_data = generate_scripts_data_vx_ace(entry_path)?;
83            let size = u32::try_from(scripts_data.len())?;
84
85            file_sink.write_file(&relative_path_components, size, &*scripts_data)?;
86        }
87        ["Data", "Scripts.rvdata2", ..] => {
88            // Ignore entries, we explore them in the above branch.
89        }
90        ["Data", "MapInfos.rvdata2"] if entry_file_type.is_dir() => {
91            println!("packing \"{}\"", relative_path.display());
92
93            let data = generate_map_infos_data(entry_path)?;
94            let size = u32::try_from(data.len())?;
95
96            file_sink.write_file(&relative_path_components, size, &*data)?;
97        }
98        ["Data", "MapInfos.rvdata2", ..] => {
99            // Ignore entries, we explore them in the above branch.
100        }
101        ["Data", file] if crate::util::is_map_file_name(file, "json") => {
102            println!("packing \"{}\"", relative_path.display());
103
104            let map_data = generate_ruby_data::<rpgmvx_ace_types::Map>(entry_path)?;
105            let size = u32::try_from(map_data.len())?;
106
107            let renamed_file = set_extension_str(file, "rvdata2");
108            let mut relative_path_components = relative_path_components.clone();
109            *relative_path_components.last_mut().unwrap() = renamed_file.as_str();
110
111            file_sink.write_file(&relative_path_components, size, &*map_data)?;
112        }
113        relative_path_components if entry_file_type.is_file() => {
114            // Copy file by default
115            println!("packing \"{}\"", relative_path.display());
116
117            let input_file = File::open(entry_path).with_context(|| {
118                format!(
119                    "failed to open input file from \"{}\"",
120                    entry_path.display()
121                )
122            })?;
123            let metadata = input_file.metadata()?;
124            let size = u32::try_from(metadata.len())?;
125
126            file_sink.write_file(relative_path_components, size, input_file)?;
127        }
128        _ => {}
129    }
130
131    Ok(())
132}