diff --git a/Cargo.toml b/Cargo.toml index 1abec36..4e8597e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ hexen-haus = ["memchr", "utils-str"] hexen-haus-arc = ["hexen-haus"] hexen-haus-img = ["hexen-haus", "image"] kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"] -kirikiri-arc = ["kirikiri", "adler", "bytes", "fastcdc", "flate2", "int-enum", "md5", "msg_tool_xp3data", "parse-size", "sha2", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"] +kirikiri-arc = ["kirikiri", "adler", "bytes", "fastcdc", "flate2", "int-enum", "md5", "msg_tool_xp3data", "parse-size", "sha2", "utils-case-insensitive-string", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"] kirikiri-img = ["kirikiri", "image", "libtlg-rs"] musica = [] musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"] @@ -123,6 +123,7 @@ emote-psb = ["dep:emote-psb", "adler", "lz4"] # utils feature utils-bit-stream = [] utils-blowfish = ["byteorder"] +utils-case-insensitive-string = [] utils-crc32 = [] utils-escape = ["fancy-regex"] utils-mmx = [] diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index 834433b..cbb1f07 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -185,6 +185,9 @@ "$type": "HashCrypt", "Title": "爆走女装計画~もしも俺が女装させられてレディースに入隊させられたら~" }, + "Berry's": { + "$type": "SourireCrypt" + }, "Bishuu ~Chigyaku no Mesu Dorei~": { "$type": "CxEncryption", "Mask": 531, @@ -221,6 +224,9 @@ "$type": "PoringSoftCrypt", "Title": "ぶらばん! | 管乐恋曲!" }, + "Cafe Sourire": { + "$type": "SourireCrypt" + }, "Café Stella to Shinigami no Chou": { "$type": "RiddleCxCrypt", "Mask": 622, @@ -770,6 +776,14 @@ "$type": "PoringSoftCrypt", "Title": "妹快楽堕ち~お兄ちゃんに無理やりハメられてから~ | 沉溺快感中的妹妹~自从被哥哥强行插入后~" }, + "Imouto no Katachi": { + "$type": "SourireCrypt", + "Title": "イモウトノカタチ | 妹之形" + }, + "Imouto no Okage de Motesugite Yabai.": { + "$type": "SourireCrypt", + "Title": "妹のおかげでモテすぎてヤバい。 | 因为妹妹让我太受欢迎了糟糕了" + }, "Imouto Shokushu Rape": { "$type": "HashCrypt", "Title": "妹触手レイプ ~復讐の処女貫通~" @@ -909,6 +923,10 @@ "Key": 205, "Title": "神頼みしすぎて俺の未来がヤバい。 | 求神太多我的未来糟糕了 | 太依赖咒术的我未来堪忧。" }, + "Kanae to Meguri to no Sonogo ga Icha Love Sugite Yabai.": { + "$type": "SourireCrypt", + "Title": "叶とメグリとのその後がイチャらぶすぎてヤバい。 | 与叶和爱莉之后的故事太过亲密糟糕了" + }, "Kanjuku Sakuranbou!": { "$type": "FlyingShineCrypt", "Title": "完熟☆さくらん棒ッ! ~ヒキオタ達の姉妹交換~" @@ -1022,6 +1040,10 @@ "ControlBlockName": "koiga.bin", "Title": "恋がさくころ桜どき | 恋花绽放樱飞时" }, + "Koi Suru Kanojo no Bukiyou na Butai": { + "$type": "SourireCrypt", + "Title": "恋する彼女の不器用な舞台 | 恋爱中的她的笨拙舞台" + }, "Koikishi Purely ☆ Kiss": { "$type": "CxEncryption", "Mask": 561, @@ -1069,6 +1091,10 @@ "Title": "光輪の町、ラベンダーの少女", "StartupTjsNotEncrypted": true }, + "Kurano-kunchi no Futago Jijou": { + "$type": "SourireCrypt", + "Title": "倉野くんちのふたご事情 | 仓野家的双胞胎故事" + }, "Kurenai no Tsuki": { "$type": "CxEncryption", "Mask": 362, @@ -1160,6 +1186,10 @@ "$type": "HashCrypt", "Title": "ママとのイキすぎ甘々性活~いっぱい気持ちいいコト教えてあ・げ・る~" }, + "Mamiya-kunchi no Itsutsugo Jijou": { + "$type": "SourireCrypt", + "Title": "間宮くんちの五つ子事情 | 间宫家的五子情事" + }, "Manakashi no Yuri wa Akaku Somaru [DL]": { "$type": "CxEncryption", "Mask": 516, @@ -2105,6 +2135,9 @@ "ControlBlockName": "walpurgis.bin", "Title": "ヴァルプルギス" }, + "With Ribbon": { + "$type": "SourireCrypt" + }, "Yome no Ane ga Kyonyuu Sugite": { "$type": "HashCrypt", "Title": "嫁の姉が巨乳過ぎて我慢できない ~義姉さんがこんなにエロかったなんて!" @@ -2127,6 +2160,9 @@ "ControlBlockName": "yomibito.bin", "Title": "黄泉ビト知ラズ~甘い雌蕊の性徴詩~" }, + "your diary + H": { + "$type": "SourireCrypt" + }, "Yurameku Kokoro ni Michita Sekai de": { "$type": "XorCrypt", "Key": 205, diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index 1824811..180de1b 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -4,6 +4,7 @@ use super::archive::*; use crate::ext::io::*; use crate::scripts::base::*; use crate::types::*; +use crate::utils::case_insensitive_string::*; use crate::utils::encoding::*; use crate::utils::serde_base64bytes::*; use crate::utils::simple_pack::*; @@ -168,6 +169,7 @@ enum CryptType { PoringSoftCrypt, AppliqueCrypt, TokidokiCrypt, + SourireCrypt, } #[derive(Clone, Debug, Deserialize)] @@ -263,12 +265,13 @@ impl Schema { CryptType::PoringSoftCrypt => Box::new(PoringSoftCrypt::new(self.base.clone())), CryptType::AppliqueCrypt => Box::new(AppliqueCrypt::new(self.base.clone())), CryptType::TokidokiCrypt => Box::new(TokidokiCrypt::new(self.base.clone())), + CryptType::SourireCrypt => Box::new(SourireCrypt::new(self.base.clone())), }) } } lazy_static::lazy_static! { - static ref CRYPT_SCHEMA: BTreeMap = { + static ref CRYPT_SCHEMA: BTreeMap = { serde_json::from_str(&get_crypt_data()).expect("Failed to parse crypt.json") }; static ref ALIAS_TABLE: HashMap = { @@ -1031,6 +1034,20 @@ impl Read for TokidokiCryptReader { } } +seek_crypt_filehash_key_impl!(SourireCrypt, SourireCryptReader); + +impl Read for SourireCryptReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let readed = self.inner.read(buf)?; + let key = (self.key ^ 0xCD) as u8; + for t in (&mut buf[..readed]).iter_mut() { + *t ^= key; + } + self.pos += readed as u64; + Ok(readed) + } +} + #[test] fn test_deserialize_crypt() { for (key, schema) in CRYPT_SCHEMA.iter() { diff --git a/src/utils/case_insensitive_string.rs b/src/utils/case_insensitive_string.rs new file mode 100644 index 0000000..5283c99 --- /dev/null +++ b/src/utils/case_insensitive_string.rs @@ -0,0 +1,76 @@ +use serde::Deserialize; +use std::borrow::Borrow; +use std::cmp::Ordering; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Deserialize)] +#[serde(transparent)] +/// A case-insensitive string wrapper. Can be used as a key in BTreeMap with a case-insensitive ordering. +/// +/// WARN: Borrowing a &str/&String from this struct will not be case-insensitive. So getting a value from a BTreeMap with a &str/&String key is not ignore case. It just use it's original case. +pub struct CaseInsensitiveString(String); + +impl PartialEq for CaseInsensitiveString { + fn eq(&self, other: &Self) -> bool { + self.0.eq_ignore_ascii_case(&other.0) + } +} + +impl PartialEq for CaseInsensitiveString { + fn eq(&self, other: &String) -> bool { + self.0.eq_ignore_ascii_case(other) + } +} + +impl PartialEq<&str> for CaseInsensitiveString { + fn eq(&self, other: &&str) -> bool { + self.0.eq_ignore_ascii_case(other) + } +} + +impl Eq for CaseInsensitiveString {} + +impl PartialOrd for CaseInsensitiveString { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CaseInsensitiveString { + fn cmp(&self, other: &Self) -> Ordering { + self.0 + .to_ascii_lowercase() + .cmp(&other.0.to_ascii_lowercase()) + } +} + +impl Deref for CaseInsensitiveString { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for CaseInsensitiveString { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Borrow for CaseInsensitiveString { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Borrow for CaseInsensitiveString { + fn borrow(&self) -> &String { + &self.0 + } +} + +impl std::fmt::Display for CaseInsensitiveString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6d58893..b2ff038 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,6 +3,8 @@ pub mod bit_stream; #[cfg(feature = "utils-blowfish")] pub mod blowfish; +#[cfg(feature = "utils-case-insensitive-string")] +pub mod case_insensitive_string; pub mod counter; #[cfg(feature = "utils-crc32")] pub mod crc32;