Add SourireCrypt

Use case-insensitive method to compare game title
This commit is contained in:
2026-04-10 10:11:50 +08:00
parent d11af6fc1b
commit 0eb9de6bd8
5 changed files with 134 additions and 2 deletions

View File

@@ -95,7 +95,7 @@ hexen-haus = ["memchr", "utils-str"]
hexen-haus-arc = ["hexen-haus"] hexen-haus-arc = ["hexen-haus"]
hexen-haus-img = ["hexen-haus", "image"] hexen-haus-img = ["hexen-haus", "image"]
kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"] 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"] kirikiri-img = ["kirikiri", "image", "libtlg-rs"]
musica = [] musica = []
musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"] 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 feature
utils-bit-stream = [] utils-bit-stream = []
utils-blowfish = ["byteorder"] utils-blowfish = ["byteorder"]
utils-case-insensitive-string = []
utils-crc32 = [] utils-crc32 = []
utils-escape = ["fancy-regex"] utils-escape = ["fancy-regex"]
utils-mmx = [] utils-mmx = []

View File

@@ -185,6 +185,9 @@
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "爆走女装計画~もしも俺が女装させられてレディースに入隊させられたら~" "Title": "爆走女装計画~もしも俺が女装させられてレディースに入隊させられたら~"
}, },
"Berry's": {
"$type": "SourireCrypt"
},
"Bishuu ~Chigyaku no Mesu Dorei~": { "Bishuu ~Chigyaku no Mesu Dorei~": {
"$type": "CxEncryption", "$type": "CxEncryption",
"Mask": 531, "Mask": 531,
@@ -221,6 +224,9 @@
"$type": "PoringSoftCrypt", "$type": "PoringSoftCrypt",
"Title": "ぶらばん! | 管乐恋曲!" "Title": "ぶらばん! | 管乐恋曲!"
}, },
"Cafe Sourire": {
"$type": "SourireCrypt"
},
"Café Stella to Shinigami no Chou": { "Café Stella to Shinigami no Chou": {
"$type": "RiddleCxCrypt", "$type": "RiddleCxCrypt",
"Mask": 622, "Mask": 622,
@@ -770,6 +776,14 @@
"$type": "PoringSoftCrypt", "$type": "PoringSoftCrypt",
"Title": "妹快楽堕ち~お兄ちゃんに無理やりハメられてから~ | 沉溺快感中的妹妹~自从被哥哥强行插入后~" "Title": "妹快楽堕ち~お兄ちゃんに無理やりハメられてから~ | 沉溺快感中的妹妹~自从被哥哥强行插入后~"
}, },
"Imouto no Katachi": {
"$type": "SourireCrypt",
"Title": "イモウトノカタチ | 妹之形"
},
"Imouto no Okage de Motesugite Yabai.": {
"$type": "SourireCrypt",
"Title": "妹のおかげでモテすぎてヤバい。 | 因为妹妹让我太受欢迎了糟糕了"
},
"Imouto Shokushu Rape": { "Imouto Shokushu Rape": {
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "妹触手レイプ ~復讐の処女貫通~" "Title": "妹触手レイプ ~復讐の処女貫通~"
@@ -909,6 +923,10 @@
"Key": 205, "Key": 205,
"Title": "神頼みしすぎて俺の未来がヤバい。 | 求神太多我的未来糟糕了 | 太依赖咒术的我未来堪忧。" "Title": "神頼みしすぎて俺の未来がヤバい。 | 求神太多我的未来糟糕了 | 太依赖咒术的我未来堪忧。"
}, },
"Kanae to Meguri to no Sonogo ga Icha Love Sugite Yabai.": {
"$type": "SourireCrypt",
"Title": "叶とメグリとのその後がイチャらぶすぎてヤバい。 | 与叶和爱莉之后的故事太过亲密糟糕了"
},
"Kanjuku Sakuranbou!": { "Kanjuku Sakuranbou!": {
"$type": "FlyingShineCrypt", "$type": "FlyingShineCrypt",
"Title": "完熟☆さくらん棒ッ! ~ヒキオタ達の姉妹交換~" "Title": "完熟☆さくらん棒ッ! ~ヒキオタ達の姉妹交換~"
@@ -1022,6 +1040,10 @@
"ControlBlockName": "koiga.bin", "ControlBlockName": "koiga.bin",
"Title": "恋がさくころ桜どき | 恋花绽放樱飞时" "Title": "恋がさくころ桜どき | 恋花绽放樱飞时"
}, },
"Koi Suru Kanojo no Bukiyou na Butai": {
"$type": "SourireCrypt",
"Title": "恋する彼女の不器用な舞台 | 恋爱中的她的笨拙舞台"
},
"Koikishi Purely ☆ Kiss": { "Koikishi Purely ☆ Kiss": {
"$type": "CxEncryption", "$type": "CxEncryption",
"Mask": 561, "Mask": 561,
@@ -1069,6 +1091,10 @@
"Title": "光輪の町、ラベンダーの少女", "Title": "光輪の町、ラベンダーの少女",
"StartupTjsNotEncrypted": true "StartupTjsNotEncrypted": true
}, },
"Kurano-kunchi no Futago Jijou": {
"$type": "SourireCrypt",
"Title": "倉野くんちのふたご事情 | 仓野家的双胞胎故事"
},
"Kurenai no Tsuki": { "Kurenai no Tsuki": {
"$type": "CxEncryption", "$type": "CxEncryption",
"Mask": 362, "Mask": 362,
@@ -1160,6 +1186,10 @@
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "ママとのイキすぎ甘々性活~いっぱい気持ちいいコト教えてあ・げ・る~" "Title": "ママとのイキすぎ甘々性活~いっぱい気持ちいいコト教えてあ・げ・る~"
}, },
"Mamiya-kunchi no Itsutsugo Jijou": {
"$type": "SourireCrypt",
"Title": "間宮くんちの五つ子事情 | 间宫家的五子情事"
},
"Manakashi no Yuri wa Akaku Somaru [DL]": { "Manakashi no Yuri wa Akaku Somaru [DL]": {
"$type": "CxEncryption", "$type": "CxEncryption",
"Mask": 516, "Mask": 516,
@@ -2105,6 +2135,9 @@
"ControlBlockName": "walpurgis.bin", "ControlBlockName": "walpurgis.bin",
"Title": "ヴァルプルギス" "Title": "ヴァルプルギス"
}, },
"With Ribbon": {
"$type": "SourireCrypt"
},
"Yome no Ane ga Kyonyuu Sugite": { "Yome no Ane ga Kyonyuu Sugite": {
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "嫁の姉が巨乳過ぎて我慢できない ~義姉さんがこんなにエロかったなんて!" "Title": "嫁の姉が巨乳過ぎて我慢できない ~義姉さんがこんなにエロかったなんて!"
@@ -2127,6 +2160,9 @@
"ControlBlockName": "yomibito.bin", "ControlBlockName": "yomibito.bin",
"Title": "黄泉ビト知ラズ~甘い雌蕊の性徴詩~" "Title": "黄泉ビト知ラズ~甘い雌蕊の性徴詩~"
}, },
"your diary + H": {
"$type": "SourireCrypt"
},
"Yurameku Kokoro ni Michita Sekai de": { "Yurameku Kokoro ni Michita Sekai de": {
"$type": "XorCrypt", "$type": "XorCrypt",
"Key": 205, "Key": 205,

View File

@@ -4,6 +4,7 @@ use super::archive::*;
use crate::ext::io::*; use crate::ext::io::*;
use crate::scripts::base::*; use crate::scripts::base::*;
use crate::types::*; use crate::types::*;
use crate::utils::case_insensitive_string::*;
use crate::utils::encoding::*; use crate::utils::encoding::*;
use crate::utils::serde_base64bytes::*; use crate::utils::serde_base64bytes::*;
use crate::utils::simple_pack::*; use crate::utils::simple_pack::*;
@@ -168,6 +169,7 @@ enum CryptType {
PoringSoftCrypt, PoringSoftCrypt,
AppliqueCrypt, AppliqueCrypt,
TokidokiCrypt, TokidokiCrypt,
SourireCrypt,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@@ -263,12 +265,13 @@ impl Schema {
CryptType::PoringSoftCrypt => Box::new(PoringSoftCrypt::new(self.base.clone())), CryptType::PoringSoftCrypt => Box::new(PoringSoftCrypt::new(self.base.clone())),
CryptType::AppliqueCrypt => Box::new(AppliqueCrypt::new(self.base.clone())), CryptType::AppliqueCrypt => Box::new(AppliqueCrypt::new(self.base.clone())),
CryptType::TokidokiCrypt => Box::new(TokidokiCrypt::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! { lazy_static::lazy_static! {
static ref CRYPT_SCHEMA: BTreeMap<String, Schema> = { static ref CRYPT_SCHEMA: BTreeMap<CaseInsensitiveString, Schema> = {
serde_json::from_str(&get_crypt_data()).expect("Failed to parse crypt.json") serde_json::from_str(&get_crypt_data()).expect("Failed to parse crypt.json")
}; };
static ref ALIAS_TABLE: HashMap<String, String> = { static ref ALIAS_TABLE: HashMap<String, String> = {
@@ -1031,6 +1034,20 @@ impl<R: Read> Read for TokidokiCryptReader<R> {
} }
} }
seek_crypt_filehash_key_impl!(SourireCrypt, SourireCryptReader<T>);
impl<R: Read> Read for SourireCryptReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
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] #[test]
fn test_deserialize_crypt() { fn test_deserialize_crypt() {
for (key, schema) in CRYPT_SCHEMA.iter() { for (key, schema) in CRYPT_SCHEMA.iter() {

View File

@@ -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<String> 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<Ordering> {
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<str> for CaseInsensitiveString {
fn borrow(&self) -> &str {
&self.0
}
}
impl Borrow<String> 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)
}
}

View File

@@ -3,6 +3,8 @@
pub mod bit_stream; pub mod bit_stream;
#[cfg(feature = "utils-blowfish")] #[cfg(feature = "utils-blowfish")]
pub mod blowfish; pub mod blowfish;
#[cfg(feature = "utils-case-insensitive-string")]
pub mod case_insensitive_string;
pub mod counter; pub mod counter;
#[cfg(feature = "utils-crc32")] #[cfg(feature = "utils-crc32")]
pub mod crc32; pub mod crc32;