Add SmxCrypt

This commit is contained in:
2026-04-11 22:10:52 +08:00
parent 9b983728ef
commit 6265c8f46b
2 changed files with 140 additions and 0 deletions

View File

@@ -258,6 +258,12 @@
"Seed": 3854429322, "Seed": 3854429322,
"Title": "BUKKAKE! ドシコリングMINUKIマックスアクメしてもいいですか? ~ドエロいポーズして貰って見抜きぶっかけしたらお互い興奮がドチャクソヤバイです~" "Title": "BUKKAKE! ドシコリングMINUKIマックスアクメしてもいいですか? ~ドエロいポーズして貰って見抜きぶっかけしたらお互い興奮がドチャクソヤバイです~"
}, },
"Bura Bura": {
"$type": "SmxCrypt",
"Mask": 3,
"KeySeq": "AwUEAwg=",
"Title": "ぶら ぶら"
},
"Cafe Sourire": { "Cafe Sourire": {
"$type": "SourireCrypt" "$type": "SourireCrypt"
}, },
@@ -641,6 +647,12 @@
"ControlBlockName": "hataraku.bin", "ControlBlockName": "hataraku.bin",
"Title": "働くオタクの恋愛事情" "Title": "働くオタクの恋愛事情"
}, },
"Hataraku Otona no Ren'ai Jijou": {
"$type": "SmxCrypt",
"Mask": 2,
"KeySeq": "AwEAAg==",
"Title": "働くオトナの恋愛事情"
},
"Hatsujou Switch": { "Hatsujou Switch": {
"$type": "CxEncryption", "$type": "CxEncryption",
"Mask": 281, "Mask": 281,
@@ -923,6 +935,12 @@
"$type": "DieselmineCrypt", "$type": "DieselmineCrypt",
"Title": "いつでも女の子に精液大量注入!して褒められる世界" "Title": "いつでも女の子に精液大量注入!して褒められる世界"
}, },
"Izuna Zanshinken": {
"$type": "SmxCrypt",
"Mask": 5,
"KeySeq": "AwgHBgUEAg==",
"Title": "イヅナ斬審剣"
},
"Japanese School Life": { "Japanese School Life": {
"$type": "CxEncryption", "$type": "CxEncryption",
"Mask": 697, "Mask": 697,
@@ -1972,6 +1990,12 @@
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "清楚なあの娘は、隠れビッチ ~いつでもどこでも欲情SEX~" "Title": "清楚なあの娘は、隠れビッチ ~いつでもどこでも欲情SEX~"
}, },
"Sekisaba!": {
"$type": "SmxCrypt",
"Mask": 5,
"KeySeq": "AgUEAwICAw==",
"Title": "せきさば!~私立せきがはら学園女子サバイバルゲーム部~"
},
"Se-n-pa-i ~Hiromi-senpai": { "Se-n-pa-i ~Hiromi-senpai": {
"$type": "NephriteCrypt", "$type": "NephriteCrypt",
"Title": "セ・ン・パ・イ〜広海センパイとのラブエロ部活" "Title": "セ・ン・パ・イ〜広海センパイとのラブエロ部活"
@@ -2405,6 +2429,12 @@
"Seed": 4076513695, "Seed": 4076513695,
"Title": "ヤキモチストリーム | 吃醋大作战 | 醋意乱流" "Title": "ヤキモチストリーム | 吃醋大作战 | 醋意乱流"
}, },
"Yaya, Okiba ga Nai!": {
"$type": "SmxCrypt",
"Mask": 4,
"KeySeq": "AQMDAgQB",
"Title": "やや置き場がない!"
},
"Yome no Ane ga Kyonyuu Sugite": { "Yome no Ane ga Kyonyuu Sugite": {
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "嫁の姉が巨乳過ぎて我慢できない ~義姉さんがこんなにエロかったなんて!" "Title": "嫁の姉が巨乳過ぎて我慢できない ~義姉さんがこんなにエロかったなんて!"
@@ -2439,6 +2469,12 @@
"Key": 205, "Key": 205,
"Title": "ゆらめく心に満ちた世界で、君の夢と欲望は叶うか | 三千心世界,梦终将实现" "Title": "ゆらめく心に満ちた世界で、君の夢と欲望は叶うか | 三千心世界,梦终将实现"
}, },
"Yurikago yori Tenshi made": {
"$type": "SmxCrypt",
"Mask": 4,
"KeySeq": "AgMEBQQC",
"Title": "揺り籠より天使まで"
},
"Yurikago yori Tenshi made [Trial]": { "Yurikago yori Tenshi made [Trial]": {
"$type": "AppliqueCrypt", "$type": "AppliqueCrypt",
"Title": "揺り籠より天使まで 体験版" "Title": "揺り籠より天使まで 体験版"

View File

@@ -235,6 +235,11 @@ enum CryptType {
MadoCrypt { MadoCrypt {
seed: u32, seed: u32,
}, },
#[serde(rename_all = "PascalCase")]
SmxCrypt {
mask: u32,
key_seq: Base64Bytes,
},
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@@ -364,6 +369,9 @@ impl Schema {
config.xp3_file_list_path.as_ref().map(|s| s.as_str()), config.xp3_file_list_path.as_ref().map(|s| s.as_str()),
)?), )?),
CryptType::MadoCrypt { seed } => Box::new(MadoCrypt::new(self.base.clone(), *seed)), CryptType::MadoCrypt { seed } => Box::new(MadoCrypt::new(self.base.clone(), *seed)),
CryptType::SmxCrypt { mask, key_seq } => {
Box::new(SmxCrypt::new(self.base.clone(), *mask, &key_seq.bytes)?)
}
}) })
} }
} }
@@ -1753,6 +1761,102 @@ impl<R: Read> Read for MadoCryptReader<R> {
} }
} }
#[derive(Debug)]
pub struct SmxCrypt {
base: BaseSchema,
mask: u32,
key_seq: Vec<u8>,
}
impl SmxCrypt {
pub fn new(base: BaseSchema, mask: u32, key_seq: &[u8]) -> Result<Self> {
if key_seq.len() <= mask as usize + 1 {
anyhow::bail!(
"Key sequence length must be greater than mask + 1, but got {} and {}",
key_seq.len(),
mask
);
}
if key_seq.len() < 2 {
anyhow::bail!(
"Key sequence length must be at least 2, but got {}",
key_seq.len()
);
}
Ok(Self {
base,
mask,
key_seq: key_seq.to_vec(),
})
}
fn generate_key(&self, file_hash: u32) -> Vec<u8> {
let mut key = vec![0u8; self.key_seq.len() - 1];
for i in 1..self.key_seq.len() {
key[i - 1] = (file_hash >> self.key_seq[i]) as u8;
}
key
}
}
impl Crypt for SmxCrypt {
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<dyn Read + 'a>,
) -> Result<Box<dyn ReadDebug + 'a>> {
let start_key = (entry.file_hash >> self.key_seq[0]) as u8;
Ok(Box::new(SmxCryptReader::new(
stream,
cur_seg,
(self.mask, start_key, self.generate_key(entry.file_hash)),
)))
}
fn decrypt_with_seek<'a>(
&self,
entry: &Xp3Entry,
cur_seg: &Segment,
stream: Box<dyn ReadSeek + 'a>,
) -> Result<Box<dyn ReadSeek + 'a>> {
let start_key = (entry.file_hash >> self.key_seq[0]) as u8;
Ok(Box::new(SmxCryptReader::new(
stream,
cur_seg,
(self.mask, start_key, self.generate_key(entry.file_hash)),
)))
}
}
seek_reader_key_impl!(SmxCryptReader<T>, (u32, u8, Vec<u8>));
impl<R: Read> Read for SmxCryptReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let readed = self.inner.read(buf)?;
let (mask, start_key, key) = &self.key;
let mask = *mask as u64;
let mut offset = self.seg_start + self.pos;
for t in (&mut buf[..readed]).iter_mut() {
let key = if offset <= 100 {
*start_key
} else {
key[(offset & mask) as usize]
};
*t ^= key;
offset += 1;
}
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() {