msg_tool\scripts\kirikiri/
tjs2.rs

1//! Kirikiri compiled TJS2 script
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::io::{Read, Seek, Write};
10
11#[derive(Debug)]
12/// Kirikiri TJS2 Script Builder
13pub struct Tjs2Builder {}
14
15impl Tjs2Builder {
16    /// Creates a new instance of `Tjs2Builder`
17    pub fn new() -> Self {
18        Self {}
19    }
20}
21
22impl ScriptBuilder for Tjs2Builder {
23    fn default_encoding(&self) -> Encoding {
24        Encoding::Utf16LE
25    }
26
27    fn build_script(
28        &self,
29        buf: Vec<u8>,
30        _filename: &str,
31        encoding: Encoding,
32        _archive_encoding: Encoding,
33        config: &ExtraConfig,
34        _archive: Option<&Box<dyn Script>>,
35    ) -> Result<Box<dyn Script>> {
36        Ok(Box::new(Tjs2::new(buf, encoding, config)?))
37    }
38
39    fn extensions(&self) -> &'static [&'static str] {
40        &["tjs"]
41    }
42
43    fn script_type(&self) -> &'static ScriptType {
44        &ScriptType::KirikiriTjs2
45    }
46
47    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
48        // TJS2 tag 100 version
49        if buf_len >= 8 && buf.starts_with(b"TJS2100\0") {
50            return Some(40);
51        }
52        None
53    }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57struct DataArea {
58    byte_array: Vec<u8>,
59    short_array: Vec<i16>,
60    long_array: Vec<i32>,
61    longlong_array: Vec<i64>,
62    double_array: Vec<f64>,
63    string_array: Vec<String>,
64    octet_array: Vec<Vec<u8>>,
65}
66
67impl StructUnpack for DataArea {
68    fn unpack<R: Read + Seek>(
69        reader: &mut R,
70        big: bool,
71        encoding: Encoding,
72        info: &Option<Box<dyn std::any::Any>>,
73    ) -> Result<Self> {
74        reader.align(4)?;
75        let start_loc = reader.stream_position()?;
76        let mut data_tag = [0; 4];
77        reader.read_exact(&mut data_tag)?;
78        if &data_tag != b"DATA" {
79            return Err(anyhow::anyhow!("Invalid DATA tag"));
80        }
81        let data_size = u32::unpack(reader, big, encoding, info)?;
82        let count = u32::unpack(reader, big, encoding, info)? as usize;
83        let byte_array = reader.read_exact_vec(count)?;
84        reader.align(4)?;
85        let short_count = u32::unpack(reader, big, encoding, info)? as usize;
86        let short_array = reader.read_struct_vec(short_count, big, encoding, info)?;
87        reader.align(4)?;
88        let long_count = u32::unpack(reader, big, encoding, info)? as usize;
89        let long_array = reader.read_struct_vec(long_count, big, encoding, info)?;
90        let longlong_count = u32::unpack(reader, big, encoding, info)? as usize;
91        let longlong_array = reader.read_struct_vec(longlong_count, big, encoding, info)?;
92        let double_count = u32::unpack(reader, big, encoding, info)? as usize;
93        let double_array = reader.read_struct_vec(double_count, big, encoding, info)?;
94        let str_count = u32::unpack(reader, big, encoding, info)? as usize;
95        let mut string_array = Vec::with_capacity(str_count);
96        for _ in 0..str_count {
97            let str_len = u32::unpack(reader, big, encoding, info)? as usize;
98            let str_bytes = reader.read_exact_vec(if encoding.is_utf16le() {
99                str_len * 2
100            } else {
101                str_len
102            })?;
103            let s = decode_to_string(encoding, &str_bytes, true)?;
104            reader.align(4)?;
105            string_array.push(s);
106        }
107        let octet_count = u32::unpack(reader, big, encoding, info)? as usize;
108        let mut octet_array = Vec::with_capacity(octet_count);
109        for _ in 0..octet_count {
110            let octet_len = u32::unpack(reader, big, encoding, info)? as usize;
111            let octet_bytes = reader.read_exact_vec(octet_len)?;
112            reader.align(4)?;
113            octet_array.push(octet_bytes);
114        }
115        let end_loc = reader.stream_position()?;
116        if end_loc - start_loc != data_size as u64 {
117            return Err(anyhow::anyhow!(
118                "DATA size mismatch: expected {}, got {}",
119                data_size,
120                end_loc - start_loc
121            ));
122        }
123        Ok(DataArea {
124            byte_array,
125            short_array,
126            long_array,
127            longlong_array,
128            double_array,
129            string_array,
130            octet_array,
131        })
132    }
133}
134
135impl StructPack for DataArea {
136    fn pack<W: Write>(
137        &self,
138        writer: &mut W,
139        big: bool,
140        encoding: Encoding,
141        info: &Option<Box<dyn std::any::Any>>,
142    ) -> Result<()> {
143        writer.write_all(b"DATA")?;
144        let mut tmp = MemWriter::new();
145        tmp.write_struct(&(self.byte_array.len() as u32), big, encoding, info)?;
146        tmp.write_all(&self.byte_array)?;
147        tmp.align(4)?;
148        tmp.write_struct(&(self.short_array.len() as u32), big, encoding, info)?;
149        for v in &self.short_array {
150            tmp.write_struct(v, big, encoding, info)?;
151        }
152        tmp.align(4)?;
153        tmp.write_struct(&(self.long_array.len() as u32), big, encoding, info)?;
154        for v in &self.long_array {
155            tmp.write_struct(v, big, encoding, info)?;
156        }
157        tmp.write_struct(&(self.longlong_array.len() as u32), big, encoding, info)?;
158        for v in &self.longlong_array {
159            tmp.write_struct(v, big, encoding, info)?;
160        }
161        tmp.write_struct(&(self.double_array.len() as u32), big, encoding, info)?;
162        for v in &self.double_array {
163            tmp.write_struct(v, big, encoding, info)?;
164        }
165        tmp.write_struct(&(self.string_array.len() as u32), big, encoding, info)?;
166        for s in &self.string_array {
167            let encoded = encode_string(encoding, s, false)?;
168            let str_len = if encoding.is_utf16le() {
169                encoded.len() / 2
170            } else {
171                encoded.len()
172            };
173            tmp.write_struct(&(str_len as u32), big, encoding, info)?;
174            tmp.write_all(&encoded)?;
175            tmp.align(4)?;
176        }
177        tmp.write_struct(&(self.octet_array.len() as u32), big, encoding, info)?;
178        for o in &self.octet_array {
179            tmp.write_struct(&(o.len() as u32), big, encoding, info)?;
180            tmp.write_all(o)?;
181            tmp.align(4)?;
182        }
183        // make sure final size is aligned to 4 bytes
184        tmp.data.resize(tmp.pos, 0);
185        let data = tmp.into_inner();
186        writer.write_struct(&(data.len() as u32 + 8), big, encoding, info)?;
187        writer.write_all(&data)?;
188        Ok(())
189    }
190}
191
192/// Kirikiri TJS2 Script
193#[derive(Debug)]
194pub struct Tjs2 {
195    data_area: DataArea,
196    remaing: Vec<u8>,
197    custom_yaml: bool,
198}
199
200impl Tjs2 {
201    /// Creates a new `Tjs2` script from the given buffer
202    ///
203    /// * `buf` - The buffer containing the TJS2 data
204    /// * `encoding` - The encoding to use for strings
205    /// * `config` - Extra configuration options
206    pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
207        let mut reader = MemReader::new(buf);
208        let mut header = [0u8; 8];
209        reader.read_exact(&mut header)?;
210        if &header != b"TJS2100\0" {
211            return Err(anyhow::anyhow!("Invalid TJS2 header: {:?}", &header));
212        }
213        let _file_size = reader.read_u32()?;
214        let data_area = DataArea::unpack(&mut reader, false, encoding, &None)?;
215        let mut remaing = Vec::new();
216        reader.read_to_end(&mut remaing)?;
217        Ok(Self {
218            data_area,
219            remaing,
220            custom_yaml: config.custom_yaml,
221        })
222    }
223}
224
225impl Script for Tjs2 {
226    fn default_output_script_type(&self) -> OutputScriptType {
227        OutputScriptType::Json
228    }
229
230    fn default_format_type(&self) -> FormatOptions {
231        FormatOptions::None
232    }
233
234    fn is_output_supported(&self, _: OutputScriptType) -> bool {
235        true
236    }
237
238    fn custom_output_extension<'a>(&'a self) -> &'a str {
239        if self.custom_yaml { "yaml" } else { "json" }
240    }
241
242    fn extract_messages(&self) -> Result<Vec<Message>> {
243        let mut messages = Vec::new();
244        for s in self.data_area.string_array.iter() {
245            messages.push(Message {
246                name: None,
247                message: s.clone(),
248            });
249        }
250        Ok(messages)
251    }
252
253    fn import_messages<'a>(
254        &'a self,
255        messages: Vec<Message>,
256        mut file: Box<dyn WriteSeek + 'a>,
257        _filename: &str,
258        encoding: Encoding,
259        replacement: Option<&'a ReplacementTable>,
260    ) -> Result<()> {
261        let mut data_area = self.data_area.clone();
262        data_area.string_array = messages
263            .iter()
264            .map(|m| {
265                let mut s = m.message.clone();
266                if let Some(table) = replacement {
267                    for (from, to) in &table.map {
268                        s = s.replace(from, to);
269                    }
270                }
271                s
272            })
273            .collect();
274        file.write_all(b"TJS2100\0")?;
275        file.write_u32(0)?; // placeholder for file size
276        data_area.pack(&mut file, false, encoding, &None)?;
277        file.write_all(&self.remaing)?;
278        let file_size = file.stream_length()?;
279        file.write_u32_at(8, file_size as u32)?; // write actual file size
280        Ok(())
281    }
282
283    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
284        let s = if self.custom_yaml {
285            serde_yaml_ng::to_string(&self.data_area)?
286        } else {
287            serde_json::to_string_pretty(&self.data_area)?
288        };
289        let encoded = encode_string(encoding, &s, false)?;
290        let mut file = crate::utils::files::write_file(filename)?;
291        file.write_all(&encoded)?;
292        Ok(())
293    }
294
295    fn custom_import<'a>(
296        &'a self,
297        custom_filename: &'a str,
298        mut file: Box<dyn WriteSeek + 'a>,
299        encoding: Encoding,
300        output_encoding: Encoding,
301    ) -> Result<()> {
302        let data = crate::utils::files::read_file(custom_filename)?;
303        let s = decode_to_string(output_encoding, &data, true)?;
304        let data_area: DataArea = if self.custom_yaml {
305            serde_yaml_ng::from_str(&s)?
306        } else {
307            serde_json::from_str(&s)?
308        };
309        file.write_all(b"TJS2100\0")?;
310        file.write_u32(0)?; // placeholder for file size
311        data_area.pack(&mut file, false, encoding, &None)?;
312        file.write_all(&self.remaing)?;
313        let file_size = file.stream_length()?;
314        file.write_u32_at(8, file_size as u32)?; // write actual file size
315        Ok(())
316    }
317}