msg_tool\scripts\yuris/
ystl.rs

1//! Yu-Ris YSTL(file list) file (.ybn)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use chrono::TimeZone;
9use chrono::Timelike;
10use chrono::{DateTime, Local, Utc};
11use msg_tool_macro::*;
12use serde::{Deserialize, Serialize};
13use std::any::Any;
14use std::io::{Read, Seek, Write};
15use std::ops::{Deref, DerefMut};
16
17#[derive(Debug, Serialize, Deserialize)]
18struct YSTLData {
19    version: u32,
20    entries: Vec<YSTLEntry>,
21}
22
23impl StructUnpack for YSTLData {
24    fn unpack<R: Read + Seek>(
25        reader: &mut R,
26        big: bool,
27        encoding: Encoding,
28        info: &Option<Box<dyn Any>>,
29    ) -> Result<Self> {
30        let version = u32::unpack(reader, big, encoding, info)?;
31        let ninfo = Box::new(version) as Box<dyn Any>;
32        let count = u32::unpack(reader, big, encoding, info)?;
33        let entries = reader.read_struct_vec(count as usize, big, encoding, &Some(ninfo))?;
34        Ok(Self { version, entries })
35    }
36}
37
38impl StructPack for YSTLData {
39    fn pack<W: Write>(
40        &self,
41        writer: &mut W,
42        big: bool,
43        encoding: Encoding,
44        info: &Option<Box<dyn Any>>,
45    ) -> Result<()> {
46        self.version.pack(writer, big, encoding, info)?;
47        let ninfo = Box::new(self.version) as Box<dyn Any>;
48        let count = self.entries.len() as u32;
49        count.pack(writer, big, encoding, info)?;
50        let info = &Some(ninfo);
51        for (i, entry) in self.entries.iter().enumerate() {
52            if entry.seq == i as u32 {
53                entry.pack(writer, big, encoding, info)?;
54            } else {
55                let mut entry = entry.clone();
56                entry.seq = i as u32;
57                entry.pack(writer, big, encoding, info)?;
58            }
59        }
60        Ok(())
61    }
62}
63
64#[derive(Debug, Clone, Deserialize, Serialize)]
65#[serde(transparent)]
66struct FileSystemTime(DateTime<Local>);
67
68impl Deref for FileSystemTime {
69    type Target = DateTime<Local>;
70
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl DerefMut for FileSystemTime {
77    fn deref_mut(&mut self) -> &mut Self::Target {
78        &mut self.0
79    }
80}
81
82impl std::fmt::Display for FileSystemTime {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        self.0.fmt(f)
85    }
86}
87
88impl StructUnpack for FileSystemTime {
89    fn unpack<R: Read + Seek>(
90        reader: &mut R,
91        big: bool,
92        encoding: Encoding,
93        info: &Option<Box<dyn std::any::Any>>,
94    ) -> Result<Self> {
95        let high = u32::unpack(reader, big, encoding, info)?;
96        let low = u32::unpack(reader, big, encoding, info)?;
97        let time = low as u64 | ((high as u64) << 32);
98        const FILETIME_OFFSET: u64 = 116_444_736_000_000_000;
99        if time < FILETIME_OFFSET {
100            anyhow::bail!("Time to small.");
101        }
102        let intervals_since_1970 = time - FILETIME_OFFSET;
103        let seconds = (intervals_since_1970 / 10_000_000) as i64;
104        let nsecs = ((intervals_since_1970 % 10_000_000) * 100) as u32;
105        let time = Utc
106            .timestamp_opt(seconds, nsecs)
107            .single()
108            .ok_or_else(|| anyhow::anyhow!("Time is not existed or ambiguous."))?;
109        let time = time.with_timezone(&Local);
110        Ok(Self(time))
111    }
112}
113
114impl StructPack for FileSystemTime {
115    fn pack<W: Write>(
116        &self,
117        writer: &mut W,
118        big: bool,
119        encoding: Encoding,
120        info: &Option<Box<dyn std::any::Any>>,
121    ) -> Result<()> {
122        let time = self.0.with_timezone(&Utc);
123        let tseconds = time.timestamp();
124        let nsecs = time.nanosecond() / 100;
125        const FILETIME_OFFSET: u64 = 116_444_736_000_000_000;
126        let seconds = (tseconds as u64)
127            .checked_mul(10_000_000)
128            .ok_or_else(|| anyhow::anyhow!("Too big time"))?
129            .checked_add(nsecs as u64)
130            .ok_or_else(|| anyhow::anyhow!("Too big time"))?
131            .checked_add(FILETIME_OFFSET)
132            .ok_or_else(|| anyhow::anyhow!("Too big time"))?;
133        let high = (seconds >> 32) as u32;
134        let low = seconds as u32;
135        high.pack(writer, big, encoding, info)?;
136        low.pack(writer, big, encoding, info)?;
137        Ok(())
138    }
139}
140
141#[derive(Debug, Clone, Deserialize, Serialize, StructUnpack, StructPack)]
142struct YSTLEntry {
143    seq: u32,
144    #[pstring(u32)]
145    path: String,
146    modification_time: FileSystemTime,
147    num_variables: u32,
148    num_labels: u32,
149    // TODO: version may need more check
150    #[skip_pack_if(get_info_as_version(__info)? < 300)]
151    #[skip_unpack_if(get_info_as_version(__info)? < 300)]
152    num_texts: u32,
153}
154
155fn get_info_as_version(info: &Option<Box<dyn Any>>) -> Result<u32> {
156    Ok(*info
157        .as_ref()
158        .ok_or_else(|| anyhow::anyhow!("info not found"))?
159        .downcast_ref()
160        .ok_or_else(|| anyhow::anyhow!("not YSTBHeader"))?)
161}
162
163#[derive(Debug)]
164pub struct YSTLBuilder {}
165
166impl YSTLBuilder {
167    /// Creates a new instance of `YSTLBuilder`
168    pub const fn new() -> Self {
169        YSTLBuilder {}
170    }
171}
172
173impl ScriptBuilder for YSTLBuilder {
174    fn default_encoding(&self) -> Encoding {
175        Encoding::Cp932
176    }
177
178    fn build_script(
179        &self,
180        buf: Vec<u8>,
181        _filename: &str,
182        encoding: Encoding,
183        _archive_encoding: Encoding,
184        config: &ExtraConfig,
185        _archive: Option<&Box<dyn Script>>,
186    ) -> Result<Box<dyn Script + Send + Sync>> {
187        Ok(Box::new(YSTL::new(MemReader::new(buf), encoding, config)?))
188    }
189
190    fn extensions(&self) -> &'static [&'static str] {
191        &["ybn"]
192    }
193
194    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
195        if buf_len >= 4 && buf.starts_with(b"YSTL") {
196            return Some(20);
197        }
198        None
199    }
200
201    fn script_type(&self) -> &'static ScriptType {
202        &ScriptType::YurisYSTL
203    }
204
205    fn can_create_file(&self) -> bool {
206        true
207    }
208
209    fn create_file<'a>(
210        &'a self,
211        filename: &'a str,
212        writer: Box<dyn WriteSeek + 'a>,
213        encoding: Encoding,
214        file_encoding: Encoding,
215        config: &ExtraConfig,
216    ) -> Result<()> {
217        create_file(
218            filename,
219            writer,
220            encoding,
221            file_encoding,
222            config.custom_yaml,
223        )
224    }
225}
226
227#[derive(Debug)]
228pub struct YSTL {
229    data: YSTLData,
230    custom_yaml: bool,
231}
232
233impl YSTL {
234    pub fn new<T: Read + Seek>(
235        mut reader: T,
236        encoding: Encoding,
237        config: &ExtraConfig,
238    ) -> Result<Self> {
239        let mut sig = [0; 4];
240        reader.read_exact(&mut sig)?;
241        if &sig != b"YSTL" {
242            anyhow::bail!("Unsupported YSTL file.");
243        }
244        let data = YSTLData::unpack(&mut reader, false, encoding, &None)?;
245        Ok(Self {
246            data,
247            custom_yaml: config.custom_yaml,
248        })
249    }
250}
251
252impl Script for YSTL {
253    fn default_output_script_type(&self) -> OutputScriptType {
254        OutputScriptType::Custom
255    }
256
257    fn is_output_supported(&self, output: OutputScriptType) -> bool {
258        matches!(output, OutputScriptType::Custom)
259    }
260
261    fn default_format_type(&self) -> FormatOptions {
262        FormatOptions::None
263    }
264
265    fn custom_output_extension(&self) -> &'static str {
266        if self.custom_yaml { "yaml" } else { "json" }
267    }
268
269    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
270        let s = if self.custom_yaml {
271            serde_yaml_ng::to_string(&self.data)
272                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
273        } else {
274            serde_json::to_string_pretty(&self.data)
275                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
276        };
277        let mut writer = crate::utils::files::write_file(filename)?;
278        let s = encode_string(encoding, &s, false)?;
279        writer.write_all(&s)?;
280        writer.flush()?;
281        Ok(())
282    }
283
284    fn custom_import<'a>(
285        &'a self,
286        custom_filename: &'a str,
287        file: Box<dyn WriteSeek + 'a>,
288        encoding: Encoding,
289        output_encoding: Encoding,
290    ) -> Result<()> {
291        create_file(
292            custom_filename,
293            file,
294            encoding,
295            output_encoding,
296            self.custom_yaml,
297        )
298    }
299}
300
301fn create_file<'a>(
302    custom_filename: &'a str,
303    mut writer: Box<dyn WriteSeek + 'a>,
304    encoding: Encoding,
305    output_encoding: Encoding,
306    yaml: bool,
307) -> Result<()> {
308    let input = crate::utils::files::read_file(custom_filename)?;
309    let s = decode_to_string(output_encoding, &input, true)?;
310    let mut data: YSTLData = if yaml {
311        serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
312    } else {
313        serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
314    };
315    writer.write_all(b"YSTL")?;
316    writer.write_u32(data.version)?;
317    writer.write_u32(data.entries.len() as u32)?;
318    let info = Box::new(data.version) as Box<dyn Any>;
319    let info = &Some(info);
320    for (i, entry) in data.entries.iter_mut().enumerate() {
321        entry.seq = i as u32;
322        entry.pack(&mut writer, false, encoding, info)?;
323    }
324    Ok(())
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330    #[test]
331    fn test_unpack_file_system_time() {
332        let mut reader = MemReaderRef::new(b" \x0c\xd8\x01\x00k`\xdd");
333        let ts = FileSystemTime::unpack(&mut reader, false, Encoding::Cp932, &None).unwrap();
334        println!("{}", ts);
335        let utc = ts.to_utc();
336        let t = serde_json::to_string(&utc).unwrap();
337        assert_eq!(t, "\"2022-01-18T04:07:10Z\"");
338    }
339    #[test]
340    fn test_pack_file_system_time() {
341        let ts: FileSystemTime = serde_json::from_str("\"2022-01-18T13:07:10+09:00\"").unwrap();
342        let mut buf = [0; 8];
343        let mut writer = MemWriterRef::new(&mut buf);
344        ts.pack(&mut writer, false, Encoding::Cp932, &None).unwrap();
345        assert_eq!(&buf, b" \x0c\xd8\x01\x00k`\xdd");
346    }
347}