diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index ae57b37..97e8d4e 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -1536,6 +1536,31 @@ "$type": "HashCrypt", "Title": "黒ギャル娘との淫欲相姦~翻弄される父親の性欲~" }, + "Limelight Lemonade Jam": { + "$type": "HxCrypt", + "Mask": 738, + "Offset": 643, + "PrologOrder": "AQAC", + "OddBranchOrder": "AQIEAwAF", + "EvenBranchOrder": "AgUABwYBAwQ=", + "IndexKey1": { + "Key": "fMktWafCUSPGVDvR/8LUx9f+yh3Y+PIq90XmzJ6xZhI=", + "Nonce": "aDnPYFowPzhVdOsfJweaYA==" + }, + "IndexKey2": [ + { + "Key": "/IZHSi5vZTrAxMC4OyQ/+yminq7Uz5yotLa47H1QGI4=", + "Nonce": "GiXPX9XR1GyQIMCSLOIy5w==" + }, + { + "Key": "kHMdDweFjeDFVTwQA10XQAPUAOfXzKp2ukygeLzGzHg=", + "Nonce": "CSBjzSXQNTioPhDp710WCQ==" + } + ], + "FilterKey": 8093658935271803197, + "ControlBlockName": "limelight.bin", + "Title": "ライムライト・レモネードジャム | 聚光灯下的青柠恋曲 | 橘光柠水随想曲" + }, "Little Hole": { "$type": "HashCrypt", "Title": "りとるほーる ~俺の娘がこんなに……なハズがない | Littlehole~我的女儿不可能这么……" diff --git a/msg_tool_xp3data/cx_cb/limelight.bin b/msg_tool_xp3data/cx_cb/limelight.bin new file mode 100644 index 0000000..1f8edfc Binary files /dev/null and b/msg_tool_xp3data/cx_cb/limelight.bin differ diff --git a/src/scripts/kirikiri/archive/xp3/crypt/cx.rs b/src/scripts/kirikiri/archive/xp3/crypt/cx.rs index 7e1bcf1..bf2bcf2 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/cx.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/cx.rs @@ -6,7 +6,7 @@ use anyhow::Result; use chacha20::ChaCha20Legacy; use serde::{Deserializer, de}; use std::collections::HashSet; -use std::ops::Index; +use std::ops::{Deref, DerefMut, Index}; use std::path::PathBuf; use std::sync::{Mutex, Weak}; @@ -2031,7 +2031,7 @@ impl std::fmt::Debug for CxdecDb { pub struct HxCrypt { base: CxEncryption, key1: IndexKey, - key2: IndexKey, + key2: IndexKeys, filter_key: u64, file_mapping: HashMap, path_mapping: HashMap, @@ -2085,12 +2085,48 @@ impl<'de> Deserialize<'de> for IndexKey { } } +#[derive(Clone, Debug)] +pub struct IndexKeys(pub Vec); + +impl Deref for IndexKeys { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for IndexKeys { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum IndexKeysTmp { + List(Vec), + Single(IndexKey), +} + +impl<'de> Deserialize<'de> for IndexKeys { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let tmp = IndexKeysTmp::deserialize(deserializer)?; + Ok(match tmp { + IndexKeysTmp::List(list) => Self(list), + IndexKeysTmp::Single(one) => Self(vec![one]), + }) + } +} + impl HxCrypt { pub fn new( base: BaseSchema, cx: &CxSchema, index_key1: &IndexKey, - index_key2: &IndexKey, + index_key2: &IndexKeys, filter_key: u64, random_type: i32, file_list_name: Option<&str>, @@ -2216,10 +2252,13 @@ impl HxCrypt { Ok((file_map, path_map)) } - fn create_chacha20_crypt(&self, flags: u16) -> Result { + fn create_chacha20_crypt(&self, flags: u16, key_index: usize) -> Result { use chacha20::{KeyIvInit, cipher::StreamCipherSeek}; let key = match flags { - 0 => &self.key2, + 0 => self + .key2 + .get(key_index) + .ok_or_else(|| anyhow::anyhow!("Index out of bound"))?, 1 => &self.key1, _ => anyhow::bail!("Unknown hxv4 flags: {}", flags), }; @@ -2230,10 +2269,16 @@ impl HxCrypt { Ok(crypt) } - fn read_index(&self, mut stream: T, flags: u16) -> Result<()> { + fn read_index_stream_internel( + &self, + stream: &mut T, + flags: u16, + key_index: usize, + ) -> Result { use chacha20::cipher::StreamCipher; + stream.rewind()?; let len = stream.stream_length()?; - let mut crypt = self.create_chacha20_crypt(flags)?; + let mut crypt = self.create_chacha20_crypt(flags, key_index)?; let tlen = len as usize - 16; let mut buf = Vec::with_capacity(tlen); stream.seek(SeekFrom::Start(16))?; @@ -2242,7 +2287,26 @@ impl HxCrypt { let mut stream = flate2::read::ZlibDecoder::new(MemReaderRef::new(&buf[4..])); let mut buf = Vec::new(); stream.read_to_end(&mut buf)?; - let mut reader = MemReader::new(buf); + Ok(MemReader::new(buf)) + } + + fn read_index_stream(&self, mut stream: T, flags: u16) -> Result { + if flags != 0 { + self.read_index_stream_internel(&mut stream, flags, 0) + } else if self.key2.len() == 1 { + self.read_index_stream_internel(&mut stream, flags, 0) + } else { + for i in 0..self.key2.len() { + if let Ok(reader) = self.read_index_stream_internel(&mut stream, flags, i) { + return Ok(reader); + } + } + anyhow::bail!("All index key decrypt failed.") + } + } + + fn read_index(&self, stream: T, flags: u16) -> Result<()> { + let mut reader = self.read_index_stream(stream, flags)?; let root_obj = TjsValue::unpack(&mut reader, true, Encoding::Utf16LE, &None)?; if !root_obj.is_array() { anyhow::bail!("Index object is not an array."); diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index 71bae32..0842197 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -289,7 +289,7 @@ enum CryptType { #[serde(flatten)] cx: CxSchema, index_key1: cx::IndexKey, - index_key2: cx::IndexKey, + index_key2: cx::IndexKeys, filter_key: u64, #[serde(default)] random_type: i32,