diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index 290a3c3..b21d3c9 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -258,6 +258,12 @@ "Seed": 3854429322, "Title": "BUKKAKE! ドシコリングMINUKIマックスアクメしてもいいですか? ~ドエロいポーズして貰って見抜きぶっかけしたらお互い興奮がドチャクソヤバイです~" }, + "Bura Bura": { + "$type": "SmxCrypt", + "Mask": 3, + "KeySeq": "AwUEAwg=", + "Title": "ぶら ぶら" + }, "Cafe Sourire": { "$type": "SourireCrypt" }, @@ -641,6 +647,12 @@ "ControlBlockName": "hataraku.bin", "Title": "働くオタクの恋愛事情" }, + "Hataraku Otona no Ren'ai Jijou": { + "$type": "SmxCrypt", + "Mask": 2, + "KeySeq": "AwEAAg==", + "Title": "働くオトナの恋愛事情" + }, "Hatsujou Switch": { "$type": "CxEncryption", "Mask": 281, @@ -923,6 +935,12 @@ "$type": "DieselmineCrypt", "Title": "いつでも女の子に精液大量注入!して褒められる世界" }, + "Izuna Zanshinken": { + "$type": "SmxCrypt", + "Mask": 5, + "KeySeq": "AwgHBgUEAg==", + "Title": "イヅナ斬審剣" + }, "Japanese School Life": { "$type": "CxEncryption", "Mask": 697, @@ -1972,6 +1990,12 @@ "$type": "HashCrypt", "Title": "清楚なあの娘は、隠れビッチ ~いつでもどこでも欲情SEX~" }, + "Sekisaba!": { + "$type": "SmxCrypt", + "Mask": 5, + "KeySeq": "AgUEAwICAw==", + "Title": "せきさば!~私立せきがはら学園女子サバイバルゲーム部~" + }, "Se-n-pa-i ~Hiromi-senpai": { "$type": "NephriteCrypt", "Title": "セ・ン・パ・イ〜広海センパイとのラブエロ部活" @@ -2405,6 +2429,12 @@ "Seed": 4076513695, "Title": "ヤキモチストリーム | 吃醋大作战 | 醋意乱流" }, + "Yaya, Okiba ga Nai!": { + "$type": "SmxCrypt", + "Mask": 4, + "KeySeq": "AQMDAgQB", + "Title": "やや置き場がない!" + }, "Yome no Ane ga Kyonyuu Sugite": { "$type": "HashCrypt", "Title": "嫁の姉が巨乳過ぎて我慢できない ~義姉さんがこんなにエロかったなんて!" @@ -2439,6 +2469,12 @@ "Key": 205, "Title": "ゆらめく心に満ちた世界で、君の夢と欲望は叶うか | 三千心世界,梦终将实现" }, + "Yurikago yori Tenshi made": { + "$type": "SmxCrypt", + "Mask": 4, + "KeySeq": "AgMEBQQC", + "Title": "揺り籠より天使まで" + }, "Yurikago yori Tenshi made [Trial]": { "$type": "AppliqueCrypt", "Title": "揺り籠より天使まで 体験版" diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index e2e1d4e..f1634a3 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -235,6 +235,11 @@ enum CryptType { MadoCrypt { seed: u32, }, + #[serde(rename_all = "PascalCase")] + SmxCrypt { + mask: u32, + key_seq: Base64Bytes, + }, } #[derive(Clone, Debug, Deserialize)] @@ -364,6 +369,9 @@ impl Schema { config.xp3_file_list_path.as_ref().map(|s| s.as_str()), )?), CryptType::MadoCrypt { seed } => Box::new(MadoCrypt::new(self.base.clone(), *seed)), + CryptType::SmxCrypt { mask, key_seq } => { + Box::new(SmxCrypt::new(self.base.clone(), *mask, &key_seq.bytes)?) + } }) } } @@ -1753,6 +1761,102 @@ impl Read for MadoCryptReader { } } +#[derive(Debug)] +pub struct SmxCrypt { + base: BaseSchema, + mask: u32, + key_seq: Vec, +} + +impl SmxCrypt { + pub fn new(base: BaseSchema, mask: u32, key_seq: &[u8]) -> Result { + if key_seq.len() <= mask as usize + 1 { + anyhow::bail!( + "Key sequence length must be greater than mask + 1, but got {} and {}", + key_seq.len(), + mask + ); + } + if key_seq.len() < 2 { + anyhow::bail!( + "Key sequence length must be at least 2, but got {}", + key_seq.len() + ); + } + Ok(Self { + base, + mask, + key_seq: key_seq.to_vec(), + }) + } + + fn generate_key(&self, file_hash: u32) -> Vec { + let mut key = vec![0u8; self.key_seq.len() - 1]; + for i in 1..self.key_seq.len() { + key[i - 1] = (file_hash >> self.key_seq[i]) as u8; + } + key + } +} + +impl Crypt for SmxCrypt { + 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> { + let start_key = (entry.file_hash >> self.key_seq[0]) as u8; + Ok(Box::new(SmxCryptReader::new( + stream, + cur_seg, + (self.mask, start_key, self.generate_key(entry.file_hash)), + ))) + } + fn decrypt_with_seek<'a>( + &self, + entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + let start_key = (entry.file_hash >> self.key_seq[0]) as u8; + Ok(Box::new(SmxCryptReader::new( + stream, + cur_seg, + (self.mask, start_key, self.generate_key(entry.file_hash)), + ))) + } +} + +seek_reader_key_impl!(SmxCryptReader, (u32, u8, Vec)); + +impl Read for SmxCryptReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let readed = self.inner.read(buf)?; + let (mask, start_key, key) = &self.key; + let mask = *mask as u64; + let mut offset = self.seg_start + self.pos; + for t in (&mut buf[..readed]).iter_mut() { + let key = if offset <= 100 { + *start_key + } else { + key[(offset & mask) as usize] + }; + *t ^= key; + offset += 1; + } + self.pos += readed as u64; + Ok(readed) + } +} + #[test] fn test_deserialize_crypt() { for (key, schema) in CRYPT_SCHEMA.iter() {