diff --git a/msg_tool_xp3data/crypt.json b/msg_tool_xp3data/crypt.json index bde7fef..f573fca 100644 --- a/msg_tool_xp3data/crypt.json +++ b/msg_tool_xp3data/crypt.json @@ -574,6 +574,18 @@ "$type": "HashCrypt", "Title": "ほら、そんなに声を漏らすと彼氏にバレちまうぜ? ~エロゲ世界でモブの少女を寝取ってみた~" }, + "Hoshi Koi * Twinkle": { + "$type": "CabbageCxCrypt", + "Mask": 701, + "Offset": 172, + "PrologOrder": "AAEC", + "OddBranchOrder": "AgUDBAEA", + "EvenBranchOrder": "AAIDAQUGBwQ=", + "NamesSectionId": "cbg:", + "RandomSeed": 2463534242, + "ControlBlockName": "hoshikoi.bin", + "Title": "星恋*ティンクル | 星恋*twinkle | 星恋*闪烁" + }, "Ichizu ni Zettai Fukujuu na Imouto": { "$type": "HashCrypt", "Title": "一途に絶対服従な妹をめちゃくちゃにしてみた ~愛欲兄妹~" diff --git a/msg_tool_xp3data/cx_cb/hoshikoi.bin b/msg_tool_xp3data/cx_cb/hoshikoi.bin new file mode 100644 index 0000000..020c1ed Binary files /dev/null and b/msg_tool_xp3data/cx_cb/hoshikoi.bin differ diff --git a/src/scripts/kirikiri/archive/xp3/crypt/cx.rs b/src/scripts/kirikiri/archive/xp3/crypt/cx.rs index 7c7116d..0142552 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/cx.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/cx.rs @@ -925,3 +925,143 @@ impl Crypt for Arc { Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key))) } } + +#[derive(Debug)] +struct CxProgramNana { + base: CxProgram, + random_seed: u32, +} + +impl CxProgramNana { + fn new(seed: u32, control_blocks: Weak>, random_seed: u32) -> Self { + Self { + base: CxProgram { + code: Vec::with_capacity(CX_PROGRAM_SIZE), + control_block: control_blocks, + length: 0, + seed, + }, + random_seed, + } + } +} + +impl ICxProgram for CxProgramNana { + fn execute(&self, hash: u32) -> Result { + self.base.execute(hash) + } + fn clear(&mut self) { + self.base.clear(); + } + fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool { + self.base.emit(bytecode, length) + } + fn emit_nop(&mut self, count: usize) -> bool { + self.base.emit_nop(count) + } + fn emit_u32(&mut self, x: u32) -> bool { + self.base.emit_u32(x) + } + fn emit_random(&mut self) -> bool { + let random = self.get_random(); + self.emit_u32(random) + } + fn get_random(&mut self) -> u32 { + let mut s = self.base.seed ^ (self.base.seed << 17); + s ^= (s << 18) | (s >> 15); + self.base.seed = !s; + let mut r = self.random_seed ^ (self.random_seed << 13); + r ^= r >> 17; + self.random_seed = r ^ (r << 5); + self.base.seed ^ self.random_seed + } +} + +#[derive(Debug)] +struct CxProgramNanaBuilder { + random_seed: u32, +} + +impl CxProgramNanaBuilder { + fn new(random_seed: u32) -> Self { + Self { random_seed } + } +} + +impl ICxProgramBuilder for CxProgramNanaBuilder { + fn build(&self, seed: u32, control_blocks: Weak>) -> Box { + Box::new(CxProgramNana::new(seed, control_blocks, self.random_seed)) + } +} + +#[derive(Debug)] +pub struct CabbageCxCrypt { + base: SenrenCxCrypt, +} + +impl AsRef for CabbageCxCrypt { + fn as_ref(&self) -> &BaseSchema { + self.base.as_ref() + } +} + +impl CabbageCxCrypt { + pub fn new( + base: BaseSchema, + schema: &CxSchema, + filename: &str, + names_section_id: String, + random_seed: u32, + ) -> Result> { + Ok(Arc::new(Self { + base: SenrenCxCrypt::new_inner( + base, + schema, + filename, + Box::new(CxProgramNanaBuilder::new(random_seed)), + names_section_id, + )?, + })) + } +} + +icx_enc_impl!(CabbageCxCrypt); +icx_enc_arc_impl!(CabbageCxCrypt); + +impl Crypt for Arc { + base_schema_impl!(); + fn init(&self, archive: &mut Xp3Archive) -> Result<()> { + default_init_crypt(archive)?; + self.base.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 f324120..b1886b2 100644 --- a/src/scripts/kirikiri/archive/xp3/crypt/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/crypt/mod.rs @@ -130,6 +130,13 @@ enum CryptType { cx: CxSchema, names_section_id: String, }, + #[serde(rename_all = "PascalCase")] + CabbageCxCrypt { + #[serde(flatten)] + cx: CxSchema, + names_section_id: String, + random_seed: u32, + }, } #[derive(Clone, Debug, Deserialize)] @@ -174,6 +181,17 @@ impl Schema { filename, names_section_id.clone(), )?), + CryptType::CabbageCxCrypt { + cx, + names_section_id, + random_seed, + } => Box::new(cx::CabbageCxCrypt::new( + self.base.clone(), + cx, + filename, + names_section_id.clone(), + *random_seed, + )?), }) } } diff --git a/src/scripts/kirikiri/archive/xp3/mod.rs b/src/scripts/kirikiri/archive/xp3/mod.rs index 374053d..18bdc5d 100644 --- a/src/scripts/kirikiri/archive/xp3/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/mod.rs @@ -18,7 +18,7 @@ pub use crypt::get_supported_games_with_title; use flate2::read::ZlibDecoder; use overf::wrapping; pub use segmenter::SegmenterConfig; -use std::io::{Read, Seek, SeekFrom}; +use std::io::{Read, Seek, SeekFrom, Write}; use std::sync::{Arc, Mutex}; use writer::Xp3ArchiveWriter; use zstd::stream::read::Decoder as ZstdDecoder; @@ -174,6 +174,8 @@ impl Xp3Archive { let mut archive = archive::Xp3Archive::new(stream, config, filename)?; if config.xp3_debug_archive { println!("Debug info for {}:\n{:#?}", filename, archive); + // Try flush stdout. + let _ = std::io::stdout().flush(); } archive.entries.retain(|entry| { let i = &entry.name;