msg_tool\scripts\yuris/
yscm.rs

1//! Yu-Ris YSCM files
2use super::types::*;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::*;
7use crate::utils::struct_pack::*;
8use anyhow::Result;
9use serde::{Deserialize, Serialize};
10use std::io::{Read, Seek};
11
12#[derive(Debug)]
13pub struct YSCMBuilder {}
14
15impl YSCMBuilder {
16    /// Creates a new instance of `YSCMBuilder`
17    pub const fn new() -> Self {
18        YSCMBuilder {}
19    }
20}
21
22impl ScriptBuilder for YSCMBuilder {
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(YSCM::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"YSCM") {
45            return Some(20);
46        }
47        None
48    }
49
50    fn script_type(&self) -> &'static ScriptType {
51        &ScriptType::YurisYSCM
52    }
53}
54
55#[derive(Debug, Deserialize, Serialize)]
56pub(crate) struct YSCMData {
57    pub engine: u32,
58    pub opcode_length: u32,
59    pub unk: u32,
60    pub opcodes: Vec<CodeMeta>,
61    pub errmsgs: Vec<String>,
62    pub unk_tbl: Vec<u8>,
63}
64
65impl StructUnpack for YSCMData {
66    fn unpack<R: Read + Seek>(
67        reader: &mut R,
68        big: bool,
69        encoding: Encoding,
70        info: &Option<Box<dyn std::any::Any>>,
71    ) -> Result<Self> {
72        let engine = u32::unpack(reader, big, encoding, info)?;
73        let opcode_length = u32::unpack(reader, big, encoding, info)?;
74        let unk = u32::unpack(reader, big, encoding, info)?;
75        let opcodes = reader.read_struct_vec(opcode_length as usize, big, encoding, info)?;
76        let target_len = reader.stream_length()? - 0x100;
77        let mut errmsgs = Vec::new();
78        while reader.stream_position()? < target_len {
79            let s = reader.read_cstring()?;
80            errmsgs.push(decode_to_string(encoding, s.as_bytes(), true)?);
81        }
82        let unk_tbl = reader.read_exact_vec(0x100)?;
83        Ok(Self {
84            engine,
85            opcode_length,
86            unk,
87            opcodes,
88            errmsgs,
89            unk_tbl,
90        })
91    }
92}
93
94#[derive(Debug)]
95pub struct YSCM {
96    pub(crate) data: YSCMData,
97    custom_yaml: bool,
98}
99
100impl YSCM {
101    pub fn new<T: Read + Seek>(
102        mut reader: T,
103        encoding: Encoding,
104        config: &ExtraConfig,
105    ) -> Result<Self> {
106        let mut sig = [0; 4];
107        reader.read_exact(&mut sig)?;
108        if &sig != b"YSCM" {
109            anyhow::bail!("Unsupported YSCM file.");
110        }
111        let data = YSCMData::unpack(&mut reader, false, encoding, &None)?;
112        Ok(Self {
113            data,
114            custom_yaml: config.custom_yaml,
115        })
116    }
117}
118
119impl Script for YSCM {
120    fn default_output_script_type(&self) -> OutputScriptType {
121        OutputScriptType::Custom
122    }
123
124    fn is_output_supported(&self, output: OutputScriptType) -> bool {
125        matches!(output, OutputScriptType::Custom)
126    }
127
128    fn default_format_type(&self) -> FormatOptions {
129        FormatOptions::None
130    }
131
132    fn custom_output_extension(&self) -> &'static str {
133        if self.custom_yaml { "yaml" } else { "json" }
134    }
135
136    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
137        let s = if self.custom_yaml {
138            serde_yaml_ng::to_string(&self.data)
139                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
140        } else {
141            serde_json::to_string_pretty(&self.data)
142                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
143        };
144        let mut writer = crate::utils::files::write_file(filename)?;
145        let s = encode_string(encoding, &s, false)?;
146        writer.write_all(&s)?;
147        writer.flush()?;
148        Ok(())
149    }
150}