From 025b802fc98c074d6462f5793f9b39d228b43ae7 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Mon, 6 Apr 2026 15:57:14 +0800 Subject: [PATCH] Add XorCrypt (tested game: https://vndb.org/v23388 ) --- src/scripts/kirikiri/archive/xp3/crypt.json | 30 ++++++++++++ src/scripts/kirikiri/archive/xp3/crypt.rs | 54 +++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/scripts/kirikiri/archive/xp3/crypt.json b/src/scripts/kirikiri/archive/xp3/crypt.json index 3fc491e..0252661 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt.json +++ b/src/scripts/kirikiri/archive/xp3/crypt.json @@ -35,6 +35,16 @@ "$type": "HashCrypt", "Title": "母奪~禁断の触手相姦~" }, + "Boku no Mirai wa, Koi to Kakin to": { + "$type": "XorCrypt", + "Key": 227, + "Title": "僕の未来は、恋と課金と。~Charge To The Future~ | 我的未来是恋爱与氪金" + }, + "Boku no Mirai wa, Koi to Kakin to [Trial]": { + "$type": "XorCrypt", + "Key": 49, + "Title": "僕の未来は、恋と課金と。~Charge To The Future~ [体験版] | 我的未来是恋爱与氪金 [试用版]" + }, "Custom Reido 4/4+/V": { "$type": "HashCrypt", "Title": "カスタム隷奴 4/4+/V" @@ -43,6 +53,11 @@ "$type": "HashCrypt", "Title": "誰もが彼女を狙ってる。 | 众人觊觎的女友" }, + "Deatte 5-fun wa Ore no Mono!": { + "$type": "XorCrypt", + "Key": 53, + "Title": "出会って5分は俺のもの! 時間停止と不可避な運命 | 5分钟的邂逅!时间停止与不可避免的命运 | 相见5分属于我!时间停止不可避的命运" + }, "Do-M Imouto O-nedari Kojin Lesson": { "$type": "HashCrypt", "Title": "ドM妹おねだり個人レッスン~私のSEX家庭教師お兄ちゃん~ | 义妹私教辅导~哥哥我是家庭教师" @@ -170,6 +185,11 @@ "$type": "HashCrypt", "Title": "カホゴな寮母の射精管理 ~ちゃんと我慢してくださいね?~" }, + "Kamidanomi Shisugite Ore no Mirai ga Yabai.": { + "$type": "XorCrypt", + "Key": 205, + "Title": "神頼みしすぎて俺の未来がヤバい。 | 求神太多我的未来糟糕了 | 太依赖咒术的我未来堪忧。" + }, "Kanpeki Seitokaichou no Himitsu no Seiheki": { "$type": "HashCrypt", "Title": "完璧生徒会長の秘密の性癖 ~高嶺の花の彼女をM女に開花させてやる~" @@ -413,6 +433,16 @@ "$type": "HashCrypt", "Title": "嫁探しが捗りすぎてヤバい。 | 新娘太好找了我很方!" }, + "Yurameku Kokoro ni Michita Sekai de": { + "$type": "XorCrypt", + "Key": 205, + "Title": "ゆらめく心に満ちた世界で、君の夢と欲望は叶うか | 三千心世界,梦终将实现" + }, + "Yuusha to Maou to, Majo no Cafe": { + "$type": "XorCrypt", + "Key": 205, + "Title": "勇者と魔王と、魔女のカフェ" + }, "Zettai Sakusei! Succubus-chan": { "$type": "HashCrypt", "Title": "絶対搾精! サキュバスちゃん" diff --git a/src/scripts/kirikiri/archive/xp3/crypt.rs b/src/scripts/kirikiri/archive/xp3/crypt.rs index 99a0b05..9f02498 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt.rs @@ -64,6 +64,10 @@ enum CryptType { FateCrypt, MizukakeCrypt, HashCrypt, + #[serde(rename_all = "PascalCase")] + XorCrypt { + key: u8, + }, } #[derive(Clone, Debug, Deserialize)] @@ -81,6 +85,7 @@ impl Schema { CryptType::FateCrypt => Box::new(FateCrypt::new()), CryptType::MizukakeCrypt => Box::new(MizukakeCrypt::new()), CryptType::HashCrypt => Box::new(HashCrypt::new()), + CryptType::XorCrypt { key } => Box::new(XorCrypt::new(key)), } } } @@ -358,6 +363,55 @@ impl Read for HashCryptReader { } } +#[derive(Debug)] +pub struct XorCrypt { + key: u8, +} + +impl XorCrypt { + pub fn new(key: u8) -> Self { + Self { key } + } +} + +impl Crypt for XorCrypt { + 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(XorCryptReader::new(stream, cur_seg, self.key))) + } + fn decrypt_with_seek<'a>( + &self, + _entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + Ok(Box::new(XorCryptReader::new(stream, cur_seg, self.key))) + } +} + +seek_reader_key_impl!(XorCryptReader, u8); + +impl Read for XorCryptReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let readed = self.inner.read(buf)?; + for t in (&mut buf[..readed]).iter_mut() { + *t ^= self.key; + } + self.pos += readed as u64; + Ok(readed) + } +} + #[test] fn test_deserialize_crypt() { for (key, schema) in CRYPT_SCHEMA.iter() {