diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index 55f8d4e..a575655 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -61,6 +61,11 @@ "EvenBranchOrder": "BQcBBgMCAAQ=", "ControlBlockName": "9nine_ep1_sekai.bin" }, + "Aa Mama ni Naru!": { + "$type": "AkabeiCrypt", + "Seed": 3598228156, + "Title": "ああっママになるっ! ~アナタを想って溢れるおっぱい~ | 母爱如山 胸中无法压抑对你的爱 | 向妈妈撒娇吧" + }, "Aete Mushi Suru Kimi to no Mirai ~Relay Broadcast~": { "$type": "CxEncryption", "Mask": 443, @@ -81,6 +86,11 @@ "TpmFileName": "plugin/cxdec.tpm", "Title": "ああっお嬢様っ" }, + "AI Love": { + "$type": "AkabeiCrypt", + "Seed": 734916043, + "Title": "Ai 爱" + }, "Aibo Nyuujoku": { "$type": "FlyingShineCrypt", "Title": "愛母乳辱~妄執の巨乳責めザンマイ~" @@ -220,10 +230,20 @@ "Key": 49, "Title": "僕の未来は、恋と課金と。~Charge To The Future~ [体験版] | 我的未来是恋爱与氪金 [试用版]" }, + "Bosei Kanojo -Shikyuu Kikan Hen-": { + "$type": "AkabeiCrypt", + "Seed": 3598228156, + "Title": "母性カノジョ-子宮 帰還編- | 母性女友" + }, "Bra-ban!": { "$type": "PoringSoftCrypt", "Title": "ぶらばん! | 管乐恋曲!" }, + "Bukkake! Doshikoring MINUKI Max Acme...": { + "$type": "AkabeiCrypt", + "Seed": 3854429322, + "Title": "BUKKAKE! ドシコリングMINUKIマックスアクメしてもいいですか? ~ドエロいポーズして貰って見抜きぶっかけしたらお互い興奮がドチャクソヤバイです~" + }, "Cafe Sourire": { "$type": "SourireCrypt" }, @@ -249,6 +269,11 @@ "ControlBlockName": "cafe_stella.bin", "Title": "喫茶ステラと死神の蝶 | 星光咖啡馆与死神之蝶 | 星光咖啡館與死神之蝶" }, + "CharaBration!": { + "$type": "AkabeiCrypt", + "Seed": 3854429322, + "Title": "きゃらぶれーしょん! ~乙女は恋してキャラぶれる~ | charabration!恋爱少女人格崩坏" + }, "Chikan Yuugi": { "$type": "CxEncryption", "Mask": 531, @@ -568,6 +593,11 @@ "$type": "HashCrypt", "Title": "ハードコア・ティーチャー ~絶対服従へのロジック~" }, + "Haru to Yuki": { + "$type": "AkabeiCrypt", + "Seed": 3854429322, + "Title": "はるとゆき、" + }, "Haruiro☆Communication♪": { "$type": "CxEncryption", "Mask": 257, @@ -826,6 +856,16 @@ "$type": "HashCrypt", "Title": "淫!辱!ピンクレンジャー!! ~堕ちた変身ヒロイン~" }, + "Inochi no Spare": { + "$type": "AkabeiCrypt", + "Seed": 771534797, + "Title": "生命のスペア | 生命的备件" + }, + "Inochi no Spare [Trial]": { + "$type": "AkabeiCrypt", + "Seed": 716950749, + "Title": "生命のスペア [体験版] | 生命的备件 [试玩版]" + }, "Inraku Counseling": { "$type": "HashCrypt", "Title": "淫落カウンセリング ~夫の目の前で、妻は本性に堕ちてゆく~" @@ -913,6 +953,11 @@ "ControlBlockName": "kakenuke.bin", "Title": "かけぬけ★青春スパーキング! | 闪耀青春追逐记 | 绽放★青春全力向前冲!" }, + "Kamaitachi no Yoru": { + "$type": "AkabeiCrypt", + "Seed": 3854429322, + "Title": "かまいたちの夜 | 镰鼬之夜" + }, "Kami-sama no You na Kimi e": { "$type": "XorCrypt", "Key": 199, @@ -1425,6 +1470,11 @@ "Key": 232, "Title": "同じクラスのアイドルさん。Around me is full by a celebrity." }, + "Onee-chan no Yuuwaku": { + "$type": "AkabeiCrypt", + "Seed": 705275717, + "Title": "如月真綾の誘惑 | 如月真绫的指导" + }, "Onigokko!": { "$type": "CxEncryption", "Mask": 334, @@ -1661,6 +1711,11 @@ "$type": "PoringSoftCrypt", "Title": "らいでぃんぐ いんきゅばす" }, + "Role Player: Okayu Shimai no Nenmaku Portrait - Gurigucha Live!": { + "$type": "AkabeiCrypt", + "Seed": 798088789, + "Title": "Role player:小粥姉妹の粘膜ポトレ ぐりぐちゃLIVE! | ROLEPLAYER:小粥姐妹的黏膜游戏!" + }, "Rui wa Tomo o Yobu": { "$type": "CxEncryption", "Mask": 740, @@ -1691,6 +1746,11 @@ "ControlBlockName": "ruiwatomo.bin", "Title": "るいは智を呼ぶファンディスク ―明日のむこうに視える風― | 智以泪聚FD -吹向明日彼岸的可视之风-" }, + "Ryuukishi Bloody†Saga": { + "$type": "AkabeiCrypt", + "Seed": 3854429322, + "Title": "竜騎士Bloody†Saga | 龙骑士Bloody†Saga" + }, "Sabbat of the Witch": { "$type": "CxEncryption", "Mask": 703, @@ -1848,6 +1908,11 @@ "$type": "HashCrypt", "Title": "触手王子の肉嫁探し~母と姉弟の終わらない絶頂地獄~" }, + "Shoujo to Toshi no Sa, Futamawari.": { + "$type": "AkabeiCrypt", + "Seed": 3854429322, + "Title": "少女と年の差、ふたまわり。 | 少女与我,两纪之差。" + }, "Shugaten! -sugarfull tempering-": { "$type": "CxEncryption", "Mask": 676, @@ -1895,6 +1960,16 @@ "$type": "FlyingShineCrypt", "Title": "相姦恥療病棟 ~ナースはママで、女医は叔母" }, + "Sousaku Kanojo no Ren'ai Koushiki": { + "$type": "AkabeiCrypt", + "Seed": 798088789, + "Title": "創作彼女の恋愛公式 | 创作少女的恋爱公式 | 创作彼女的恋爱方程式 | 創作彼女的戀愛方程式" + }, + "Sousaku Kanojo no Ren'ai Koushiki - Trial Edition": { + "$type": "AkabeiCrypt", + "Seed": 798088789, + "Title": "創作彼女の恋愛公式 体験版" + }, "Sousei no Himegimi": { "$type": "HashCrypt", "Title": "双性の姫君 ~ふたなり姉妹と魔王の求愛~" @@ -1909,6 +1984,11 @@ "TpmFileName": "plugin/mogla.tpm", "Title": "水平線まで何マイル? - Deep Blue Sky & Pure White Wings - | 离水平线还有多远?-深蓝的天空和洁白的翅膀- | 水平线尽头有多远" }, + "Suiren to Shion": { + "$type": "AkabeiCrypt", + "Seed": 798088789, + "Title": "水蓮と紫苑 | 水莲与紫苑" + }, "Suisei Ginka": { "$type": "CxEncryption", "Mask": 353, @@ -1980,6 +2060,21 @@ "$type": "HashCrypt", "Title": "たわわ奥さん×ハプニングジム ~むっちりボディとすけべなエクササイズ~" }, + "Teakamamire no Danshi": { + "$type": "AkabeiCrypt", + "Seed": 3854429322, + "Title": "手垢塗れの堕天使" + }, + "Teakamamire no Fukushuu": { + "$type": "AkabeiCrypt", + "Seed": 3598228156, + "Title": "手垢塗れの復讐" + }, + "Teakamamire no Tenshi": { + "$type": "AkabeiCrypt", + "Seed": 3737124058, + "Title": "手垢塗れの天使 | 被黑暗洗礼的天使" + }, "Temptation -Saiin no Naraku-": { "$type": "NatsupochiCrypt", "Title": "Temptation -催淫の奈落-" diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index e2f7e21..d71628a 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -171,6 +171,10 @@ enum CryptType { TokidokiCrypt, SourireCrypt, HibikiCrypt, + #[serde(rename_all = "PascalCase")] + AkabeiCrypt { + seed: u32, + }, } #[derive(Clone, Debug, Deserialize)] @@ -269,6 +273,7 @@ impl Schema { CryptType::TokidokiCrypt => Box::new(TokidokiCrypt::new(self.base.clone())), CryptType::SourireCrypt => Box::new(SourireCrypt::new(self.base.clone())), CryptType::HibikiCrypt => Box::new(HibikiCrypt::new(self.base.clone())), + CryptType::AkabeiCrypt { seed } => Box::new(AkabeiCrypt::new(self.base.clone(), *seed)), }) } } @@ -1053,7 +1058,7 @@ impl Read for SourireCryptReader { seek_crypt_filehash_key_impl!(HibikiCrypt, HibikiCryptReader); -impl Read for HibikiCryptReader { +impl Read for HibikiCryptReader { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let readed = self.inner.read(buf)?; let key1 = (self.key >> 5) as u8; @@ -1072,6 +1077,77 @@ impl Read for HibikiCryptReader { } } +#[derive(Debug)] +pub struct AkabeiCrypt { + base: BaseSchema, + seed: u32, +} + +impl AkabeiCrypt { + pub fn new(base: BaseSchema, seed: u32) -> Self { + Self { base, seed } + } + + fn get_key(&self, mut hash: u32) -> [u8; 0x20] { + let mut state = [0; 0x20]; + hash = (hash ^ self.seed) & 0x7FFFFFFF; + hash = hash << 31 | hash; + for i in 0..0x20 { + state[i] = (hash & 0xFF) as u8; + hash = (hash & 0xFFFFFFFE) << 23 | hash >> 8; + } + state + } +} + +impl Crypt for AkabeiCrypt { + 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(AkabeiCryptReader::new( + stream, + cur_seg, + self.get_key(entry.file_hash), + ))) + } + fn decrypt_with_seek<'a>( + &self, + entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + Ok(Box::new(AkabeiCryptReader::new( + stream, + cur_seg, + self.get_key(entry.file_hash), + ))) + } +} + +seek_reader_key_impl!(AkabeiCryptReader, [u8; 0x20]); + +impl Read for AkabeiCryptReader { + 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() { diff --git a/src/scripts/kirikiri/archive/xp3/mod.rs b/src/scripts/kirikiri/archive/xp3/mod.rs index eb2e57c..db4c28b 100644 --- a/src/scripts/kirikiri/archive/xp3/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/mod.rs @@ -134,7 +134,7 @@ impl ScriptBuilder for Xp3ArchiveBuilder { } fn extensions(&self) -> &'static [&'static str] { - &["xp3"] + &["xp3", "bin"] } fn script_type(&self) -> &'static ScriptType { @@ -154,6 +154,13 @@ impl ScriptBuilder for Xp3ArchiveBuilder { ) -> Result> { Ok(Box::new(Xp3ArchiveWriter::new(filename, files, config)?)) } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 11 && buf.starts_with(consts::XP3_MAGIC) { + return Some(100); + } + None + } } #[derive(Debug)]