msg_tool\scripts\bgi/
bsi.rs

1//! Buriko General Interpreter/Ethornell BSI Script (._bsi)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::{decode_to_string, encode_string};
6use anyhow::Result;
7use std::collections::BTreeMap;
8use std::ffi::CString;
9
10#[derive(Debug)]
11/// Builder for BGI BSI scripts.
12pub struct BGIBsiScriptBuilder {}
13
14impl BGIBsiScriptBuilder {
15    /// Creates a new instance of `BGIBsiScriptBuilder`.
16    pub fn new() -> Self {
17        BGIBsiScriptBuilder {}
18    }
19}
20
21impl ScriptBuilder for BGIBsiScriptBuilder {
22    fn default_encoding(&self) -> Encoding {
23        Encoding::Utf8
24    }
25
26    fn build_script(
27        &self,
28        buf: Vec<u8>,
29        _filename: &str,
30        encoding: Encoding,
31        _archive_encoding: Encoding,
32        config: &ExtraConfig,
33        _archive: Option<&Box<dyn Script>>,
34    ) -> Result<Box<dyn Script>> {
35        Ok(Box::new(BGIBsiScript::new(buf, encoding, config)?))
36    }
37
38    fn extensions(&self) -> &'static [&'static str] {
39        &["_bsi"]
40    }
41
42    fn script_type(&self) -> &'static ScriptType {
43        &ScriptType::BGIBsi
44    }
45
46    fn can_create_file(&self) -> bool {
47        true
48    }
49
50    fn create_file<'a>(
51        &'a self,
52        filename: &'a str,
53        writer: Box<dyn WriteSeek + 'a>,
54        encoding: Encoding,
55        file_encoding: Encoding,
56        config: &ExtraConfig,
57    ) -> Result<()> {
58        create_file(
59            filename,
60            writer,
61            encoding,
62            file_encoding,
63            config.custom_yaml,
64        )
65    }
66}
67
68#[derive(Debug)]
69/// BGI BSI script.
70pub struct BGIBsiScript {
71    /// Section name and its data map.
72    pub data: BTreeMap<String, BTreeMap<String, String>>,
73    custom_yaml: bool,
74}
75
76impl BGIBsiScript {
77    /// Creates a new instance of `BGIBsiScript` from a buffer.
78    ///
79    /// * `buf` - The buffer containing the script data.
80    /// * `encoding` - The encoding of the script.
81    /// * `config` - Extra configuration options.
82    pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
83        let mut data = BTreeMap::new();
84        let mut reader = MemReader::new(buf);
85        let section_count = reader.read_u32()?;
86        for _ in 0..section_count {
87            let section_name = reader.read_cstring()?;
88            let section_name = decode_to_string(encoding, section_name.as_bytes(), true)?;
89            let mut section_data = BTreeMap::new();
90            let entry_count = reader.read_u32()?;
91            for _ in 0..entry_count {
92                let key = reader.read_cstring()?;
93                let key = decode_to_string(encoding, key.as_bytes(), true)?;
94                let value = reader.read_cstring()?;
95                let value = decode_to_string(encoding, value.as_bytes(), true)?;
96                section_data.insert(key, value);
97            }
98            data.insert(section_name, section_data);
99        }
100        if !reader.is_eof() {
101            eprintln!(
102                "Warning: BGIBsiScript data not fully read, remaining bytes: {}",
103                reader.data.len() - reader.pos
104            );
105            crate::COUNTER.inc_warning();
106        }
107        Ok(BGIBsiScript {
108            data,
109            custom_yaml: config.custom_yaml,
110        })
111    }
112}
113
114impl Script for BGIBsiScript {
115    fn default_output_script_type(&self) -> OutputScriptType {
116        OutputScriptType::Custom
117    }
118
119    fn is_output_supported(&self, output: OutputScriptType) -> bool {
120        matches!(output, OutputScriptType::Custom)
121    }
122
123    fn default_format_type(&self) -> FormatOptions {
124        FormatOptions::None
125    }
126
127    fn custom_output_extension(&self) -> &'static str {
128        if self.custom_yaml { "yaml" } else { "json" }
129    }
130
131    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
132        let s = if self.custom_yaml {
133            serde_yaml_ng::to_string(&self.data)
134                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
135        } else {
136            serde_json::to_string(&self.data)
137                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
138        };
139        let mut writer = crate::utils::files::write_file(filename)?;
140        let s = encode_string(encoding, &s, false)?;
141        writer.write_all(&s)?;
142        writer.flush()?;
143        Ok(())
144    }
145
146    fn custom_import<'a>(
147        &'a self,
148        custom_filename: &'a str,
149        file: Box<dyn WriteSeek + 'a>,
150        encoding: Encoding,
151        output_encoding: Encoding,
152    ) -> Result<()> {
153        create_file(
154            custom_filename,
155            file,
156            encoding,
157            output_encoding,
158            self.custom_yaml,
159        )
160    }
161}
162
163fn create_file<'a>(
164    custom_filename: &'a str,
165    mut writer: Box<dyn WriteSeek + 'a>,
166    encoding: Encoding,
167    output_encoding: Encoding,
168    yaml: bool,
169) -> Result<()> {
170    let input = crate::utils::files::read_file(custom_filename)?;
171    let s = decode_to_string(output_encoding, &input, true)?;
172    let data: BTreeMap<String, BTreeMap<String, String>> = if yaml {
173        serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
174    } else {
175        serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
176    };
177    writer.write_u32(data.len() as u32)?;
178    for (section_name, section_data) in data {
179        let section_name_bytes = encode_string(encoding, &section_name, false)?;
180        let section_name = CString::new(section_name_bytes)?;
181        writer.write_cstring(&section_name)?;
182        writer.write_u32(section_data.len() as u32)?;
183        for (key, value) in section_data {
184            let key_bytes = encode_string(encoding, &key, false)?;
185            let key = CString::new(key_bytes)?;
186            writer.write_cstring(&key)?;
187            let value_bytes = encode_string(encoding, &value, false)?;
188            let value = CString::new(value_bytes)?;
189            writer.write_cstring(&value)?;
190        }
191    }
192    Ok(())
193}