From d11af6fc1b6ed22dd2c676c21fcea26f04eb3bf3 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Thu, 9 Apr 2026 14:41:46 +0800 Subject: [PATCH] Add TokidokiCrypt (untested) --- msg_tool_xp3data/crypt.json | 5 ++ src/scripts/kirikiri/archive/xp3/crypt/mod.rs | 88 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index ebdba09..834433b 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -1975,6 +1975,11 @@ "$type": "HashCrypt", "Title": "とある熟女の変態調教 ~老いてなお滴る秘蜜と悦楽~" }, + "Tokidoki Pakucchao!": { + "$type": "TokidokiCrypt", + "HashAfterCrypt": true, + "Title": "ときどきパクッちゃお!" + }, "Tonarizuma 2 ~Otto ja Dekinai Koto Ippai Shite~": { "$type": "HashCrypt", "Title": "隣妻2 ~夫じゃできないこといっぱいシて~" diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index 64e4366..1824811 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -167,6 +167,7 @@ enum CryptType { NatsupochiCrypt, PoringSoftCrypt, AppliqueCrypt, + TokidokiCrypt, } #[derive(Clone, Debug, Deserialize)] @@ -261,6 +262,7 @@ impl Schema { CryptType::NatsupochiCrypt => Box::new(NatsupochiCrypt::new(self.base.clone())), CryptType::PoringSoftCrypt => Box::new(PoringSoftCrypt::new(self.base.clone())), CryptType::AppliqueCrypt => Box::new(AppliqueCrypt::new(self.base.clone())), + CryptType::TokidokiCrypt => Box::new(TokidokiCrypt::new(self.base.clone())), }) } } @@ -943,6 +945,92 @@ impl Read for AppliqueCryptReader { } } +#[derive(Debug)] +pub struct TokidokiCrypt { + base: BaseSchema, +} + +impl TokidokiCrypt { + pub fn new(base: BaseSchema) -> Self { + Self { base } + } + + /// Retruns limit and key + fn get_key(&self, entry: &Xp3Entry) -> Result<(u64, u32)> { + let ext = entry + .name + .rsplit('.') + .next() + .unwrap_or("") + .to_ascii_lowercase(); + if !ext.is_empty() { + let ext = format!(".{}", ext); + let mut ext_bin = encode_string(Encoding::Cp932, &ext, true)?; + ext_bin.resize(4, 0); + let mut reader = MemReaderRef::new(&ext_bin); + let key = !reader.read_u32()?; + if ext == ".asd" || ext == ".ks" || ext == ".tjs" { + Ok((entry.original_size, key)) + } else { + Ok((entry.original_size.min(0x100), key)) + } + } else { + Ok((entry.original_size.min(0x100), u32::MAX)) + } + } +} + +impl Crypt for TokidokiCrypt { + base_schema_impl!(); + fn decrypt_supported(&self) -> bool { + true + } + fn decrypt_seek_supported(&self) -> bool { + true + } + fn decrypt<'a>( + &self, + entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + Ok(Box::new(TokidokiCryptReader::new( + stream, + cur_seg, + self.get_key(entry)?, + ))) + } + fn decrypt_with_seek<'a>( + &self, + entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + Ok(Box::new(TokidokiCryptReader::new( + stream, + cur_seg, + self.get_key(entry)?, + ))) + } +} + +seek_reader_key_impl!(TokidokiCryptReader, (u64, u32)); + +impl Read for TokidokiCryptReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let (limit, key) = self.key; + let readed = self.inner.read(buf)?; + for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() { + let offset = self.seg_start + self.pos + i as u64; + if offset < limit { + *t ^= (key >> ((offset as i32 & 3) << 3)) as u8; + } + } + self.pos += readed as u64; + Ok(readed) + } +} + #[test] fn test_deserialize_crypt() { for (key, schema) in CRYPT_SCHEMA.iter() {