From 7b58e08299d528f24a3948424b8a9b31039253b4 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Tue, 2 Jun 2026 16:42:25 +0800 Subject: [PATCH] Add support for Yu-Ris YSTD(Global counts) file (.ybn) --- README.md | 1 + src/scripts/mod.rs | 2 + src/scripts/yuris/mod.rs | 1 + src/scripts/yuris/ystd.rs | 174 ++++++++++++++++++++++++++++++++++++++ src/types.rs | 3 + 5 files changed, 181 insertions(+) create mode 100644 src/scripts/yuris/ystd.rs diff --git a/README.md b/README.md index fd14171..c25f71b 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,7 @@ msg-tool create -t | `yuris-ystl` | `yuris` | Yu-Ris YSTL(file list) file (.ybn) | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | | | `yuris-yslb` | `yuris` | Yu-Ris YSLB(labels) file (.ybn) | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | | | `yuris-ysvr` | `yuris` | Yu-Ris YSVR(Variables) file (.ybn) | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | | +| `yuris-ystd` | `yuris` | Yu-Ris YSTD(Global counts) file (.ybn) | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | | | Image Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Create | Remarks | |---|---|---|---|---|---|---|---|---| diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index 9a14c16..750ad11 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -194,6 +194,8 @@ lazy_static::lazy_static! { Box::new(yuris::yslb::YSLBBuilder::new()), #[cfg(feature = "yuris")] Box::new(yuris::ysvr::YSVRBuilder::new()), + #[cfg(feature = "yuris")] + Box::new(yuris::ystd::YSTDBuilder::new()), ]; /// A list of all script extensions. pub static ref ALL_EXTS: Vec = diff --git a/src/scripts/yuris/mod.rs b/src/scripts/yuris/mod.rs index 4b1f5b4..406d1d9 100644 --- a/src/scripts/yuris/mod.rs +++ b/src/scripts/yuris/mod.rs @@ -8,5 +8,6 @@ pub mod yscm; pub mod yser; pub mod yslb; pub mod ystb; +pub mod ystd; pub mod ystl; pub mod ysvr; diff --git a/src/scripts/yuris/ystd.rs b/src/scripts/yuris/ystd.rs new file mode 100644 index 0000000..bf7f444 --- /dev/null +++ b/src/scripts/yuris/ystd.rs @@ -0,0 +1,174 @@ +//! Yu-Ris YSTD(Global counts) file (.ybn) +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use crate::utils::encoding::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use msg_tool_macro::*; +use serde::{Deserialize, Serialize}; +use std::io::{Read, Seek, Write}; + +#[derive(Debug, StructUnpack, StructPack, Serialize, Deserialize)] +struct YSTDData { + version: u32, + num_variables: u32, + num_texts: u32, +} + +#[derive(Debug)] +pub struct YSTDBuilder {} + +impl YSTDBuilder { + /// Creates a new instance of `YSTDBuilder` + pub const fn new() -> Self { + YSTDBuilder {} + } +} + +impl ScriptBuilder for YSTDBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Cp932 + } + + fn build_script( + &self, + buf: Vec, + _filename: &str, + encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + _archive: Option<&Box>, + ) -> Result> { + Ok(Box::new(YSTD::new(MemReader::new(buf), encoding, config)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &["ybn"] + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 4 && buf.starts_with(b"YSTD") { + return Some(20); + } + None + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::YurisYSTD + } + + 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)] +pub struct YSTD { + data: YSTDData, + custom_yaml: bool, +} + +impl YSTD { + pub fn new( + mut reader: T, + encoding: Encoding, + config: &ExtraConfig, + ) -> Result { + let mut sig = [0; 4]; + reader.read_exact(&mut sig)?; + if &sig != b"YSTD" { + anyhow::bail!("Unsupported YSTD file."); + } + let data = YSTDData::unpack(&mut reader, false, encoding, &None)?; + Ok(Self { + data, + custom_yaml: config.custom_yaml, + }) + } +} + +impl Script for YSTD { + 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.data) + .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? + } else { + serde_json::to_string_pretty(&self.data) + .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, + file: Box, + encoding: Encoding, + output_encoding: Encoding, + ) -> Result<()> { + create_file( + custom_filename, + file, + encoding, + output_encoding, + self.custom_yaml, + ) + } +} + +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 data: YSTDData = 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"YSTD")?; + data.pack(&mut writer, false, encoding, &None)?; + Ok(()) +} diff --git a/src/types.rs b/src/types.rs index d0c7ba4..beb5e44 100644 --- a/src/types.rs +++ b/src/types.rs @@ -941,6 +941,9 @@ pub enum ScriptType { #[cfg(feature = "yuris")] /// Yu-Ris YSVR(Variables) file (.ybn) YurisYSVR, + #[cfg(feature = "yuris")] + /// Yu-Ris YSTD(Global counts) file (.ybn) + YurisYSTD, #[cfg(feature = "yuris-img")] /// YU-RIS compressed image file (.ydg) YurisYDG,