1use 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 #[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 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}