diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index 5d8bf26..cfc0188 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -1523,6 +1523,13 @@ "$type": "NephriteCrypt", "Title": "にいなちゃん Lovely Life" }, + "Ninki Seiyuu [Steam]": { + "$type": "NinkiSeiyuuCrypt", + "Key1": 7953573635333415499, + "Key2": 12618520504104391278, + "Key3": 16867801142055146094, + "Title": "人気声優のつくりかた [Steam] | 人气声优的养成方式 [Steam] | 人氣聲優的養成方式 [Steam]" + }, "Nitou Ou Mono wa Ittou mo Ezu": { "$type": "NatsupochiCrypt", "Title": "二刀追うものは一刀も得ず | 追二刀者不得一刀" diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index 3e778a8..04d1c7c 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -250,6 +250,12 @@ enum CryptType { NekoWorksCrypt { key: Base64Bytes, }, + #[serde(rename_all = "PascalCase")] + NinkiSeiyuuCrypt { + key1: u64, + key2: u64, + key3: u64, + }, } #[derive(Clone, Debug, Deserialize)] @@ -392,6 +398,12 @@ impl Schema { CryptType::NekoWorksCrypt { key } => { Box::new(NekoWorksCrypt::new(self.base.clone(), key.bytes.clone())?) } + CryptType::NinkiSeiyuuCrypt { key1, key2, key3 } => Box::new(NinkiSeiyuuCrypt::new( + self.base.clone(), + *key1, + *key2, + *key3, + )), }) } } @@ -2007,6 +2019,114 @@ impl Read for NekoWorksCryptReader { } } +#[derive(Debug)] +pub struct NinkiSeiyuuCrypt { + base: BaseSchema, + tbl2: [u8; 64], + tbl3: [u8; 64], +} + +impl NinkiSeiyuuCrypt { + pub fn new(base: BaseSchema, key1: u64, key2: u64, key3: u64) -> Self { + let tbl2 = Self::get_table2(3080); + let tbl3 = Self::get_table3(3080, key1, key2, key3); + Self { base, tbl2, tbl3 } + } + fn get_table1(seed: u32) -> [u8; 32] { + let mut key = [0; 32]; + let mut v48 = seed & 0x7FFFFFFF; + for i in 0..31 { + key[i] = v48 as u8; + v48 = (v48 >> 8) | (((v48 as u8) as u32) << 23); + } + key + } + fn get_table2(seed: u32) -> [u8; 64] { + let mut key = [0; 64]; + let v51 = seed & 0xFFF; + let v52 = ((v51 | (v51 << 13)) as u64) | (((v51 >> 19) as u64) << 32); + let mut v53 = v51 | ((v52 as u32) << 13); + let mut v54 = (((((v52 as u32) << 7) & 0x1FFFFFFF) as u64) | (v52 >> 19)) as u32; + for i in 0..61 { + let v56 = v53 as u8; + key[i] = v56; + v53 = ((((v54 as u64) << 32) | (v53 as u64)) >> 8) as u32; + v54 = (v54 >> 8) | ((v56 as u32) << 21); + } + key + } + fn get_table3(seed: u32, key1: u64, key2: u64, key3: u64) -> [u8; 64] { + let mut key = [0; 64]; + let v88 = seed & 0xFFF; + let v89 = ((v88 | (v88 << 13)) as u64) | (((v88 >> 19) as u64) << 32); + let mut v90 = ((key1 + key2) ^ ((v88 | ((v89 as u32) << 13)) as u64)) as u32; + let mut v91 = + ((((key1 + ((key3 & 0xFFFFFFFF00000000) | (key2 & 0xFFFFFFFF))) >> 32) & 0x1FFFFFFF) + ^ (((((v89 as u32) << 7) & 0x1FFFFFFF) as u64) | (v89 >> 19))) as u32; + for i in 0..61 { + let v93 = v90 as u8; + key[i] = v93; + v90 = ((((v91 as u64) << 32) | (v90 as u64)) >> 8) as u32; + v91 = (v91 >> 8) | ((v93 as u32) << 21); + } + key + } +} + +impl Crypt for NinkiSeiyuuCrypt { + 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 key = ( + Self::get_table1(entry.file_hash), + self.tbl2.clone(), + self.tbl3.clone(), + ); + Ok(Box::new(NinkiSeiyuuCryptReader::new(stream, cur_seg, key))) + } + fn decrypt_with_seek<'a>( + &self, + entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + let key = ( + Self::get_table1(entry.file_hash), + self.tbl2.clone(), + self.tbl3.clone(), + ); + Ok(Box::new(NinkiSeiyuuCryptReader::new(stream, cur_seg, key))) + } +} + +seek_reader_key_impl!(NinkiSeiyuuCryptReader, ([u8; 32], [u8; 64], [u8; 64])); + +impl Read for NinkiSeiyuuCryptReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let readed = self.inner.read(buf)?; + let mut offset1 = ((self.seg_start + self.pos) % 0x1F) as usize; + let mut offset2 = ((self.seg_start + self.pos) % 0x3D) as usize; + for t in (&mut buf[..readed]).iter_mut() { + *t ^= self.key.0[offset1]; + *t = (*t).wrapping_add(self.key.1[offset2] ^ self.key.2[offset2]); + offset1 = (offset1 + 1) % 0x1F; + offset2 = (offset2 + 1) % 0x3D; + } + self.pos += readed as u64; + Ok(readed) + } +} + #[test] fn test_deserialize_crypt() { for (key, schema) in CRYPT_SCHEMA.iter() {