From 9b983728ef48ff73167f39d0a2fef1dfe4dd9201 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 11 Apr 2026 16:15:21 +0800 Subject: [PATCH] Add MadoCrypt --- msg_tool_xp3data/crypt.json | 35 ++++++++ src/scripts/kirikiri/archive/xp3/crypt/mod.rs | 86 +++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index 4e73dd2..290a3c3 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -726,6 +726,11 @@ "ControlBlockName": "hello_lady_super.bin", "Title": "ハロー・レディ! -Superior Entelecheia- | 淑女同萌!-Superior Entelecheia-" }, + "Hentai Keimusho 24-ji": { + "$type": "MadoCrypt", + "Seed": 0, + "Title": "変態刑務所24時~懲役2年 イキ続けた少女~" + }, "Hentai Kyonyuu Nurse": { "$type": "HashCrypt", "Title": "HENTAI 巨乳ナース ~草食男子、治療計画!?~" @@ -1058,6 +1063,11 @@ "$type": "HashCrypt", "Title": "彼氏に内緒で快楽堕ち ~隠れヤリサーのリア充種付痴育~" }, + "Kari Gurashi Ren'ai": { + "$type": "MadoCrypt", + "Seed": 0, + "Title": "かりぐらし恋愛 | 寄宿之恋 | 寄宿之戀" + }, "Kasshoku Cool Bitch Hitozuma no Seiyoku Kaishou": { "$type": "HashCrypt", "Title": "褐色クールビッチ人妻の性欲解消 ~今度は海でセックスレッスン!?~" @@ -1379,6 +1389,11 @@ "Key": 85, "Title": "夢幻のティル・ナ・ノーグ | 梦幻的提尔纳诺" }, + "Namaiki Delation": { + "$type": "MadoCrypt", + "Seed": 0, + "Title": "ナマイキデレーション" + }, "Namaiki JK ni Fukushuu no Seikatsu Shidou": { "$type": "HashCrypt", "Title": "ナマイキJKに復讐の性活指導 ~先生、お願いだからもう許して…~" @@ -1524,6 +1539,11 @@ "Seed": 705275717, "Title": "如月真綾の誘惑 | 如月真绫的指导" }, + "Oni no Wakusei": { + "$type": "MadoCrypt", + "Seed": 0, + "Title": "鬼の惑星" + }, "Onigokko!": { "$type": "CxEncryption", "Mask": 334, @@ -1678,6 +1698,11 @@ "$type": "FlyingShineCrypt", "Title": "プリンセスサーガ" }, + "Puramai Wars": { + "$type": "MadoCrypt", + "Seed": 0, + "Title": "プラマイウォーズ | 正负战争" + }, "PURELY x CATION": { "$type": "PuCaCrypt", "HashTable": [ @@ -2256,6 +2281,11 @@ "ControlBlockName": "towazugatari.bin", "Title": "永遠ズ語リ~少女凌辱秘抄~" }, + "Treasure Hunting": { + "$type": "MadoCrypt", + "Seed": 0, + "Title": "トレジャーハンティング—クレアのビッチな冒険—" + }, "Tsubasa no Oka no Hime": { "$type": "PoringSoftCrypt", "Title": "つばさの丘の姫王 A red and blue moon -finite loop-" @@ -2370,6 +2400,11 @@ "With Ribbon": { "$type": "SourireCrypt" }, + "Yakimochi Stream": { + "$type": "MadoCrypt", + "Seed": 4076513695, + "Title": "ヤキモチストリーム | 吃醋大作战 | 醋意乱流" + }, "Yome no Ane ga Kyonyuu Sugite": { "$type": "HashCrypt", "Title": "嫁の姉が巨乳過ぎて我慢できない ~義姉さんがこんなにエロかったなんて!" diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index ffbee91..e2e1d4e 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -231,6 +231,10 @@ enum CryptType { RhapsodyCrypt { file_list_name: String, }, + #[serde(rename_all = "PascalCase")] + MadoCrypt { + seed: u32, + }, } #[derive(Clone, Debug, Deserialize)] @@ -359,6 +363,7 @@ impl Schema { &file_list_name, config.xp3_file_list_path.as_ref().map(|s| s.as_str()), )?), + CryptType::MadoCrypt { seed } => Box::new(MadoCrypt::new(self.base.clone(), *seed)), }) } } @@ -543,6 +548,20 @@ macro_rules! base_schema_impl { }; } +macro_rules! base_schema2_impl { + () => { + fn hash_after_crypt(&self) -> bool { + AsRef::::as_ref(self).hash_after_crypt + } + fn startup_tjs_not_encrypted(&self) -> bool { + AsRef::::as_ref(self).startup_tjs_not_encrypted + } + fn obfuscated_index(&self) -> bool { + AsRef::::as_ref(self).obfuscated_index + } + }; +} + macro_rules! seek_crypt_base_impl { ($crypt:ident, $reader:ident) => { #[derive(Debug)] @@ -1667,6 +1686,73 @@ impl Read for RhapsodyCryptReader { } } +#[derive(Debug)] +pub struct MadoCrypt { + base: AkabeiCrypt, +} + +impl MadoCrypt { + pub fn new(base: BaseSchema, seed: u32) -> Self { + Self { + base: AkabeiCrypt::new(base, seed), + } + } +} + +impl AsRef for MadoCrypt { + fn as_ref(&self) -> &BaseSchema { + &self.base.base + } +} + +impl Crypt for MadoCrypt { + base_schema2_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(MadoCryptReader::new( + stream, + cur_seg, + self.base.get_key(entry.file_hash), + ))) + } + fn decrypt_with_seek<'a>( + &self, + entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + Ok(Box::new(MadoCryptReader::new( + stream, + cur_seg, + self.base.get_key(entry.file_hash), + ))) + } +} + +seek_reader_key_impl!(MadoCryptReader, [u8; 0x20]); + +impl Read for MadoCryptReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + 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; + *t ^= self.key[(offset % 0x1F) as usize]; + } + self.pos += readed as u64; + Ok(readed) + } +} + #[test] fn test_deserialize_crypt() { for (key, schema) in CRYPT_SCHEMA.iter() {