From a8ed40db2c9a449d15e9adb290ce61bea37891ba Mon Sep 17 00:00:00 2001 From: lifegpc Date: Wed, 8 Apr 2026 15:32:11 +0800 Subject: [PATCH] Add SenrenCxCrypt --- Cargo.lock | 7 + Cargo.toml | 3 +- msg_tool_xp3data/crypt.json | 11 + msg_tool_xp3data/cx_cb/senren_banka.bin | 15 + src/scripts/kirikiri/archive/xp3/archive.rs | 12 + src/scripts/kirikiri/archive/xp3/crypt/cx.rs | 403 +++++++++++++++--- src/scripts/kirikiri/archive/xp3/crypt/mod.rs | 18 +- 7 files changed, 417 insertions(+), 52 deletions(-) create mode 100644 msg_tool_xp3data/cx_cb/senren_banka.bin diff --git a/Cargo.lock b/Cargo.lock index 4206463..68f61f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1345,6 +1345,12 @@ dependencies = [ "xml5ever", ] +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.8.0" @@ -1435,6 +1441,7 @@ dependencies = [ "lz4", "markup5ever", "markup5ever_rcdom", + "md5", "memchr", "mozjpeg", "msg-tool-jpegxl-sys", diff --git a/Cargo.toml b/Cargo.toml index 5a9019f..1abec36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ libtlg-rs = { version = "0.2", optional = true, features = ["encode"] } lz4 = { version = "1.28", optional = true } markup5ever = { version = "0.38", optional = true } markup5ever_rcdom = { version = "0.38", optional = true } +md5 = { version = "0.8", optional = true } memchr = { version = "2.7", optional = true } mozjpeg = { version = "0.10", optional = true } msg_tool_macro = { path = "./msg_tool_macro" } @@ -94,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", "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-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"] diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index d713e67..bde7fef 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -1378,6 +1378,17 @@ "$type": "HashCrypt", "Title": "閃電の守護騎士ニィナ" }, + "Senren*Banka": { + "$type": "SenrenCxCrypt", + "Mask": 308, + "Offset": 1846, + "PrologOrder": "AQAC", + "OddBranchOrder": "AwECBAAF", + "EvenBranchOrder": "BwQDBgIAAQU=", + "NamesSectionId": "sen:", + "ControlBlockName": "senren_banka.bin", + "Title": "千恋*万花 | 千戀*萬花" + }, "Shikijou Kyoudan": { "$type": "HashCrypt", "Title": "色情教団 | 妃神会秘史" diff --git a/msg_tool_xp3data/cx_cb/senren_banka.bin b/msg_tool_xp3data/cx_cb/senren_banka.bin new file mode 100644 index 0000000..a50ee98 --- /dev/null +++ b/msg_tool_xp3data/cx_cb/senren_banka.bin @@ -0,0 +1,15 @@ +%WZo*4@xVEo-ԗ?-^ cL]27ms=@20}ՏEa{qdC(C j"g\.';FLZܽ_+Iu `?ƑM\܋o[{e'FWQnr_?hi&4öH=Rv.mYDܥFb>f1Xac;SAgթTό 18"#vT ^>VOgvٛ/\*nݖgzueB=IdBIxfq+ń[jw +;`\D-`(t=7 fF`?P7I% xNz\5KOYh∅J͋SbZaAnXp_̂(#kQbv~_mȜew'l׻?h\u4F]N;;G;mqv+N[>E~NX|le(gŴNm\I#n +װq}LVHF2ʿtQi!ԷPqMyŢ^;r(GbIwR#1Ih+bw_\+J i.xO!uqHݨIc^oG[2&5٦9%NDK&E ןe7L\[,=WKZ?iP&6?KjJ=L#d 9k |Zp1/wTpO'%CPL :SSEphi$jİ'Yp.hfўnp(D~/j2̄ڋc):]$w-9伷^Y _yh.:̦d;^W;^s"2vX$dX.3)/bjP XqKoSY6䌸y'4Gbf L_YuV9ZHG3GbwvX, +d xekbE:-s]Nj3˷Wȇܦ,^eutvf0-st>ygLsgh6/iЩ +r4"y%vSLAʜ70f<\^?%և ,-V"~Eͭb#Η +a_rakkiٽa12F12KgJƜ;Gu1NYL?  sXc'aX>PB)dgPBD*_9ֳ3;2 & WUG0)_G>L%D>qkԘ(\u&r6zɐ|鉝&F {5tnGož%gXΗ/0p͙zbz ^pD*W3h \\2 8bړx4/Z W>bu x-9vaIqrʹ&fs@F ߮ ݨ?o{䮦20 !\x ~;ȉfn'8.szUα(b en{)z!hԨ|ӍoM?qbzn܄-tHnPw4)_| 2(cLYsajŶ +U) +=[dtbR &QV3/T*7*ܨ͟91'Mۜ>sӈY% +e;IcEP,g(S2z*^<1 vAR٩5t8|b5{У176 +.t޾^=A1iXE^-wCEWN@< 2pD`j3܀..,Ր116&؟f6JY3@-eG jG@@_/Z]q}|o0gӕ˲: A6,Ήg) n!,Q{r(r7[e{ϊ&p5\!CպxrU!d`- BlR@nu '-Qd /;a}uk-ҽoa_jha&0 QdȠߤQ{*oONԊ]j<8Z׶'B+(Jf&z6 U2t _p!EU(7vss oa KMe^$[tIYYA0p8 Z ^O`~*`Scmc8㓒K}_ +*ehn Ơg v~,&Œ-Ⱦ@ZUje2GZF]2k.L3yXnezp!:v'XfÉePi尸\}+o k)7KR!?Q ٦vrE˹{_x/Qcʊ-0DΊC<`Br~5"cYR 3-Dm`V]Pt.qh"xw#li㬹1]zDP= f.$YaoBsVXVꊈ0Op msUIUĽ0Kޯ?۝w_HHϰzzD2ROsMҌ[+h:ׂy 7#{uOZQv᭖{#GC;)6Pl\/@ ;)ڽʬ)㇜؛he*eE eʉCn{3) {ZZ-S4cL93b V~O6hb)VY;?]/sŞ/4#}؀8x{ɔGF 3֛DKFʋT5[ܨ+)#AP@*j/B[voHTK,G:j,ܞIa_f2)D`J0!ӷ\Quέ!j4CB4t,Q7rz>BD\4!n߸@=, P #}vÉ"zB{໣ȓEÁ +לirC&&)}Qҽ\R,ٱA_CSIǀ\ dBbzQ&єiJ~|^Yz* b ˵Tby:tY85 汘[Gfa[lnaoQ82Ye$+=&f1!.wAj#`BY[>CT>tPDH[ǝ}|1dސy%m8'COtWzGraC%K /p:,2vY˸([r7wE=7d/ȫ`'dj׻؃ +g +1ht'gª5C.-A H4QCQ6NVWi5WChvlje? +eg6R3^Jn{39q_L6^6 for PropTag { } } +impl PartialEq for PropTag { + fn eq(&self, other: &String) -> bool { + self.tag == other.as_bytes() + } +} + +impl PartialEq<&str> for PropTag { + fn eq(&self, other: &&str) -> bool { + self.tag == other.as_bytes() + } +} + impl ExtraProp { pub fn is_filename_hash(&self) -> bool { self.tag == CHUNK_HNFN diff --git a/src/scripts/kirikiri/archive/xp3/crypt/cx.rs b/src/scripts/kirikiri/archive/xp3/crypt/cx.rs index 96b29c3..7c7116d 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/cx.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/cx.rs @@ -8,13 +8,13 @@ const S_CTL_BLOCK_SIGNATURE: &[u8] = b" Encryption control block"; macro_rules! base_schema_impl { () => { fn hash_after_crypt(&self) -> bool { - self.base.hash_after_crypt + AsRef::::as_ref(self.as_ref()).hash_after_crypt } fn startup_tjs_not_encrypted(&self) -> bool { - self.base.startup_tjs_not_encrypted + AsRef::::as_ref(self.as_ref()).startup_tjs_not_encrypted } fn obfuscated_index(&self) -> bool { - self.base.obfuscated_index + AsRef::::as_ref(self.as_ref()).obfuscated_index } }; } @@ -27,12 +27,60 @@ pub struct CxEncryption { odd_branch_order: Vec, even_branch_order: Vec, control_block: Arc>, - programs: Vec, + programs: Vec>, + program_builder: Box, base: BaseSchema, } +trait ICxEncryption: std::fmt::Debug { + fn get_base_offset(&self, hash: u32) -> u32; + fn inner_decrypt( + &self, + mut key: u32, + mut offset: u64, + buffer: &mut [u8], + mut pos: usize, + mut count: usize, + ) -> Result<()> { + let base_offset = self.get_base_offset(key); + if offset < base_offset as u64 { + let base_length = ((base_offset as u64 - offset) as usize).min(count); + self.decode(key, offset, buffer, pos, base_length)?; + offset += base_length as u64; + pos += base_length; + count -= base_length; + } + if count > 0 { + key = (key >> 16) ^ key; + self.decode(key, offset, buffer, pos, count)?; + } + Ok(()) + } + fn decode( + &self, + key: u32, + offset: u64, + buffer: &mut [u8], + pos: usize, + count: usize, + ) -> Result<()>; +} + impl CxEncryption { pub fn new(base: BaseSchema, schema: &CxSchema, filename: &str) -> Result> { + Ok(Arc::new(Self::new_inner( + base, + schema, + filename, + Box::new(CxProgramBuilder::default()), + )?)) + } + fn new_inner( + base: BaseSchema, + schema: &CxSchema, + filename: &str, + program_builder: Box, + ) -> Result { if schema.prolog_order.len() != 3 { return Err(anyhow::anyhow!("Prolog order must have 3 elements")); } @@ -70,23 +118,20 @@ impl CxEncryption { even_branch_order: schema.even_branch_order.bytes.clone(), control_block: control_block, programs, + program_builder, }; for seed in 0..0x80 { obj.programs.push(obj.generate_program(seed)?); } - Ok(Arc::new(obj)) + Ok(obj) } - fn new_program(&self, seed: u32) -> CxProgram { - CxProgram { - code: Vec::with_capacity(CX_PROGRAM_SIZE), - control_block: Arc::downgrade(&self.control_block), - length: 0, - seed, - } + fn new_program(&self, seed: u32) -> Box { + self.program_builder + .build(seed, Arc::downgrade(&self.control_block)) } - fn generate_program(&self, seed: u32) -> Result { + fn generate_program(&self, seed: u32) -> Result> { let mut program = self.new_program(seed); for stage in (1..=5).rev() { if self.emit_code(&mut program, stage) { @@ -135,7 +180,7 @@ impl CxEncryption { Err(anyhow::anyhow!("TPM file not found: {}", tpm_path)) } - fn emit_code(&self, program: &mut CxProgram, stage: i32) -> bool { + fn emit_code(&self, program: &mut Box, stage: i32) -> bool { program.emit_nop(5) && program.emit(MovEdiArg, 4) && self.emit_body(program, stage) @@ -143,7 +188,7 @@ impl CxEncryption { && program.emit(Retn, 1) } - fn emit_body(&self, program: &mut CxProgram, stage: i32) -> bool { + fn emit_body(&self, program: &mut Box, stage: i32) -> bool { if stage == 1 { return self.emit_prolog(program); } @@ -174,7 +219,7 @@ impl CxEncryption { self.emit_odd_branch(program) && program.emit(PopEbx, 1) } - fn emit_body2(&self, program: &mut CxProgram, stage: i32) -> bool { + fn emit_body2(&self, program: &mut Box, stage: i32) -> bool { if stage == 1 { return self.emit_prolog(program); } @@ -185,7 +230,7 @@ impl CxEncryption { }; r && self.emit_even_branch(program) } - fn emit_prolog(&self, program: &mut CxProgram) -> bool { + fn emit_prolog(&self, program: &mut Box) -> bool { match self.prolog_order[(program.get_random() % 3) as usize] { 2 => { program.emit_nop(5) @@ -202,7 +247,7 @@ impl CxEncryption { } } - fn emit_even_branch(&self, program: &mut CxProgram) -> bool { + fn emit_even_branch(&self, program: &mut Box) -> bool { match self.even_branch_order[(program.get_random() & 7) as usize] { 0 => program.emit(NotEax, 2), 1 => program.emit(DecEax, 1), @@ -240,7 +285,7 @@ impl CxEncryption { } } - fn emit_odd_branch(&self, program: &mut CxProgram) -> bool { + fn emit_odd_branch(&self, program: &mut Box) -> bool { match self.odd_branch_order[(program.get_random() % 6) as usize] { 0 => { program.emit(PushEcx, 1) @@ -264,10 +309,6 @@ impl CxEncryption { } } - fn get_base_offset(&self, hash: u32) -> u32 { - (hash & self.mask).wrapping_add(self.offset) - } - fn execute_xcode(&self, mut hash: u32) -> Result<(u32, u32)> { let seed = hash & 0x7f; hash >>= 7; @@ -276,28 +317,17 @@ impl CxEncryption { let ret2 = program.execute(!hash)?; Ok((ret1, ret2)) } +} - fn inner_decrypt( - &self, - mut key: u32, - mut offset: u64, - buffer: &mut [u8], - mut pos: usize, - mut count: usize, - ) -> Result<()> { - let base_offset = self.get_base_offset(key); - if offset < base_offset as u64 { - let base_length = ((base_offset as u64 - offset) as usize).min(count); - self.decode(key, offset, buffer, pos, base_length)?; - offset += base_length as u64; - pos += base_length; - count -= base_length; - } - if count > 0 { - key = (key >> 16) ^ key; - self.decode(key, offset, buffer, pos, count)?; - } - Ok(()) +impl AsRef for CxEncryption { + fn as_ref(&self) -> &BaseSchema { + &self.base + } +} + +impl ICxEncryption for CxEncryption { + fn get_base_offset(&self, hash: u32) -> u32 { + (hash & self.mask).wrapping_add(self.offset) } fn decode( @@ -331,6 +361,68 @@ impl CxEncryption { } } +macro_rules! icx_enc_arc_impl { + ($t:ident) => { + impl ICxEncryption for Arc<$t> { + fn get_base_offset(&self, hash: u32) -> u32 { + self.as_ref().get_base_offset(hash) + } + fn inner_decrypt( + &self, + key: u32, + offset: u64, + buffer: &mut [u8], + pos: usize, + count: usize, + ) -> Result<()> { + self.as_ref().inner_decrypt(key, offset, buffer, pos, count) + } + fn decode( + &self, + key: u32, + offset: u64, + buffer: &mut [u8], + pos: usize, + count: usize, + ) -> Result<()> { + self.as_ref().decode(key, offset, buffer, pos, count) + } + } + }; +} + +macro_rules! icx_enc_impl { + ($t:ident) => { + impl ICxEncryption for $t { + fn get_base_offset(&self, hash: u32) -> u32 { + self.base.get_base_offset(hash) + } + fn inner_decrypt( + &self, + key: u32, + offset: u64, + buffer: &mut [u8], + pos: usize, + count: usize, + ) -> Result<()> { + self.base.inner_decrypt(key, offset, buffer, pos, count) + } + fn decode( + &self, + key: u32, + offset: u64, + buffer: &mut [u8], + pos: usize, + count: usize, + ) -> Result<()> { + self.base.decode(key, offset, buffer, pos, count) + } + } + }; +} + +icx_enc_arc_impl!(CxEncryption); + impl Crypt for Arc { base_schema_impl!(); fn decrypt_supported(&self) -> bool { @@ -345,7 +437,10 @@ impl Crypt for Arc { cur_seg: &Segment, stream: Box, ) -> Result> { - let key = (entry.file_hash, self.clone()); + let key = ( + entry.file_hash, + Box::new(self.clone()) as Box, + ); Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key))) } fn decrypt_with_seek<'a>( @@ -354,7 +449,10 @@ impl Crypt for Arc { cur_seg: &Segment, stream: Box, ) -> Result> { - let key = (entry.file_hash, self.clone()); + let key = ( + entry.file_hash, + Box::new(self.clone()) as Box, + ); Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key))) } } @@ -418,7 +516,41 @@ struct Context { stack: Vec, } -impl CxProgram { +#[derive(Debug)] +struct CxProgramBuilder {} + +impl Default for CxProgramBuilder { + fn default() -> Self { + Self {} + } +} + +trait ICxProgramBuilder: std::fmt::Debug { + fn build(&self, seed: u32, control_blocks: Weak>) -> Box; +} + +impl ICxProgramBuilder for CxProgramBuilder { + fn build(&self, seed: u32, control_blocks: Weak>) -> Box { + Box::new(CxProgram { + code: Vec::with_capacity(CX_PROGRAM_SIZE), + control_block: control_blocks, + length: 0, + seed, + }) + } +} + +trait ICxProgram: std::fmt::Debug { + fn execute(&self, hash: u32) -> Result; + fn clear(&mut self); + fn emit_nop(&mut self, count: usize) -> bool; + fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool; + fn emit_u32(&mut self, x: u32) -> bool; + fn emit_random(&mut self) -> bool; + fn get_random(&mut self) -> u32; +} + +impl ICxProgram for CxProgram { fn execute(&self, hash: u32) -> Result { let mut context = Context::default(); let mut iterator = self.code.iter(); @@ -602,7 +734,45 @@ impl CxProgram { } } -impl Read for CxEncryptionReader { +#[derive(msg_tool_macro::MyDebug)] +struct CxEncryptionReader<'a, T> { + #[skip_fmt] + inner: T, + seg_start: u64, + seg_size: u64, + pos: u64, + key: (u32, Box), +} + +impl<'a, T: Read> CxEncryptionReader<'a, T> { + pub fn new(inner: T, seg: &Segment, key: (u32, Box)) -> Self { + Self { + inner, + seg_start: seg.offset_in_file, + seg_size: seg.original_size, + pos: 0, + key, + } + } +} + +impl<'a, T: Read + Seek> Seek for CxEncryptionReader<'a, T> { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + let new_pos: i64 = match pos { + SeekFrom::Start(offset) => offset as i64, + SeekFrom::End(offset) => self.seg_size as i64 + offset, + SeekFrom::Current(offset) => self.pos as i64 + offset, + }; + let offset = new_pos - self.pos as i64; + if offset != 0 { + self.inner.seek(SeekFrom::Current(offset))?; + self.pos = new_pos as u64; + } + Ok(self.pos) + } +} + +impl<'a, R: Read> Read for CxEncryptionReader<'a, R> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let offset = self.seg_start + self.pos; let count = self.inner.read(buf)?; @@ -618,3 +788,140 @@ impl Read for CxEncryptionReader { Ok(count) } } + +#[derive(Debug)] +pub struct SenrenCxCrypt { + base: CxEncryption, + names_section_id: String, +} + +impl AsRef for SenrenCxCrypt { + fn as_ref(&self) -> &BaseSchema { + self.base.as_ref() + } +} + +impl SenrenCxCrypt { + pub fn new( + base: BaseSchema, + schema: &CxSchema, + filename: &str, + names_section_id: String, + ) -> Result> { + Ok(Arc::new(Self::new_inner( + base, + schema, + filename, + Box::new(CxProgramBuilder::default()), + names_section_id, + )?)) + } + fn new_inner( + base: BaseSchema, + schema: &CxSchema, + filename: &str, + program_builder: Box, + names_section_id: String, + ) -> Result { + let cx = CxEncryption::new_inner(base, schema, filename, program_builder)?; + Ok(Self { + base: cx, + names_section_id, + }) + } + fn read_yuzu_names(&self, archive: &mut Xp3Archive) -> Result<()> { + if let Some(section) = archive + .extras + .iter() + .find(|s| s.tag == self.names_section_id) + { + let mut sreader = MemReaderRef::new(§ion.data); + let offset = sreader.read_u64()? + archive.base_offset; + let unpacked_size = sreader.read_u32()?; + let packed_size = sreader.read_u32()?; + let index_stream = + MutexWrapper::new(archive.inner.clone(), offset).take(packed_size as u64); + let mut decoded = MemWriter::from_vec(Vec::with_capacity(unpacked_size as usize)); + { + let mut decoder = flate2::read::ZlibDecoder::new(index_stream); + std::io::copy(&mut decoder, &mut decoded)?; + } + let decoded = decoded.into_inner(); + let mut reader = MemReader::new(decoded); + let mut hash_map = HashMap::new(); + let mut md5_map = HashMap::new(); + let mut dir_offset = 0u64; + while !reader.is_eof() { + let _entry_sign = reader.read_u32()?; + let mut entry_size = reader.read_u64()?; + dir_offset += 12 + entry_size; + let hash = reader.read_u32()?; + let name_len = reader.read_u16()?; + entry_size -= 6; + if (name_len as u64) * 2 <= entry_size { + let name = reader.read_exact_vec((name_len) as usize * 2)?; + let name = decode_to_string(Encoding::Utf16LE, &name, true)?; + if !hash_map.contains_key(&hash) { + hash_map.insert(hash, name.clone()); + } + let encoded = + encode_string(Encoding::Utf16LE, &name.to_ascii_lowercase(), true)?; + let md5 = format!("{:x}", md5::compute(encoded)); + md5_map.insert(md5, name); + } + reader.pos = dir_offset as usize; + md5_map.insert("$".into(), "startup.tjs".into()); + } + for entry in archive.entries.iter_mut() { + if let Some(name) = hash_map.get(&entry.file_hash) { + entry.name = name.clone(); + } else if let Some(name) = md5_map.get(&entry.name) { + entry.name = name.clone(); + } + } + } + archive.extras.retain(|s| s.tag != self.names_section_id); + Ok(()) + } +} + +icx_enc_impl!(SenrenCxCrypt); +icx_enc_arc_impl!(SenrenCxCrypt); + +impl Crypt for Arc { + base_schema_impl!(); + fn init(&self, archive: &mut Xp3Archive) -> Result<()> { + default_init_crypt(archive)?; + self.read_yuzu_names(archive) + } + 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 = ( + entry.file_hash, + Box::new(self.clone()) as Box, + ); + Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key))) + } + fn decrypt_with_seek<'a>( + &self, + entry: &Xp3Entry, + cur_seg: &Segment, + stream: Box, + ) -> Result> { + let key = ( + entry.file_hash, + Box::new(self.clone()) as Box, + ); + Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key))) + } +} diff --git a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs index 3d0aef8..f324120 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -124,6 +124,12 @@ enum CryptType { }, FlyingShineCrypt, CxEncryption(CxSchema), + #[serde(rename_all = "PascalCase")] + SenrenCxCrypt { + #[serde(flatten)] + cx: CxSchema, + names_section_id: String, + }, } #[derive(Clone, Debug, Deserialize)] @@ -159,6 +165,15 @@ impl Schema { CryptType::CxEncryption(schema) => { Box::new(cx::CxEncryption::new(self.base.clone(), &schema, filename)?) } + CryptType::SenrenCxCrypt { + cx, + names_section_id, + } => Box::new(cx::SenrenCxCrypt::new( + self.base.clone(), + cx, + filename, + names_section_id.clone(), + )?), }) } } @@ -601,9 +616,6 @@ impl Read for FlyingShineCryptReader { } } -// extended in cx.rs -seek_reader_key_impl!(CxEncryptionReader, (u32, Arc)); - #[test] fn test_deserialize_crypt() { for (key, schema) in CRYPT_SCHEMA.iter() {