msg_tool\scripts\kirikiri\archive\xp3\crypt/
chain_reaction.rs

1use super::*;
2use crate::ext::mutex::*;
3use crate::utils::lzss::*;
4use std::sync::Mutex;
5
6macro_rules! base_schema_impl {
7    () => {
8        fn hash_after_crypt(&self) -> bool {
9            AsRef::<BaseSchema>::as_ref(self).hash_after_crypt
10        }
11        fn startup_tjs_not_encrypted(&self) -> bool {
12            AsRef::<BaseSchema>::as_ref(self).startup_tjs_not_encrypted
13        }
14        fn obfuscated_index(&self) -> bool {
15            AsRef::<BaseSchema>::as_ref(self).obfuscated_index
16        }
17    };
18}
19
20fn convert_u32_from_string(input: &str) -> Result<u32> {
21    let s = input.trim();
22    if s.is_empty() {
23        anyhow::bail!("String is empty");
24    }
25    Ok(if s.starts_with("0x") || s.starts_with("0X") {
26        u32::from_str_radix(&s[2..], 16)?
27    } else if s.starts_with('#') {
28        u32::from_str_radix(&s[1..], 16)?
29    } else if s.to_lowercase().starts_with("&h") {
30        u32::from_str_radix(&s[2..], 16)?
31    } else {
32        s.parse::<u32>()?
33    })
34}
35
36trait IChainReactionCrypt: std::fmt::Debug {
37    fn get_encryption_limit(&self, entry: &Xp3Entry) -> u32;
38    fn init(&self, archive: &mut Xp3Archive) -> Result<()>;
39}
40
41#[derive(Debug)]
42struct ChainReactionCryptBase {
43    encryption_threshold_map: Mutex<HashMap<u32, u32>>,
44    list_bin: String,
45}
46
47impl ChainReactionCryptBase {
48    fn new(list_bin: String) -> Self {
49        Self {
50            encryption_threshold_map: Mutex::new(HashMap::new()),
51            list_bin,
52        }
53    }
54
55    fn init2(&self, mut bin: Vec<u8>) -> Result<()> {
56        if !bin.starts_with(b"\"\r\n") {
57            for _ in 0..3 {
58                bin = Self::decode_list_bin(bin)?;
59            }
60            // std::fs::write("test.bin", &bin)?;
61        }
62        self.encryption_threshold_map.lock_blocking().clear();
63        self.parse_list_bin(bin)
64    }
65
66    fn read_list_bin(archive: &mut Xp3Archive, list_name: &str) -> Result<Option<Vec<u8>>> {
67        let bin = match archive.entries.iter().find(|x| x.name == list_name) {
68            Some(index) => index.clone(),
69            None => return Ok(None),
70        };
71        let mut entry = Entry::new2(
72            archive.inner.clone(),
73            bin,
74            archive.base_offset,
75            archive.crypt.clone(),
76        );
77        let mut data = Vec::new();
78        entry.read_to_end(&mut data)?;
79        Ok(Some(data))
80    }
81
82    fn parse_list_bin(&self, data: Vec<u8>) -> Result<()> {
83        let mut map = self.encryption_threshold_map.lock_blocking();
84        let decoded = decode_to_string(Encoding::Utf8, &data, true)?;
85        for line in decoded.lines() {
86            let line = line.trim();
87            if line.is_empty() || !line.starts_with("0") {
88                continue;
89            }
90            let pair: Vec<_> = line.split(',').collect();
91            if pair.len() > 1 {
92                let hash = convert_u32_from_string(pair[0])?;
93                let threshold = convert_u32_from_string(pair[1])?;
94                map.insert(hash, threshold);
95            }
96        }
97        Ok(())
98    }
99
100    fn decode_list_bin(data: Vec<u8>) -> Result<Vec<u8>> {
101        let mut header = [0; 0x30];
102        Self::decode_dpd(&data[..0x30], &mut header)?;
103        let hread = MemReaderRef::new(&header);
104        let packed_size = hread.cpeek_u32_at(0x0c)? as usize;
105        let unpacked_size = hread.cpeek_u32_at(0x10)? as usize;
106        if packed_size > data.len() - 0x30 {
107            anyhow::bail!("Data is too smail.");
108        }
109        let sig = &header[0..4];
110        if sig == b"DPDC" {
111            let mut decrypted = Vec::with_capacity(packed_size);
112            decrypted.resize(packed_size, 0);
113            Self::decode_dpd(&data[0x30..packed_size + 0x30], &mut decrypted)?;
114            Ok(decrypted)
115        } else if sig == b"SZLC" {
116            let reader = MemReaderRef::new(&data[0x30..packed_size + 0x30]);
117            let mut lzss = LzssReader::new(reader);
118            let mut result = Vec::with_capacity(unpacked_size);
119            lzss.read_to_end(&mut result)?;
120            if result.len() > unpacked_size {
121                result.truncate(unpacked_size);
122            }
123            Ok(result)
124        } else if sig == b"ELRC" {
125            let min_repeat = hread.cpeek_u32_at(0x1C)?;
126            let mut decoded = Vec::with_capacity(unpacked_size);
127            decoded.resize(unpacked_size, 0);
128            Self::decode_rle(&data[0x30..packed_size + 0x30], &mut decoded, min_repeat)?;
129            Ok(decoded)
130        } else {
131            anyhow::bail!("Unknown signature: {:?}", sig);
132        }
133    }
134
135    fn decode_dpd(src: &[u8], dst: &mut [u8]) -> Result<()> {
136        let length = src.len();
137        if length != dst.len() {
138            anyhow::bail!("Length no matched.");
139        }
140        if length < 8 {
141            dst.copy_from_slice(src);
142            return Ok(());
143        }
144        let tail = length & 3;
145        if tail > 0 {
146            dst[length - tail..].copy_from_slice(&src[length - tail..]);
147        }
148        let length = length / 4;
149        let mut reader = MemReaderRef::new(src);
150        let mut writer = MemWriterRef::new(dst);
151        let mut val = reader.read_u32()?;
152        for _ in 0..length - 1 {
153            let nval = reader.read_u32()?;
154            writer.write_u32(val ^ nval)?;
155            val = nval;
156        }
157        let fdst = writer.peek_u32_at(0)?;
158        writer.write_u32(fdst ^ val)?;
159        Ok(())
160    }
161
162    fn decode_rle(src: &[u8], dst: &mut [u8], min_repeat: u32) -> Result<()> {
163        let mut reader = MemReaderRef::new(src);
164        let mut writer = MemWriterRef::new(dst);
165        while !reader.is_eof() {
166            let b = reader.read_u8()?;
167            let mut repeat = 1;
168            while repeat < min_repeat && !reader.is_eof() && reader.cpeek_u8()? == b {
169                repeat += 1;
170                reader.pos += 1;
171            }
172            if repeat == min_repeat {
173                let ctl = reader.read_u8()?;
174                if ctl > 0x7F {
175                    repeat += (reader.read_u8()? as u32) + (((ctl & 0x7F) as u32) << 8) + 0x80;
176                } else {
177                    repeat += ctl as u32;
178                }
179            }
180            for _ in 0..repeat {
181                writer.write_u8(b)?;
182            }
183        }
184        Ok(())
185    }
186}
187
188impl IChainReactionCrypt for ChainReactionCryptBase {
189    fn get_encryption_limit(&self, entry: &Xp3Entry) -> u32 {
190        self.encryption_threshold_map
191            .lock_blocking()
192            .get(&entry.file_hash)
193            .map(|s| *s)
194            .unwrap_or(0x200)
195    }
196    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
197        let bin = Self::read_list_bin(archive, &self.list_bin)?;
198        if let Some(bin) = bin {
199            if bin.len() >= 0x30 {
200                self.init2(bin)?;
201            }
202        }
203        Ok(())
204    }
205}
206
207#[derive(Debug)]
208pub struct ChainReactionCrypt {
209    base: BaseSchema,
210    inner: Box<dyn IChainReactionCrypt + Send + Sync>,
211}
212
213impl ChainReactionCrypt {
214    pub fn new(base: BaseSchema) -> Self {
215        Self {
216            base,
217            inner: Box::new(ChainReactionCryptBase::new("plugin/list.bin".into())),
218        }
219    }
220
221    fn new_inner(base: BaseSchema, inner: Box<dyn IChainReactionCrypt + Send + Sync>) -> Self {
222        Self { base, inner }
223    }
224}
225
226impl AsRef<BaseSchema> for ChainReactionCrypt {
227    fn as_ref(&self) -> &BaseSchema {
228        &self.base
229    }
230}
231
232impl Crypt for ChainReactionCrypt {
233    base_schema_impl!();
234    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
235        self.inner.init(archive)
236    }
237    fn decrypt_supported(&self) -> bool {
238        true
239    }
240    fn decrypt_seek_supported(&self) -> bool {
241        true
242    }
243    fn decrypt<'a>(
244        &self,
245        entry: &Xp3Entry,
246        cur_seg: &Segment,
247        stream: Box<dyn Read + Send + Sync + 'a>,
248    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
249        Ok(Box::new(ChainReactionCryptReader::new(
250            stream,
251            cur_seg,
252            (self.inner.get_encryption_limit(entry), entry.file_hash),
253        )))
254    }
255    fn decrypt_with_seek<'a>(
256        &self,
257        entry: &Xp3Entry,
258        cur_seg: &Segment,
259        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
260    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
261        Ok(Box::new(ChainReactionCryptReader::new(
262            stream,
263            cur_seg,
264            (self.inner.get_encryption_limit(entry), entry.file_hash),
265        )))
266    }
267}
268
269impl<R: Read> Read for ChainReactionCryptReader<R> {
270    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
271        let readed = self.inner.read(buf)?;
272        let (limit, hash) = self.key;
273        let limit = limit as u64;
274        let mut offset = self.seg_start + self.pos;
275        if offset < limit {
276            let count = (limit - offset).min(readed as u64);
277            for t in buf[..count as usize].iter_mut() {
278                *t ^= (offset ^ ((hash >> ((offset & 3) << 3)) as u8) as u64) as u8;
279                offset += 1;
280            }
281        }
282        self.pos += readed as u64;
283        Ok(readed)
284    }
285}
286
287#[derive(Debug)]
288pub struct HachukanoCrypt {
289    base: ChainReactionCryptBase,
290}
291
292impl HachukanoCrypt {
293    pub fn new(base: BaseSchema) -> ChainReactionCrypt {
294        ChainReactionCrypt::new_inner(
295            base,
296            Box::new(Self {
297                base: ChainReactionCryptBase::new("plugins/list.txt".into()),
298            }),
299        )
300    }
301}
302
303impl IChainReactionCrypt for HachukanoCrypt {
304    fn get_encryption_limit(&self, entry: &Xp3Entry) -> u32 {
305        let limit = self.base.get_encryption_limit(entry);
306        match limit {
307            0 => 0,
308            1 => 0x100,
309            2 => 0x200,
310            3 => entry.original_size as u32,
311            _ => limit,
312        }
313    }
314    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
315        self.base.init(archive)
316    }
317}
318
319#[derive(Debug)]
320pub struct ChocolatCrypt {
321    base: ChainReactionCryptBase,
322}
323
324impl ChocolatCrypt {
325    pub fn new(base: BaseSchema) -> ChainReactionCrypt {
326        ChainReactionCrypt::new_inner(
327            base,
328            Box::new(Self {
329                base: ChainReactionCryptBase::new("plugins/list.txt".into()),
330            }),
331        )
332    }
333}
334
335impl IChainReactionCrypt for ChocolatCrypt {
336    fn get_encryption_limit(&self, entry: &Xp3Entry) -> u32 {
337        let limit = self.base.get_encryption_limit(entry);
338        match limit {
339            0 => 0,
340            2 => entry.original_size as u32,
341            _ => 0x100,
342        }
343    }
344    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
345        self.base.init(archive)
346    }
347}
348
349#[derive(Debug)]
350pub struct XanaduCrypt {
351    base: BaseSchema,
352    inner: ChainReactionCryptBase,
353}
354
355impl XanaduCrypt {
356    pub fn new(base: BaseSchema) -> Self {
357        Self {
358            base,
359            inner: ChainReactionCryptBase::new("plugins/list.txt".into()),
360        }
361    }
362}
363
364impl AsRef<BaseSchema> for XanaduCrypt {
365    fn as_ref(&self) -> &BaseSchema {
366        &self.base
367    }
368}
369
370impl IChainReactionCrypt for XanaduCrypt {
371    fn get_encryption_limit(&self, entry: &Xp3Entry) -> u32 {
372        let limit = self.inner.get_encryption_limit(entry);
373        match limit {
374            0 => 0,
375            2 => entry.original_size as u32,
376            _ => 0x100,
377        }
378    }
379    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
380        let mut bin = ChainReactionCryptBase::read_list_bin(archive, "list2.txt")?;
381        if bin.is_none() {
382            bin = ChainReactionCryptBase::read_list_bin(archive, "plugins/list.txt")?;
383        }
384        if let Some(bin) = bin {
385            self.inner.init2(bin)?;
386        }
387        Ok(())
388    }
389}
390
391impl Crypt for XanaduCrypt {
392    base_schema_impl!();
393    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
394        IChainReactionCrypt::init(self, archive)
395    }
396    fn decrypt_supported(&self) -> bool {
397        true
398    }
399    fn decrypt_seek_supported(&self) -> bool {
400        true
401    }
402    fn decrypt<'a>(
403        &self,
404        entry: &Xp3Entry,
405        cur_seg: &Segment,
406        stream: Box<dyn Read + Send + Sync + 'a>,
407    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
408        Ok(Box::new(XanaduCryptReader::new(
409            stream,
410            cur_seg,
411            (self.get_encryption_limit(entry), entry.file_hash),
412        )))
413    }
414    fn decrypt_with_seek<'a>(
415        &self,
416        entry: &Xp3Entry,
417        cur_seg: &Segment,
418        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
419    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
420        Ok(Box::new(XanaduCryptReader::new(
421            stream,
422            cur_seg,
423            (self.get_encryption_limit(entry), entry.file_hash),
424        )))
425    }
426}
427
428impl<R: Read> Read for XanaduCryptReader<R> {
429    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
430        let readed = self.inner.read(buf)?;
431        let (limit, hash) = self.key;
432        let limit = limit as u64;
433        let mut offset = self.seg_start + self.pos;
434        if offset < limit {
435            let key = hash ^ (!0x03020100);
436            let count = (limit - offset).min(readed as u64);
437            for t in buf[..count as usize].iter_mut() {
438                let extra = (((offset & 0xFF) >> 2) << 2) as u8;
439                *t ^= (key >> (((offset & 3) << 3) as u32)) as u8 ^ extra;
440                offset += 1;
441            }
442        }
443        self.pos += readed as u64;
444        Ok(readed)
445    }
446}
447
448#[derive(Debug)]
449pub struct SisMikoCrypt {
450    base: XanaduCrypt,
451}
452
453impl SisMikoCrypt {
454    pub fn new(base: BaseSchema) -> Self {
455        Self {
456            base: XanaduCrypt::new(base),
457        }
458    }
459}
460
461impl AsRef<BaseSchema> for SisMikoCrypt {
462    fn as_ref(&self) -> &BaseSchema {
463        self.base.as_ref()
464    }
465}
466
467impl Crypt for SisMikoCrypt {
468    base_schema_impl!();
469    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
470        IChainReactionCrypt::init(&self.base, archive)
471    }
472    fn decrypt_supported(&self) -> bool {
473        true
474    }
475    fn decrypt_seek_supported(&self) -> bool {
476        true
477    }
478    fn decrypt<'a>(
479        &self,
480        entry: &Xp3Entry,
481        cur_seg: &Segment,
482        stream: Box<dyn Read + Send + Sync + 'a>,
483    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
484        Ok(Box::new(SisMikoCryptReader::new(
485            stream,
486            cur_seg,
487            (self.base.get_encryption_limit(entry), entry.file_hash),
488        )))
489    }
490    fn decrypt_with_seek<'a>(
491        &self,
492        entry: &Xp3Entry,
493        cur_seg: &Segment,
494        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
495    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
496        Ok(Box::new(SisMikoCryptReader::new(
497            stream,
498            cur_seg,
499            (self.base.get_encryption_limit(entry), entry.file_hash),
500        )))
501    }
502}
503
504impl<R: Read> Read for SisMikoCryptReader<R> {
505    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
506        let readed = self.inner.read(buf)?;
507        let (limit, hash) = self.key;
508        let limit = limit as u64;
509        let mut offset = self.seg_start + self.pos;
510        if offset < limit {
511            let key = !(hash.rotate_right(16));
512            let count = (limit - offset).min(readed as u64);
513            for t in buf[..count as usize].iter_mut() {
514                *t ^= (key >> ((offset & 3) << 3)) as u8;
515                offset += 1;
516            }
517        }
518        self.pos += readed as u64;
519        Ok(readed)
520    }
521}