msg_tool\scripts\yuris/
yscfg.rs

1//! Yu-Ris YSCFG files
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use msg_tool_macro::*;
9use serde::{Deserialize, Serialize};
10use std::io::{Read, Seek, Write};
11
12#[derive(Debug)]
13pub struct YSCFGBuilder {}
14
15impl YSCFGBuilder {
16    /// Creates a new instance of `YSERBuilder`
17    pub const fn new() -> Self {
18        YSCFGBuilder {}
19    }
20}
21
22impl ScriptBuilder for YSCFGBuilder {
23    fn default_encoding(&self) -> Encoding {
24        Encoding::Cp932
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 + Send + Sync>> {
36        Ok(Box::new(YSCFG::new(MemReader::new(buf), encoding, config)?))
37    }
38
39    fn extensions(&self) -> &'static [&'static str] {
40        &["ybn"]
41    }
42
43    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
44        if buf_len >= 4 && buf.starts_with(b"YSCF") {
45            return Some(20);
46        }
47        None
48    }
49
50    fn script_type(&self) -> &'static ScriptType {
51        &ScriptType::YurisYSCFG
52    }
53
54    fn can_create_file(&self) -> bool {
55        true
56    }
57
58    fn create_file<'a>(
59        &'a self,
60        filename: &'a str,
61        writer: Box<dyn WriteSeek + 'a>,
62        encoding: Encoding,
63        file_encoding: Encoding,
64        config: &ExtraConfig,
65    ) -> Result<()> {
66        create_file(
67            filename,
68            writer,
69            encoding,
70            file_encoding,
71            config.custom_yaml,
72        )
73    }
74}
75
76#[derive(Debug, StructPack, StructUnpack, Deserialize, Serialize)]
77struct YSCFGData {
78    engine: u32,
79    unk0: u32,
80    compile: u32,
81    screen_width: u32,
82    screen_height: u32,
83    enable: u32,
84    image_type_slots: [u8; 8],
85    sound_type_slots: [u8; 4],
86    thread: u32,
87    debug_mode: u32,
88    sound: u32,
89    window_resize: u32,
90    window_frame: u32,
91    file_priority_dev: u32,
92    file_priority_debug: u32,
93    file_priority_release: u32,
94    unk1: u32,
95    // #TODO: Better version handle
96    #[skip_pack_if(self.engine < 500)]
97    #[skip_unpack_if(engine < 500)]
98    unk2: u32,
99    #[skip_pack_if(self.engine < 500)]
100    #[skip_unpack_if(engine < 500)]
101    unk3: u32,
102    #[skip_pack_if(self.engine < 500)]
103    #[skip_unpack_if(engine < 500)]
104    unk4: u32,
105    #[pstring(u16)]
106    caption: String,
107}
108
109#[derive(Debug)]
110pub struct YSCFG {
111    data: YSCFGData,
112    custom_yaml: bool,
113}
114
115impl YSCFG {
116    pub fn new<T: Read + Seek>(
117        mut reader: T,
118        encoding: Encoding,
119        config: &ExtraConfig,
120    ) -> Result<Self> {
121        let mut sig = [0; 4];
122        reader.read_exact(&mut sig)?;
123        if &sig != b"YSCF" {
124            anyhow::bail!("Unsupported YSCFG file.");
125        }
126        let data = YSCFGData::unpack(&mut reader, false, encoding, &None)?;
127        Ok(Self {
128            data,
129            custom_yaml: config.custom_yaml,
130        })
131    }
132}
133
134impl Script for YSCFG {
135    fn default_output_script_type(&self) -> OutputScriptType {
136        OutputScriptType::Custom
137    }
138
139    fn is_output_supported(&self, output: OutputScriptType) -> bool {
140        matches!(output, OutputScriptType::Custom)
141    }
142
143    fn default_format_type(&self) -> FormatOptions {
144        FormatOptions::None
145    }
146
147    fn custom_output_extension(&self) -> &'static str {
148        if self.custom_yaml { "yaml" } else { "json" }
149    }
150
151    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
152        let s = if self.custom_yaml {
153            serde_yaml_ng::to_string(&self.data)
154                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
155        } else {
156            serde_json::to_string_pretty(&self.data)
157                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
158        };
159        let mut writer = crate::utils::files::write_file(filename)?;
160        let s = encode_string(encoding, &s, false)?;
161        writer.write_all(&s)?;
162        writer.flush()?;
163        Ok(())
164    }
165
166    fn custom_import<'a>(
167        &'a self,
168        custom_filename: &'a str,
169        file: Box<dyn WriteSeek + 'a>,
170        encoding: Encoding,
171        output_encoding: Encoding,
172    ) -> Result<()> {
173        create_file(
174            custom_filename,
175            file,
176            encoding,
177            output_encoding,
178            self.custom_yaml,
179        )
180    }
181}
182
183fn create_file<'a>(
184    custom_filename: &'a str,
185    mut writer: Box<dyn WriteSeek + 'a>,
186    encoding: Encoding,
187    output_encoding: Encoding,
188    yaml: bool,
189) -> Result<()> {
190    let input = crate::utils::files::read_file(custom_filename)?;
191    let s = decode_to_string(output_encoding, &input, true)?;
192    let data: YSCFGData = if yaml {
193        serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
194    } else {
195        serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
196    };
197    writer.write_all(b"YSCF")?;
198    data.pack(&mut writer, false, encoding, &None)?;
199    Ok(())
200}