//! Escu:de List File (.bin) use crate::ext::io::*; use crate::scripts::base::*; use crate::types::*; use crate::utils::encoding::{decode_to_string, encode_string}; use crate::utils::struct_pack::*; use anyhow::Result; use msg_tool_macro::*; use serde::{Deserialize, Serialize}; use std::io::{Read, Seek, Write}; #[derive(Debug)] /// Escu:de List Builder pub struct EscudeBinListBuilder {} impl EscudeBinListBuilder { /// Creates a new instance of `EscudeBinListBuilder` pub const fn new() -> Self { EscudeBinListBuilder {} } } impl ScriptBuilder for EscudeBinListBuilder { fn default_encoding(&self) -> Encoding { Encoding::Cp932 } fn build_script( &self, data: Vec, filename: &str, encoding: Encoding, _archive_encoding: Encoding, config: &ExtraConfig, _archive: Option<&Box>, ) -> Result> { Ok(Box::new(EscudeBinList::new( data, filename, encoding, config, )?)) } fn extensions(&self) -> &'static [&'static str] { &["bin"] } fn script_type(&self) -> &'static ScriptType { &ScriptType::EscudeList } fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { if buf_len > 4 && buf.starts_with(b"LIST") { return Some(255); } None } fn can_create_file(&self) -> bool { true } fn create_file<'a>( &'a self, filename: &'a str, writer: Box, encoding: Encoding, file_encoding: Encoding, config: &ExtraConfig, ) -> Result<()> { create_file( filename, writer, encoding, file_encoding, config.custom_yaml, ) } } #[derive(Debug)] /// Escu:de Binary List pub struct EscudeBinList { /// List of entries in the Escu:de list pub entries: Vec, custom_yaml: bool, } impl EscudeBinList { /// Creates a new `EscudeBinList` from the given data /// /// * `data` - The reader to read the data from /// * `filename` - The name of the file /// * `encoding` - The encoding /// * `config` - Extra configuration options pub fn new( data: Vec, filename: &str, encoding: Encoding, config: &ExtraConfig, ) -> Result { let mut reader = MemReader::new(data); let mut magic = [0; 4]; reader.read_exact(&mut magic)?; if &magic != b"LIST" { return Err(anyhow::anyhow!("Invalid Escude list file format")); } let wsize = reader.read_u32()?; let mut entries = Vec::new(); loop { let current = reader.stream_position()?; if current as usize >= wsize as usize + 8 { break; } let id = reader.read_u32()?; let size = reader.read_u32()?; let data = reader.read_exact_vec(size as usize)?; entries.push(ListEntry { id: id, data: ListData::Unknown(data), }); } let mut s = EscudeBinList { entries, custom_yaml: config.custom_yaml, }; match s.try_decode(filename, encoding) { Ok(_) => {} Err(e) => { eprintln!("WARN: Failed to decode Escude list: {}", e); crate::COUNTER.inc_warning(); } } Ok(s) } /// Attempts to decode the entries in the list based on the filename and encoding /// /// * `filename` - The name of the file /// * `encoding` - The encoding to use for decoding pub fn try_decode(&mut self, filename: &str, encoding: Encoding) -> Result<()> { let filename = std::path::Path::new(filename); if let Some(filename) = filename.file_name() { let filename = filename.to_ascii_lowercase(); if filename == "enum_scr.bin" { for ent in self.entries.iter_mut() { let id = ent.id; if let ListData::Unknown(unk) = &ent.data { let mut reader = MemReader::new(unk.clone()); let mut element_size = if id == 0 { 132 } else if id == 1 { 100 } else if id == 2 { 36 } else if id == 3 { 104 } else if id == 9999 { 1 } else { return Err(anyhow::anyhow!("Unknown enum source ID: {}", id)); }; let len = unk.len(); let mut script_old = false; if id == 0 && len % element_size != 0 { element_size = 128; script_old = true; } if len % element_size != 0 { return Err(anyhow::anyhow!( "Invalid enum source length: {} for ID: {}", len, id )); } let count = len / element_size; let data_entry = match id { 0 => { if script_old { ListData::Scr(EnumScr::Scripts2( reader.read_struct_vec::( count, false, encoding, &None, )?, )) } else { ListData::Scr(EnumScr::Scripts( reader.read_struct_vec::( count, false, encoding, &None, )?, )) } } 1 => ListData::Scr(EnumScr::Names( reader.read_struct_vec::(count, false, encoding, &None)?, )), 2 => ListData::Scr(EnumScr::Vars( reader.read_struct_vec::(count, false, encoding, &None)?, )), 3 => ListData::Scr(EnumScr::Scenes( reader.read_struct_vec::(count, false, encoding, &None)?, )), 9999 => { // Special case for unknown enum source ID ListData::Unknown(unk.clone()) } _ => return Err(anyhow::anyhow!("Unknown enum source ID: {}", id)), }; ent.data = data_entry; } } } else if filename == "enum_gfx.bin" { for ent in self.entries.iter_mut() { let id = ent.id; if let ListData::Unknown(unk) = &ent.data { let mut reader = MemReader::new(unk.clone()); let element_size = if id == 0 { 248 } else if id == 1 { 248 } else if id == 2 { 248 } else if id == 3 { 112 } else if id == 4 { 32 } else if id == 9999 { 1 } else { return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id)); }; let len = unk.len(); if len % element_size != 0 { return Err(anyhow::anyhow!( "Invalid enum gfx length: {} for ID: {}", len, id )); } let count = len / element_size; let data_entry = match id { 0 => ListData::Gfx(EnumGfx::Bgs( reader.read_struct_vec::(count, false, encoding, &None)?, )), 1 => ListData::Gfx(EnumGfx::Evs( reader.read_struct_vec::(count, false, encoding, &None)?, )), 2 => ListData::Gfx(EnumGfx::Sts( reader.read_struct_vec::(count, false, encoding, &None)?, )), 3 => ListData::Gfx(EnumGfx::Efxs( reader.read_struct_vec::(count, false, encoding, &None)?, )), 4 => ListData::Gfx(EnumGfx::Locs( reader.read_struct_vec::(count, false, encoding, &None)?, )), 9999 => { // Special case for unknown enum gfx ID ListData::Unknown(unk.clone()) } _ => return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id)), }; ent.data = data_entry; } } } else if filename == "enum_snd.bin" { for ent in self.entries.iter_mut() { let id = ent.id; if let ListData::Unknown(unk) = &ent.data { let mut reader = MemReader::new(unk.clone()); let element_size = if id == 0 { 196 } else if id == 1 { 128 } else if id == 2 { 128 } else if id == 3 { 128 } else if id == 9999 { 1 } else { return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id)); }; let len = unk.len(); if len % element_size != 0 { return Err(anyhow::anyhow!( "Invalid enum sound length: {} for ID: {}", len, id )); } let count = len / element_size; let data_entry = match id { 0 => ListData::Snd(EnumSnd::Bgm( reader.read_struct_vec::(count, false, encoding, &None)?, )), 1 => ListData::Snd(EnumSnd::Amb( reader.read_struct_vec::(count, false, encoding, &None)?, )), 2 => ListData::Snd(EnumSnd::Se( reader.read_struct_vec::(count, false, encoding, &None)?, )), 3 => ListData::Snd(EnumSnd::Sfx( reader.read_struct_vec::(count, false, encoding, &None)?, )), 9999 => { // Special case for unknown enum sound ID ListData::Unknown(unk.clone()) } _ => return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id)), }; ent.data = data_entry; } } } } Ok(()) } } fn create_file<'a>( custom_filename: &'a str, mut writer: Box, encoding: Encoding, output_encoding: Encoding, yaml: bool, ) -> Result<()> { let input = crate::utils::files::read_file(custom_filename)?; let s = decode_to_string(output_encoding, &input, true)?; let entries: Vec = if yaml { serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))? } else { serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))? }; writer.write_all(b"LIST")?; writer.write_u32(0)?; // Placeholder for size let mut total_size = 0; for entry in entries { let cur_pos = writer.stream_position()?; writer.write_u32(entry.id)?; writer.write_u32(0)?; // Placeholder for size entry.data.pack(&mut writer, false, encoding, &None)?; let end_pos = writer.stream_position()?; let size = (end_pos - cur_pos - 8) as u32; // 8 bytes for id and size writer.seek(std::io::SeekFrom::Start(cur_pos + 4))?; // Seek to size position writer.write_u32(size)?; writer.seek(std::io::SeekFrom::Start(end_pos))?; // Seek to end total_size += size + 8; } writer.seek(std::io::SeekFrom::Start(4))?; // Seek back to size position writer.write_u32(total_size)?; writer.flush()?; Ok(()) } impl Script for EscudeBinList { fn default_output_script_type(&self) -> OutputScriptType { OutputScriptType::Custom } fn is_output_supported(&self, output: OutputScriptType) -> bool { matches!(output, OutputScriptType::Custom) } fn default_format_type(&self) -> FormatOptions { FormatOptions::None } fn custom_output_extension(&self) -> &'static str { if self.custom_yaml { "yaml" } else { "json" } } fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> { let s = if self.custom_yaml { serde_yaml_ng::to_string(&self.entries) .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? } else { serde_json::to_string_pretty(&self.entries) .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))? }; let mut writer = crate::utils::files::write_file(filename)?; let s = encode_string(encoding, &s, false)?; writer.write_all(&s)?; writer.flush()?; Ok(()) } fn custom_import<'a>( &'a self, custom_filename: &'a str, writer: Box, encoding: Encoding, output_encoding: Encoding, ) -> Result<()> { create_file( custom_filename, writer, encoding, output_encoding, self.custom_yaml, ) } } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Script entry in the Escu:de list pub struct ScriptT { #[fstring = 64] #[fstring_pad = 0x20] /// File name pub file: String, /// Script ID pub source: u32, #[fstring = 64] #[fstring_pad = 0x20] /// Script title pub title: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Script entry in the Escu:de list pub struct ScriptT2 { #[fstring = 64] #[fstring_pad = 0x20] /// File name pub file: String, #[fstring = 64] #[fstring_pad = 0x20] /// Script title pub title: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Name entry in the Escu:de list pub struct NameT { #[fstring = 64] #[fstring_pad = 0x20] /// Name of the character pub text: String, /// Text color pub color: u32, #[fstring = 32] #[fstring_pad = 0x20] /// Face image file name pub face: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Variable entry in the Escu:de list pub struct VarT { /// Variable name #[fstring = 32] #[fstring_pad = 0x20] pub name: String, /// Variable value pub value: u16, /// Variable flag pub flag: u16, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Scene entry in the Escu:de list pub struct SceneT { /// The scene script ID pub script: u32, /// The scene name #[fstring = 64] #[fstring_pad = 0x20] pub name: String, /// The scene thumbail image file name #[fstring = 32] #[fstring_pad = 0x20] pub thumbnail: String, /// The scene order in the scene (Extra) pub order: i32, } #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] /// Enum for different types of script data in the Escu:de list (enum._scr.bin) pub enum EnumScr { /// Scripts data Scripts(Vec), /// Scripts data (old) Scripts2(Vec), /// Names data Names(Vec), /// Variables data Vars(Vec), /// Scenes data Scenes(Vec), } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Background entry in the Escu:de list pub struct BgT { /// Background image name #[fstring = 32] #[fstring_pad = 0x20] name: String, /// Background image file name #[fstring = 64] #[fstring_pad = 0x20] file: String, #[fstring = 128] #[fstring_pad = 0x20] /// Background options option: String, /// Background covered flag coverd: u32, /// Background color color: u32, /// Background ID id: u32, /// Background location ID loc: u32, /// Background order in the scene order: i32, /// Background link ID link: u32, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Event image entry in the Escu:de list pub struct EvT { /// Event image name #[fstring = 32] #[fstring_pad = 0x20] name: String, /// Event image file name #[fstring = 64] #[fstring_pad = 0x20] file: String, #[fstring = 128] #[fstring_pad = 0x20] /// Event image options option: String, /// Event image covered flag coverd: u32, /// Event image color color: u32, /// Event image ID id: u32, /// Event image location ID loc: u32, /// Event image order in the scene order: i32, /// Event image link ID link: u32, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Stage entry in the Escu:de list pub struct StT { #[fstring = 32] #[fstring_pad = 0x20] /// Stage name name: String, #[fstring = 64] #[fstring_pad = 0x20] /// Stage file name file: String, #[fstring = 128] #[fstring_pad = 0x20] /// Stage options option: String, /// Stage covered flag coverd: u32, /// Stage color color: u32, /// Stage ID id: u32, /// Stage location ID loc: u32, /// Stage order in the scene order: i32, /// Stage link ID link: u32, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Effect image entry in the Escu:de list pub struct EfxT { /// Effect image name #[fstring = 32] #[fstring_pad = 0x20] name: String, /// Effect image file name #[fstring = 64] #[fstring_pad = 0x20] file: String, /// Effect image options spot: i32, /// Effect image x position dx: i32, /// Effect image y position dy: i32, /// Effect image loop flag r#loop: bool, #[fvec = 3] #[serde(skip, default = "exft_padding")] /// padding for alignment padding: Vec, } fn exft_padding() -> Vec { vec![0; 3] } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Point pub struct Point { /// X coordinate x: i16, /// Y coordinate y: i16, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Location entry in the Escu:de list pub struct LocT { /// List of points #[fvec = 8] pt: Vec, } #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] /// Enum for different types of graphics data in the Escu:de list (enum_gfx.bin) pub enum EnumGfx { /// Backgrounds data Bgs(Vec), /// Event images data Evs(Vec), /// Stages data Sts(Vec), /// Effects data Efxs(Vec), /// Locations data Locs(Vec), } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Background music entry in the Escu:de list pub struct BgmT { #[fstring = 64] #[fstring_pad = 0x20] /// Background music name pub name: String, #[fstring = 64] #[fstring_pad = 0x20] /// Background music file name pub file: String, #[fstring = 64] #[fstring_pad = 0x20] /// Background music title pub title: String, /// Background music order in the scene pub order: i32, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Ambient sound entry in the Escu:de list pub struct AmbT { #[fstring = 64] #[fstring_pad = 0x20] /// Ambient sound name pub name: String, #[fstring = 64] #[fstring_pad = 0x20] /// Ambient sound file name pub file: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Sound effect entry in the Escu:de list pub struct SeT { #[fstring = 64] #[fstring_pad = 0x20] /// Sound effect name pub name: String, #[fstring = 64] #[fstring_pad = 0x20] /// Sound effect file name pub file: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] /// Sound effect (SFX) entry in the Escu:de list pub struct SfxT { #[fstring = 64] #[fstring_pad = 0x20] /// SFX name pub name: String, #[fstring = 64] #[fstring_pad = 0x20] /// SFX file name pub file: String, } #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] /// Enum for different types of sound data in the Escu:de list (enum_snd.bin) pub enum EnumSnd { /// Background music data Bgm(Vec), /// Ambient sound data Amb(Vec), /// Sound effect data Se(Vec), /// Sound effect (SFX) data Sfx(Vec), } #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] /// Enum for different types of data in the Escu:de list pub enum ListData { /// Script data Scr(EnumScr), /// Graphics data Gfx(EnumGfx), /// Sound data Snd(EnumSnd), /// Unknown data Unknown(Vec), } #[derive(Debug, Serialize, Deserialize)] /// Entry in the Escu:de list pub struct ListEntry { id: u32, /// The data associated with the entry pub data: ListData, }