diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index 6c3a5d0..ee318e4 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -362,6 +362,11 @@ "ControlBlockName": "chikan.bin", "Title": "痴漢遊戯" }, + "Chouxiaoya de Tian'e Hu": { + "$type": "NVLCrypt", + "Key": "5eJFeAAoEgA=", + "Title": "丑小鸭的天鹅湖" + }, "Clover Day's": { "$type": "CxEncryption", "Mask": 443, @@ -755,6 +760,16 @@ "$type": "DieselmineCrypt", "Title": "顔騎天国" }, + "Gaokao Lian'ai Yibai Tian [PKG]": { + "$type": "NVLCrypt", + "Key": "DPAEYQBKQgA=", + "Title": "高考恋爱一百天 [PKG] | 高考戀愛一百天 [PKG]" + }, + "Gaokao Lian'ai Yibai Tian [Steam]": { + "$type": "NVLCrypt", + "Key": "He9bowDKQQA=", + "Title": "高考恋爱一百天 [Steam] | 高考戀愛一百天 [Steam]" + }, "GINKA": { "$type": "Hxv4Crypt", "KeyPackages": [ @@ -1104,6 +1119,11 @@ "$type": "HashCrypt", "Title": "本番有り! 人妻デリバリー ~どこかで見た美人妻たちを一人占め~" }, + "Hong-se Xuanlü [Steam]": { + "$type": "NVLCrypt", + "Key": "mt43PgAiSAA=", + "Title": "虹色旋律 [Steam]" + }, "Hora, Sonna ni Koe o Morasu...": { "$type": "HashCrypt", "Title": "ほら、そんなに声を漏らすと彼氏にバレちまうぜ? ~エロゲ世界でモブの少女を寝取ってみた~" @@ -1127,6 +1147,26 @@ "Hotel. [Trial]": { "$type": "AppliqueCrypt" }, + "Hua Luo Dong Yang Snowdreams -lost in winter- [Demo]": { + "$type": "NVLCrypt", + "Key": "og3ge1+mHzo=", + "Title": "花落冬陽 Snowdreams -lost in winter- [Demo] | 花落冬阳 Snowdreams -lost in winter- [Demo]" + }, + "Hua Luo Dong Yang Snowdreams -lost in winter- [Steam]": { + "$type": "NVLCrypt", + "Key": "eCmjFPPfYso=", + "Title": "花落冬陽 Snowdreams -lost in winter- [Steam] | 花落冬阳 Snowdreams -lost in winter- [Steam]" + }, + "Huiyi Wangque zhi Xia [PKG]": { + "$type": "NVLCrypt", + "Key": "az0gbQB0QQA=", + "Title": "回忆忘却之匣 [PKG] | 回憶忘卻之匣 [PKG]" + }, + "Huiyi Wangque zhi Xia [Steam]": { + "$type": "NVLCrypt", + "Key": "3ojzLADUQQA=", + "Title": "回忆忘却之匣 [Steam] | 回憶忘卻之匣 [Steam]" + }, "Ichizu ni Zettai Fukujuu na Imouto": { "$type": "HashCrypt", "Title": "一途に絶対服従な妹をめちゃくちゃにしてみた ~愛欲兄妹~" @@ -1314,6 +1354,11 @@ "$type": "HashCrypt", "Title": "蹂躙!オーク学園! ~転校生は褐色ビッチな女勇者!?~" }, + "Juzi Ban Duanpian Zuopinji [Steam]": { + "$type": "NVLCrypt", + "Key": "HmADHnCnSAA=", + "Title": "橘子班短篇作品集 [Steam] | Short Stories Collection of Class Tangerine [Steam]" + }, "Kagachi-sama Onagusame Tatematsurimasu": { "$type": "HashCrypt", "Title": "かがち様お慰め奉ります ~寝取られ村淫夜噺~" @@ -2521,6 +2566,16 @@ "$type": "AppliqueCrypt", "Title": "PURELY×CATION 体験版" }, + "Qi Feng [Steam Demo]": { + "$type": "NVLCrypt", + "Key": "kkRLbfKkPQA=", + "Title": "祈風 [Steam Demo] | 祈风 [Steam Demo]" + }, + "Qi Feng [Steam]": { + "$type": "NVLCrypt", + "Key": "tO/augDUQQA=", + "Title": "祈風 [Steam] | 祈风 [Steam]" + }, "Rakugaki Overheart": { "$type": "NephriteCrypt", "Title": "らくがきオーバーハート" @@ -2636,6 +2691,11 @@ "Seed": 798088789, "Title": "Role player:小粥姉妹の粘膜ポトレ ぐりぐちゃLIVE! | ROLEPLAYER:小粥姐妹的黏膜游戏!" }, + "Rong Xue [Steam]": { + "$type": "NVLCrypt", + "Key": "+64areSLJUY=", + "Title": "茸雪 [Steam] | Tiny Snow [Steam]" + }, "Royal Garden": { "$type": "SmileCrypt", "KeyXor": 1651056185, @@ -3359,6 +3419,11 @@ "Key2": 57, "Title": "雾之本境" }, + "Wu zhi Benjing S [Steam]": { + "$type": "NVLCrypt", + "Key": "L/0VPDRxerg=", + "Title": "雾之本境S [Steam]" + }, "Xue zhi Benjing 2010": { "$type": "HashCrypt", "Title": "雪之本境 2010" @@ -3371,6 +3436,11 @@ "$type": "NatsupochiCrypt", "Title": "雪之本境Ex" }, + "Xue zhi Benjing S [Steam]": { + "$type": "NVLCrypt", + "Key": "L7qlaLOri4I=", + "Title": "雪之本境S [Steam]" + }, "xx na Kanojo no Tsukurikata 2": { "$type": "Kano2Crypt", "Title": "××な彼女のつくりかた2" @@ -3490,6 +3560,11 @@ "Key": 205, "Title": "勇者と魔王と、魔女のカフェ" }, + "Yuxiang [Steam]": { + "$type": "NVLCrypt", + "Key": "e5hjiwBwQgA=", + "Title": "余香 [Steam] | Lingering Fragrance [Steam]" + }, "Yuye": { "$type": "NatsupochiCrypt", "Title": "雨夜" @@ -3519,6 +3594,11 @@ "$type": "HashCrypt", "Title": "絶対搾精! サキュバスちゃん" }, + "Zhen Lian ~Jiyu Fengqiu~ [Steam]": { + "$type": "NVLCrypt", + "Key": "lv1cSgA0SAA=", + "Title": "真恋~寄语枫秋~ [Steam] | 真戀~寄語楓秋~ [Steam]" + }, "Ziluolan: Li - Season 2 -Shui Zhong Daoying-": { "$type": "Xor2Crypt", "Key1": 61, diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index 07e5059..23df966 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -1,6 +1,7 @@ mod chain_reaction; mod cx; mod cz; +mod nvl; use super::Entry; use super::archive::*; @@ -308,6 +309,10 @@ enum CryptType { key2: u8, }, LeaveSLeaveCrypt, + #[serde(rename_all = "PascalCase")] + NVLCrypt { + key: Base64Bytes, + }, } #[derive(Clone, Debug, Deserialize)] @@ -527,6 +532,7 @@ impl Schema { Box::new(Xor2Crypt::new(self.base.clone(), *key1, *key2)) } CryptType::LeaveSLeaveCrypt => Box::new(LeaveSLeaveCrypt::new(self.base.clone())), + CryptType::NVLCrypt { key } => Box::new(nvl::NVLCrypt::new(self.base.clone(), &key.bytes)?), }) } } @@ -2649,6 +2655,7 @@ impl Read for LeaveSLeaveCryptReader { seek_reader_key_impl!(ChainReactionCryptReader, (u32, u32)); seek_reader_key_impl!(XanaduCryptReader, (u32, u32)); seek_reader_key_impl!(SisMikoCryptReader, (u32, u32)); +seek_reader_key_impl!(NVLCryptReader, [u8; 12]); #[test] fn test_deserialize_crypt() { diff --git a/src/scripts/kirikiri/archive/xp3/crypt/nvl.rs b/src/scripts/kirikiri/archive/xp3/crypt/nvl.rs new file mode 100644 index 0000000..8e26921 --- /dev/null +++ b/src/scripts/kirikiri/archive/xp3/crypt/nvl.rs @@ -0,0 +1,87 @@ +use super::*; + +#[derive(Debug)] +pub struct NVLCrypt { + base: BaseSchema, + key: [u8; 8], +} + +impl NVLCrypt { + pub fn new(base: BaseSchema, key: &[u8]) -> Result { + if key.len() != 8 { + anyhow::bail!("Key must be 8 bytes."); + } + Ok(Self { + base, + key: key.try_into()? + }) + } + + fn get_key(&self, hash: u32) -> [u8; 12] { + let mut key = [0; 12]; + key[..4].copy_from_slice(&hash.to_le_bytes()); + key[4..].copy_from_slice(&self.key); + key + } +} + +impl Crypt for NVLCrypt { + fn hash_after_crypt(&self) -> bool { + self.base.hash_after_crypt + } + fn startup_tjs_not_encrypted(&self) -> bool { + self.base.startup_tjs_not_encrypted + } + fn obfuscated_index(&self) -> bool { + self.base.obfuscated_index + } + fn read_name<'a>(&self, reader: &mut Box) -> Result<(String, u64)> { + let hash_key = reader.read_u32()?; + let name_hash = reader.read_u32()?; + let extension_hash = reader.read_u32()?; + Ok((format!("{:08x}.{:08x}", hash_key ^ name_hash, hash_key ^ extension_hash), 12)) + } + 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(NVLCryptReader::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(NVLCryptReader::new( + stream, + cur_seg, + self.get_key(entry.file_hash), + ))) + } +} + +impl Read for NVLCryptReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let readed = self.inner.read(buf)?; + let mut offset = ((self.pos + self.seg_start) % 12) as usize; + for t in (&mut buf[..readed]).iter_mut() { + *t ^= self.key[offset]; + offset = (offset + 1) % 12; + } + self.pos += readed as u64; + Ok(readed) + } +}