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

1mod chain_reaction;
2mod cx;
3mod cz;
4mod nvl;
5
6use super::Entry;
7use super::archive::*;
8use crate::ext::io::*;
9use crate::scripts::base::*;
10use crate::types::*;
11use crate::utils::case_insensitive_string::*;
12use crate::utils::encoding::*;
13use crate::utils::serde_base64bytes::*;
14use crate::utils::simple_pack::*;
15use anyhow::Result;
16use msg_tool_xp3data::*;
17use overf::wrapping as w;
18use serde::Deserialize;
19use std::collections::{BTreeMap, HashMap};
20use std::io::{Read, Seek, SeekFrom};
21use std::sync::Arc;
22
23pub use cx::Hxv4Crypt;
24
25type CIS = CaseInsensitiveStr;
26
27pub fn default_init_crypt(archive: &mut Xp3Archive) -> Result<()> {
28    if archive.extras.iter().any(|extra| extra.is_filename_hash()) {
29        let mut filename_map = HashMap::new();
30        for extra in &archive.extras {
31            if extra.is_filename_hash() {
32                let mut reader = MemReaderRef::new(&extra.data);
33                let hash = reader.read_u32()?;
34                let name_length = reader.read_u16()?;
35                let name = reader.read_exact_vec(name_length as usize * 2)?;
36                let name = decode_to_string(Encoding::Utf16LE, &name, true)?;
37                filename_map.insert(hash, name);
38            }
39        }
40        archive.extras.retain(|extra| !extra.is_filename_hash());
41        for entry in &mut archive.entries {
42            if let Some(name) = filename_map.get(&entry.file_hash) {
43                entry.name = name.clone();
44            }
45        }
46    }
47    Ok(())
48}
49
50fn default_read_name<'a>(reader: &mut Box<dyn Read + 'a>) -> Result<(String, u64)> {
51    let name_length = reader.read_u16()?;
52    let name = reader.read_exact_vec(name_length as usize * 2)?;
53    Ok((
54        decode_to_string(Encoding::Utf16LE, &name, true)?,
55        name_length as u64 * 2 + 2,
56    ))
57}
58
59pub trait Crypt: std::fmt::Debug {
60    #[allow(dead_code)]
61    /// whether Adler32 checksum should be calculated after contents have been encrypted.
62    fn hash_after_crypt(&self) -> bool;
63
64    /// whether the startup.tjs script is not encrypted even when the archive is encrypted.
65    fn startup_tjs_not_encrypted(&self) -> bool;
66
67    /// whether XP3 index is obfuscated:
68    ///  - duplicate entries
69    ///  - entries have additional dummy segments
70    #[allow(dead_code)]
71    fn obfuscated_index(&self) -> bool;
72
73    /// Initializes the cryptographic context for the archive.
74    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
75        default_init_crypt(archive)
76    }
77
78    /// Read a entry name from archive index
79    fn read_name<'a>(&self, reader: &mut Box<dyn Read + 'a>) -> Result<(String, u64)> {
80        default_read_name(reader)
81    }
82
83    /// Decrypts the given stream of data for the specified entry and segment.
84    fn decrypt<'a>(
85        &self,
86        _entry: &Xp3Entry,
87        _cur_seg: &Segment,
88        _stream: Box<dyn Read + Send + Sync + 'a>,
89    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
90        Err(anyhow::anyhow!("This crypt does not support decrypt"))
91    }
92
93    /// Decrypts the given stream of data for the specified entry and segment, with seek support.
94    fn decrypt_with_seek<'a>(
95        &self,
96        _entry: &Xp3Entry,
97        _cur_seg: &Segment,
98        _stream: Box<dyn ReadSeek + Send + Sync + 'a>,
99    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
100        Err(anyhow::anyhow!(
101            "This crypt does not support decrypt with seek"
102        ))
103    }
104
105    /// Returns true if this crypt support decrypt
106    fn decrypt_supported(&self) -> bool {
107        false
108    }
109
110    /// Returns true if this crypt support seek when decrypting
111    fn decrypt_seek_supported(&self) -> bool {
112        false
113    }
114
115    /// Determine whether the file with the given name and content need to be extra processed after decryption. (e.g. extra decryption by file type)
116    fn need_filter(&self, _filename: &str, _buf: &[u8], _buf_len: usize) -> bool {
117        false
118    }
119
120    /// Returns true if this crypt support seek when filtering
121    fn filter_seek_supported(&self) -> bool {
122        false
123    }
124
125    /// Apply extra processing to the decrypted content of the file.
126    fn filter<'a>(&self, _entry: Entry<'a>) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
127        Err(anyhow::anyhow!(
128            "This crypt does not support content filter after decrypt"
129        ))
130    }
131
132    /// Apply extra processing to the decrypted content of the file, with seek support.
133    fn filter_with_seek<'a>(
134        &self,
135        _entry: Entry<'a>,
136    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
137        Err(anyhow::anyhow!(
138            "This crypt does not support content filter with seek after decrypt"
139        ))
140    }
141}
142
143#[derive(Clone, Debug, Deserialize)]
144#[serde(rename_all = "PascalCase")]
145pub struct CxSchema {
146    mask: u32,
147    offset: u32,
148    prolog_order: Base64Bytes,
149    odd_branch_order: Base64Bytes,
150    even_branch_order: Base64Bytes,
151    control_block_name: Option<String>,
152    tpm_file_name: Option<String>,
153}
154
155#[derive(Clone, Debug, Deserialize)]
156#[serde(rename_all = "PascalCase", tag = "$type")]
157enum CryptType {
158    NoCrypt,
159    FateCrypt,
160    MizukakeCrypt,
161    HashCrypt,
162    #[serde(rename_all = "PascalCase")]
163    XorCrypt {
164        key: u8,
165    },
166    FlyingShineCrypt,
167    CxEncryption(CxSchema),
168    #[serde(rename_all = "PascalCase")]
169    SenrenCxCrypt {
170        #[serde(flatten)]
171        cx: CxSchema,
172        names_section_id: String,
173    },
174    #[serde(rename_all = "PascalCase")]
175    CabbageCxCrypt {
176        #[serde(flatten)]
177        cx: CxSchema,
178        names_section_id: String,
179        random_seed: u32,
180    },
181    #[serde(rename_all = "PascalCase")]
182    NanaCxCrypt {
183        #[serde(flatten)]
184        cx: CxSchema,
185        names_section_id: String,
186        random_seed: u32,
187        yuz_key: Vec<u32>,
188    },
189    #[serde(rename_all = "PascalCase")]
190    RiddleCxCrypt {
191        #[serde(flatten)]
192        cx: CxSchema,
193        names_section_id: String,
194        random_seed: u32,
195        yuz_key: Vec<u32>,
196        #[serde(default)]
197        key1: u32,
198        #[serde(default)]
199        key2: u32,
200    },
201    SeitenCrypt,
202    OkibaCrypt,
203    DieselmineCrypt,
204    DameganeCrypt,
205    NephriteCrypt,
206    AlteredPinkCrypt,
207    NatsupochiCrypt,
208    PoringSoftCrypt,
209    AppliqueCrypt,
210    TokidokiCrypt,
211    SourireCrypt,
212    HibikiCrypt,
213    #[serde(rename_all = "PascalCase")]
214    AkabeiCrypt {
215        seed: u32,
216    },
217    HaikuoCrypt,
218    #[serde(rename_all = "PascalCase")]
219    StripeCrypt {
220        key: u8,
221    },
222    ExaCrypt,
223    #[serde(rename_all = "PascalCase")]
224    SmileCrypt {
225        key_xor: u32,
226        first_xor: u8,
227        zero_xor: u8,
228    },
229    YuzuCrypt,
230    HighRunningCrypt,
231    KissCrypt,
232    #[serde(rename_all = "PascalCase")]
233    PuCaCrypt {
234        hash_table: Vec<u32>,
235        key_table: Base64Bytes,
236    },
237    #[serde(rename_all = "PascalCase")]
238    RhapsodyCrypt {
239        file_list_name: String,
240    },
241    #[serde(rename_all = "PascalCase")]
242    MadoCrypt {
243        seed: u32,
244    },
245    #[serde(rename_all = "PascalCase")]
246    SmxCrypt {
247        mask: u32,
248        key_seq: Base64Bytes,
249    },
250    FestivalCrypt,
251    PinPointCrypt,
252    HybridCrypt,
253    #[serde(rename_all = "PascalCase")]
254    NekoWorksCrypt {
255        key: Base64Bytes,
256    },
257    #[serde(rename_all = "PascalCase")]
258    NinkiSeiyuuCrypt {
259        key1: u64,
260        key2: u64,
261        key3: u64,
262    },
263    SyangrilaSmartCrypt,
264    Kano2Crypt,
265    MiburoCrypt,
266    #[serde(rename_all = "PascalCase")]
267    PureMoreCrypt {
268        file_list_name: String,
269        char_map: String,
270        layer_name_suffix: String,
271    },
272    ChainReactionCrypt,
273    HachukanoCrypt,
274    ChocolatCrypt,
275    XanaduCrypt,
276    SisMikoCrypt,
277    #[serde(rename_all = "PascalCase")]
278    HxCryptLite {
279        #[serde(flatten)]
280        cx: CxSchema,
281        #[serde(default)]
282        header_key: Option<Base64Bytes>,
283        #[serde(default)]
284        header_split_position: u64,
285        #[serde(default)]
286        file_crypt_flag: bool,
287        #[serde(default)]
288        random_type: i32,
289    },
290    #[serde(rename_all = "PascalCase")]
291    HxCrypt {
292        #[serde(flatten)]
293        cx: CxSchema,
294        index_key1: cx::IndexKey,
295        index_key2: cx::IndexKeys,
296        filter_key: u64,
297        #[serde(default)]
298        random_type: i32,
299        #[serde(default)]
300        file_list_name: Option<String>,
301    },
302    #[serde(rename_all = "PascalCase")]
303    Hxv4Crypt {
304        key_packages: Vec<cx::KeyPackage>,
305    },
306    #[serde(rename_all = "PascalCase")]
307    Xor2Crypt {
308        key1: u8,
309        key2: u8,
310    },
311    LeaveSLeaveCrypt,
312    #[serde(rename_all = "PascalCase")]
313    NVLCrypt {
314        key: Base64Bytes,
315    },
316}
317
318#[derive(Clone, Debug, Deserialize)]
319#[serde(rename_all = "PascalCase")]
320#[allow(dead_code)]
321pub struct BaseSchema {
322    #[serde(default)]
323    hash_after_crypt: bool,
324    #[serde(default)]
325    startup_tjs_not_encrypted: bool,
326    #[serde(default)]
327    obfuscated_index: bool,
328}
329
330#[derive(Clone, Debug, Deserialize)]
331#[serde(rename_all = "PascalCase")]
332pub struct Schema {
333    #[serde(flatten)]
334    crypt: CryptType,
335    title: Option<String>,
336    #[serde(flatten)]
337    base: BaseSchema,
338}
339
340impl Schema {
341    pub fn create_crypt(
342        &self,
343        filename: &str,
344        config: &ExtraConfig,
345    ) -> Result<Box<dyn Crypt + Send + Sync>> {
346        Ok(match &self.crypt {
347            CryptType::NoCrypt => Box::new(NoCrypt::new()),
348            CryptType::FateCrypt => Box::new(FateCrypt::new(self.base.clone())),
349            CryptType::MizukakeCrypt => Box::new(MizukakeCrypt::new(self.base.clone())),
350            CryptType::HashCrypt => Box::new(HashCrypt::new(self.base.clone())),
351            CryptType::XorCrypt { key } => Box::new(XorCrypt::new(self.base.clone(), *key)),
352            CryptType::FlyingShineCrypt => Box::new(FlyingShineCrypt::new(self.base.clone())),
353            CryptType::CxEncryption(schema) => {
354                Box::new(cx::CxEncryption::new(self.base.clone(), &schema, filename)?)
355            }
356            CryptType::SenrenCxCrypt {
357                cx,
358                names_section_id,
359            } => Box::new(cx::SenrenCxCrypt::new(
360                self.base.clone(),
361                cx,
362                filename,
363                names_section_id.clone(),
364            )?),
365            CryptType::CabbageCxCrypt {
366                cx,
367                names_section_id,
368                random_seed,
369            } => Box::new(cx::CabbageCxCrypt::new(
370                self.base.clone(),
371                cx,
372                filename,
373                names_section_id.clone(),
374                *random_seed,
375            )?),
376            CryptType::NanaCxCrypt {
377                cx,
378                names_section_id,
379                random_seed,
380                yuz_key,
381            } => Box::new(cx::NanaCxCrypt::new(
382                self.base.clone(),
383                cx,
384                filename,
385                names_section_id.clone(),
386                *random_seed,
387                &yuz_key,
388            )?),
389            CryptType::RiddleCxCrypt {
390                cx,
391                names_section_id,
392                random_seed,
393                yuz_key,
394                key1,
395                key2,
396            } => Box::new(cx::RiddleCxCrypt::new(
397                self.base.clone(),
398                cx,
399                filename,
400                names_section_id.clone(),
401                *random_seed,
402                &yuz_key,
403                *key1,
404                *key2,
405            )?),
406            CryptType::SeitenCrypt => Box::new(SeitenCrypt::new(self.base.clone())),
407            CryptType::OkibaCrypt => Box::new(OkibaCrypt::new(self.base.clone())),
408            CryptType::DieselmineCrypt => Box::new(DieselmineCrypt::new(self.base.clone())),
409            CryptType::DameganeCrypt => Box::new(DameganeCrypt::new(self.base.clone())),
410            CryptType::NephriteCrypt => Box::new(NephriteCrypt::new(self.base.clone())),
411            CryptType::AlteredPinkCrypt => Box::new(AlteredPinkCrypt::new(self.base.clone())),
412            CryptType::NatsupochiCrypt => Box::new(NatsupochiCrypt::new(self.base.clone())),
413            CryptType::PoringSoftCrypt => Box::new(PoringSoftCrypt::new(self.base.clone())),
414            CryptType::AppliqueCrypt => Box::new(AppliqueCrypt::new(self.base.clone())),
415            CryptType::TokidokiCrypt => Box::new(TokidokiCrypt::new(self.base.clone())),
416            CryptType::SourireCrypt => Box::new(SourireCrypt::new(self.base.clone())),
417            CryptType::HibikiCrypt => Box::new(HibikiCrypt::new(self.base.clone())),
418            CryptType::AkabeiCrypt { seed } => Box::new(AkabeiCrypt::new(self.base.clone(), *seed)),
419            CryptType::HaikuoCrypt => Box::new(HaikuoCrypt::new(self.base.clone())),
420            CryptType::StripeCrypt { key } => Box::new(StripeCrypt::new(self.base.clone(), *key)),
421            CryptType::ExaCrypt => Box::new(ExaCrypt::new(self.base.clone())),
422            CryptType::SmileCrypt {
423                key_xor,
424                first_xor,
425                zero_xor,
426            } => Box::new(SmileCrypt::new(
427                self.base.clone(),
428                *key_xor,
429                *first_xor,
430                *zero_xor,
431            )),
432            CryptType::YuzuCrypt => Box::new(YuzuCrypt::new(self.base.clone())),
433            CryptType::HighRunningCrypt => Box::new(HighRunningCrypt::new(self.base.clone())),
434            CryptType::KissCrypt => Box::new(cz::KissCrypt::new(self.base.clone())),
435            CryptType::PuCaCrypt {
436                hash_table,
437                key_table,
438            } => Box::new(PuCaCrypt::new(
439                self.base.clone(),
440                hash_table.clone(),
441                key_table.bytes.clone(),
442            )?),
443            CryptType::RhapsodyCrypt { file_list_name } => Box::new(RhapsodyCrypt::new(
444                self.base.clone(),
445                &file_list_name,
446                config.xp3_file_list_path.as_ref().map(|s| s.as_str()),
447            )?),
448            CryptType::MadoCrypt { seed } => Box::new(MadoCrypt::new(self.base.clone(), *seed)),
449            CryptType::SmxCrypt { mask, key_seq } => {
450                Box::new(SmxCrypt::new(self.base.clone(), *mask, &key_seq.bytes)?)
451            }
452            CryptType::FestivalCrypt => Box::new(FestivalCrypt::new(self.base.clone())),
453            CryptType::PinPointCrypt => Box::new(PinPointCrypt::new(self.base.clone())),
454            CryptType::HybridCrypt => Box::new(HybridCrypt::new(self.base.clone())),
455            CryptType::NekoWorksCrypt { key } => {
456                Box::new(NekoWorksCrypt::new(self.base.clone(), key.bytes.clone())?)
457            }
458            CryptType::NinkiSeiyuuCrypt { key1, key2, key3 } => Box::new(NinkiSeiyuuCrypt::new(
459                self.base.clone(),
460                *key1,
461                *key2,
462                *key3,
463            )),
464            CryptType::SyangrilaSmartCrypt => Box::new(SyangrilaSmartCrypt::new(self.base.clone())),
465            CryptType::Kano2Crypt => Box::new(Kano2Crypt::new(self.base.clone())),
466            CryptType::MiburoCrypt => Box::new(MiburoCrypt::new(self.base.clone())),
467            CryptType::PureMoreCrypt {
468                file_list_name,
469                char_map,
470                layer_name_suffix,
471            } => Box::new(PureMoreCrypt::new(
472                self.base.clone(),
473                file_list_name,
474                config.xp3_file_list_path.as_ref().map(|s| s.as_str()),
475                char_map,
476                layer_name_suffix,
477            )?),
478            CryptType::ChainReactionCrypt => {
479                Box::new(chain_reaction::ChainReactionCrypt::new(self.base.clone()))
480            }
481            CryptType::HachukanoCrypt => {
482                Box::new(chain_reaction::HachukanoCrypt::new(self.base.clone()))
483            }
484            CryptType::ChocolatCrypt => {
485                Box::new(chain_reaction::ChocolatCrypt::new(self.base.clone()))
486            }
487            CryptType::XanaduCrypt => Box::new(chain_reaction::XanaduCrypt::new(self.base.clone())),
488            CryptType::SisMikoCrypt => {
489                Box::new(chain_reaction::SisMikoCrypt::new(self.base.clone()))
490            }
491            CryptType::HxCryptLite {
492                cx,
493                header_key,
494                header_split_position,
495                file_crypt_flag,
496                random_type,
497            } => Box::new(cx::HxCryptLite::new(
498                self.base.clone(),
499                cx,
500                filename,
501                header_key.as_ref().map(|s| s.bytes.clone()),
502                *header_split_position,
503                *file_crypt_flag,
504                *random_type,
505            )?),
506            CryptType::HxCrypt {
507                cx,
508                index_key1,
509                index_key2,
510                filter_key,
511                random_type,
512                file_list_name,
513            } => Box::new(cx::HxCrypt::new(
514                self.base.clone(),
515                cx,
516                index_key1,
517                index_key2,
518                *filter_key,
519                *random_type,
520                file_list_name.as_ref().map(|s| s.as_str()),
521                config.xp3_file_list_path.as_ref().map(|s| s.as_str()),
522                filename,
523                config,
524            )?),
525            CryptType::Hxv4Crypt { key_packages } => Box::new(cx::Hxv4Crypt::new2(
526                &key_packages,
527                config.xp3_game_title.as_deref().unwrap_or_default(),
528                filename,
529                config,
530            )?),
531            CryptType::Xor2Crypt { key1, key2 } => {
532                Box::new(Xor2Crypt::new(self.base.clone(), *key1, *key2))
533            }
534            CryptType::LeaveSLeaveCrypt => Box::new(LeaveSLeaveCrypt::new(self.base.clone())),
535            CryptType::NVLCrypt { key } => Box::new(nvl::NVLCrypt::new(self.base.clone(), &key.bytes)?),
536        })
537    }
538}
539
540lazy_static::lazy_static! {
541    static ref CRYPT_SCHEMA: BTreeMap<CaseInsensitiveString, Schema> = {
542        serde_json::from_str(&get_crypt_data()).expect("Failed to parse crypt.json")
543    };
544    static ref ALIAS_TABLE: HashMap<String, String> = {
545        let mut table = HashMap::new();
546        for (game, fulltitle) in get_supported_games_with_title() {
547            if let Some(title) = fulltitle {
548                let mut alias_count = 0usize;
549                for part in title.split("|") {
550                    let alias = part.trim();
551                    table.insert(alias.to_string(), game.to_string());
552                    alias_count += 1;
553                }
554                // also insert full title if there are multiple aliases
555                if alias_count > 1 {
556                    table.insert(title.to_string(), game.to_string());
557                }
558            }
559        }
560        table
561    };
562    static ref CX_CB_TABLE: HashMap<String, Vec<u32>> = {
563        let reader = MemReaderRef::new(CX_CB_DATA);
564        let mut pack = read_simple_pack(reader).expect("Failed to read cx_cb.pck");
565        let mut table = HashMap::new();
566        while let Some(mut entry) = pack.next().expect("Failed to read entry in cx_cb.pck") {
567            let mut list = Vec::with_capacity(0x400);
568            let errmsg = format!("Failed to read u32 in cx_cb.pck entry {}", entry.name);
569            for _ in 0..0x400 {
570                list.push(entry.read_u32().expect(&errmsg));
571            }
572            while !entry.is_eof() {
573                list.push(entry.read_u32().expect(&errmsg));
574            }
575            table.insert(entry.name.clone(), list);
576        }
577        table
578    };
579}
580
581pub fn query_filename_list(name: &str) -> Result<String> {
582    let reader = MemReaderRef::new(NAME_LIST_DATA);
583    let mut pack = read_simple_pack(reader)?;
584    while let Some(mut entry) = pack.next()? {
585        if entry.name == name {
586            let mut str = String::new();
587            entry.read_to_string(&mut str)?;
588            return Ok(str);
589        }
590    }
591    Err(anyhow::anyhow!("Name list entry not found: {}", name))
592}
593
594/// Get the supported game titles for encrypted xp3 archives.
595pub fn get_supported_games() -> Vec<&'static str> {
596    CRYPT_SCHEMA.keys().map(|s| s.as_str()).collect()
597}
598
599/// Get the supported game titles for encrypted xp3 archives with their full titles.
600pub fn get_supported_games_with_title() -> Vec<(&'static str, Option<&'static str>)> {
601    CRYPT_SCHEMA
602        .iter()
603        .map(|(k, v)| (k.as_str(), v.title.as_deref()))
604        .collect()
605}
606
607pub fn query_crypt_schema(game: &str) -> Option<&'static Schema> {
608    CRYPT_SCHEMA.get(CIS::from_str(game)).or_else(|| {
609        ALIAS_TABLE
610            .get(game)
611            .and_then(|real_game| CRYPT_SCHEMA.get(CIS::from_str(real_game)))
612    })
613}
614
615#[derive(Debug)]
616pub struct NoCrypt {}
617
618impl NoCrypt {
619    pub fn new() -> Self {
620        Self {}
621    }
622}
623
624impl Crypt for NoCrypt {
625    fn hash_after_crypt(&self) -> bool {
626        false
627    }
628    fn startup_tjs_not_encrypted(&self) -> bool {
629        false
630    }
631    fn obfuscated_index(&self) -> bool {
632        false
633    }
634}
635
636macro_rules! seek_impl {
637    ($reader:ident<$t:ident>) => {
638        impl<$t: Read + Seek> Seek for $reader<$t> {
639            fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
640                let new_pos: i64 = match pos {
641                    SeekFrom::Start(offset) => offset as i64,
642                    SeekFrom::End(offset) => self.seg_size as i64 + offset,
643                    SeekFrom::Current(offset) => self.pos as i64 + offset,
644                };
645                let offset = new_pos - self.pos as i64;
646                if offset != 0 {
647                    self.inner.seek(SeekFrom::Current(offset))?;
648                    self.pos = new_pos as u64;
649                }
650                Ok(self.pos)
651            }
652        }
653    };
654}
655
656macro_rules! seek_reader_impl {
657    ($reader:ident<$t:ident>) => {
658        #[derive(msg_tool_macro::MyDebug)]
659        struct $reader<$t: Read> {
660            #[skip_fmt]
661            inner: $t,
662            #[allow(unused)]
663            /// Start offset of the current xp3 entry.
664            seg_start: u64,
665            seg_size: u64,
666            pos: u64,
667        }
668        impl<$t: Read> $reader<$t> {
669            pub fn new(inner: $t, seg: &Segment) -> Self {
670                Self {
671                    inner,
672                    seg_start: seg.offset_in_file,
673                    seg_size: seg.original_size,
674                    pos: 0,
675                }
676            }
677        }
678        seek_impl!($reader<$t>);
679    };
680}
681
682macro_rules! seek_reader_key_impl {
683    ($reader:ident<$t:ident>, $key:ty) => {
684        #[derive(msg_tool_macro::MyDebug)]
685        #[allow(dead_code)]
686        struct $reader<$t: Read> {
687            #[skip_fmt]
688            inner: $t,
689            /// Start offset of the current xp3 entry.
690            seg_start: u64,
691            seg_size: u64,
692            pos: u64,
693            key: $key,
694        }
695        impl<$t: Read> $reader<$t> {
696            pub fn new(inner: $t, seg: &Segment, key: $key) -> Self {
697                Self {
698                    inner,
699                    seg_start: seg.offset_in_file,
700                    seg_size: seg.original_size,
701                    pos: 0,
702                    key,
703                }
704            }
705        }
706        seek_impl!($reader<$t>);
707    };
708}
709
710macro_rules! base_schema_impl {
711    () => {
712        fn hash_after_crypt(&self) -> bool {
713            self.base.hash_after_crypt
714        }
715        fn startup_tjs_not_encrypted(&self) -> bool {
716            self.base.startup_tjs_not_encrypted
717        }
718        fn obfuscated_index(&self) -> bool {
719            self.base.obfuscated_index
720        }
721    };
722}
723
724macro_rules! base_schema2_impl {
725    () => {
726        fn hash_after_crypt(&self) -> bool {
727            AsRef::<BaseSchema>::as_ref(self).hash_after_crypt
728        }
729        fn startup_tjs_not_encrypted(&self) -> bool {
730            AsRef::<BaseSchema>::as_ref(self).startup_tjs_not_encrypted
731        }
732        fn obfuscated_index(&self) -> bool {
733            AsRef::<BaseSchema>::as_ref(self).obfuscated_index
734        }
735    };
736}
737
738macro_rules! seek_crypt_base_impl {
739    ($crypt:ident, $reader:ident) => {
740        #[derive(Debug)]
741        pub struct $crypt {
742            base: BaseSchema,
743        }
744        impl $crypt {
745            pub fn new(base: BaseSchema) -> Self {
746                Self { base }
747            }
748        }
749        impl Crypt for $crypt {
750            base_schema_impl!();
751            fn decrypt_supported(&self) -> bool {
752                true
753            }
754            fn decrypt_seek_supported(&self) -> bool {
755                true
756            }
757            fn decrypt<'a>(
758                &self,
759                _entry: &Xp3Entry,
760                cur_seg: &Segment,
761                stream: Box<dyn Read + Send + Sync + 'a>,
762            ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
763                Ok(Box::new($reader::new(stream, cur_seg)))
764            }
765            fn decrypt_with_seek<'a>(
766                &self,
767                _entry: &Xp3Entry,
768                cur_seg: &Segment,
769                stream: Box<dyn ReadSeek + Send + Sync + 'a>,
770            ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
771                Ok(Box::new($reader::new(stream, cur_seg)))
772            }
773        }
774    };
775}
776
777macro_rules! seek_crypt_impl {
778    ($crypt:ident, $reader:ident<$t:ident>) => {
779        seek_crypt_base_impl!($crypt, $reader);
780        seek_reader_impl!($reader<$t>);
781    };
782}
783
784seek_crypt_impl!(FateCrypt, FateCryptReader<T>);
785
786impl<R: Read> Read for FateCryptReader<R> {
787    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
788        const XOR1_OFFSET: u64 = 0x13;
789        const XOR3_OFFSET: u64 = 0x2ea29;
790        let readed = self.inner.read(buf)?;
791        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
792            let tpos = self.seg_start + self.pos + i as u64;
793            *t ^= 0x36;
794            if tpos == XOR1_OFFSET {
795                *t ^= 0x1;
796            } else if tpos == XOR3_OFFSET {
797                *t ^= 0x3;
798            }
799        }
800        self.pos += readed as u64;
801        Ok(readed)
802    }
803}
804
805seek_crypt_impl!(MizukakeCrypt, MizukakeCryptReader<T>);
806
807impl<R: Read> Read for MizukakeCryptReader<R> {
808    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
809        let readed = self.inner.read(buf)?;
810        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
811            let tpos = self.seg_start + self.pos + i as u64;
812            if tpos == 0x103 {
813                *t = (*t).wrapping_sub(1);
814            }
815            *t ^= 0xb6;
816            if tpos == 0x3F82 {
817                *t ^= 1;
818            }
819            if tpos == 0x83 {
820                *t ^= 3;
821            }
822        }
823        self.pos += readed as u64;
824        Ok(readed)
825    }
826}
827
828macro_rules! seek_crypt_filehash_key_u8_base_impl {
829    ($crypt:ident, $reader:ident) => {
830        #[derive(Debug)]
831        pub struct $crypt {
832            base: BaseSchema,
833        }
834        impl $crypt {
835            pub fn new(base: BaseSchema) -> Self {
836                Self { base }
837            }
838        }
839        impl Crypt for $crypt {
840            base_schema_impl!();
841            fn decrypt_supported(&self) -> bool {
842                true
843            }
844            fn decrypt_seek_supported(&self) -> bool {
845                true
846            }
847            fn decrypt<'a>(
848                &self,
849                entry: &Xp3Entry,
850                cur_seg: &Segment,
851                stream: Box<dyn Read + Send + Sync + 'a>,
852            ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
853                Ok(Box::new($reader::new(
854                    stream,
855                    cur_seg,
856                    entry.file_hash as u8,
857                )))
858            }
859            fn decrypt_with_seek<'a>(
860                &self,
861                entry: &Xp3Entry,
862                cur_seg: &Segment,
863                stream: Box<dyn ReadSeek + Send + Sync + 'a>,
864            ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
865                Ok(Box::new($reader::new(
866                    stream,
867                    cur_seg,
868                    entry.file_hash as u8,
869                )))
870            }
871        }
872    };
873}
874
875macro_rules! seek_crypt_filehash_key_u8_impl {
876    ($crypt:ident,$reader:ident<$t:ident>) => {
877        seek_crypt_filehash_key_u8_base_impl!($crypt, $reader);
878        seek_reader_key_impl!($reader<$t>, u8);
879    };
880}
881
882seek_crypt_filehash_key_u8_impl!(HashCrypt, HashCryptReader<T>);
883
884impl<R: Read> Read for HashCryptReader<R> {
885    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
886        let readed = self.inner.read(buf)?;
887        for t in (&mut buf[..readed]).iter_mut() {
888            *t ^= self.key;
889        }
890        self.pos += readed as u64;
891        Ok(readed)
892    }
893}
894
895macro_rules! seek_crypt_key_base_impl {
896    ($crypt:ident, $reader:ident, $typ:ty) => {
897        #[derive(Debug)]
898        pub struct $crypt {
899            base: BaseSchema,
900            key: $typ,
901        }
902        impl $crypt {
903            pub fn new(base: BaseSchema, key: $typ) -> Self {
904                Self { base, key }
905            }
906        }
907        impl Crypt for $crypt {
908            base_schema_impl!();
909            fn decrypt_supported(&self) -> bool {
910                true
911            }
912            fn decrypt_seek_supported(&self) -> bool {
913                true
914            }
915            fn decrypt<'a>(
916                &self,
917                _entry: &Xp3Entry,
918                cur_seg: &Segment,
919                stream: Box<dyn Read + Send + Sync + 'a>,
920            ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
921                Ok(Box::new($reader::new(stream, cur_seg, self.key)))
922            }
923            fn decrypt_with_seek<'a>(
924                &self,
925                _entry: &Xp3Entry,
926                cur_seg: &Segment,
927                stream: Box<dyn ReadSeek + Send + Sync + 'a>,
928            ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
929                Ok(Box::new($reader::new(stream, cur_seg, self.key)))
930            }
931        }
932    };
933}
934
935macro_rules! seek_crypt_key_impl {
936    ($crypt:ident, $reader:ident<$t:ident>, $typ:ty) => {
937        seek_crypt_key_base_impl!($crypt, $reader, $typ);
938        seek_reader_key_impl!($reader<$t>, $typ);
939    };
940}
941
942seek_crypt_key_impl!(XorCrypt, XorCryptReader<T>, u8);
943
944impl<R: Read> Read for XorCryptReader<R> {
945    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
946        let readed = self.inner.read(buf)?;
947        for t in (&mut buf[..readed]).iter_mut() {
948            *t ^= self.key;
949        }
950        self.pos += readed as u64;
951        Ok(readed)
952    }
953}
954
955#[derive(Debug)]
956pub struct FlyingShineCrypt {
957    base: BaseSchema,
958}
959
960impl FlyingShineCrypt {
961    pub fn new(base: BaseSchema) -> Self {
962        Self { base }
963    }
964
965    fn adjust(&self, hash: u32) -> (u8, u32) {
966        let mut shift = hash & 0xFF;
967        if shift == 0 {
968            shift = 0xF;
969        }
970        let mut key = ((hash >> 8) & 0xFF) as u8;
971        if key == 0 {
972            key = 0xF0;
973        }
974        (key, shift)
975    }
976}
977
978impl Crypt for FlyingShineCrypt {
979    base_schema_impl!();
980    fn decrypt_supported(&self) -> bool {
981        true
982    }
983    fn decrypt_seek_supported(&self) -> bool {
984        true
985    }
986    fn decrypt<'a>(
987        &self,
988        entry: &Xp3Entry,
989        cur_seg: &Segment,
990        stream: Box<dyn Read + Send + Sync + 'a>,
991    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
992        Ok(Box::new(FlyingShineCryptReader::new(
993            stream,
994            cur_seg,
995            self.adjust(entry.file_hash),
996        )))
997    }
998    fn decrypt_with_seek<'a>(
999        &self,
1000        entry: &Xp3Entry,
1001        cur_seg: &Segment,
1002        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1003    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1004        Ok(Box::new(FlyingShineCryptReader::new(
1005            stream,
1006            cur_seg,
1007            self.adjust(entry.file_hash),
1008        )))
1009    }
1010}
1011
1012seek_reader_key_impl!(FlyingShineCryptReader<T>, (u8, u32));
1013
1014impl<R: Read> Read for FlyingShineCryptReader<R> {
1015    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1016        let (xor, shift) = self.key;
1017        let readed = self.inner.read(buf)?;
1018        for t in (&mut buf[..readed]).iter_mut() {
1019            *t ^= xor;
1020            *t = t.rotate_right(shift);
1021        }
1022        self.pos += readed as u64;
1023        Ok(readed)
1024    }
1025}
1026
1027macro_rules! seek_crypt_filehash_key_base_impl {
1028    ($crypt:ident, $reader:ident) => {
1029        #[derive(Debug)]
1030        pub struct $crypt {
1031            base: BaseSchema,
1032        }
1033        impl $crypt {
1034            pub fn new(base: BaseSchema) -> Self {
1035                Self { base }
1036            }
1037        }
1038        impl Crypt for $crypt {
1039            base_schema_impl!();
1040            fn decrypt_supported(&self) -> bool {
1041                true
1042            }
1043            fn decrypt_seek_supported(&self) -> bool {
1044                true
1045            }
1046            fn decrypt<'a>(
1047                &self,
1048                entry: &Xp3Entry,
1049                cur_seg: &Segment,
1050                stream: Box<dyn Read + Send + Sync + 'a>,
1051            ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1052                Ok(Box::new($reader::new(stream, cur_seg, entry.file_hash)))
1053            }
1054            fn decrypt_with_seek<'a>(
1055                &self,
1056                entry: &Xp3Entry,
1057                cur_seg: &Segment,
1058                stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1059            ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1060                Ok(Box::new($reader::new(stream, cur_seg, entry.file_hash)))
1061            }
1062        }
1063    };
1064}
1065
1066macro_rules! seek_crypt_filehash_key_impl {
1067    ($crypt:ident,$reader:ident<$t:ident>) => {
1068        seek_crypt_filehash_key_base_impl!($crypt, $reader);
1069        seek_reader_key_impl!($reader<$t>, u32);
1070    };
1071}
1072
1073seek_crypt_filehash_key_impl!(SeitenCrypt, SeitenCryptReader<T>);
1074
1075impl<R: Read> Read for SeitenCryptReader<R> {
1076    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1077        let readed = self.inner.read(buf)?;
1078        let mut offset = self.seg_start + self.pos;
1079        for t in (&mut buf[..readed]).iter_mut() {
1080            let mut shift;
1081            let key = self.key ^ (offset as u32);
1082            if key & 2 != 0 {
1083                shift = key & 0x18;
1084                let ebx = key >> shift;
1085                shift &= 8;
1086                *t ^= (ebx | (key >> shift)) as u8;
1087            }
1088            if key & 4 != 0 {
1089                w!(*t += key as u8);
1090            }
1091            if key & 8 != 0 {
1092                shift = key & 0x10;
1093                w!(*t -= (key >> shift) as u8);
1094            }
1095            offset += 1;
1096        }
1097        self.pos += readed as u64;
1098        Ok(readed)
1099    }
1100}
1101
1102seek_crypt_filehash_key_impl!(OkibaCrypt, OkibaCryptReader<T>);
1103
1104impl<R: Read> Read for OkibaCryptReader<R> {
1105    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1106        let readed = self.inner.read(buf)?;
1107        let mut offset = self.seg_start + self.pos;
1108        let mut i = 0;
1109        if offset < 0x65 {
1110            let key = self.key >> 4;
1111            let limit = readed.min(0x65 - offset as usize);
1112            for _ in 0..limit {
1113                buf[i] ^= key as u8;
1114                i += 1;
1115                offset += 1;
1116            }
1117        }
1118        if i < readed {
1119            offset -= 0x65;
1120            let mut key = self.key;
1121            key = ((key & 0xff0000) << 8)
1122                | ((key & 0xff000000) >> 8)
1123                | ((key & 0xff00) >> 8)
1124                | ((key & 0xff) << 8);
1125            loop {
1126                buf[i] ^= (key >> (8 * (offset as u32 & 3))) as u8;
1127                offset += 1;
1128                i += 1;
1129                if i >= readed {
1130                    break;
1131                }
1132            }
1133        }
1134        self.pos += readed as u64;
1135        Ok(readed)
1136    }
1137}
1138
1139seek_crypt_filehash_key_u8_impl!(DieselmineCrypt, DieselmineCryptReader<T>);
1140
1141impl<R: Read> Read for DieselmineCryptReader<R> {
1142    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1143        let readed = self.inner.read(buf)?;
1144        let key = self.key as i32;
1145        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1146            let offset = self.seg_start + self.pos + i as u64;
1147            let key = if offset < 123 {
1148                21 * key
1149            } else if offset < 246 {
1150                -32 * key
1151            } else if offset < 369 {
1152                43 * key
1153            } else {
1154                -54 * key
1155            } as u8;
1156            *t ^= key;
1157        }
1158        self.pos += readed as u64;
1159        Ok(readed)
1160    }
1161}
1162
1163seek_crypt_filehash_key_u8_impl!(DameganeCrypt, DameganeCryptReader<T>);
1164
1165impl<R: Read> Read for DameganeCryptReader<R> {
1166    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1167        let readed = self.inner.read(buf)?;
1168        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1169            let offset = self.seg_start + self.pos + i as u64;
1170            let key = if offset & 1 != 0 {
1171                self.key
1172            } else {
1173                offset as u8
1174            };
1175            *t ^= key;
1176        }
1177        self.pos += readed as u64;
1178        Ok(readed)
1179    }
1180}
1181
1182seek_crypt_filehash_key_u8_impl!(NephriteCrypt, NephriteCryptReader<T>);
1183
1184impl<R: Read> Read for NephriteCryptReader<R> {
1185    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1186        let readed = self.inner.read(buf)?;
1187        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1188            let offset = self.seg_start + self.pos + i as u64;
1189            let key = if offset & 1 == 0 {
1190                self.key
1191            } else {
1192                offset as u8
1193            };
1194            *t ^= key;
1195        }
1196        self.pos += readed as u64;
1197        Ok(readed)
1198    }
1199}
1200
1201seek_crypt_impl!(AlteredPinkCrypt, AlteredPinkCryptReader<T>);
1202
1203impl<R: Read> Read for AlteredPinkCryptReader<R> {
1204    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1205        let readed = self.inner.read(buf)?;
1206        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1207            let offset = self.seg_start + self.pos + i as u64;
1208            *t ^= ALTERED_PINK_KEY_TABLE[(offset & 0xFF) as usize];
1209        }
1210        self.pos += readed as u64;
1211        Ok(readed)
1212    }
1213}
1214
1215seek_crypt_filehash_key_impl!(NatsupochiCrypt, NatsupochiCryptReader<T>);
1216
1217impl<R: Read> Read for NatsupochiCryptReader<R> {
1218    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1219        let readed = self.inner.read(buf)?;
1220        let key = (self.key >> 3) as u8;
1221        for t in (&mut buf[..readed]).iter_mut() {
1222            *t ^= key;
1223        }
1224        self.pos += readed as u64;
1225        Ok(readed)
1226    }
1227}
1228
1229seek_crypt_filehash_key_impl!(PoringSoftCrypt, PoringSoftCryptReader<T>);
1230
1231impl<R: Read> Read for PoringSoftCryptReader<R> {
1232    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1233        let readed = self.inner.read(buf)?;
1234        let key = (!w!(self.key + 1)) as u8;
1235        for t in (&mut buf[..readed]).iter_mut() {
1236            *t ^= key;
1237        }
1238        self.pos += readed as u64;
1239        Ok(readed)
1240    }
1241}
1242
1243seek_crypt_filehash_key_impl!(AppliqueCrypt, AppliqueCryptReader<T>);
1244
1245impl<R: Read> Read for AppliqueCryptReader<R> {
1246    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1247        let readed = self.inner.read(buf)?;
1248        let key = (self.key >> 12) as u8;
1249        let skip = (5 - (self.seg_start + self.pos).min(5) as usize).min(readed);
1250        for t in (&mut buf[skip..readed]).iter_mut() {
1251            *t ^= key;
1252        }
1253        self.pos += readed as u64;
1254        Ok(readed)
1255    }
1256}
1257
1258#[derive(Debug)]
1259pub struct TokidokiCrypt {
1260    base: BaseSchema,
1261}
1262
1263impl TokidokiCrypt {
1264    pub fn new(base: BaseSchema) -> Self {
1265        Self { base }
1266    }
1267
1268    /// Retruns limit and key
1269    fn get_key(&self, entry: &Xp3Entry) -> Result<(u64, u32)> {
1270        let ext = entry
1271            .name
1272            .rsplit('.')
1273            .next()
1274            .unwrap_or("")
1275            .to_ascii_lowercase();
1276        if !ext.is_empty() {
1277            let ext = format!(".{}", ext);
1278            let mut ext_bin = encode_string(Encoding::Cp932, &ext, true)?;
1279            ext_bin.resize(4, 0);
1280            let mut reader = MemReaderRef::new(&ext_bin);
1281            let key = !reader.read_u32()?;
1282            if ext == ".asd" || ext == ".ks" || ext == ".tjs" {
1283                Ok((entry.original_size, key))
1284            } else {
1285                Ok((entry.original_size.min(0x100), key))
1286            }
1287        } else {
1288            Ok((entry.original_size.min(0x100), u32::MAX))
1289        }
1290    }
1291}
1292
1293impl Crypt for TokidokiCrypt {
1294    base_schema_impl!();
1295    fn decrypt_supported(&self) -> bool {
1296        true
1297    }
1298    fn decrypt_seek_supported(&self) -> bool {
1299        true
1300    }
1301    fn decrypt<'a>(
1302        &self,
1303        entry: &Xp3Entry,
1304        cur_seg: &Segment,
1305        stream: Box<dyn Read + Send + Sync + 'a>,
1306    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1307        Ok(Box::new(TokidokiCryptReader::new(
1308            stream,
1309            cur_seg,
1310            self.get_key(entry)?,
1311        )))
1312    }
1313    fn decrypt_with_seek<'a>(
1314        &self,
1315        entry: &Xp3Entry,
1316        cur_seg: &Segment,
1317        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1318    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1319        Ok(Box::new(TokidokiCryptReader::new(
1320            stream,
1321            cur_seg,
1322            self.get_key(entry)?,
1323        )))
1324    }
1325}
1326
1327seek_reader_key_impl!(TokidokiCryptReader<T>, (u64, u32));
1328
1329impl<R: Read> Read for TokidokiCryptReader<R> {
1330    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1331        let (limit, key) = self.key;
1332        let readed = self.inner.read(buf)?;
1333        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1334            let offset = self.seg_start + self.pos + i as u64;
1335            if offset < limit {
1336                *t ^= (key >> ((offset as i32 & 3) << 3)) as u8;
1337            }
1338        }
1339        self.pos += readed as u64;
1340        Ok(readed)
1341    }
1342}
1343
1344seek_crypt_filehash_key_impl!(SourireCrypt, SourireCryptReader<T>);
1345
1346impl<R: Read> Read for SourireCryptReader<R> {
1347    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1348        let readed = self.inner.read(buf)?;
1349        let key = (self.key ^ 0xCD) as u8;
1350        for t in (&mut buf[..readed]).iter_mut() {
1351            *t ^= key;
1352        }
1353        self.pos += readed as u64;
1354        Ok(readed)
1355    }
1356}
1357
1358seek_crypt_filehash_key_impl!(HibikiCrypt, HibikiCryptReader<T>);
1359
1360impl<R: Read> Read for HibikiCryptReader<R> {
1361    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1362        let readed = self.inner.read(buf)?;
1363        let key1 = (self.key >> 5) as u8;
1364        let key2 = (self.key >> 8) as u8;
1365        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1366            let offset = self.seg_start + self.pos + i as u64;
1367            let key = if offset <= 0x64 || offset & 4 != 0 {
1368                key1
1369            } else {
1370                key2
1371            };
1372            *t ^= key;
1373        }
1374        self.pos += readed as u64;
1375        Ok(readed)
1376    }
1377}
1378
1379#[derive(Debug)]
1380pub struct AkabeiCrypt {
1381    base: BaseSchema,
1382    seed: u32,
1383}
1384
1385impl AkabeiCrypt {
1386    pub fn new(base: BaseSchema, seed: u32) -> Self {
1387        Self { base, seed }
1388    }
1389
1390    fn get_key(&self, mut hash: u32) -> [u8; 0x20] {
1391        let mut state = [0; 0x20];
1392        hash = (hash ^ self.seed) & 0x7FFFFFFF;
1393        hash = hash << 31 | hash;
1394        for i in 0..0x20 {
1395            state[i] = (hash & 0xFF) as u8;
1396            hash = (hash & 0xFFFFFFFE) << 23 | hash >> 8;
1397        }
1398        state
1399    }
1400}
1401
1402impl Crypt for AkabeiCrypt {
1403    base_schema_impl!();
1404    fn decrypt_supported(&self) -> bool {
1405        true
1406    }
1407    fn decrypt_seek_supported(&self) -> bool {
1408        true
1409    }
1410    fn decrypt<'a>(
1411        &self,
1412        entry: &Xp3Entry,
1413        cur_seg: &Segment,
1414        stream: Box<dyn Read + Send + Sync + 'a>,
1415    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1416        Ok(Box::new(AkabeiCryptReader::new(
1417            stream,
1418            cur_seg,
1419            self.get_key(entry.file_hash),
1420        )))
1421    }
1422    fn decrypt_with_seek<'a>(
1423        &self,
1424        entry: &Xp3Entry,
1425        cur_seg: &Segment,
1426        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1427    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1428        Ok(Box::new(AkabeiCryptReader::new(
1429            stream,
1430            cur_seg,
1431            self.get_key(entry.file_hash),
1432        )))
1433    }
1434}
1435
1436seek_reader_key_impl!(AkabeiCryptReader<T>, [u8; 0x20]);
1437
1438impl<R: Read> Read for AkabeiCryptReader<R> {
1439    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1440        let readed = self.inner.read(buf)?;
1441        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1442            let offset = self.seg_start + self.pos + i as u64;
1443            *t ^= self.key[(offset & 0x1F) as usize];
1444        }
1445        self.pos += readed as u64;
1446        Ok(readed)
1447    }
1448}
1449
1450seek_crypt_filehash_key_impl!(HaikuoCrypt, HaikuoCryptReader<T>);
1451
1452impl<R: Read> Read for HaikuoCryptReader<R> {
1453    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1454        let readed = self.inner.read(buf)?;
1455        let key = (self.key ^ (self.key >> 8)) as u8;
1456        for t in (&mut buf[..readed]).iter_mut() {
1457            *t ^= key;
1458        }
1459        self.pos += readed as u64;
1460        Ok(readed)
1461    }
1462}
1463
1464seek_crypt_key_impl!(StripeCrypt, StripeCryptReader<T>, u8);
1465
1466impl<R: Read> Read for StripeCryptReader<R> {
1467    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1468        let readed = self.inner.read(buf)?;
1469        for t in (&mut buf[..readed]).iter_mut() {
1470            *t ^= self.key;
1471            w!(*t += 1);
1472        }
1473        self.pos += readed as u64;
1474        Ok(readed)
1475    }
1476}
1477
1478seek_crypt_filehash_key_impl!(ExaCrypt, ExaCryptReader<T>);
1479
1480impl<R: Read> Read for ExaCryptReader<R> {
1481    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1482        let readed = self.inner.read(buf)?;
1483        let mut shift = ((self.seg_start + self.pos) % 5) as u32;
1484        for t in (&mut buf[..readed]).iter_mut() {
1485            *t ^= (self.key >> shift) as u8;
1486            shift = (shift + 1) % 5;
1487        }
1488        self.pos += readed as u64;
1489        Ok(readed)
1490    }
1491}
1492
1493#[derive(Debug)]
1494pub struct SmileCrypt {
1495    base: BaseSchema,
1496    key_xor: u32,
1497    first_xor: u8,
1498    zero_xor: u8,
1499}
1500
1501impl SmileCrypt {
1502    pub fn new(base: BaseSchema, key_xor: u32, first_xor: u8, zero_xor: u8) -> Self {
1503        Self {
1504            base,
1505            key_xor,
1506            first_xor,
1507            zero_xor,
1508        }
1509    }
1510}
1511
1512impl Crypt for SmileCrypt {
1513    base_schema_impl!();
1514    fn decrypt_supported(&self) -> bool {
1515        true
1516    }
1517    fn decrypt_seek_supported(&self) -> bool {
1518        true
1519    }
1520    fn decrypt<'a>(
1521        &self,
1522        entry: &Xp3Entry,
1523        cur_seg: &Segment,
1524        stream: Box<dyn Read + Send + Sync + 'a>,
1525    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1526        let key = entry.file_hash ^ self.key_xor;
1527        Ok(Box::new(SmileCryptReader::new(
1528            stream,
1529            cur_seg,
1530            (key, self.first_xor, self.zero_xor),
1531        )))
1532    }
1533    fn decrypt_with_seek<'a>(
1534        &self,
1535        entry: &Xp3Entry,
1536        cur_seg: &Segment,
1537        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1538    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1539        let key = entry.file_hash ^ self.key_xor;
1540        Ok(Box::new(SmileCryptReader::new(
1541            stream,
1542            cur_seg,
1543            (key, self.first_xor, self.zero_xor),
1544        )))
1545    }
1546}
1547
1548seek_reader_key_impl!(SmileCryptReader<T>, (u32, u8, u8));
1549
1550impl<R: Read> Read for SmileCryptReader<R> {
1551    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1552        let readed = self.inner.read(buf)?;
1553        let (mut hash, first_xor, zero_xor) = self.key;
1554        let mut key = (hash ^ (hash >> 8) ^ (hash >> 16) ^ (hash >> 24)) as u8;
1555        if key == 0 {
1556            key = zero_xor;
1557        }
1558        if self.pos == 0 && self.seg_start == 0 && readed > 0 {
1559            if hash & 0xFF == 0 {
1560                hash = first_xor as u32;
1561            }
1562            buf[0] ^= hash as u8;
1563        }
1564        for t in (&mut buf[..readed]).iter_mut() {
1565            *t ^= key;
1566        }
1567        self.pos += readed as u64;
1568        Ok(readed)
1569    }
1570}
1571
1572seek_crypt_filehash_key_impl!(YuzuCrypt, YuzuCryptReader<T>);
1573
1574impl<R: Read> Read for YuzuCryptReader<R> {
1575    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1576        let readed = self.inner.read(buf)?;
1577        let hash = self.key ^ 0x1DDB6E7A;
1578        let mut key = (hash ^ (hash >> 8) ^ (hash >> 16) ^ (hash >> 24)) as u8;
1579        if key == 0 {
1580            key = 0xD0;
1581        }
1582        for t in (&mut buf[..readed]).iter_mut() {
1583            *t ^= key;
1584        }
1585        self.pos += readed as u64;
1586        Ok(readed)
1587    }
1588}
1589
1590seek_crypt_filehash_key_u8_impl!(HighRunningCrypt, HighRunningCryptReader<T>);
1591
1592impl<R: Read> Read for HighRunningCryptReader<R> {
1593    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1594        let readed = self.inner.read(buf)?;
1595        let key = self.key as u64;
1596        if key != 0 {
1597            for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1598                let offset = self.seg_start + self.pos + i as u64;
1599                if offset % key != 0 {
1600                    *t ^= self.key;
1601                }
1602            }
1603        }
1604        self.pos += readed as u64;
1605        Ok(readed)
1606    }
1607}
1608
1609seek_reader_key_impl!(KissCryptReader<T>, u32);
1610
1611#[derive(Debug)]
1612pub struct PuCaCrypt {
1613    base: BaseSchema,
1614    hash_table: Vec<u32>,
1615    key_table: Vec<u8>,
1616}
1617
1618impl PuCaCrypt {
1619    pub fn new(base: BaseSchema, hash_table: Vec<u32>, key_table: Vec<u8>) -> Result<Self> {
1620        if hash_table.len() != key_table.len() {
1621            anyhow::bail!(
1622                "Hash table and key table must have the same length, but got {} and {}",
1623                hash_table.len(),
1624                key_table.len()
1625            );
1626        }
1627        Ok(Self {
1628            base,
1629            hash_table,
1630            key_table,
1631        })
1632    }
1633    fn get_key_table(&self, file_hash: u32) -> [u8; 0x400] {
1634        let mut hash_table = [0u8; 32];
1635        let mut hash = file_hash;
1636        for k in (0..32).step_by(4) {
1637            if hash & 1 != 0 {
1638                hash |= 0x80000000;
1639            } else {
1640                hash &= 0x7FFFFFFF;
1641            }
1642            hash_table[k..k + 4].copy_from_slice(&hash.to_le_bytes());
1643            hash >>= 1;
1644        }
1645        let mut key_table = [0u8; 0x400];
1646        for l in 0..32 {
1647            for m in 0..32 {
1648                key_table[l * 32 + m] = (!hash_table[l]) ^ hash_table[m];
1649            }
1650        }
1651        key_table
1652    }
1653}
1654
1655impl Crypt for PuCaCrypt {
1656    base_schema_impl!();
1657    fn decrypt_supported(&self) -> bool {
1658        true
1659    }
1660    fn decrypt_seek_supported(&self) -> bool {
1661        true
1662    }
1663    fn decrypt<'a>(
1664        &self,
1665        entry: &Xp3Entry,
1666        cur_seg: &Segment,
1667        stream: Box<dyn Read + Send + Sync + 'a>,
1668    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1669        if let Some(pos) = self.hash_table.iter().position(|&h| h == entry.file_hash) {
1670            Ok(Box::new(PuCaCryptReader::new(
1671                stream,
1672                cur_seg,
1673                self.key_table[pos],
1674            )))
1675        } else {
1676            Ok(Box::new(PuCaCryptReader2::new(
1677                stream,
1678                cur_seg,
1679                self.get_key_table(entry.file_hash),
1680            )))
1681        }
1682    }
1683    fn decrypt_with_seek<'a>(
1684        &self,
1685        entry: &Xp3Entry,
1686        cur_seg: &Segment,
1687        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1688    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1689        if let Some(pos) = self.hash_table.iter().position(|&h| h == entry.file_hash) {
1690            Ok(Box::new(PuCaCryptReader::new(
1691                stream,
1692                cur_seg,
1693                self.key_table[pos],
1694            )))
1695        } else {
1696            Ok(Box::new(PuCaCryptReader2::new(
1697                stream,
1698                cur_seg,
1699                self.get_key_table(entry.file_hash),
1700            )))
1701        }
1702    }
1703}
1704
1705seek_reader_key_impl!(PuCaCryptReader<T>, u8);
1706
1707impl<T: Read> Read for PuCaCryptReader<T> {
1708    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1709        let readed = self.inner.read(buf)?;
1710        for t in (&mut buf[..readed]).iter_mut() {
1711            *t ^= self.key;
1712        }
1713        self.pos += readed as u64;
1714        Ok(readed)
1715    }
1716}
1717
1718seek_reader_key_impl!(PuCaCryptReader2<T>, [u8; 0x400]);
1719
1720impl<R: Read> Read for PuCaCryptReader2<R> {
1721    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1722        let readed = self.inner.read(buf)?;
1723        let mut offset = ((self.seg_start + self.pos) & 0x3FF) as usize;
1724        for t in (&mut buf[..readed]).iter_mut() {
1725            *t ^= self.key[offset];
1726            offset = (offset + 1) & 0x3FF;
1727        }
1728        self.pos += readed as u64;
1729        Ok(readed)
1730    }
1731}
1732
1733#[derive(Debug)]
1734pub struct RhapsodyCrypt {
1735    base: BaseSchema,
1736    names: HashMap<u32, String>,
1737}
1738
1739impl RhapsodyCrypt {
1740    pub fn new(
1741        base: BaseSchema,
1742        file_list_name: &str,
1743        file_list_path: Option<&str>,
1744    ) -> Result<Self> {
1745        let file_list = if let Some(path) = file_list_path {
1746            std::fs::read_to_string(path)?
1747        } else {
1748            query_filename_list(file_list_name)?
1749        };
1750        let mut names = HashMap::new();
1751        for name in file_list.lines() {
1752            let name = name.trim();
1753            if !name.is_empty() {
1754                names.insert(Self::get_name_hash(name.chars()), name.to_string());
1755            }
1756        }
1757        Ok(Self { base, names })
1758    }
1759    fn get_name_hash<T: Iterator<Item = char>>(name: T) -> u32 {
1760        let mut hash = 0;
1761        for c in name {
1762            hash = Self::update_name_hash(hash, c);
1763        }
1764        hash
1765    }
1766    const fn update_name_hash(hash: u32, c: char) -> u32 {
1767        let c = c.to_ascii_lowercase() as u32;
1768        let mut hash = w!(0x1000193u32 * hash ^ (c & 0xFF));
1769        hash = w!(0x1000193u32 * hash ^ ((c >> 8) & 0xFF));
1770        hash
1771    }
1772    fn get_key(&self, hash: u32) -> [u8; 12] {
1773        let mut key = [0u8; 12];
1774        key[0..4].copy_from_slice(&hash.to_le_bytes());
1775        key[4..8].copy_from_slice(&(0x6E1DA9B2u32).to_le_bytes());
1776        key[8..12].copy_from_slice(&(0x0040C800u32).to_le_bytes());
1777        key
1778    }
1779}
1780
1781impl Crypt for RhapsodyCrypt {
1782    base_schema_impl!();
1783    fn read_name<'a>(&self, reader: &mut Box<dyn Read + 'a>) -> Result<(String, u64)> {
1784        use msg_tool_macro::rhapsody_crypt_const_name_hash as hash;
1785        const PNG_HASH: u32 = hash!(".png");
1786        const MAP_HASH: u32 = hash!(".map");
1787        const ASD_HASH: u32 = hash!(".asd");
1788        const TJS_HASH: u32 = hash!(".tjs");
1789        const TXT_HASH: u32 = hash!(".txt");
1790        const KS_HASH: u32 = hash!(".ks");
1791        const WAV_HASH: u32 = hash!(".wav");
1792        const JPG_HASH: u32 = hash!(".jpg");
1793        const OGG_HASH: u32 = hash!(".ogg");
1794        let key = reader.read_u32()?;
1795        let name_hash = reader.read_u32()? ^ key;
1796        if let Some(name) = self.names.get(&name_hash) {
1797            return Ok((name.clone(), 8));
1798        }
1799        let ext_hash = reader.read_u32()? ^ key;
1800        let mut name = format!("{:08X}", name_hash);
1801        match ext_hash {
1802            PNG_HASH => name += ".png",
1803            MAP_HASH => name += ".map",
1804            ASD_HASH => name += ".asd",
1805            TJS_HASH => name += ".tjs",
1806            TXT_HASH => name += ".txt",
1807            KS_HASH => name += ".ks",
1808            WAV_HASH => name += ".wav",
1809            JPG_HASH => name += ".jpg",
1810            OGG_HASH => name += ".ogg",
1811            _ => name += format!(".{:08X}", ext_hash).as_str(),
1812        };
1813        Ok((name, 12))
1814    }
1815    fn decrypt_supported(&self) -> bool {
1816        true
1817    }
1818    fn decrypt_seek_supported(&self) -> bool {
1819        true
1820    }
1821    fn decrypt<'a>(
1822        &self,
1823        entry: &Xp3Entry,
1824        cur_seg: &Segment,
1825        stream: Box<dyn Read + Send + Sync + 'a>,
1826    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1827        Ok(Box::new(RhapsodyCryptReader::new(
1828            stream,
1829            cur_seg,
1830            self.get_key(entry.file_hash),
1831        )))
1832    }
1833    fn decrypt_with_seek<'a>(
1834        &self,
1835        entry: &Xp3Entry,
1836        cur_seg: &Segment,
1837        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1838    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1839        Ok(Box::new(RhapsodyCryptReader::new(
1840            stream,
1841            cur_seg,
1842            self.get_key(entry.file_hash),
1843        )))
1844    }
1845}
1846
1847seek_reader_key_impl!(RhapsodyCryptReader<T>, [u8; 12]);
1848
1849impl<R: Read> Read for RhapsodyCryptReader<R> {
1850    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1851        let readed = self.inner.read(buf)?;
1852        let mut offset = ((self.seg_start + self.pos) % 12) as usize;
1853        for t in (&mut buf[..readed]).iter_mut() {
1854            *t ^= self.key[offset];
1855            offset = (offset + 1) % 12;
1856        }
1857        self.pos += readed as u64;
1858        Ok(readed)
1859    }
1860}
1861
1862#[derive(Debug)]
1863pub struct MadoCrypt {
1864    base: AkabeiCrypt,
1865}
1866
1867impl MadoCrypt {
1868    pub fn new(base: BaseSchema, seed: u32) -> Self {
1869        Self {
1870            base: AkabeiCrypt::new(base, seed),
1871        }
1872    }
1873}
1874
1875impl AsRef<BaseSchema> for MadoCrypt {
1876    fn as_ref(&self) -> &BaseSchema {
1877        &self.base.base
1878    }
1879}
1880
1881impl Crypt for MadoCrypt {
1882    base_schema2_impl!();
1883    fn decrypt_supported(&self) -> bool {
1884        true
1885    }
1886    fn decrypt_seek_supported(&self) -> bool {
1887        true
1888    }
1889    fn decrypt<'a>(
1890        &self,
1891        entry: &Xp3Entry,
1892        cur_seg: &Segment,
1893        stream: Box<dyn Read + Send + Sync + 'a>,
1894    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1895        Ok(Box::new(MadoCryptReader::new(
1896            stream,
1897            cur_seg,
1898            self.base.get_key(entry.file_hash),
1899        )))
1900    }
1901    fn decrypt_with_seek<'a>(
1902        &self,
1903        entry: &Xp3Entry,
1904        cur_seg: &Segment,
1905        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1906    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1907        Ok(Box::new(MadoCryptReader::new(
1908            stream,
1909            cur_seg,
1910            self.base.get_key(entry.file_hash),
1911        )))
1912    }
1913}
1914
1915seek_reader_key_impl!(MadoCryptReader<T>, [u8; 0x20]);
1916
1917impl<R: Read> Read for MadoCryptReader<R> {
1918    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1919        let readed = self.inner.read(buf)?;
1920        for (i, t) in (&mut buf[..readed]).iter_mut().enumerate() {
1921            let offset = self.seg_start + self.pos + i as u64;
1922            *t ^= self.key[(offset % 0x1F) as usize];
1923        }
1924        self.pos += readed as u64;
1925        Ok(readed)
1926    }
1927}
1928
1929#[derive(Debug)]
1930pub struct SmxCrypt {
1931    base: BaseSchema,
1932    mask: u32,
1933    key_seq: Vec<u8>,
1934}
1935
1936impl SmxCrypt {
1937    pub fn new(base: BaseSchema, mask: u32, key_seq: &[u8]) -> Result<Self> {
1938        if key_seq.len() <= mask as usize + 1 {
1939            anyhow::bail!(
1940                "Key sequence length must be greater than mask + 1, but got {} and {}",
1941                key_seq.len(),
1942                mask
1943            );
1944        }
1945        if key_seq.len() < 2 {
1946            anyhow::bail!(
1947                "Key sequence length must be at least 2, but got {}",
1948                key_seq.len()
1949            );
1950        }
1951        Ok(Self {
1952            base,
1953            mask,
1954            key_seq: key_seq.to_vec(),
1955        })
1956    }
1957
1958    fn generate_key(&self, file_hash: u32) -> Vec<u8> {
1959        let mut key = vec![0u8; self.key_seq.len() - 1];
1960        for i in 1..self.key_seq.len() {
1961            key[i - 1] = (file_hash >> self.key_seq[i]) as u8;
1962        }
1963        key
1964    }
1965}
1966
1967impl Crypt for SmxCrypt {
1968    base_schema_impl!();
1969    fn decrypt_supported(&self) -> bool {
1970        true
1971    }
1972    fn decrypt_seek_supported(&self) -> bool {
1973        true
1974    }
1975    fn decrypt<'a>(
1976        &self,
1977        entry: &Xp3Entry,
1978        cur_seg: &Segment,
1979        stream: Box<dyn Read + Send + Sync + 'a>,
1980    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1981        let start_key = (entry.file_hash >> self.key_seq[0]) as u8;
1982        Ok(Box::new(SmxCryptReader::new(
1983            stream,
1984            cur_seg,
1985            (self.mask, start_key, self.generate_key(entry.file_hash)),
1986        )))
1987    }
1988    fn decrypt_with_seek<'a>(
1989        &self,
1990        entry: &Xp3Entry,
1991        cur_seg: &Segment,
1992        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1993    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1994        let start_key = (entry.file_hash >> self.key_seq[0]) as u8;
1995        Ok(Box::new(SmxCryptReader::new(
1996            stream,
1997            cur_seg,
1998            (self.mask, start_key, self.generate_key(entry.file_hash)),
1999        )))
2000    }
2001}
2002
2003seek_reader_key_impl!(SmxCryptReader<T>, (u32, u8, Vec<u8>));
2004
2005impl<R: Read> Read for SmxCryptReader<R> {
2006    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2007        let readed = self.inner.read(buf)?;
2008        let (mask, start_key, key) = &self.key;
2009        let mask = *mask as u64;
2010        let mut offset = self.seg_start + self.pos;
2011        for t in (&mut buf[..readed]).iter_mut() {
2012            let key = if offset <= 100 {
2013                *start_key
2014            } else {
2015                key[(offset & mask) as usize]
2016            };
2017            *t ^= key;
2018            offset += 1;
2019        }
2020        self.pos += readed as u64;
2021        Ok(readed)
2022    }
2023}
2024
2025seek_crypt_filehash_key_impl!(FestivalCrypt, FestivalCryptReader<T>);
2026
2027impl<R: Read> Read for FestivalCryptReader<R> {
2028    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2029        let readed = self.inner.read(buf)?;
2030        let key = (!(self.key >> 7)) as u8;
2031        for t in (&mut buf[..readed]).iter_mut() {
2032            *t ^= key;
2033        }
2034        self.pos += readed as u64;
2035        Ok(readed)
2036    }
2037}
2038
2039seek_crypt_impl!(PinPointCrypt, PinPointCryptReader<T>);
2040
2041impl<R: Read> PinPointCryptReader<R> {
2042    #[inline(always)]
2043    fn count_set_bits(x: u8) -> u32 {
2044        let mut bit_count = ((x & 0x55) + ((x >> 1) & 0x55)) as u32;
2045        bit_count = (bit_count & 0x33) + ((bit_count >> 2) & 0x33);
2046        ((bit_count & 0xF) + ((bit_count >> 4) & 0xF)) & 0xF
2047    }
2048}
2049
2050impl<R: Read> Read for PinPointCryptReader<R> {
2051    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2052        let readed = self.inner.read(buf)?;
2053        for t in (&mut buf[..readed]).iter_mut() {
2054            let bit_count = Self::count_set_bits(*t);
2055            if bit_count > 0 {
2056                *t = (*t).rotate_left(bit_count);
2057            }
2058        }
2059        self.pos += readed as u64;
2060        Ok(readed)
2061    }
2062}
2063
2064seek_crypt_filehash_key_impl!(HybridCrypt, HybridCryptReader<T>);
2065
2066impl<R: Read> Read for HybridCryptReader<R> {
2067    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2068        let readed = self.inner.read(buf)?;
2069        let key = (self.key >> 5) as u8;
2070        for t in (&mut buf[..readed]).iter_mut() {
2071            *t ^= key;
2072        }
2073        self.pos += readed as u64;
2074        Ok(readed)
2075    }
2076}
2077
2078#[derive(Debug)]
2079pub struct NekoWorksCrypt {
2080    base: BaseSchema,
2081    key: Vec<u8>,
2082}
2083
2084impl NekoWorksCrypt {
2085    pub fn new(base: BaseSchema, key: Vec<u8>) -> Result<Self> {
2086        if key.len() < 31 {
2087            anyhow::bail!("NekoWorksCrypt: key is too small.");
2088        }
2089        Ok(Self { base, key })
2090    }
2091
2092    fn init_key(&self, mut hash: u32) -> [u8; 31] {
2093        hash &= 0x7FFFFFFF;
2094        hash = hash << 31 | hash;
2095        let mut key = [0; 31];
2096        key.copy_from_slice(&self.key[..31]);
2097        for i in 0..31 {
2098            key[i] ^= hash as u8;
2099            hash = (hash & 0xFFFFFFFE) << 23 | hash >> 8;
2100        }
2101        key
2102    }
2103}
2104
2105impl Crypt for NekoWorksCrypt {
2106    base_schema_impl!();
2107    fn decrypt_supported(&self) -> bool {
2108        true
2109    }
2110    fn decrypt_seek_supported(&self) -> bool {
2111        true
2112    }
2113    fn decrypt<'a>(
2114        &self,
2115        entry: &Xp3Entry,
2116        cur_seg: &Segment,
2117        stream: Box<dyn Read + Send + Sync + 'a>,
2118    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2119        Ok(Box::new(NekoWorksCryptReader::new(
2120            stream,
2121            cur_seg,
2122            self.init_key(entry.file_hash),
2123        )))
2124    }
2125    fn decrypt_with_seek<'a>(
2126        &self,
2127        entry: &Xp3Entry,
2128        cur_seg: &Segment,
2129        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2130    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2131        Ok(Box::new(NekoWorksCryptReader::new(
2132            stream,
2133            cur_seg,
2134            self.init_key(entry.file_hash),
2135        )))
2136    }
2137}
2138
2139seek_reader_key_impl!(NekoWorksCryptReader<T>, [u8; 31]);
2140
2141impl<R: Read> Read for NekoWorksCryptReader<R> {
2142    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2143        let readed = self.inner.read(buf)?;
2144        let mut offset = ((self.seg_start + self.pos) % 31) as usize;
2145        for t in (&mut buf[..readed]).iter_mut() {
2146            *t ^= self.key[offset];
2147            offset = (offset + 1) % 31;
2148        }
2149        self.pos += readed as u64;
2150        Ok(readed)
2151    }
2152}
2153
2154#[derive(Debug)]
2155pub struct NinkiSeiyuuCrypt {
2156    base: BaseSchema,
2157    tbl2: [u8; 64],
2158    tbl3: [u8; 64],
2159}
2160
2161impl NinkiSeiyuuCrypt {
2162    pub fn new(base: BaseSchema, key1: u64, key2: u64, key3: u64) -> Self {
2163        let tbl2 = Self::get_table2(3080);
2164        let tbl3 = Self::get_table3(3080, key1, key2, key3);
2165        Self { base, tbl2, tbl3 }
2166    }
2167    fn get_table1(seed: u32) -> [u8; 32] {
2168        let mut key = [0; 32];
2169        let mut v48 = seed & 0x7FFFFFFF;
2170        for i in 0..31 {
2171            key[i] = v48 as u8;
2172            v48 = (v48 >> 8) | (((v48 as u8) as u32) << 23);
2173        }
2174        key
2175    }
2176    fn get_table2(seed: u32) -> [u8; 64] {
2177        let mut key = [0; 64];
2178        let v51 = seed & 0xFFF;
2179        let v52 = ((v51 | (v51 << 13)) as u64) | (((v51 >> 19) as u64) << 32);
2180        let mut v53 = v51 | ((v52 as u32) << 13);
2181        let mut v54 = (((((v52 as u32) << 7) & 0x1FFFFFFF) as u64) | (v52 >> 19)) as u32;
2182        for i in 0..61 {
2183            let v56 = v53 as u8;
2184            key[i] = v56;
2185            v53 = ((((v54 as u64) << 32) | (v53 as u64)) >> 8) as u32;
2186            v54 = (v54 >> 8) | ((v56 as u32) << 21);
2187        }
2188        key
2189    }
2190    fn get_table3(seed: u32, key1: u64, key2: u64, key3: u64) -> [u8; 64] {
2191        let mut key = [0; 64];
2192        let v88 = seed & 0xFFF;
2193        let v89 = ((v88 | (v88 << 13)) as u64) | (((v88 >> 19) as u64) << 32);
2194        let mut v90 = ((key1 + key2) ^ ((v88 | ((v89 as u32) << 13)) as u64)) as u32;
2195        let mut v91 =
2196            ((((key1 + ((key3 & 0xFFFFFFFF00000000) | (key2 & 0xFFFFFFFF))) >> 32) & 0x1FFFFFFF)
2197                ^ (((((v89 as u32) << 7) & 0x1FFFFFFF) as u64) | (v89 >> 19))) as u32;
2198        for i in 0..61 {
2199            let v93 = v90 as u8;
2200            key[i] = v93;
2201            v90 = ((((v91 as u64) << 32) | (v90 as u64)) >> 8) as u32;
2202            v91 = (v91 >> 8) | ((v93 as u32) << 21);
2203        }
2204        key
2205    }
2206}
2207
2208impl Crypt for NinkiSeiyuuCrypt {
2209    base_schema_impl!();
2210    fn decrypt_supported(&self) -> bool {
2211        true
2212    }
2213    fn decrypt_seek_supported(&self) -> bool {
2214        true
2215    }
2216    fn decrypt<'a>(
2217        &self,
2218        entry: &Xp3Entry,
2219        cur_seg: &Segment,
2220        stream: Box<dyn Read + Send + Sync + 'a>,
2221    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2222        let key = (
2223            Self::get_table1(entry.file_hash),
2224            self.tbl2.clone(),
2225            self.tbl3.clone(),
2226        );
2227        Ok(Box::new(NinkiSeiyuuCryptReader::new(stream, cur_seg, key)))
2228    }
2229    fn decrypt_with_seek<'a>(
2230        &self,
2231        entry: &Xp3Entry,
2232        cur_seg: &Segment,
2233        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2234    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2235        let key = (
2236            Self::get_table1(entry.file_hash),
2237            self.tbl2.clone(),
2238            self.tbl3.clone(),
2239        );
2240        Ok(Box::new(NinkiSeiyuuCryptReader::new(stream, cur_seg, key)))
2241    }
2242}
2243
2244seek_reader_key_impl!(NinkiSeiyuuCryptReader<T>, ([u8; 32], [u8; 64], [u8; 64]));
2245
2246impl<R: Read> Read for NinkiSeiyuuCryptReader<R> {
2247    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2248        let readed = self.inner.read(buf)?;
2249        let mut offset1 = ((self.seg_start + self.pos) % 0x1F) as usize;
2250        let mut offset2 = ((self.seg_start + self.pos) % 0x3D) as usize;
2251        for t in (&mut buf[..readed]).iter_mut() {
2252            *t ^= self.key.0[offset1];
2253            *t = (*t).wrapping_add(self.key.1[offset2] ^ self.key.2[offset2]);
2254            offset1 = (offset1 + 1) % 0x1F;
2255            offset2 = (offset2 + 1) % 0x3D;
2256        }
2257        self.pos += readed as u64;
2258        Ok(readed)
2259    }
2260}
2261
2262#[derive(Debug)]
2263pub struct SyangrilaSmartCrypt {
2264    base: BaseSchema,
2265}
2266
2267impl SyangrilaSmartCrypt {
2268    pub fn new(base: BaseSchema) -> Self {
2269        Self { base }
2270    }
2271
2272    fn get_key(hash: u32) -> [u8; 5] {
2273        [
2274            (hash >> 5) as u8,
2275            (hash >> 5) as u8,
2276            (hash >> 7) as u8,
2277            (hash >> 1) as u8,
2278            (hash >> 4) as u8,
2279        ]
2280    }
2281}
2282
2283impl Crypt for SyangrilaSmartCrypt {
2284    base_schema_impl!();
2285    fn decrypt_supported(&self) -> bool {
2286        true
2287    }
2288    fn decrypt_seek_supported(&self) -> bool {
2289        true
2290    }
2291    fn decrypt<'a>(
2292        &self,
2293        entry: &Xp3Entry,
2294        cur_seg: &Segment,
2295        stream: Box<dyn Read + Send + Sync + 'a>,
2296    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2297        Ok(Box::new(SyangrilaSmartCryptReader::new(
2298            stream,
2299            cur_seg,
2300            Self::get_key(entry.file_hash),
2301        )))
2302    }
2303    fn decrypt_with_seek<'a>(
2304        &self,
2305        entry: &Xp3Entry,
2306        cur_seg: &Segment,
2307        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2308    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2309        Ok(Box::new(SyangrilaSmartCryptReader::new(
2310            stream,
2311            cur_seg,
2312            Self::get_key(entry.file_hash),
2313        )))
2314    }
2315}
2316
2317seek_reader_key_impl!(SyangrilaSmartCryptReader<T>, [u8; 5]);
2318
2319impl<R: Read> Read for SyangrilaSmartCryptReader<R> {
2320    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2321        let readed = self.inner.read(buf)?;
2322        let offset = self.seg_start + self.pos;
2323        for (i, t) in buf[..readed].iter_mut().enumerate() {
2324            let tpos = offset + i as u64;
2325            if tpos <= 0x64 {
2326                *t ^= self.key[4];
2327            } else {
2328                *t ^= self.key[(tpos & 3) as usize];
2329            }
2330        }
2331        self.pos += readed as u64;
2332        Ok(readed)
2333    }
2334}
2335
2336const WARC_MAGIC: &[u8] = b"warc";
2337const WARC_TYPE_KEY: [u8; 3] = [0x27, 0xaf, 0x67];
2338const WARC_SIZE_KEY: [u8; 4] = [0x7d, 0x16, 0x9f, 0xf1];
2339
2340#[derive(Debug)]
2341pub struct Kano2Crypt {
2342    base: BaseSchema,
2343}
2344
2345impl Kano2Crypt {
2346    pub fn new(base: BaseSchema) -> Self {
2347        Self { base }
2348    }
2349}
2350
2351impl Crypt for Kano2Crypt {
2352    base_schema_impl!();
2353    fn decrypt_supported(&self) -> bool {
2354        true
2355    }
2356    fn decrypt_seek_supported(&self) -> bool {
2357        true
2358    }
2359    fn decrypt<'a>(
2360        &self,
2361        entry: &Xp3Entry,
2362        cur_seg: &Segment,
2363        stream: Box<dyn Read + Send + Sync + 'a>,
2364    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2365        Ok(Box::new(Kano2CryptReader::new(
2366            stream,
2367            cur_seg,
2368            entry.file_hash as u8,
2369        )))
2370    }
2371    fn decrypt_with_seek<'a>(
2372        &self,
2373        entry: &Xp3Entry,
2374        cur_seg: &Segment,
2375        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2376    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2377        Ok(Box::new(Kano2CryptReader::new(
2378            stream,
2379            cur_seg,
2380            entry.file_hash as u8,
2381        )))
2382    }
2383    fn need_filter(&self, _filename: &str, buf: &[u8], buf_len: usize) -> bool {
2384        buf_len >= 4 && buf.starts_with(WARC_MAGIC)
2385    }
2386    fn filter<'a>(&self, mut entry: Entry<'a>) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2387        let mut magic = [0; 4];
2388        entry.read_exact(&mut magic)?;
2389        if &magic != WARC_MAGIC {
2390            anyhow::bail!("Unsupported magic: {:?}.", magic);
2391        }
2392        let mut typ = [0; 3];
2393        entry.read_exact(&mut typ)?;
2394        for i in 0..3 {
2395            typ[i] ^= WARC_TYPE_KEY[i];
2396        }
2397        if &typ != b"STR" && &typ != b"OCT" && &typ != b"AOD" && &typ != b"INT" && &typ != b"REL" {
2398            eprintln!("WARNING: Unknown type key: {:?}", typ);
2399            crate::COUNTER.inc_warning();
2400        }
2401        let mut size = [0; 4];
2402        entry.read_exact(&mut size)?;
2403        for i in 0..4 {
2404            size[i] ^= typ[0] ^ WARC_SIZE_KEY[i];
2405        }
2406        let _uncompressed_size = u32::from_le_bytes(size);
2407        let reader = flate2::read::ZlibDecoder::new(entry);
2408        if &typ == b"STR" {
2409            return Ok(Box::new(PrefixStream::new(vec![0xFF, 0xFE], reader)));
2410        }
2411        Ok(Box::new(reader))
2412    }
2413}
2414
2415seek_reader_key_impl!(Kano2CryptReader<T>, u8);
2416
2417impl<R: Read> Read for Kano2CryptReader<R> {
2418    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2419        let readed = self.inner.read(buf)?;
2420        let mut offset = (self.seg_start + self.pos) % 8;
2421        for t in buf[..readed].iter_mut() {
2422            if offset == 0 {
2423                *t ^= self.key;
2424            }
2425            offset = (offset + 1) % 8;
2426        }
2427        self.pos += readed as u64;
2428        Ok(readed)
2429    }
2430}
2431
2432#[derive(Debug)]
2433pub struct MiburoCrypt {
2434    base: BaseSchema,
2435}
2436
2437impl MiburoCrypt {
2438    pub fn new(base: BaseSchema) -> Self {
2439        Self { base }
2440    }
2441
2442    fn init_key(mut hash: u32) -> [u8; 29] {
2443        hash &= 0x1FFFFFFF;
2444        hash |= (hash & 1) << 29;
2445        let mut key = [0; 29];
2446        for i in 0..29 {
2447            key[i] = hash as u8;
2448            hash = (hash >> 8) | ((hash << 0x15) & 0xFF000000);
2449        }
2450        key
2451    }
2452}
2453
2454impl Crypt for MiburoCrypt {
2455    base_schema_impl!();
2456    fn decrypt_supported(&self) -> bool {
2457        true
2458    }
2459    fn decrypt_seek_supported(&self) -> bool {
2460        true
2461    }
2462    fn decrypt<'a>(
2463        &self,
2464        entry: &Xp3Entry,
2465        cur_seg: &Segment,
2466        stream: Box<dyn Read + Send + Sync + 'a>,
2467    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2468        Ok(Box::new(MiburoCryptReader::new(
2469            stream,
2470            cur_seg,
2471            Self::init_key(entry.file_hash),
2472        )))
2473    }
2474    fn decrypt_with_seek<'a>(
2475        &self,
2476        entry: &Xp3Entry,
2477        cur_seg: &Segment,
2478        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2479    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2480        Ok(Box::new(MiburoCryptReader::new(
2481            stream,
2482            cur_seg,
2483            Self::init_key(entry.file_hash),
2484        )))
2485    }
2486}
2487
2488seek_reader_key_impl!(MiburoCryptReader<T>, [u8; 29]);
2489
2490impl<R: Read> Read for MiburoCryptReader<R> {
2491    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2492        let readed = self.inner.read(buf)?;
2493        let mut offset = ((self.seg_start + self.pos) % 29) as usize;
2494        for t in buf[..readed].iter_mut() {
2495            *t ^= self.key[offset];
2496            offset = (offset + 1) % 29;
2497        }
2498        self.pos += readed as u64;
2499        Ok(readed)
2500    }
2501}
2502
2503#[derive(Debug)]
2504pub struct PureMoreCrypt {
2505    base: BaseSchema,
2506    names: HashMap<String, String>,
2507}
2508
2509impl PureMoreCrypt {
2510    pub fn new(
2511        base: BaseSchema,
2512        file_list_name: &str,
2513        file_list_path: Option<&str>,
2514        char_map: &str,
2515        layer_name_suffix: &str,
2516    ) -> Result<Self> {
2517        use sha2::Digest;
2518        use unicode_segmentation::UnicodeSegmentation;
2519        let mut names = HashMap::new();
2520        let file_list = if let Some(path) = file_list_path {
2521            std::fs::read_to_string(path)?
2522        } else {
2523            query_filename_list(file_list_name)?
2524        };
2525        let char_map: Vec<_> = char_map.graphemes(true).collect();
2526        for name in file_list.lines() {
2527            let name = name.trim();
2528            if name.is_empty() {
2529                continue;
2530            }
2531            let parts: Vec<_> = name.splitn(2, ",").collect();
2532            let mut name = parts[0].to_owned();
2533            if let Some(ext) = name.rfind(".") {
2534                name = name[..ext].to_owned();
2535            }
2536            if parts.len() == 2 {
2537                name += layer_name_suffix;
2538            }
2539            let encoded = encode_string(Encoding::Utf16LE, &name, true)?;
2540            let mut sha256 = sha2::Sha256::new();
2541            sha256.update(&encoded);
2542            name = String::new();
2543            for hash in sha256.finalize() {
2544                name += char_map[hash as usize];
2545            }
2546            name += ".tlg";
2547            names.insert(name, parts[0].to_owned());
2548        }
2549        Ok(Self { base, names })
2550    }
2551}
2552
2553impl Crypt for PureMoreCrypt {
2554    base_schema_impl!();
2555    fn read_name<'a>(&self, reader: &mut Box<dyn Read + 'a>) -> Result<(String, u64)> {
2556        let (name, size) = default_read_name(reader)?;
2557        let name_length = name.encode_utf16().count();
2558        if name_length != 36 || !name.ends_with(".tlg") {
2559            return Ok((name, size));
2560        }
2561        Ok((
2562            self.names.get(&name).map(|s| s.to_string()).unwrap_or(name),
2563            size,
2564        ))
2565    }
2566    fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
2567        for entry in archive.entries.iter_mut() {
2568            // mark all files are not encrypted.
2569            entry.flags = 0;
2570        }
2571        Ok(())
2572    }
2573}
2574
2575#[derive(Debug)]
2576pub struct Xor2Crypt {
2577    base: BaseSchema,
2578    key1: u8,
2579    key2: u8,
2580}
2581
2582impl Xor2Crypt {
2583    pub fn new(base: BaseSchema, key1: u8, key2: u8) -> Self {
2584        Self { base, key1, key2 }
2585    }
2586}
2587
2588impl Crypt for Xor2Crypt {
2589    base_schema_impl!();
2590    fn decrypt_supported(&self) -> bool {
2591        true
2592    }
2593    fn decrypt_seek_supported(&self) -> bool {
2594        true
2595    }
2596    fn decrypt<'a>(
2597        &self,
2598        _entry: &Xp3Entry,
2599        cur_seg: &Segment,
2600        stream: Box<dyn Read + Send + Sync + 'a>,
2601    ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2602        Ok(Box::new(Xor2CryptReader::new(
2603            stream,
2604            cur_seg,
2605            (self.key1, self.key2),
2606        )))
2607    }
2608    fn decrypt_with_seek<'a>(
2609        &self,
2610        _entry: &Xp3Entry,
2611        cur_seg: &Segment,
2612        stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2613    ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2614        Ok(Box::new(Xor2CryptReader::new(
2615            stream,
2616            cur_seg,
2617            (self.key1, self.key2),
2618        )))
2619    }
2620}
2621
2622seek_reader_key_impl!(Xor2CryptReader<T>, (u8, u8));
2623
2624impl<R: Read> Read for Xor2CryptReader<R> {
2625    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2626        let readed = self.inner.read(buf)?;
2627        let mut offset = self.seg_start + self.pos;
2628        for t in (&mut buf[..readed]).iter_mut() {
2629            *t ^= if offset % 2 == 0 {
2630                self.key.0
2631            } else {
2632                self.key.1
2633            };
2634            offset += 1;
2635        }
2636        self.pos += readed as u64;
2637        Ok(readed)
2638    }
2639}
2640
2641seek_crypt_filehash_key_u8_impl!(LeaveSLeaveCrypt, LeaveSLeaveCryptReader<T>);
2642
2643impl<R: Read> Read for LeaveSLeaveCryptReader<R> {
2644    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2645        let readed = self.inner.read(buf)?;
2646        for t in (&mut buf[..readed]).iter_mut() {
2647            let b = *t ^ self.key;
2648            *t = (b << 4) | (b >> 4);
2649        }
2650        self.pos += readed as u64;
2651        Ok(readed)
2652    }
2653}
2654
2655seek_reader_key_impl!(ChainReactionCryptReader<T>, (u32, u32));
2656seek_reader_key_impl!(XanaduCryptReader<T>, (u32, u32));
2657seek_reader_key_impl!(SisMikoCryptReader<T>, (u32, u32));
2658seek_reader_key_impl!(NVLCryptReader<T>, [u8; 12]);
2659
2660#[test]
2661fn test_deserialize_crypt() {
2662    for (key, schema) in CRYPT_SCHEMA.iter() {
2663        println!("Title: {}, Schema: {:?}", key, schema);
2664    }
2665    assert!(CRYPT_SCHEMA.contains_key(CIS::from_str("PURELY x CATION")));
2666}
2667
2668#[test]
2669fn check_alias_exists() {
2670    let mut alias = std::collections::HashSet::new();
2671    for (key, schema) in CRYPT_SCHEMA.iter() {
2672        if alias.contains(key.as_str()) {
2673            panic!("Game {} is already used", key);
2674        }
2675        alias.insert(key.to_string());
2676        if let Some(title) = &schema.title {
2677            for part in title.split("|") {
2678                let part = part.trim();
2679                if alias.contains(part) {
2680                    panic!("Game alias {} in {} is already used", part, key);
2681                }
2682                alias.insert(part.to_string());
2683            }
2684        }
2685    }
2686}
2687
2688#[test]
2689fn test_cx_cb_table() {
2690    for (key, list) in CX_CB_TABLE.iter() {
2691        println!("Key: {}, List length: {}", key, list.len());
2692    }
2693}
2694
2695#[test]
2696fn test_altered_pink_key_table() {
2697    assert_eq!(ALTERED_PINK_KEY_TABLE.len(), 0x100);
2698}