msg_tool\scripts\escude/
list.rs

1//! Escu:de List File (.bin)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::{decode_to_string, encode_string};
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)]
13/// Escu:de List Builder
14pub struct EscudeBinListBuilder {}
15
16impl EscudeBinListBuilder {
17    /// Creates a new instance of `EscudeBinListBuilder`
18    pub const fn new() -> Self {
19        EscudeBinListBuilder {}
20    }
21}
22
23impl ScriptBuilder for EscudeBinListBuilder {
24    fn default_encoding(&self) -> Encoding {
25        Encoding::Cp932
26    }
27
28    fn build_script(
29        &self,
30        data: Vec<u8>,
31        filename: &str,
32        encoding: Encoding,
33        _archive_encoding: Encoding,
34        config: &ExtraConfig,
35        _archive: Option<&Box<dyn Script>>,
36    ) -> Result<Box<dyn Script>> {
37        Ok(Box::new(EscudeBinList::new(
38            data, filename, encoding, config,
39        )?))
40    }
41
42    fn extensions(&self) -> &'static [&'static str] {
43        &["bin"]
44    }
45
46    fn script_type(&self) -> &'static ScriptType {
47        &ScriptType::EscudeList
48    }
49
50    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
51        if buf_len > 4 && buf.starts_with(b"LIST") {
52            return Some(255);
53        }
54        None
55    }
56
57    fn can_create_file(&self) -> bool {
58        true
59    }
60
61    fn create_file<'a>(
62        &'a self,
63        filename: &'a str,
64        writer: Box<dyn WriteSeek + 'a>,
65        encoding: Encoding,
66        file_encoding: Encoding,
67        config: &ExtraConfig,
68    ) -> Result<()> {
69        create_file(
70            filename,
71            writer,
72            encoding,
73            file_encoding,
74            config.custom_yaml,
75        )
76    }
77}
78
79#[derive(Debug)]
80/// Escu:de Binary List
81pub struct EscudeBinList {
82    /// List of entries in the Escu:de list
83    pub entries: Vec<ListEntry>,
84    custom_yaml: bool,
85}
86
87impl EscudeBinList {
88    /// Creates a new `EscudeBinList` from the given data
89    ///
90    /// * `data` - The reader to read the data from
91    /// * `filename` - The name of the file
92    /// * `encoding` - The encoding
93    /// * `config` - Extra configuration options
94    pub fn new(
95        data: Vec<u8>,
96        filename: &str,
97        encoding: Encoding,
98        config: &ExtraConfig,
99    ) -> Result<Self> {
100        let mut reader = MemReader::new(data);
101        let mut magic = [0; 4];
102        reader.read_exact(&mut magic)?;
103        if &magic != b"LIST" {
104            return Err(anyhow::anyhow!("Invalid Escude list file format"));
105        }
106        let wsize = reader.read_u32()?;
107        let mut entries = Vec::new();
108        loop {
109            let current = reader.stream_position()?;
110            if current as usize >= wsize as usize + 8 {
111                break;
112            }
113            let id = reader.read_u32()?;
114            let size = reader.read_u32()?;
115            let data = reader.read_exact_vec(size as usize)?;
116            entries.push(ListEntry {
117                id: id,
118                data: ListData::Unknown(data),
119            });
120        }
121        let mut s = EscudeBinList {
122            entries,
123            custom_yaml: config.custom_yaml,
124        };
125        match s.try_decode(filename, encoding) {
126            Ok(_) => {}
127            Err(e) => {
128                eprintln!("WARN: Failed to decode Escude list: {}", e);
129                crate::COUNTER.inc_warning();
130            }
131        }
132        Ok(s)
133    }
134
135    /// Attempts to decode the entries in the list based on the filename and encoding
136    ///
137    /// * `filename` - The name of the file
138    /// * `encoding` - The encoding to use for decoding
139    pub fn try_decode(&mut self, filename: &str, encoding: Encoding) -> Result<()> {
140        let filename = std::path::Path::new(filename);
141        if let Some(filename) = filename.file_name() {
142            let filename = filename.to_ascii_lowercase();
143            if filename == "enum_scr.bin" {
144                for ent in self.entries.iter_mut() {
145                    let id = ent.id;
146                    if let ListData::Unknown(unk) = &ent.data {
147                        let mut reader = MemReader::new(unk.clone());
148                        let element_size = if id == 0 {
149                            132
150                        } else if id == 1 {
151                            100
152                        } else if id == 2 {
153                            36
154                        } else if id == 3 {
155                            104
156                        } else if id == 9999 {
157                            1
158                        } else {
159                            return Err(anyhow::anyhow!("Unknown enum source ID: {}", id));
160                        };
161                        let len = unk.len();
162                        if len % element_size != 0 {
163                            return Err(anyhow::anyhow!(
164                                "Invalid enum source length: {} for ID: {}",
165                                len,
166                                id
167                            ));
168                        }
169                        let count = len / element_size;
170                        let data_entry = match id {
171                            0 => ListData::Scr(EnumScr::Scripts(
172                                reader.read_struct_vec::<ScriptT>(count, false, encoding)?,
173                            )),
174                            1 => ListData::Scr(EnumScr::Names(
175                                reader.read_struct_vec::<NameT>(count, false, encoding)?,
176                            )),
177                            2 => ListData::Scr(EnumScr::Vars(
178                                reader.read_struct_vec::<VarT>(count, false, encoding)?,
179                            )),
180                            3 => ListData::Scr(EnumScr::Scenes(
181                                reader.read_struct_vec::<SceneT>(count, false, encoding)?,
182                            )),
183                            9999 => {
184                                // Special case for unknown enum source ID
185                                ListData::Unknown(unk.clone())
186                            }
187                            _ => return Err(anyhow::anyhow!("Unknown enum source ID: {}", id)),
188                        };
189                        ent.data = data_entry;
190                    }
191                }
192            } else if filename == "enum_gfx.bin" {
193                for ent in self.entries.iter_mut() {
194                    let id = ent.id;
195                    if let ListData::Unknown(unk) = &ent.data {
196                        let mut reader = MemReader::new(unk.clone());
197                        let element_size = if id == 0 {
198                            248
199                        } else if id == 1 {
200                            248
201                        } else if id == 2 {
202                            248
203                        } else if id == 3 {
204                            112
205                        } else if id == 4 {
206                            32
207                        } else if id == 9999 {
208                            1
209                        } else {
210                            return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id));
211                        };
212                        let len = unk.len();
213                        if len % element_size != 0 {
214                            return Err(anyhow::anyhow!(
215                                "Invalid enum gfx length: {} for ID: {}",
216                                len,
217                                id
218                            ));
219                        }
220                        let count = len / element_size;
221                        let data_entry = match id {
222                            0 => ListData::Gfx(EnumGfx::Bgs(
223                                reader.read_struct_vec::<BgT>(count, false, encoding)?,
224                            )),
225                            1 => ListData::Gfx(EnumGfx::Evs(
226                                reader.read_struct_vec::<EvT>(count, false, encoding)?,
227                            )),
228                            2 => ListData::Gfx(EnumGfx::Sts(
229                                reader.read_struct_vec::<StT>(count, false, encoding)?,
230                            )),
231                            3 => ListData::Gfx(EnumGfx::Efxs(
232                                reader.read_struct_vec::<EfxT>(count, false, encoding)?,
233                            )),
234                            4 => ListData::Gfx(EnumGfx::Locs(
235                                reader.read_struct_vec::<LocT>(count, false, encoding)?,
236                            )),
237                            9999 => {
238                                // Special case for unknown enum gfx ID
239                                ListData::Unknown(unk.clone())
240                            }
241                            _ => return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id)),
242                        };
243                        ent.data = data_entry;
244                    }
245                }
246            } else if filename == "enum_snd.bin" {
247                for ent in self.entries.iter_mut() {
248                    let id = ent.id;
249                    if let ListData::Unknown(unk) = &ent.data {
250                        let mut reader = MemReader::new(unk.clone());
251                        let element_size = if id == 0 {
252                            196
253                        } else if id == 1 {
254                            128
255                        } else if id == 2 {
256                            128
257                        } else if id == 3 {
258                            128
259                        } else if id == 9999 {
260                            1
261                        } else {
262                            return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id));
263                        };
264                        let len = unk.len();
265                        if len % element_size != 0 {
266                            return Err(anyhow::anyhow!(
267                                "Invalid enum sound length: {} for ID: {}",
268                                len,
269                                id
270                            ));
271                        }
272                        let count = len / element_size;
273                        let data_entry = match id {
274                            0 => ListData::Snd(EnumSnd::Bgm(
275                                reader.read_struct_vec::<BgmT>(count, false, encoding)?,
276                            )),
277                            1 => ListData::Snd(EnumSnd::Amb(
278                                reader.read_struct_vec::<AmbT>(count, false, encoding)?,
279                            )),
280                            2 => ListData::Snd(EnumSnd::Se(
281                                reader.read_struct_vec::<SeT>(count, false, encoding)?,
282                            )),
283                            3 => ListData::Snd(EnumSnd::Sfx(
284                                reader.read_struct_vec::<SfxT>(count, false, encoding)?,
285                            )),
286                            9999 => {
287                                // Special case for unknown enum sound ID
288                                ListData::Unknown(unk.clone())
289                            }
290                            _ => return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id)),
291                        };
292                        ent.data = data_entry;
293                    }
294                }
295            }
296        }
297        Ok(())
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 entries: Vec<ListEntry> = 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"LIST")?;
316    writer.write_u32(0)?; // Placeholder for size
317    let mut total_size = 0;
318    for entry in entries {
319        let cur_pos = writer.stream_position()?;
320        writer.write_u32(entry.id)?;
321        writer.write_u32(0)?; // Placeholder for size
322        entry.data.pack(&mut writer, false, encoding)?;
323        let end_pos = writer.stream_position()?;
324        let size = (end_pos - cur_pos - 8) as u32; // 8 bytes for id and size
325        writer.seek(std::io::SeekFrom::Start(cur_pos + 4))?; // Seek to size position
326        writer.write_u32(size)?;
327        writer.seek(std::io::SeekFrom::Start(end_pos))?; // Seek to end
328        total_size += size + 8;
329    }
330    writer.seek(std::io::SeekFrom::Start(4))?; // Seek back to size position
331    writer.write_u32(total_size)?;
332    writer.flush()?;
333    Ok(())
334}
335
336impl Script for EscudeBinList {
337    fn default_output_script_type(&self) -> OutputScriptType {
338        OutputScriptType::Custom
339    }
340
341    fn is_output_supported(&self, output: OutputScriptType) -> bool {
342        matches!(output, OutputScriptType::Custom)
343    }
344
345    fn default_format_type(&self) -> FormatOptions {
346        FormatOptions::None
347    }
348
349    fn custom_output_extension(&self) -> &'static str {
350        if self.custom_yaml { "yaml" } else { "json" }
351    }
352
353    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
354        let s = if self.custom_yaml {
355            serde_yaml_ng::to_string(&self.entries)
356                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
357        } else {
358            serde_json::to_string(&self.entries)
359                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
360        };
361        let mut writer = crate::utils::files::write_file(filename)?;
362        let s = encode_string(encoding, &s, false)?;
363        writer.write_all(&s)?;
364        writer.flush()?;
365        Ok(())
366    }
367
368    fn custom_import<'a>(
369        &'a self,
370        custom_filename: &'a str,
371        writer: Box<dyn WriteSeek + 'a>,
372        encoding: Encoding,
373        output_encoding: Encoding,
374    ) -> Result<()> {
375        create_file(
376            custom_filename,
377            writer,
378            encoding,
379            output_encoding,
380            self.custom_yaml,
381        )
382    }
383}
384
385#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
386/// Script entry in the Escu:de list
387pub struct ScriptT {
388    #[fstring = 64]
389    #[fstring_pad = 0x20]
390    /// File name
391    pub file: String,
392    /// Script ID
393    pub source: u32,
394    #[fstring = 64]
395    #[fstring_pad = 0x20]
396    /// Script title
397    pub title: String,
398}
399
400#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
401/// Name entry in the Escu:de list
402pub struct NameT {
403    #[fstring = 64]
404    #[fstring_pad = 0x20]
405    /// Name of the character
406    pub text: String,
407    /// Text color
408    pub color: u32,
409    #[fstring = 32]
410    #[fstring_pad = 0x20]
411    /// Face image file name
412    pub face: String,
413}
414
415#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
416/// Variable entry in the Escu:de list
417pub struct VarT {
418    /// Variable name
419    #[fstring = 32]
420    #[fstring_pad = 0x20]
421    pub name: String,
422    /// Variable value
423    pub value: u16,
424    /// Variable flag
425    pub flag: u16,
426}
427
428#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
429/// Scene entry in the Escu:de list
430pub struct SceneT {
431    /// The scene script ID
432    pub script: u32,
433    /// The scene name
434    #[fstring = 64]
435    #[fstring_pad = 0x20]
436    pub name: String,
437    /// The scene thumbail image file name
438    #[fstring = 32]
439    #[fstring_pad = 0x20]
440    pub thumbnail: String,
441    /// The scene order in the scene (Extra)
442    pub order: i32,
443}
444
445#[derive(Debug, Serialize, Deserialize, StructPack)]
446#[serde(tag = "type", content = "data")]
447/// Enum for different types of script data in the Escu:de list (enum._scr.bin)
448pub enum EnumScr {
449    /// Scripts data
450    Scripts(Vec<ScriptT>),
451    /// Names data
452    Names(Vec<NameT>),
453    /// Variables data
454    Vars(Vec<VarT>),
455    /// Scenes data
456    Scenes(Vec<SceneT>),
457}
458
459#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
460/// Background entry in the Escu:de list
461pub struct BgT {
462    /// Background image name
463    #[fstring = 32]
464    #[fstring_pad = 0x20]
465    name: String,
466    /// Background image file name
467    #[fstring = 64]
468    #[fstring_pad = 0x20]
469    file: String,
470    #[fstring = 128]
471    #[fstring_pad = 0x20]
472    /// Background options
473    option: String,
474    /// Background covered flag
475    coverd: u32,
476    /// Background color
477    color: u32,
478    /// Background ID
479    id: u32,
480    /// Background location ID
481    loc: u32,
482    /// Background order in the scene
483    order: i32,
484    /// Background link ID
485    link: u32,
486}
487
488#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
489/// Event image entry in the Escu:de list
490pub struct EvT {
491    /// Event image name
492    #[fstring = 32]
493    #[fstring_pad = 0x20]
494    name: String,
495    /// Event image file name
496    #[fstring = 64]
497    #[fstring_pad = 0x20]
498    file: String,
499    #[fstring = 128]
500    #[fstring_pad = 0x20]
501    /// Event image options
502    option: String,
503    /// Event image covered flag
504    coverd: u32,
505    /// Event image color
506    color: u32,
507    /// Event image ID
508    id: u32,
509    /// Event image location ID
510    loc: u32,
511    /// Event image order in the scene
512    order: i32,
513    /// Event image link ID
514    link: u32,
515}
516
517#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
518/// Stage entry in the Escu:de list
519pub struct StT {
520    #[fstring = 32]
521    #[fstring_pad = 0x20]
522    /// Stage name
523    name: String,
524    #[fstring = 64]
525    #[fstring_pad = 0x20]
526    /// Stage file name
527    file: String,
528    #[fstring = 128]
529    #[fstring_pad = 0x20]
530    /// Stage options
531    option: String,
532    /// Stage covered flag
533    coverd: u32,
534    /// Stage color
535    color: u32,
536    /// Stage ID
537    id: u32,
538    /// Stage location ID
539    loc: u32,
540    /// Stage order in the scene
541    order: i32,
542    /// Stage link ID
543    link: u32,
544}
545
546#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
547/// Effect image entry in the Escu:de list
548pub struct EfxT {
549    /// Effect image name
550    #[fstring = 32]
551    #[fstring_pad = 0x20]
552    name: String,
553    /// Effect image file name
554    #[fstring = 64]
555    #[fstring_pad = 0x20]
556    file: String,
557    /// Effect image options
558    spot: i32,
559    /// Effect image x position
560    dx: i32,
561    /// Effect image y position
562    dy: i32,
563    /// Effect image loop flag
564    r#loop: bool,
565    #[fvec = 3]
566    #[serde(skip, default = "exft_padding")]
567    /// padding for alignment
568    padding: Vec<u8>,
569}
570
571fn exft_padding() -> Vec<u8> {
572    vec![0; 3]
573}
574
575#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
576/// Point
577pub struct Point {
578    /// X coordinate
579    x: i16,
580    /// Y coordinate
581    y: i16,
582}
583
584#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
585/// Location entry in the Escu:de list
586pub struct LocT {
587    /// List of points
588    #[fvec = 8]
589    pt: Vec<Point>,
590}
591
592#[derive(Debug, Serialize, Deserialize, StructPack)]
593#[serde(tag = "type", content = "data")]
594/// Enum for different types of graphics data in the Escu:de list (enum_gfx.bin)
595pub enum EnumGfx {
596    /// Backgrounds data
597    Bgs(Vec<BgT>),
598    /// Event images data
599    Evs(Vec<EvT>),
600    /// Stages data
601    Sts(Vec<StT>),
602    /// Effects data
603    Efxs(Vec<EfxT>),
604    /// Locations data
605    Locs(Vec<LocT>),
606}
607
608#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
609/// Background music entry in the Escu:de list
610pub struct BgmT {
611    #[fstring = 64]
612    #[fstring_pad = 0x20]
613    /// Background music name
614    pub name: String,
615    #[fstring = 64]
616    #[fstring_pad = 0x20]
617    /// Background music file name
618    pub file: String,
619    #[fstring = 64]
620    #[fstring_pad = 0x20]
621    /// Background music title
622    pub title: String,
623    /// Background music order in the scene
624    pub order: i32,
625}
626
627#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
628/// Ambient sound entry in the Escu:de list
629pub struct AmbT {
630    #[fstring = 64]
631    #[fstring_pad = 0x20]
632    /// Ambient sound name
633    pub name: String,
634    #[fstring = 64]
635    #[fstring_pad = 0x20]
636    /// Ambient sound file name
637    pub file: String,
638}
639
640#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
641/// Sound effect entry in the Escu:de list
642pub struct SeT {
643    #[fstring = 64]
644    #[fstring_pad = 0x20]
645    /// Sound effect name
646    pub name: String,
647    #[fstring = 64]
648    #[fstring_pad = 0x20]
649    /// Sound effect file name
650    pub file: String,
651}
652
653#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
654/// Sound effect (SFX) entry in the Escu:de list
655pub struct SfxT {
656    #[fstring = 64]
657    #[fstring_pad = 0x20]
658    /// SFX name
659    pub name: String,
660    #[fstring = 64]
661    #[fstring_pad = 0x20]
662    /// SFX file name
663    pub file: String,
664}
665
666#[derive(Debug, Serialize, Deserialize, StructPack)]
667#[serde(tag = "type", content = "data")]
668/// Enum for different types of sound data in the Escu:de list (enum_snd.bin)
669pub enum EnumSnd {
670    /// Background music data
671    Bgm(Vec<BgmT>),
672    /// Ambient sound data
673    Amb(Vec<AmbT>),
674    /// Sound effect data
675    Se(Vec<SeT>),
676    /// Sound effect (SFX) data
677    Sfx(Vec<SfxT>),
678}
679
680#[derive(Debug, Serialize, Deserialize, StructPack)]
681#[serde(tag = "type", content = "data")]
682/// Enum for different types of data in the Escu:de list
683pub enum ListData {
684    /// Script data
685    Scr(EnumScr),
686    /// Graphics data
687    Gfx(EnumGfx),
688    /// Sound data
689    Snd(EnumSnd),
690    /// Unknown data
691    Unknown(Vec<u8>),
692}
693
694#[derive(Debug, Serialize, Deserialize)]
695/// Entry in the Escu:de list
696pub struct ListEntry {
697    id: u32,
698    /// The data associated with the entry
699    pub data: ListData,
700}