From 90f56c5e2d0252a42cb1f5a3c0980981bc22e7bd Mon Sep 17 00:00:00 2001 From: lifegpc Date: Tue, 1 Jul 2025 18:11:10 +0800 Subject: [PATCH] add kr simpile crypt support --- Cargo.toml | 2 +- src/scripts/base.rs | 2 +- src/scripts/kirikiri/mod.rs | 1 + src/scripts/kirikiri/simple_crypt.rs | 166 +++++++++++++++++++++++++++ src/scripts/mod.rs | 2 + src/types.rs | 4 + 6 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 src/scripts/kirikiri/simple_crypt.rs diff --git a/Cargo.toml b/Cargo.toml index dc5365b..fc21d32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ cat-system-img = ["cat-system", "flate2", "image", "utils-bit-stream"] circus = [] escude = ["int-enum"] escude-arc = ["escude", "rand", "utils-bit-stream"] -kirikiri = ["emote-psb"] +kirikiri = ["emote-psb", "flate2"] yaneurao = [] yaneurao-itufuru = ["yaneurao"] # basic feature diff --git a/src/scripts/base.rs b/src/scripts/base.rs index 29c870c..723f0cf 100644 --- a/src/scripts/base.rs +++ b/src/scripts/base.rs @@ -169,7 +169,7 @@ pub trait Script: std::fmt::Debug { !matches!(output, OutputScriptType::Custom) } - fn custom_output_extension(&self) -> &'static str { + fn custom_output_extension<'a>(&'a self) -> &'a str { "" } diff --git a/src/scripts/kirikiri/mod.rs b/src/scripts/kirikiri/mod.rs index 8a33933..0b055ac 100644 --- a/src/scripts/kirikiri/mod.rs +++ b/src/scripts/kirikiri/mod.rs @@ -1,4 +1,5 @@ pub mod scn; +pub mod simple_crypt; use std::collections::HashMap; use std::sync::Arc; diff --git a/src/scripts/kirikiri/simple_crypt.rs b/src/scripts/kirikiri/simple_crypt.rs new file mode 100644 index 0000000..4864ec2 --- /dev/null +++ b/src/scripts/kirikiri/simple_crypt.rs @@ -0,0 +1,166 @@ +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use anyhow::Result; +use overf::wrapping; +use std::io::Read; + +#[derive(Debug)] +pub struct SimpleCryptBuilder {} + +impl SimpleCryptBuilder { + pub fn new() -> Self { + Self {} + } +} + +impl ScriptBuilder for SimpleCryptBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Utf8 + } + + fn build_script( + &self, + buf: Vec, + filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + _config: &ExtraConfig, + ) -> Result> { + Ok(Box::new(SimpleCrypt::new(buf, filename)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &[] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::KirikiriSimpleCrypt + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 5 + && buf[0] == 0xfe + && buf[1] == 0xfe + && (buf[2] == 0 || buf[2] == 1 || buf[2] == 2) + && buf[3] == 0xff + && buf[4] == 0xfe + { + Some(10) + } else { + None + } + } +} + +#[derive(Debug)] +pub struct SimpleCrypt { + /// Crypt mode + crypt: u8, + data: MemReader, + ext: String, +} + +impl SimpleCrypt { + pub fn new(buf: Vec, filename: &str) -> Result { + let mut reader = MemReader::new(buf); + let mut header = [0u8; 5]; + reader.read_exact(&mut header)?; + if header[0] != 0xfe + || header[1] != 0xfe + || (header[2] != 0 && header[2] != 1 && header[2] != 2) + || header[3] != 0xff + || header[4] != 0xfe + { + return Err(anyhow::anyhow!("Invalid SimpleCrypt header")); + } + Ok(Self { + crypt: header[2], + data: reader, + ext: std::path::Path::new(filename) + .extension() + .and_then(|s| s.to_str()) + .unwrap_or("") + .to_string(), + }) + } + + pub fn unpack(crypt: u8, data: MemReaderRef) -> Result> { + match crypt { + 0 => Self::unpack_mode0(data), + 1 => Self::unpack_mode1(data), + 2 => Self::unpack_mode2(data), + _ => Err(anyhow::anyhow!("Unsupported SimpleCrypt mode: {}", crypt)), + } + } + + fn unpack_mode0(input: MemReaderRef) -> Result> { + let mut data = Vec::with_capacity(input.data.len() - 3); + data.push(0xff); + data.push(0xfe); + data.extend_from_slice(&input.data[5..]); + for i in 2..data.len() { + let ch = data[i] as u16; + if ch >= 20 { + data[i] = wrapping! {ch ^ (((ch & 0xfe) << 8) ^ 1)} as u8; + } + } + Ok(data) + } + + fn unpack_mode1(input: MemReaderRef) -> Result> { + let mut data = Vec::with_capacity(input.data.len() - 3); + data.push(0xff); + data.push(0xfe); + data.extend_from_slice(&input.data[5..]); + for i in 2..data.len() { + let mut ch = data[i] as u32; + ch = wrapping! {((ch & 0xaaaaaaaa) >> 1) | ((ch & 0x55555555) << 1)}; + data[i] = ch as u8; + } + Ok(data) + } + + fn unpack_mode2(mut reader: MemReaderRef) -> Result> { + reader.pos = 5; + let compressed = reader.read_u64()?; + debug_assert!(compressed + 5 == reader.data.len() as u64); + let uncompressed = reader.read_u64()?; + let mut stream = flate2::Decompress::new(false); + let mut data = Vec::with_capacity(uncompressed as usize + 2); + data.push(0xff); + data.push(0xfe); + data.resize(uncompressed as usize, 0); + stream.decompress( + &reader.data[reader.pos..], + &mut data[2..], + flate2::FlushDecompress::Finish, + )?; + Ok(data) + } +} + +impl Script for SimpleCrypt { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Custom + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn is_output_supported(&self, output: OutputScriptType) -> bool { + matches!(output, OutputScriptType::Custom) + } + + fn custom_output_extension<'a>(&'a self) -> &'a str { + &self.ext + } + + fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> { + let data = Self::unpack(self.crypt, self.data.to_ref())?; + let mut writer = crate::utils::files::write_file(filename)?; + writer.write_all(&data)?; + Ok(()) + } +} diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index 98cd2ce..29071fe 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -50,6 +50,8 @@ lazy_static::lazy_static! { Box::new(cat_system::image::hg3::Hg3ImageBuilder::new()), #[cfg(feature = "kirikiri")] Box::new(kirikiri::scn::ScnScriptBuilder::new()), + #[cfg(feature = "kirikiri")] + Box::new(kirikiri::simple_crypt::SimpleCryptBuilder::new()), ]; pub static ref ALL_EXTS: Vec = BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect(); diff --git a/src/types.rs b/src/types.rs index 6683452..019c3c1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -272,6 +272,10 @@ pub enum ScriptType { #[value(alias("kr-scn"))] /// Kirikiri SCN script KirikiriScn, + #[cfg(feature = "kirikiri")] + #[value(alias("kr-simple-crypt"))] + /// Kirikiri SimpleCrypt's text file + KirikiriSimpleCrypt, #[cfg(feature = "yaneurao-itufuru")] #[value(alias("itufuru"))] /// Yaneurao Itufuru script