msg_tool\scripts\entis_gls\csx/
mod.rs

1//! Entis GLS CSX Script Support
2//!
3//! Ported from Crsky/EntisGLS_Tools C# project  
4//! Original license: GPL-3.0
5mod base;
6mod v1;
7mod v2;
8
9use crate::ext::io::*;
10use crate::scripts::base::*;
11use crate::types::*;
12use crate::utils::encoding::*;
13use anyhow::Result;
14use base::ECSImage;
15use v1::ECSExecutionImageV1;
16use v2::ECSExecutionImageV2;
17
18#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
19/// CSX Script Version
20pub enum CSXScriptVersion {
21    /// Version 1
22    V1,
23    /// Version 2 (2.x-3.x)
24    V2,
25}
26
27#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
28/// CSX Script Full Version
29pub enum CSXScriptV2FullVer {
30    /// Version 2.x
31    V2,
32    /// Version 3.x
33    /// Example game: お兄ちゃん、右手の使用を禁止します!
34    V3,
35}
36
37#[derive(Debug)]
38pub struct CSXScriptBuilder {}
39
40impl CSXScriptBuilder {
41    pub fn new() -> Self {
42        Self {}
43    }
44}
45
46impl ScriptBuilder for CSXScriptBuilder {
47    fn default_encoding(&self) -> Encoding {
48        Encoding::Utf16LE
49    }
50
51    fn build_script(
52        &self,
53        buf: Vec<u8>,
54        _filename: &str,
55        _encoding: Encoding,
56        _archive_encoding: Encoding,
57        config: &ExtraConfig,
58        _archive: Option<&Box<dyn Script>>,
59    ) -> Result<Box<dyn Script>> {
60        Ok(Box::new(CSXScript::new(buf, config)?))
61    }
62
63    fn extensions(&self) -> &'static [&'static str] {
64        &["csx"]
65    }
66
67    fn script_type(&self) -> &'static ScriptType {
68        &ScriptType::EntisGlsCsx
69    }
70
71    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
72        if buf_len >= 8 && &buf[0..8] == b"Entis\x1a\0\0" {
73            Some(30)
74        } else {
75            None
76        }
77    }
78}
79
80#[derive(Debug)]
81pub struct CSXScript {
82    img: Box<dyn ECSImage>,
83    disasm: bool,
84    custom_yaml: bool,
85}
86
87impl CSXScript {
88    pub fn new(buf: Vec<u8>, config: &ExtraConfig) -> Result<Self> {
89        let reader = MemReader::new(buf);
90        let img = if let Some(ver) = &config.entis_gls_csx_ver {
91            match ver {
92                CSXScriptVersion::V1 => {
93                    Box::new(ECSExecutionImageV1::new(reader.to_ref(), config)?)
94                        as Box<dyn ECSImage>
95                }
96                CSXScriptVersion::V2 => {
97                    Box::new(ECSExecutionImageV2::new(reader.to_ref(), config)?)
98                        as Box<dyn ECSImage>
99                }
100            }
101        } else {
102            match ECSExecutionImageV1::new(reader.to_ref(), config) {
103                Ok(img) => Box::new(img),
104                Err(_) => Box::new(ECSExecutionImageV2::new(reader.to_ref(), config)?)
105                    as Box<dyn ECSImage>,
106            }
107        };
108        Ok(Self {
109            img,
110            disasm: config.entis_gls_csx_disasm,
111            custom_yaml: config.custom_yaml,
112        })
113    }
114}
115
116impl Script for CSXScript {
117    fn default_output_script_type(&self) -> OutputScriptType {
118        OutputScriptType::Json
119    }
120
121    fn is_output_supported(&self, _output: OutputScriptType) -> bool {
122        true
123    }
124
125    fn default_format_type(&self) -> FormatOptions {
126        FormatOptions::None
127    }
128
129    fn extract_messages(&self) -> Result<Vec<Message>> {
130        self.img.export()
131    }
132
133    fn import_messages<'a>(
134        &'a self,
135        messages: Vec<Message>,
136        file: Box<dyn WriteSeek + 'a>,
137        _filename: &str,
138        _encoding: Encoding,
139        replacement: Option<&'a ReplacementTable>,
140    ) -> Result<()> {
141        self.img.import(messages, file, replacement)
142    }
143
144    fn multiple_message_files(&self) -> bool {
145        true
146    }
147
148    fn extract_multiple_messages(&self) -> Result<std::collections::HashMap<String, Vec<Message>>> {
149        self.img.export_multi()
150    }
151
152    fn import_multiple_messages<'a>(
153        &'a self,
154        messages: std::collections::HashMap<String, Vec<Message>>,
155        file: Box<dyn WriteSeek + 'a>,
156        _filename: &str,
157        _encoding: Encoding,
158        replacement: Option<&'a ReplacementTable>,
159    ) -> Result<()> {
160        self.img.import_multi(messages, file, replacement)
161    }
162
163    fn custom_output_extension<'a>(&'a self) -> &'a str {
164        if self.disasm {
165            "d.txt"
166        } else if self.custom_yaml {
167            "yaml"
168        } else {
169            "json"
170        }
171    }
172
173    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
174        if self.disasm {
175            let file = crate::utils::files::write_file(filename)?;
176            let file = std::io::BufWriter::new(file);
177            self.img.disasm(Box::new(file))?;
178        } else {
179            let messages = self.img.export_all()?;
180            let s = if self.custom_yaml {
181                serde_yaml_ng::to_string(&messages)?
182            } else {
183                serde_json::to_string_pretty(&messages)?
184            };
185            let s = encode_string(encoding, &s, false)?;
186            let mut file = crate::utils::files::write_file(filename)?;
187            file.write_all(&s)?;
188        }
189        Ok(())
190    }
191
192    fn custom_import<'a>(
193        &'a self,
194        custom_filename: &'a str,
195        file: Box<dyn WriteSeek + 'a>,
196        _encoding: Encoding,
197        output_encoding: Encoding,
198    ) -> Result<()> {
199        if self.disasm {
200            Err(anyhow::anyhow!(
201                "Importing from disassembly is not supported."
202            ))
203        } else {
204            let data = crate::utils::files::read_file(custom_filename)?;
205            let s = decode_to_string(output_encoding, &data, false)?;
206            let messages: Vec<String> = if self.custom_yaml {
207                serde_yaml_ng::from_str(&s)?
208            } else {
209                serde_json::from_str(&s)?
210            };
211            self.img.import_all(messages, file)?;
212            Ok(())
213        }
214    }
215}