From 5702a1d24888a1a332fbc7bb38ba188701e6d908 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Wed, 13 May 2026 18:15:04 +0800 Subject: [PATCH] Add support for Yu-Ris YSCM(opcodes metadata) file (.ybn) --- Cargo.toml | 3 +- README.md | 5 ++ src/scripts/mod.rs | 4 ++ src/scripts/yuris/mod.rs | 3 + src/scripts/yuris/types.rs | 23 +++++++ src/scripts/yuris/yscm.rs | 123 +++++++++++++++++++++++++++++++++++++ src/types.rs | 3 + 7 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/scripts/yuris/mod.rs create mode 100644 src/scripts/yuris/types.rs create mode 100644 src/scripts/yuris/yscm.rs diff --git a/Cargo.toml b/Cargo.toml index c72a2c2..077b19c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ default = ["all-fmt", "image-jpg", "image-jxl", "image-webp", "audio-flac", "jie # Zig can not build libjxl zig = ["all-fmt", "image-jpg", "image-webp", "audio-flac", "jieba"] all-fmt = ["all-script", "all-img", "all-arc", "all-audio"] -all-script = ["artemis", "artemis-panmimisoft", "bgi", "cat-system", "circus", "entis-gls", "escude", "ex-hibit", "favorite", "hexen-haus", "kirikiri", "musica", "qlie", "silky", "softpal", "will-plus", "yaneurao", "yaneurao-itufuru"] +all-script = ["artemis", "artemis-panmimisoft", "bgi", "cat-system", "circus", "entis-gls", "escude", "ex-hibit", "favorite", "hexen-haus", "kirikiri", "musica", "qlie", "silky", "softpal", "will-plus", "yaneurao", "yaneurao-itufuru", "yuris"] all-img = ["bgi-img", "cat-system-img", "circus-img", "emote-img", "hexen-haus-img", "kirikiri-img", "qlie-img", "softpal-img", "will-plus-img"] all-arc = ["artemis-arc", "bgi-arc", "cat-system-arc", "circus-arc", "escude-arc", "ex-hibit-arc", "hexen-haus-arc", "kirikiri-arc", "musica-arc", "qlie-arc", "softpal-arc"] all-audio = ["bgi-audio", "circus-audio"] @@ -117,6 +117,7 @@ will-plus = ["utils-str"] will-plus-img = ["will-plus", "image"] yaneurao = [] yaneurao-itufuru = ["yaneurao", "utils-xored-stream"] +yuris = [] # basic feature image = ["png"] image-jpg = ["mozjpeg"] diff --git a/README.md b/README.md index 94f413d..5f2390d 100644 --- a/README.md +++ b/README.md @@ -262,3 +262,8 @@ msg-tool create -t | Archive Type | Feature Name | Name | Unpack | Pack | Remarks | |---|---|---|---|---|---| | `yaneurao-itufuru-arc`/`itufuru-arc` | `yaneurao-itufuru-arc` | Yaneurao Itufuru Archive File (.scd) | ✔️ | ✔️ | | + +### Yu-Ris +| Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks | +|---|---|---|---|---|---|---|---|---|---|---| +| `yuris-yscm` | `yuris` | Yu-Ris YSCM(opcodes metadata) file (.ybn) | ❌ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | | diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index 87c5bd8..bbb8fca 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -34,6 +34,8 @@ pub mod softpal; pub mod will_plus; #[cfg(feature = "yaneurao")] pub mod yaneurao; +#[cfg(feature = "yuris")] +pub mod yuris; pub use base::{Script, ScriptBuilder}; @@ -174,6 +176,8 @@ lazy_static::lazy_static! { Box::new(qlie::image::dpng::DpngImageBuilder::new()), #[cfg(feature = "qlie-img")] Box::new(qlie::image::abmp10::Abmp10ImageBuilder::new()), + #[cfg(feature = "yuris")] + Box::new(yuris::yscm::YSCMBuilder::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 new file mode 100644 index 0000000..b16b9e9 --- /dev/null +++ b/src/scripts/yuris/mod.rs @@ -0,0 +1,3 @@ +//! Yu-Ris Engine Scripts +mod types; +pub mod yscm; diff --git a/src/scripts/yuris/types.rs b/src/scripts/yuris/types.rs new file mode 100644 index 0000000..37b31b8 --- /dev/null +++ b/src/scripts/yuris/types.rs @@ -0,0 +1,23 @@ +use crate::ext::io::*; +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, Deserialize, Serialize)] +pub struct ArgumentMeta { + #[cstring] + pub name: String, + pub data: u16, +} + +#[derive(Debug, StructPack, StructUnpack, Deserialize, Serialize)] +pub struct CodeMeta { + #[cstring] + pub name: String, + #[pvec(u8)] + pub arguments: Vec, +} diff --git a/src/scripts/yuris/yscm.rs b/src/scripts/yuris/yscm.rs new file mode 100644 index 0000000..0e19115 --- /dev/null +++ b/src/scripts/yuris/yscm.rs @@ -0,0 +1,123 @@ +//! Yu-Ris YSCM files +use super::types::*; +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use crate::utils::encoding::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use std::io::{Read, Seek}; + +#[derive(Debug)] +pub struct YSCMBuilder {} + +impl YSCMBuilder { + /// Creates a new instance of `YSCMBuilder` + pub const fn new() -> Self { + YSCMBuilder {} + } +} + +impl ScriptBuilder for YSCMBuilder { + 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(YSCM::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"YSCM") { + return Some(20); + } + None + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::YurisYSCM + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct YSCMData { + pub engine: u32, + pub opcodes: Vec, +} + +#[derive(Debug)] +pub struct YSCM { + pub(crate) data: YSCMData, + custom_yaml: bool, +} + +impl YSCM { + pub fn new( + mut reader: T, + encoding: Encoding, + config: &ExtraConfig, + ) -> Result { + let mut sig = [0; 4]; + reader.read_exact(&mut sig)?; + if &sig != b"YSCM" { + anyhow::bail!("Unsupported YSCM file."); + } + let engine = reader.read_u32()?; + let opcode_count = reader.read_u32()?; + reader.skip(4)?; + let mut opcodes = Vec::with_capacity(opcode_count as usize); + for _ in 0..opcode_count { + opcodes.push(CodeMeta::unpack(&mut reader, false, encoding, &None)?); + } + Ok(Self { + data: YSCMData { engine, opcodes }, + custom_yaml: config.custom_yaml, + }) + } +} + +impl Script for YSCM { + 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(()) + } +} diff --git a/src/types.rs b/src/types.rs index 262d13b..7a2dbaa 100644 --- a/src/types.rs +++ b/src/types.rs @@ -908,6 +908,9 @@ pub enum ScriptType { #[value(alias("itufuru-arc"))] /// Yaneurao Itufuru script archive YaneuraoItufuruArc, + #[cfg(feature = "yuris")] + /// Yu-Ris YSCM(opcodes metadata) file (.ybn) + YurisYSCM, } #[derive(Clone, Debug, Serialize, Deserialize)]