1use super::super::{FileHashOption, PathHashOption};
2use super::*;
3use crate::ext::atomic::AtomicQuick;
4use crate::ext::mutex::MutexExt;
5use crate::utils::files::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use chacha20::cipher::array::Array;
9use chacha20::hchacha;
10use chacha20::{ChaCha20Legacy, KeyIvInit};
11use msg_tool_macro::{MyDebug, StructUnpack};
12use pelite::PeFile;
13use serde::{Deserializer, Serialize, Serializer, de};
14use sha3::Sha3_224;
15use shake::Shake256;
16use std::collections::HashSet;
17use std::ops::{Deref, DerefMut, Index};
18use std::path::PathBuf;
19use std::sync::atomic::AtomicU64;
20use std::sync::{Mutex, Weak};
21use tjs2dec::{Tjs2File, Tjs2Object};
22
23const S_CTL_BLOCK_SIGNATURE: &[u8] = b" Encryption control block";
24
25macro_rules! base_schema_impl {
26 () => {
27 fn hash_after_crypt(&self) -> bool {
28 AsRef::<BaseSchema>::as_ref(self.as_ref()).hash_after_crypt
29 }
30 fn startup_tjs_not_encrypted(&self) -> bool {
31 AsRef::<BaseSchema>::as_ref(self.as_ref()).startup_tjs_not_encrypted
32 }
33 fn obfuscated_index(&self) -> bool {
34 AsRef::<BaseSchema>::as_ref(self.as_ref()).obfuscated_index
35 }
36 };
37}
38
39#[derive(Debug)]
40pub struct CxEncryption {
41 mask: u32,
42 offset: u32,
43 prolog_order: Vec<u8>,
44 odd_branch_order: Vec<u8>,
45 even_branch_order: Vec<u8>,
46 control_block: Arc<Vec<u32>>,
47 programs: Vec<Box<dyn ICxProgram + Send + Sync>>,
48 program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
49 base: BaseSchema,
50}
51
52trait ICxEncryption: std::fmt::Debug {
53 fn get_base_offset(&self, hash: u32) -> u32;
54 fn inner_decrypt(
55 &self,
56 mut key: u32,
57 mut offset: u64,
58 buffer: &mut [u8],
59 mut pos: usize,
60 mut count: usize,
61 ) -> Result<()> {
62 let base_offset = self.get_base_offset(key);
63 if offset < base_offset as u64 {
64 let base_length = ((base_offset as u64 - offset) as usize).min(count);
65 self.decode(key, offset, buffer, pos, base_length)?;
66 offset += base_length as u64;
67 pos += base_length;
68 count -= base_length;
69 }
70 if count > 0 {
71 key = (key >> 16) ^ key;
72 self.decode(key, offset, buffer, pos, count)?;
73 }
74 Ok(())
75 }
76 fn decode(
77 &self,
78 key: u32,
79 offset: u64,
80 buffer: &mut [u8],
81 pos: usize,
82 count: usize,
83 ) -> Result<()>;
84}
85
86impl CxEncryption {
87 pub fn new(base: BaseSchema, schema: &CxSchema, filename: &str) -> Result<Arc<Self>> {
88 Ok(Arc::new(Self::new_inner(
89 base,
90 schema,
91 filename,
92 Box::new(CxProgramBuilder::default()),
93 )?))
94 }
95 fn new_inner(
96 base: BaseSchema,
97 schema: &CxSchema,
98 filename: &str,
99 program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
100 ) -> Result<Self> {
101 let control_block = if let Some(tpm_path) = &schema.tpm_file_name {
102 Self::read_tpm(tpm_path, filename)?
103 } else if let Some(control_block_name) = &schema.control_block_name {
104 CX_CB_TABLE
105 .get(control_block_name)
106 .ok_or_else(|| {
107 anyhow::anyhow!(
108 "Control block not found in cx_cb.pck: {}",
109 control_block_name
110 )
111 })?
112 .clone()
113 } else {
114 return Err(anyhow::anyhow!(
115 "TPM file name or control block is required in schema"
116 ));
117 };
118 Self::new_inner2(base, schema, program_builder, control_block)
119 }
120
121 fn new_inner2(
122 base: BaseSchema,
123 schema: &CxSchema,
124 program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
125 control_block: Vec<u32>,
126 ) -> Result<Self> {
127 if schema.prolog_order.len() != 3 {
128 return Err(anyhow::anyhow!("Prolog order must have 3 elements"));
129 }
130 if schema.odd_branch_order.len() != 6 {
131 return Err(anyhow::anyhow!("Odd branch order must have 6 elements"));
132 }
133 if schema.even_branch_order.len() != 8 {
134 return Err(anyhow::anyhow!("Even branch order must have 8 elements"));
135 }
136 let control_block = Arc::new(control_block);
137 let programs = Vec::with_capacity(0x80);
138 let mut obj = Self {
139 base,
140 mask: schema.mask,
141 offset: schema.offset,
142 prolog_order: schema.prolog_order.bytes.clone(),
143 odd_branch_order: schema.odd_branch_order.bytes.clone(),
144 even_branch_order: schema.even_branch_order.bytes.clone(),
145 control_block: control_block,
146 programs,
147 program_builder,
148 };
149 for seed in 0..0x80 {
150 obj.programs.push(obj.generate_program(seed)?);
151 }
152 Ok(obj)
153 }
154
155 fn new_program(&self, seed: u32) -> Box<dyn ICxProgram + Send + Sync> {
156 self.program_builder
157 .build(seed, Arc::downgrade(&self.control_block))
158 }
159
160 fn generate_program(&self, seed: u32) -> Result<Box<dyn ICxProgram + Send + Sync>> {
161 let mut program = self.new_program(seed);
162 for stage in (1..=5).rev() {
163 if self.emit_code(&mut program, stage) {
164 return Ok(program);
165 }
166 program.clear();
167 }
168 Err(anyhow::anyhow!("Overly large CxEncryption bytecode"))
169 }
170
171 fn read_tpm(tpm_path: &str, filename: &str) -> Result<Vec<u32>> {
172 let pfile = Self::get_tpm_path(tpm_path, filename)?;
173 let tpm = std::fs::read(&pfile)?;
174 let mut begin = 0;
175 let end = (tpm.len() - 0x1000) & !0x3;
176 while begin < end {
177 if &tpm[begin..begin + S_CTL_BLOCK_SIGNATURE.len()] == S_CTL_BLOCK_SIGNATURE {
178 let mut control_block = Vec::with_capacity(0x400);
179 let mut reader = MemReaderRef::new(&tpm[begin..]);
180 for _ in 0..0x400 {
181 control_block.push(!reader.read_u32()?);
182 }
183 return Ok(control_block);
184 }
185 begin += 4;
186 }
187 Err(anyhow::anyhow!(
188 "Control block signature not found in TPM file: {}",
189 pfile.display()
190 ))
191 }
192
193 fn get_tpm_path(tpm_path: &str, filename: &str) -> Result<PathBuf> {
194 let pb = PathBuf::from(filename);
195 let pdir = pb
196 .parent()
197 .ok_or_else(|| anyhow::anyhow!("Invalid TPM path"))?;
198 let pfile = pdir.join(tpm_path);
199 if pfile.is_file() {
200 return Ok(pfile);
201 }
202 let pfile = pdir.join("..").join(tpm_path);
203 if pfile.is_file() {
204 return Ok(pfile);
205 }
206 Err(anyhow::anyhow!("TPM file not found: {}", tpm_path))
207 }
208
209 fn emit_code(&self, program: &mut Box<dyn ICxProgram + Send + Sync>, stage: i32) -> bool {
210 program.emit_nop(5)
211 && program.emit(MovEdiArg, 4)
212 && self.emit_body(program, stage)
213 && program.emit_nop(5)
214 && program.emit(Retn, 1)
215 }
216
217 fn emit_body(&self, program: &mut Box<dyn ICxProgram + Send + Sync>, stage: i32) -> bool {
218 if stage == 1 {
219 return self.emit_prolog(program);
220 }
221 if !program.emit(PushEbx, 1) {
222 return false;
223 }
224 if (program.get_random() & 1) != 0 {
225 if !self.emit_body(program, stage - 1) {
226 return false;
227 }
228 } else {
229 if !self.emit_body2(program, stage - 1) {
230 return false;
231 }
232 }
233 if !program.emit(MovEbxEax, 2) {
234 return false;
235 }
236 if (program.get_random() & 1) != 0 {
237 if !self.emit_body(program, stage - 1) {
238 return false;
239 }
240 } else {
241 if !self.emit_body2(program, stage - 1) {
242 return false;
243 }
244 }
245 self.emit_odd_branch(program) && program.emit(PopEbx, 1)
246 }
247
248 fn emit_body2(&self, program: &mut Box<dyn ICxProgram + Send + Sync>, stage: i32) -> bool {
249 if stage == 1 {
250 return self.emit_prolog(program);
251 }
252 let r = if (program.get_random() & 1) != 0 {
253 self.emit_body(program, stage - 1)
254 } else {
255 self.emit_body2(program, stage - 1)
256 };
257 r && self.emit_even_branch(program)
258 }
259 fn emit_prolog(&self, program: &mut Box<dyn ICxProgram + Send + Sync>) -> bool {
260 match self.prolog_order[(program.get_random() % 3) as usize] {
261 2 => {
262 program.emit_nop(5)
263 && program.emit(MovEaxImmed, 2)
264 && {
265 let random = program.get_random() & 0x3ff;
266 program.emit_u32(random)
267 }
268 && program.emit(MovEaxIndirect, 0)
269 }
270 1 => program.emit(MovEaxEdi, 2),
271 0 => program.emit(MovEaxImmed, 1) && program.emit_random(),
272 _ => true,
273 }
274 }
275
276 fn emit_even_branch(&self, program: &mut Box<dyn ICxProgram + Send + Sync>) -> bool {
277 match self.even_branch_order[(program.get_random() & 7) as usize] {
278 0 => program.emit(NotEax, 2),
279 1 => program.emit(DecEax, 1),
280 2 => program.emit(NegEax, 2),
281 3 => program.emit(IncEax, 1),
282 4 => {
283 program.emit_nop(5)
284 && program.emit(AndEaxImmed, 1)
285 && program.emit_u32(0x3ff)
286 && program.emit(MovEaxIndirect, 3)
287 }
288 5 => {
289 program.emit(PushEbx, 1)
290 && program.emit(MovEbxEax, 2)
291 && program.emit(AndEbxImmed, 2)
292 && program.emit_u32(0xaaaaaaaa)
293 && program.emit(AndEaxImmed, 1)
294 && program.emit_u32(0x55555555)
295 && program.emit(ShrEbx1, 2)
296 && program.emit(ShlEax1, 2)
297 && program.emit(OrEaxEbx, 2)
298 && program.emit(PopEbx, 1)
299 }
300 6 => program.emit(XorEaxImmed, 1) && program.emit_random(),
301 7 => {
302 let mut r = if (program.get_random() & 1) != 0 {
303 program.emit(AddEaxImmed, 1)
304 } else {
305 program.emit(SubEaxImmed, 1)
306 };
307 r = r && program.emit_random();
308 r
309 }
310 _ => true,
311 }
312 }
313
314 fn emit_odd_branch(&self, program: &mut Box<dyn ICxProgram + Send + Sync>) -> bool {
315 match self.odd_branch_order[(program.get_random() % 6) as usize] {
316 0 => {
317 program.emit(PushEcx, 1)
318 && program.emit(MovEcxEbx, 2)
319 && program.emit(AndEcx0F, 3)
320 && program.emit(ShrEaxCl, 2)
321 && program.emit(PopEcx, 1)
322 }
323 1 => {
324 program.emit(PushEcx, 1)
325 && program.emit(MovEcxEbx, 2)
326 && program.emit(AndEcx0F, 3)
327 && program.emit(ShlEaxCl, 2)
328 && program.emit(PopEcx, 1)
329 }
330 2 => program.emit(AddEaxEbx, 2),
331 3 => program.emit(NegEax, 2) && program.emit(AddEaxEbx, 2),
332 4 => program.emit(ImulEaxEbx, 3),
333 5 => program.emit(SubEaxEbx, 2),
334 _ => true,
335 }
336 }
337
338 fn execute_xcode(&self, mut hash: u32) -> Result<(u32, u32)> {
339 let seed = hash & 0x7f;
340 hash >>= 7;
341 let program = &self.programs[seed as usize];
342 let ret1 = program.execute(hash)?;
343 let ret2 = program.execute(!hash)?;
344 Ok((ret1, ret2))
345 }
346}
347
348impl AsRef<BaseSchema> for CxEncryption {
349 fn as_ref(&self) -> &BaseSchema {
350 &self.base
351 }
352}
353
354impl ICxEncryption for CxEncryption {
355 fn get_base_offset(&self, hash: u32) -> u32 {
356 (hash & self.mask).wrapping_add(self.offset)
357 }
358
359 fn decode(
360 &self,
361 key: u32,
362 offset: u64,
363 buffer: &mut [u8],
364 pos: usize,
365 count: usize,
366 ) -> Result<()> {
367 let ret = self.execute_xcode(key)?;
368 let key1 = ret.1 >> 16;
369 let mut key2 = ret.1 & 0xffff;
370 let mut key3 = (ret.0 & 0xFF) as u8;
371 if key1 == key2 {
372 key2 = key2.wrapping_add(1);
373 }
374 if key3 == 0 {
375 key3 = 1;
376 }
377 if (key2 as u64) >= offset && (key2 as u64) < offset + (count as u64) {
378 buffer[pos + key2 as usize - offset as usize] ^= ((ret.0 >> 16) & 0xFF) as u8;
379 }
380 if (key1 as u64) >= offset && (key1 as u64) < offset + (count as u64) {
381 buffer[pos + key1 as usize - offset as usize] ^= ((ret.0 >> 8) & 0xFF) as u8;
382 }
383 for i in 0..count {
384 buffer[pos + i] ^= key3;
385 }
386 Ok(())
387 }
388}
389
390macro_rules! icx_enc_arc_impl {
391 ($t:ident) => {
392 impl ICxEncryption for Arc<$t> {
393 fn get_base_offset(&self, hash: u32) -> u32 {
394 self.as_ref().get_base_offset(hash)
395 }
396 fn inner_decrypt(
397 &self,
398 key: u32,
399 offset: u64,
400 buffer: &mut [u8],
401 pos: usize,
402 count: usize,
403 ) -> Result<()> {
404 self.as_ref().inner_decrypt(key, offset, buffer, pos, count)
405 }
406 fn decode(
407 &self,
408 key: u32,
409 offset: u64,
410 buffer: &mut [u8],
411 pos: usize,
412 count: usize,
413 ) -> Result<()> {
414 self.as_ref().decode(key, offset, buffer, pos, count)
415 }
416 }
417 };
418}
419
420macro_rules! icx_enc_impl {
421 ($t:ident) => {
422 impl ICxEncryption for $t {
423 fn get_base_offset(&self, hash: u32) -> u32 {
424 self.base.get_base_offset(hash)
425 }
426 fn inner_decrypt(
427 &self,
428 key: u32,
429 offset: u64,
430 buffer: &mut [u8],
431 pos: usize,
432 count: usize,
433 ) -> Result<()> {
434 self.base.inner_decrypt(key, offset, buffer, pos, count)
435 }
436 fn decode(
437 &self,
438 key: u32,
439 offset: u64,
440 buffer: &mut [u8],
441 pos: usize,
442 count: usize,
443 ) -> Result<()> {
444 self.base.decode(key, offset, buffer, pos, count)
445 }
446 }
447 };
448}
449
450icx_enc_arc_impl!(CxEncryption);
451
452impl Crypt for Arc<CxEncryption> {
453 base_schema_impl!();
454 fn decrypt_supported(&self) -> bool {
455 true
456 }
457 fn decrypt_seek_supported(&self) -> bool {
458 true
459 }
460 fn decrypt<'a>(
461 &self,
462 entry: &Xp3Entry,
463 cur_seg: &Segment,
464 stream: Box<dyn Read + Send + Sync + 'a>,
465 ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
466 let key = (
467 entry.file_hash,
468 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
469 );
470 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
471 }
472 fn decrypt_with_seek<'a>(
473 &self,
474 entry: &Xp3Entry,
475 cur_seg: &Segment,
476 stream: Box<dyn ReadSeek + Send + Sync + 'a>,
477 ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
478 let key = (
479 entry.file_hash,
480 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
481 );
482 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
483 }
484}
485
486const CX_PROGRAM_SIZE: usize = 0x80;
487
488#[derive(Debug)]
489struct CxProgram {
490 code: Vec<u32>,
491 control_block: Weak<Vec<u32>>,
492 length: usize,
493 seed: u32,
494}
495
496#[repr(u32)]
497#[derive(Debug, Clone, Copy, PartialEq, Eq, int_enum::IntEnum)]
498enum CxByteCode {
499 Nop,
500 Retn,
501 MovEdiArg,
502 PushEbx,
503 PopEbx,
504 PushEcx,
505 PopEcx,
506 MovEaxEbx,
507 MovEbxEax,
508 MovEcxEbx,
509 MovEaxControlBlock,
510 MovEaxEdi,
511 MovEaxIndirect,
512 AddEaxEbx,
513 SubEaxEbx,
514 ImulEaxEbx,
515 AndEcx0F,
516 ShrEbx1,
517 ShlEax1,
518 ShrEaxCl,
519 ShlEaxCl,
520 OrEaxEbx,
521 NotEax,
522 NegEax,
523 DecEax,
524 IncEax,
525 Immed = 0x100,
526 MovEaxImmed,
527 AndEbxImmed,
528 AndEaxImmed,
529 XorEaxImmed,
530 AddEaxImmed,
531 SubEaxImmed,
532}
533
534use CxByteCode::*;
535
536#[derive(Debug, Default)]
537struct Context {
538 eax: u32,
539 ebx: u32,
540 ecx: u32,
541 edi: u32,
542 stack: Vec<u32>,
543}
544
545#[derive(Debug)]
546struct CxProgramBuilder {}
547
548impl Default for CxProgramBuilder {
549 fn default() -> Self {
550 Self {}
551 }
552}
553
554trait ICxProgramBuilder: std::fmt::Debug {
555 fn build(&self, seed: u32, control_blocks: Weak<Vec<u32>>)
556 -> Box<dyn ICxProgram + Send + Sync>;
557}
558
559impl ICxProgramBuilder for CxProgramBuilder {
560 fn build(
561 &self,
562 seed: u32,
563 control_blocks: Weak<Vec<u32>>,
564 ) -> Box<dyn ICxProgram + Send + Sync> {
565 Box::new(CxProgram {
566 code: Vec::with_capacity(CX_PROGRAM_SIZE),
567 control_block: control_blocks,
568 length: 0,
569 seed,
570 })
571 }
572}
573
574trait ICxProgram: std::fmt::Debug {
575 fn execute(&self, hash: u32) -> Result<u32>;
576 fn clear(&mut self);
577 fn emit_nop(&mut self, count: usize) -> bool;
578 fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool;
579 fn emit_u32(&mut self, x: u32) -> bool;
580 fn emit_random(&mut self) -> bool {
581 let random = self.get_random();
582 self.emit_u32(random)
583 }
584 fn get_random(&mut self) -> u32;
585}
586
587impl ICxProgram for CxProgram {
588 fn execute(&self, hash: u32) -> Result<u32> {
589 let mut context = Context::default();
590 let mut iterator = self.code.iter();
591 let mut immed = 0u32;
592 while let Some(code) = iterator.next() {
593 let code = *code;
594 const IMMED: u32 = Immed as u32;
595 if IMMED == (code & IMMED) {
596 immed = *iterator.next().ok_or_else(|| {
597 anyhow::anyhow!("Incomplete IMMED bytecode in CxEncryption program")
598 })?;
599 }
600 let bytecode = CxByteCode::try_from(code).map_err(|_| {
601 anyhow::anyhow!("Invalid bytecode in CxEncryption program: {:#X}", code)
602 })?;
603 match bytecode {
604 Nop => {}
605 Immed => {}
606 MovEdiArg => {
607 context.edi = hash;
608 }
609 PushEbx => {
610 context.stack.push(context.ebx);
611 }
612 PopEbx => {
613 context.ebx = context.stack.pop().ok_or_else(|| {
614 anyhow::anyhow!("Stack underflow in CxEncryption program")
615 })?;
616 }
617 PushEcx => {
618 context.stack.push(context.ecx);
619 }
620 PopEcx => {
621 context.ecx = context.stack.pop().ok_or_else(|| {
622 anyhow::anyhow!("Stack underflow in CxEncryption program")
623 })?;
624 }
625 MovEbxEax => {
626 context.ebx = context.eax;
627 }
628 MovEaxEdi => {
629 context.eax = context.edi;
630 }
631 MovEcxEbx => {
632 context.ecx = context.ebx;
633 }
634 MovEaxEbx => {
635 context.eax = context.ebx;
636 }
637 AndEcx0F => {
638 context.ecx &= 0x0F;
639 }
640 ShrEbx1 => {
641 context.ebx >>= 1;
642 }
643 ShlEax1 => {
644 context.eax <<= 1;
645 }
646 ShrEaxCl => {
647 context.eax >>= context.ecx;
648 }
649 ShlEaxCl => {
650 context.eax <<= context.ecx;
651 }
652 OrEaxEbx => {
653 context.eax |= context.ebx;
654 }
655 NotEax => {
656 context.eax = !context.eax;
657 }
658 NegEax => {
659 context.eax = context.eax.wrapping_neg();
660 }
661 DecEax => {
662 context.eax = context.eax.wrapping_sub(1);
663 }
664 IncEax => {
665 context.eax = context.eax.wrapping_add(1);
666 }
667 AddEaxEbx => {
668 context.eax = context.eax.wrapping_add(context.ebx);
669 }
670 SubEaxEbx => {
671 context.eax = context.eax.wrapping_sub(context.ebx);
672 }
673 ImulEaxEbx => {
674 context.eax = context.eax.wrapping_mul(context.ebx);
675 }
676 AddEaxImmed => {
677 context.eax = context.eax.wrapping_add(immed);
678 }
679 SubEaxImmed => {
680 context.eax = context.eax.wrapping_sub(immed);
681 }
682 AndEbxImmed => {
683 context.ebx &= immed;
684 }
685 AndEaxImmed => {
686 context.eax &= immed;
687 }
688 XorEaxImmed => {
689 context.eax ^= immed;
690 }
691 MovEaxImmed => {
692 context.eax = immed;
693 }
694 MovEaxIndirect => {
695 let control_block = self
696 .control_block
697 .upgrade()
698 .ok_or_else(|| anyhow::anyhow!("Control block has been dropped"))?;
699 if context.eax as usize >= control_block.len() {
700 return Err(anyhow::anyhow!(
701 "Control block index out of bounds in CxEncryption program: {}",
702 context.eax
703 ));
704 }
705 context.eax = !control_block[context.eax as usize];
706 }
707 Retn => {
708 if context.stack.len() != 0 {
709 return Err(anyhow::anyhow!(
710 "Stack not empty at RETN in CxEncryption program"
711 ));
712 }
713 return Ok(context.eax);
714 }
715 _ => {
716 return Err(anyhow::anyhow!(
717 "Unsupported bytecode in CxEncryption program: {:?}",
718 bytecode
719 ));
720 }
721 }
722 }
723 Err(anyhow::anyhow!(
724 "CxEncryption program without RETN bytecode"
725 ))
726 }
727
728 fn clear(&mut self) {
729 self.length = 0;
730 self.code.clear();
731 }
732
733 fn emit_nop(&mut self, count: usize) -> bool {
734 if self.length + count > CX_PROGRAM_SIZE {
735 return false;
736 }
737 self.length += count;
738 return true;
739 }
740
741 fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
742 if self.length + length > CX_PROGRAM_SIZE {
743 return false;
744 }
745 self.code.push(bytecode as u32);
746 self.length += length;
747 return true;
748 }
749
750 fn emit_u32(&mut self, x: u32) -> bool {
751 if self.length + 4 > CX_PROGRAM_SIZE {
752 return false;
753 }
754 self.code.push(x);
755 self.length += 4;
756 return true;
757 }
758
759 fn get_random(&mut self) -> u32 {
760 let seed = self.seed;
761 self.seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
762 self.seed ^ (seed << 16) ^ (seed >> 16)
763 }
764}
765
766#[derive(msg_tool_macro::MyDebug)]
767struct CxEncryptionReader<'a, T> {
768 #[skip_fmt]
769 inner: T,
770 seg_start: u64,
771 seg_size: u64,
772 pos: u64,
773 key: (u32, Box<dyn ICxEncryption + Send + Sync + 'a>),
774}
775
776impl<'a, T: Read> CxEncryptionReader<'a, T> {
777 pub fn new(
778 inner: T,
779 seg: &Segment,
780 key: (u32, Box<dyn ICxEncryption + Send + Sync + 'a>),
781 ) -> Self {
782 Self {
783 inner,
784 seg_start: seg.offset_in_file,
785 seg_size: seg.original_size,
786 pos: 0,
787 key,
788 }
789 }
790}
791
792impl<'a, T: Read + Seek> Seek for CxEncryptionReader<'a, T> {
793 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
794 let new_pos: i64 = match pos {
795 SeekFrom::Start(offset) => offset as i64,
796 SeekFrom::End(offset) => self.seg_size as i64 + offset,
797 SeekFrom::Current(offset) => self.pos as i64 + offset,
798 };
799 let offset = new_pos - self.pos as i64;
800 if offset != 0 {
801 self.inner.seek(SeekFrom::Current(offset))?;
802 self.pos = new_pos as u64;
803 }
804 Ok(self.pos)
805 }
806}
807
808impl<'a, R: Read> Read for CxEncryptionReader<'a, R> {
809 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
810 let offset = self.seg_start + self.pos;
811 let count = self.inner.read(buf)?;
812 if count == 0 {
813 return Ok(0);
814 }
815 let key = self.key.0;
816 let cx = &self.key.1;
817 if let Err(e) = cx.inner_decrypt(key, offset, buf, 0, count) {
818 return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
819 }
820 self.pos += count as u64;
821 Ok(count)
822 }
823}
824
825#[derive(Debug)]
826pub struct SenrenCxCrypt {
827 base: CxEncryption,
828 names_section_id: String,
829}
830
831impl AsRef<BaseSchema> for SenrenCxCrypt {
832 fn as_ref(&self) -> &BaseSchema {
833 self.base.as_ref()
834 }
835}
836
837impl SenrenCxCrypt {
838 pub fn new(
839 base: BaseSchema,
840 schema: &CxSchema,
841 filename: &str,
842 names_section_id: String,
843 ) -> Result<Arc<Self>> {
844 Ok(Arc::new(Self::new_inner(
845 base,
846 schema,
847 filename,
848 Box::new(CxProgramBuilder::default()),
849 names_section_id,
850 )?))
851 }
852 fn new_inner(
853 base: BaseSchema,
854 schema: &CxSchema,
855 filename: &str,
856 program_builder: Box<dyn ICxProgramBuilder + Send + Sync>,
857 names_section_id: String,
858 ) -> Result<Self> {
859 let cx = CxEncryption::new_inner(base, schema, filename, program_builder)?;
860 Ok(Self {
861 base: cx,
862 names_section_id,
863 })
864 }
865
866 fn read_yuzu_names<'a>(
867 reader: Box<dyn ReadDebug + 'a>,
868 unpacked_size: u32,
869 ) -> Result<(HashMap<u32, String>, HashMap<String, String>)> {
870 let mut decoded = MemWriter::with_capacity(unpacked_size as usize);
871 {
872 let mut decoder = flate2::read::ZlibDecoder::new(reader);
873 std::io::copy(&mut decoder, &mut decoded)?;
874 }
875 let decoded = decoded.into_inner();
876 let mut reader = MemReader::new(decoded);
877 let mut hash_map = HashMap::new();
878 let mut md5_map = HashMap::new();
879 let mut dir_offset = 0u64;
880 while !reader.is_eof() {
881 let _entry_sign = reader.read_u32()?;
882 let mut entry_size = reader.read_u64()?;
883 dir_offset += 12 + entry_size;
884 let hash = reader.read_u32()?;
885 let name_len = reader.read_u16()?;
886 entry_size -= 6;
887 if (name_len as u64) * 2 <= entry_size {
888 let name = reader.read_exact_vec((name_len) as usize * 2)?;
889 let name = decode_to_string(Encoding::Utf16LE, &name, true)?;
890 if !hash_map.contains_key(&hash) {
891 hash_map.insert(hash, name.clone());
892 }
893 let encoded = encode_string(Encoding::Utf16LE, &name.to_ascii_lowercase(), true)?;
894 let md5 = format!("{:x}", md5::compute(encoded));
895 md5_map.insert(md5, name);
896 }
897 reader.pos = dir_offset as usize;
898 md5_map.insert("$".into(), "startup.tjs".into());
899 }
900 Ok((hash_map, md5_map))
901 }
902}
903
904fn read_yuzu_names<'a, T>(
905 archive: &mut Xp3Archive<'a>,
906 names_section_id: &str,
907 convert: T,
908) -> Result<()>
909where
910 T: FnOnce(
911 Box<dyn ReadDebug + 'a>,
912 u32,
913 ) -> Result<(HashMap<u32, String>, HashMap<String, String>)>,
914{
915 if let Some(section) = archive.extras.iter().find(|s| s.tag == names_section_id) {
916 let mut sreader = MemReaderRef::new(§ion.data);
917 let offset = sreader.read_u64()? + archive.base_offset;
918 let unpacked_size = sreader.read_u32()?;
919 let packed_size = sreader.read_u32()?;
920 let index_stream =
921 MutexWrapper::new(archive.inner.clone(), offset).take(packed_size as u64);
922 let (hash_map, md5_map) = convert(Box::new(index_stream), unpacked_size)?;
923 for entry in archive.entries.iter_mut() {
924 if let Some(name) = hash_map.get(&entry.file_hash) {
925 entry.name = name.clone();
926 } else if let Some(name) = md5_map.get(&entry.name) {
927 entry.name = name.clone();
928 }
929 }
930 }
931 archive.extras.retain(|s| s.tag != names_section_id);
932 Ok(())
933}
934
935icx_enc_impl!(SenrenCxCrypt);
936icx_enc_arc_impl!(SenrenCxCrypt);
937
938impl Crypt for Arc<SenrenCxCrypt> {
939 base_schema_impl!();
940 fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
941 default_init_crypt(archive)?;
942 read_yuzu_names(
943 archive,
944 &self.names_section_id,
945 SenrenCxCrypt::read_yuzu_names,
946 )
947 }
948 fn decrypt_supported(&self) -> bool {
949 true
950 }
951 fn decrypt_seek_supported(&self) -> bool {
952 true
953 }
954 fn decrypt<'a>(
955 &self,
956 entry: &Xp3Entry,
957 cur_seg: &Segment,
958 stream: Box<dyn Read + Send + Sync + 'a>,
959 ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
960 let key = (
961 entry.file_hash,
962 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
963 );
964 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
965 }
966 fn decrypt_with_seek<'a>(
967 &self,
968 entry: &Xp3Entry,
969 cur_seg: &Segment,
970 stream: Box<dyn ReadSeek + Send + Sync + 'a>,
971 ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
972 let key = (
973 entry.file_hash,
974 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
975 );
976 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
977 }
978}
979
980#[derive(Debug)]
981struct CxProgramNana {
982 base: CxProgram,
983 random_seed: u32,
984}
985
986impl CxProgramNana {
987 fn new(seed: u32, control_blocks: Weak<Vec<u32>>, random_seed: u32) -> Self {
988 Self {
989 base: CxProgram {
990 code: Vec::with_capacity(CX_PROGRAM_SIZE),
991 control_block: control_blocks,
992 length: 0,
993 seed,
994 },
995 random_seed,
996 }
997 }
998}
999
1000impl ICxProgram for CxProgramNana {
1001 fn execute(&self, hash: u32) -> Result<u32> {
1002 self.base.execute(hash)
1003 }
1004 fn clear(&mut self) {
1005 self.base.clear();
1006 }
1007 fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
1008 self.base.emit(bytecode, length)
1009 }
1010 fn emit_nop(&mut self, count: usize) -> bool {
1011 self.base.emit_nop(count)
1012 }
1013 fn emit_u32(&mut self, x: u32) -> bool {
1014 self.base.emit_u32(x)
1015 }
1016 fn get_random(&mut self) -> u32 {
1017 let mut s = self.base.seed ^ (self.base.seed << 17);
1018 s ^= (s << 18) | (s >> 15);
1019 self.base.seed = !s;
1020 let mut r = self.random_seed ^ (self.random_seed << 13);
1021 r ^= r >> 17;
1022 self.random_seed = r ^ (r << 5);
1023 self.base.seed ^ self.random_seed
1024 }
1025}
1026
1027#[derive(Debug)]
1028struct CxProgramNanaBuilder {
1029 random_seed: u32,
1030}
1031
1032impl CxProgramNanaBuilder {
1033 fn new(random_seed: u32) -> Self {
1034 Self { random_seed }
1035 }
1036}
1037
1038impl ICxProgramBuilder for CxProgramNanaBuilder {
1039 fn build(
1040 &self,
1041 seed: u32,
1042 control_blocks: Weak<Vec<u32>>,
1043 ) -> Box<dyn ICxProgram + Send + Sync> {
1044 Box::new(CxProgramNana::new(seed, control_blocks, self.random_seed))
1045 }
1046}
1047
1048#[derive(Debug)]
1049pub struct CabbageCxCrypt {
1050 base: SenrenCxCrypt,
1051}
1052
1053impl AsRef<BaseSchema> for CabbageCxCrypt {
1054 fn as_ref(&self) -> &BaseSchema {
1055 self.base.as_ref()
1056 }
1057}
1058
1059impl CabbageCxCrypt {
1060 pub fn new(
1061 base: BaseSchema,
1062 schema: &CxSchema,
1063 filename: &str,
1064 names_section_id: String,
1065 random_seed: u32,
1066 ) -> Result<Arc<Self>> {
1067 Ok(Arc::new(Self {
1068 base: SenrenCxCrypt::new_inner(
1069 base,
1070 schema,
1071 filename,
1072 Box::new(CxProgramNanaBuilder::new(random_seed)),
1073 names_section_id,
1074 )?,
1075 }))
1076 }
1077}
1078
1079icx_enc_impl!(CabbageCxCrypt);
1080icx_enc_arc_impl!(CabbageCxCrypt);
1081
1082impl Crypt for Arc<CabbageCxCrypt> {
1083 base_schema_impl!();
1084 fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
1085 default_init_crypt(archive)?;
1086 read_yuzu_names(
1087 archive,
1088 &self.base.names_section_id,
1089 SenrenCxCrypt::read_yuzu_names,
1090 )
1091 }
1092 fn decrypt_supported(&self) -> bool {
1093 true
1094 }
1095 fn decrypt_seek_supported(&self) -> bool {
1096 true
1097 }
1098 fn decrypt<'a>(
1099 &self,
1100 entry: &Xp3Entry,
1101 cur_seg: &Segment,
1102 stream: Box<dyn Read + Send + Sync + 'a>,
1103 ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1104 let key = (
1105 entry.file_hash,
1106 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1107 );
1108 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1109 }
1110 fn decrypt_with_seek<'a>(
1111 &self,
1112 entry: &Xp3Entry,
1113 cur_seg: &Segment,
1114 stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1115 ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1116 let key = (
1117 entry.file_hash,
1118 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1119 );
1120 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1121 }
1122}
1123
1124#[derive(Debug)]
1125struct NanaDecryptor {
1126 state: [u32; 27],
1127 seed: u64,
1128}
1129
1130impl NanaDecryptor {
1131 fn new(key: &[u32], seed1: u32, seed2: u32) -> Self {
1132 let mut state = [0u32; 27];
1133 let seed = (seed2 as u64) << 32 | (seed1 as u64);
1134 let mut s = [0u32; 3];
1135 let mut k = key[0];
1136 s[0] = key[1];
1137 s[1] = key[2];
1138 s[2] = key[3];
1139 state[0] = k;
1140 let mut dst = 1;
1141 for i in 0..26usize {
1142 let src = i % 3;
1143 let m = s[src].rotate_right(8);
1144 let n = (i as u32) ^ k.wrapping_add(m);
1145 k = n ^ k.rotate_left(3);
1146 state[dst] = k;
1147 dst += 1;
1148 s[src] = n;
1149 }
1150 Self { state, seed }
1151 }
1152
1153 fn decrypt(&self, data: &mut [u8]) {
1154 let mut i = 0;
1155 let mut offset = 0;
1156 let mut length = data.len();
1157 while length > 0 {
1158 offset += 1;
1159 let mut key = self.transform_key(offset ^ self.seed);
1160 let count = std::cmp::min(length, 8);
1161 for _ in 0..count {
1162 data[i] ^= (key & 0xFF) as u8;
1163 key >>= 8;
1164 i += 1;
1165 }
1166 length -= count;
1167 }
1168 }
1169
1170 fn transform_key(&self, key: u64) -> u64 {
1171 let mut lo = (key & 0xFFFFFFFF) as u32;
1172 let mut hi = (key >> 32) as u32;
1173 for i in 0..27 {
1174 hi = hi.rotate_right(8);
1175 hi = hi.wrapping_add(lo);
1176 hi ^= self.state[i];
1177 lo = lo.rotate_left(3);
1178 lo ^= hi;
1179 }
1180 (hi as u64) << 32 | (lo as u64)
1181 }
1182}
1183
1184#[derive(Debug)]
1185pub struct NanaCxCrypt {
1186 base: SenrenCxCrypt,
1187 decryptor: NanaDecryptor,
1188}
1189
1190impl AsRef<BaseSchema> for NanaCxCrypt {
1191 fn as_ref(&self) -> &BaseSchema {
1192 self.base.as_ref()
1193 }
1194}
1195
1196impl NanaCxCrypt {
1197 pub fn new(
1198 base: BaseSchema,
1199 schema: &CxSchema,
1200 filename: &str,
1201 names_section_id: String,
1202 random_seed: u32,
1203 yuz_key: &[u32],
1204 ) -> Result<Arc<Self>> {
1205 if yuz_key.len() != 6 {
1206 return Err(anyhow::anyhow!(
1207 "Invalid Yuzu keys for NanaCxCrypt: expected 6, got {}",
1208 yuz_key.len()
1209 ));
1210 }
1211 let cx = SenrenCxCrypt::new_inner(
1212 base,
1213 schema,
1214 filename,
1215 Box::new(CxProgramNanaBuilder::new(random_seed)),
1216 names_section_id,
1217 )?;
1218 let decryptor = NanaDecryptor::new(yuz_key, yuz_key[4], yuz_key[5]);
1219 Ok(Arc::new(Self {
1220 base: cx,
1221 decryptor,
1222 }))
1223 }
1224
1225 fn read_yuzu_names<'a>(
1226 &self,
1227 mut reader: Box<dyn ReadDebug + 'a>,
1228 unpacked_size: u32,
1229 ) -> Result<(HashMap<u32, String>, HashMap<String, String>)> {
1230 let mut prefix = Vec::with_capacity(0x100);
1231 (&mut reader).take(0x100).read_to_end(&mut prefix)?;
1232 self.decryptor.decrypt(&mut prefix);
1233 let reader = Box::new(PrefixStream::new(prefix, reader));
1234 SenrenCxCrypt::read_yuzu_names(reader, unpacked_size)
1235 }
1236}
1237
1238icx_enc_impl!(NanaCxCrypt);
1239icx_enc_arc_impl!(NanaCxCrypt);
1240
1241impl Crypt for Arc<NanaCxCrypt> {
1242 base_schema_impl!();
1243 fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
1244 default_init_crypt(archive)?;
1245 read_yuzu_names(
1246 archive,
1247 &self.base.names_section_id,
1248 |reader, unpacked_size| self.read_yuzu_names(reader, unpacked_size),
1249 )
1250 }
1251 fn decrypt_supported(&self) -> bool {
1252 true
1253 }
1254 fn decrypt_seek_supported(&self) -> bool {
1255 true
1256 }
1257 fn decrypt<'a>(
1258 &self,
1259 entry: &Xp3Entry,
1260 cur_seg: &Segment,
1261 stream: Box<dyn Read + Send + Sync + 'a>,
1262 ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1263 let key = (
1264 entry.file_hash,
1265 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1266 );
1267 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1268 }
1269 fn decrypt_with_seek<'a>(
1270 &self,
1271 entry: &Xp3Entry,
1272 cur_seg: &Segment,
1273 stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1274 ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1275 let key = (
1276 entry.file_hash,
1277 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1278 );
1279 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1280 }
1281}
1282
1283#[derive(Debug)]
1284struct YuzDecryptor {
1285 state: [u8; 64],
1286}
1287
1288impl YuzDecryptor {
1289 fn new(key1: &[u32], key2: &[u32], seed1: u32, seed2: u32) -> Self {
1290 let mut state = [0u8; 64];
1291 for i in 0..4 {
1292 state[i * 4..i * 4 + 4].copy_from_slice(&key2[i].to_le_bytes());
1293 }
1294 for i in 0..8 {
1295 state[i * 4 + 16..i * 4 + 20].copy_from_slice(&key1[i].to_le_bytes());
1296 }
1297 let t: u32 = !0;
1298 state[48..52].copy_from_slice(&t.to_le_bytes());
1299 state[52..56].copy_from_slice(&t.to_le_bytes());
1300 state[56..60].copy_from_slice(&(!seed1).to_le_bytes());
1301 state[60..64].copy_from_slice(&(!seed2).to_le_bytes());
1302 Self { state }
1303 }
1304
1305 fn decrypt(&self, data: &mut [u8]) {
1306 let mut state1 = [0u8; 64];
1307 let mut state2 = [0u8; 64];
1308 let mut i = 0;
1309 let mut offset: u64 = 0;
1310 let mut length = data.len();
1311 while length > 0 {
1312 state1.copy_from_slice(&self.state);
1313 state1[48..56].copy_from_slice(&(!offset).to_le_bytes());
1314 offset += 1;
1315 Self::transform_state(&state1, &mut state2, 8);
1316 let count = length.min(0x40);
1317 for j in 0..count {
1318 data[i] ^= state2[j];
1319 i += 1;
1320 }
1321 length -= count;
1322 }
1323 }
1324
1325 fn transform_state(state: &[u8], target: &mut [u8], length: usize) {
1326 let mut tmp = [0u32; 16];
1327 for i in 0..16 {
1328 tmp[i] = !u32::from_le_bytes([
1329 state[i * 4],
1330 state[i * 4 + 1],
1331 state[i * 4 + 2],
1332 state[i * 4 + 3],
1333 ]);
1334 }
1335 if length > 0 {
1336 for _ in 0..((length - 1) >> 1) + 1 {
1337 let mut t1 = w!(tmp[4] + tmp[0]);
1338 let mut t2 = (t1 ^ tmp[12]).rotate_left(16);
1339 let mut t3 = w!(t2 + tmp[8]);
1340 let mut t4 = (tmp[4] ^ t3).rotate_left(12);
1341 let mut t5 = w!(t4 + t1);
1342 let mut t6 = (t5 ^ t2).rotate_left(8);
1343 tmp[12] = t6;
1344 w!(t6 += t3);
1345 tmp[4] = (t4 ^ t6).rotate_left(7);
1346 t4 = (w!(tmp[5] + tmp[1]) ^ tmp[13]).rotate_left(16);
1347 t3 = (tmp[5] ^ w!(t4 + tmp[9])).rotate_left(12);
1348 t2 = w!(t3 + tmp[5] + tmp[1]);
1349 tmp[13] = (t2 ^ t4).rotate_left(8);
1350 w!(tmp[9] += tmp[13] + t4);
1351 tmp[5] = (t3 ^ tmp[9]).rotate_left(7);
1352 t4 = (w!(tmp[6] + tmp[2]) ^ tmp[14]).rotate_left(16);
1353 w!(tmp[10] += t4);
1354 t1 = (tmp[6] ^ tmp[10]).rotate_left(12);
1355 t3 = w!(t1 + tmp[6] + tmp[2]);
1356 tmp[14] = (t3 ^ t4).rotate_left(8);
1357 tmp[6] = (t1 ^ w!(tmp[14] + tmp[10])).rotate_left(7);
1358 w!(tmp[10] += tmp[14]);
1359 t4 = w!(tmp[7] + tmp[3]) ^ tmp[15];
1360 w!(tmp[3] += tmp[7]);
1361 t4 = t4.rotate_left(16);
1362 w!(tmp[11] += t4);
1363 t1 = (tmp[7] ^ tmp[11]).rotate_left(12);
1364 t4 ^= w!(t1 + tmp[3]);
1365 w!(tmp[3] += t1);
1366 t4 = t4.rotate_left(8);
1367 w!(tmp[11] += t4);
1368 t1 = (t1 ^ tmp[11]).rotate_left(7);
1369 w!(t5 += tmp[5]);
1370 w!(t2 += tmp[6]);
1371 t4 = (t5 ^ t4).rotate_left(16);
1372 w!(tmp[10] += t4);
1373 tmp[5] = (tmp[5] ^ tmp[10]).rotate_left(12);
1374 tmp[0] = w!(tmp[5] + t5);
1375 t4 = (tmp[0] ^ t4).rotate_left(8);
1376 tmp[15] = t4;
1377 w!(tmp[10] += t4);
1378 tmp[5] = (tmp[5] ^ tmp[10]).rotate_left(7);
1379 tmp[12] = (tmp[12] ^ t2).rotate_left(16);
1380 w!(tmp[11] += tmp[12]);
1381 t4 = (tmp[11] ^ tmp[6]).rotate_left(12);
1382 tmp[1] = w!(t4 + t2);
1383 tmp[12] = (tmp[12] ^ tmp[1]).rotate_left(8);
1384 w!(tmp[11] += tmp[12]);
1385 tmp[6] = (t4 ^ tmp[11]).rotate_left(7);
1386 w!(t3 += t1);
1387 t4 = (tmp[13] ^ t3).rotate_left(16);
1388 t2 = w!(t4 + t6);
1389 t1 = (t2 ^ t1).rotate_left(12);
1390 tmp[2] = w!(t1 + t3);
1391 tmp[13] = (t4 ^ tmp[2]).rotate_left(8);
1392 tmp[8] = w!(tmp[13] + t2);
1393 tmp[7] = (tmp[8] ^ t1).rotate_left(7);
1394 t6 = (tmp[14] ^ w!(tmp[4] + tmp[3])).rotate_left(16);
1395 t1 = (tmp[4] ^ w!(t6 + tmp[9])).rotate_left(12);
1396 w!(tmp[3] += t1 + tmp[4]);
1397 t3 = (t6 ^ tmp[3]).rotate_left(8);
1398 w!(tmp[9] += t3 + t6);
1399 tmp[4] = (t1 ^ tmp[9]).rotate_left(7);
1400 tmp[14] = t3;
1401 }
1402 }
1403 let mut pos = 0;
1404 for i in 0..16 {
1405 let d =
1406 !u32::from_le_bytes([state[pos], state[pos + 1], state[pos + 2], state[pos + 3]]);
1407 let d = w!(tmp[i] + d);
1408 target[pos..pos + 4].copy_from_slice(&d.to_le_bytes());
1409 pos += 4;
1410 }
1411 }
1412}
1413
1414#[derive(Debug)]
1415pub struct RiddleCxCrypt {
1416 base: SenrenCxCrypt,
1417 decryptor: YuzDecryptor,
1418 key1: u32,
1419 key2: u32,
1420}
1421
1422impl AsRef<BaseSchema> for RiddleCxCrypt {
1423 fn as_ref(&self) -> &BaseSchema {
1424 self.base.as_ref()
1425 }
1426}
1427
1428impl RiddleCxCrypt {
1429 pub fn new(
1430 base: BaseSchema,
1431 schema: &CxSchema,
1432 filename: &str,
1433 names_section_id: String,
1434 random_seed: u32,
1435 yuz_key: &[u32],
1436 key1: u32,
1437 key2: u32,
1438 ) -> Result<Arc<Self>> {
1439 if yuz_key.len() != 6 {
1440 return Err(anyhow::anyhow!(
1441 "Invalid Yuzu keys for RiddleCxCrypt: expected 6, got {}",
1442 yuz_key.len()
1443 ));
1444 }
1445 let cx = SenrenCxCrypt::new_inner(
1446 base,
1447 schema,
1448 filename,
1449 Box::new(CxProgramNanaBuilder::new(random_seed)),
1450 names_section_id,
1451 )?;
1452 let control_block = cx.base.control_block.as_ref();
1453 let decryptor = YuzDecryptor::new(&control_block, yuz_key, yuz_key[4], yuz_key[5]);
1454 Ok(Arc::new(Self {
1455 base: cx,
1456 decryptor,
1457 key1,
1458 key2,
1459 }))
1460 }
1461
1462 fn get_key_from_hash(&self, key: u32) -> u64 {
1463 let lo = key ^ self.key2;
1464 let mut hi = (key << 13) ^ key;
1465 hi ^= hi >> 17;
1466 hi ^= (hi << 5) ^ self.key1;
1467 ((hi as u64) << 32) | (lo as u64)
1468 }
1469
1470 fn read_yuzu_names<'a>(
1471 &self,
1472 mut reader: Box<dyn ReadDebug + 'a>,
1473 unpacked_size: u32,
1474 ) -> Result<(HashMap<u32, String>, HashMap<String, String>)> {
1475 let mut prefix = Vec::with_capacity(0x100);
1476 (&mut reader).take(0x100).read_to_end(&mut prefix)?;
1477 self.decryptor.decrypt(&mut prefix);
1478 let reader = Box::new(PrefixStream::new(prefix, reader));
1479 SenrenCxCrypt::read_yuzu_names(reader, unpacked_size)
1480 }
1481}
1482
1483impl ICxEncryption for RiddleCxCrypt {
1484 fn get_base_offset(&self, hash: u32) -> u32 {
1485 self.base.get_base_offset(hash)
1486 }
1487 fn inner_decrypt(
1488 &self,
1489 key: u32,
1490 offset: u64,
1491 buffer: &mut [u8],
1492 pos: usize,
1493 count: usize,
1494 ) -> Result<()> {
1495 if offset < 8 && count > 0 {
1496 let mut key = self.get_key_from_hash(key);
1497 key >>= offset << 3;
1498 let first_chunk = count.min(8 - offset as usize);
1499 for i in 0..first_chunk {
1500 buffer[pos + i] ^= (key & 0xFF) as u8;
1501 key >>= 8;
1502 }
1503 }
1504 self.base.inner_decrypt(key, offset, buffer, pos, count)
1505 }
1506 fn decode(
1507 &self,
1508 key: u32,
1509 offset: u64,
1510 buffer: &mut [u8],
1511 pos: usize,
1512 count: usize,
1513 ) -> Result<()> {
1514 self.base.decode(key, offset, buffer, pos, count)
1515 }
1516}
1517icx_enc_arc_impl!(RiddleCxCrypt);
1518
1519impl Crypt for Arc<RiddleCxCrypt> {
1520 base_schema_impl!();
1521 fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
1522 default_init_crypt(archive)?;
1523 read_yuzu_names(
1524 archive,
1525 &self.base.names_section_id,
1526 |reader, unpacked_size| self.read_yuzu_names(reader, unpacked_size),
1527 )
1528 }
1529 fn decrypt_supported(&self) -> bool {
1530 true
1531 }
1532 fn decrypt_seek_supported(&self) -> bool {
1533 true
1534 }
1535 fn decrypt<'a>(
1536 &self,
1537 entry: &Xp3Entry,
1538 cur_seg: &Segment,
1539 stream: Box<dyn Read + Send + Sync + 'a>,
1540 ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
1541 let key = (
1542 entry.file_hash,
1543 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1544 );
1545 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1546 }
1547 fn decrypt_with_seek<'a>(
1548 &self,
1549 entry: &Xp3Entry,
1550 cur_seg: &Segment,
1551 stream: Box<dyn ReadSeek + Send + Sync + 'a>,
1552 ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
1553 let key = (
1554 entry.file_hash,
1555 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1556 );
1557 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1558 }
1559}
1560
1561#[derive(Debug)]
1562pub struct HxCryptLite {
1563 base: CxEncryption,
1564 header_key: Option<Vec<u8>>,
1565 header_split_position: u64,
1566 file_crypt_flag: bool,
1567}
1568
1569impl HxCryptLite {
1570 pub fn new(
1571 base: BaseSchema,
1572 schema: &CxSchema,
1573 filename: &str,
1574 header_key: Option<Vec<u8>>,
1575 header_split_position: u64,
1576 file_crypt_flag: bool,
1577 random_type: i32,
1578 ) -> Result<Arc<Self>> {
1579 if let Some(key) = header_key.as_ref() {
1580 if key.len() < 8 {
1581 anyhow::bail!("header_key is too small.");
1582 }
1583 }
1584 Ok(Arc::new(Self {
1585 base: CxEncryption::new_inner(
1586 base,
1587 schema,
1588 filename,
1589 Box::new(HxProgramLiteBuilder::new(random_type)),
1590 )?,
1591 header_key,
1592 header_split_position,
1593 file_crypt_flag,
1594 }))
1595 }
1596}
1597
1598impl AsRef<BaseSchema> for HxCryptLite {
1599 fn as_ref(&self) -> &BaseSchema {
1600 self.base.as_ref()
1601 }
1602}
1603
1604#[derive(Debug)]
1605struct HxProgramLite {
1606 base: CxProgram,
1607 random_type: i32,
1608 random_block: [u32; 0x270],
1609 block_position: usize,
1610}
1611
1612impl HxProgramLite {
1613 pub fn new(seed: u32, control_block: Weak<Vec<u32>>, random_method: i32) -> Self {
1614 let block_position = 0x270;
1615 let mut block = [0; 0x270];
1616 block[0] = seed;
1617 for i in 1..0x270 {
1618 block[i] =
1619 ((block[i - 1] ^ (block[i - 1] >> 0x1E)) * 0x6C078965).wrapping_add(i as u32);
1620 }
1621 Self {
1622 base: CxProgram {
1623 code: Vec::with_capacity(CX_PROGRAM_SIZE),
1624 control_block,
1625 length: 0,
1626 seed,
1627 },
1628 random_type: random_method,
1629 random_block: block,
1630 block_position,
1631 }
1632 }
1633
1634 fn get_random_new(&mut self) -> u32 {
1635 if self.block_position == 0x270 {
1636 self.transform_block();
1637 }
1638 let s0 = self.random_block[self.block_position];
1639 let s1 = (s0 >> 11) ^ s0;
1640 let s2 = ((s1 & 0xFF3A58AD) << 7) ^ s1;
1641 let s3 = ((s2 & 0xFFFFDF8C) << 15) ^ s2;
1642 let s4 = (s3 >> 18) ^ s3;
1643 self.block_position += 1;
1644 s4
1645 }
1646
1647 fn transform_block(&mut self) {
1648 self.block_position = 0;
1649 let block = &mut self.random_block;
1650 for i in 0..0xE3 {
1652 let s0 = if (block[i + 1] & 1) != 0 {
1653 0x9908B0DFu32
1654 } else {
1655 0
1656 };
1657 let s1 = (((block[i] ^ block[i + 1]) & 0x7FFFFFFE) ^ block[i]) >> 1;
1658 let s2 = s0 ^ s1 ^ block[i + 0x18D];
1659 block[i] = s2;
1660 }
1661 for i in 0..0x18C {
1663 let s0 = if (block[i + 1 + 0xE3] & 1) != 0 {
1664 0x9908B0DFu32
1665 } else {
1666 0
1667 };
1668 let s1 =
1669 (((block[i + 0xE3] ^ block[i + 1 + 0xE3]) & 0x7FFFFFFE) ^ block[i + 0xE3]) >> 1;
1670 let s2 = s0 ^ s1 ^ block[i];
1671 block[i + 0xE3] = s2;
1672 }
1673 let s0 = if (block[0] & 1) != 0 {
1675 0x9908B0DFu32
1676 } else {
1677 0
1678 };
1679 let s1 = (((block[0x26F] ^ block[0]) & 0x7FFFFFFE) ^ block[0x26F]) >> 1;
1680 let s2 = s0 ^ s1 ^ block[0x18C];
1681 block[0x26F] = s2;
1682 }
1683}
1684
1685impl ICxProgram for HxProgramLite {
1686 fn execute(&self, hash: u32) -> Result<u32> {
1687 self.base.execute(hash)
1688 }
1689 fn clear(&mut self) {
1690 self.base.clear();
1691 }
1692 fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
1693 self.base.emit(bytecode, length)
1694 }
1695 fn emit_nop(&mut self, count: usize) -> bool {
1696 self.base.emit_nop(count)
1697 }
1698 fn emit_u32(&mut self, x: u32) -> bool {
1699 self.base.emit_u32(x)
1700 }
1701 fn get_random(&mut self) -> u32 {
1702 if self.random_type == 0 {
1703 self.base.get_random()
1704 } else {
1705 self.get_random_new()
1706 }
1707 }
1708}
1709
1710#[derive(Debug)]
1711struct HxProgramLiteBuilder {
1712 random_method: i32,
1713}
1714
1715impl HxProgramLiteBuilder {
1716 pub fn new(random_method: i32) -> Self {
1717 Self { random_method }
1718 }
1719}
1720
1721impl ICxProgramBuilder for HxProgramLiteBuilder {
1722 fn build(
1723 &self,
1724 seed: u32,
1725 control_blocks: Weak<Vec<u32>>,
1726 ) -> Box<dyn ICxProgram + Send + Sync> {
1727 Box::new(HxProgramLite::new(seed, control_blocks, self.random_method))
1728 }
1729}
1730
1731struct HxFileDecryptor {
1732 split_pos1: u64,
1733 split_pos2: u64,
1734 key: u32,
1735 key1: u8,
1736 key2: u8,
1737}
1738
1739impl HxFileDecryptor {
1740 fn new(key: u64, file_key_flag: bool) -> Self {
1741 let key_ptr = key.to_le_bytes();
1742 let mut global_key = key_ptr[0] as u32;
1743 let mut key1 = key_ptr[1];
1744 let mut key2 = key_ptr[2];
1745 let split_pos1 = u16::from_le_bytes([key_ptr[6], key_ptr[7]]) as u64;
1746 let mut split_pos2 = u16::from_le_bytes([key_ptr[4], key_ptr[5]]) as u64;
1747 if split_pos1 == split_pos2 {
1748 split_pos2 += 1;
1749 }
1750 if global_key == 0 {
1751 global_key = 1;
1752 }
1753 global_key = global_key.wrapping_mul(0x01010101);
1754 if file_key_flag {
1755 key1 = 0;
1756 key2 = 0;
1757 }
1758 Self {
1759 split_pos1,
1760 split_pos2,
1761 key: global_key,
1762 key1,
1763 key2,
1764 }
1765 }
1766
1767 fn decrypt(&self, data: &mut [u8], offset: u64, pos: usize, count: usize) {
1768 if count == 0 {
1769 return;
1770 }
1771 let key = self.key.to_le_bytes();
1772 let mut key_pos = (offset & 3) as usize;
1773 for i in 0..count {
1774 data[pos + i] ^= key[key_pos];
1775 key_pos = (key_pos + 1) & 3;
1776 }
1777 let count = count as u64;
1778 if self.split_pos1 >= offset && self.split_pos1 < offset + count {
1779 data[(self.split_pos1 - offset) as usize + pos] ^= self.key1;
1780 }
1781 if self.split_pos2 >= offset && self.split_pos2 < offset + count {
1782 data[(self.split_pos2 - offset) as usize + pos] ^= self.key2;
1783 }
1784 }
1785}
1786
1787struct HxHeaderDecryptor {
1788 key: [u8; 8],
1789 pos: u64,
1790}
1791
1792impl HxHeaderDecryptor {
1793 fn new(hash: u32, key: &[u8], pos: u64) -> Self {
1794 let key_ptr = [
1795 u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
1796 u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
1797 ];
1798 let s0 = hash ^ key_ptr[1];
1799 let s1 = hash ^ (hash << 13);
1800 let s2 = s1 ^ (s1 >> 17);
1801 let s3 = s2 ^ (s2 << 5) ^ key_ptr[0];
1802 let key = ((s3 as u64) << 32) | (s0 as u64);
1803 Self {
1804 key: key.to_le_bytes(),
1805 pos,
1806 }
1807 }
1808
1809 fn decrypt(&self, data: &mut [u8], offset: u64, pos: usize, count: usize) {
1810 let mut start_pos = offset;
1811 if start_pos <= self.pos {
1812 start_pos = self.pos;
1813 }
1814 let mut end_pos = offset + count as u64;
1815 if end_pos >= self.pos + 8 {
1816 end_pos = self.pos + 8;
1817 }
1818 if start_pos >= end_pos {
1819 return;
1820 }
1821 let dlen = end_pos - start_pos;
1822 let key_start_index = start_pos - self.pos;
1823 let data_start_index = start_pos - offset + pos as u64;
1824 for i in 0..dlen {
1825 data[(data_start_index + i) as usize] ^= self.key[(key_start_index + i) as usize];
1826 }
1827 }
1828}
1829
1830impl ICxEncryption for HxCryptLite {
1831 fn get_base_offset(&self, _hash: u32) -> u32 {
1832 _hash
1833 }
1834 fn inner_decrypt(
1835 &self,
1836 hash: u32,
1837 offset: u64,
1838 buffer: &mut [u8],
1839 pos: usize,
1840 count: usize,
1841 ) -> Result<()> {
1842 if let Some(key) = self.header_key.as_ref() {
1843 let dec = HxHeaderDecryptor::new(hash, &key, self.header_split_position);
1844 dec.decrypt(buffer, offset, pos, count);
1845 }
1846 let ret1 = self.base.execute_xcode(hash)?;
1847 let ret2 = self.base.execute_xcode(hash ^ (hash >> 16))?;
1848 let key1 = ((ret1.1 as u64) << 32) | (ret1.0 as u64);
1849 let key2 = ((ret2.1 as u64) << 32) | (ret2.0 as u64);
1850 let split_pos = (self.base.offset + (hash & self.base.mask)) as u64;
1851 let dec1 = HxFileDecryptor::new(key1, self.file_crypt_flag);
1852 let dec2 = HxFileDecryptor::new(key2, self.file_crypt_flag);
1853 if split_pos > offset {
1854 if split_pos < offset + count as u64 {
1855 let blen1 = split_pos - offset;
1856 let blen2 = offset + count as u64 - split_pos;
1857 dec1.decrypt(buffer, offset, pos, blen1 as usize);
1858 dec2.decrypt(buffer, offset + blen1, pos + blen1 as usize, blen2 as usize);
1859 } else {
1860 dec1.decrypt(buffer, offset, pos, count);
1861 }
1862 } else {
1863 dec2.decrypt(buffer, offset, pos, count);
1864 }
1865 Ok(())
1866 }
1867 fn decode(
1868 &self,
1869 _key: u32,
1870 _offset: u64,
1871 _buffer: &mut [u8],
1872 _pos: usize,
1873 _count: usize,
1874 ) -> Result<()> {
1875 Ok(())
1876 }
1877}
1878
1879icx_enc_arc_impl!(HxCryptLite);
1880
1881impl Crypt for Arc<HxCryptLite> {
1882 base_schema_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 let key = (
1896 entry.file_hash,
1897 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1898 );
1899 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
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 let key = (
1908 entry.file_hash,
1909 Box::new(self.clone()) as Box<dyn ICxEncryption + Send + Sync + 'a>,
1910 );
1911 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
1912 }
1913}
1914
1915#[derive(Copy, Clone, PartialEq, Eq, Hash)]
1916struct FileHash([u8; 32]);
1917
1918impl std::fmt::Debug for FileHash {
1919 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1920 write!(f, "FileHash(")?;
1921 write!(f, "{}", hex::encode(self.0))?;
1922 write!(f, ")")
1923 }
1924}
1925
1926impl std::fmt::Display for FileHash {
1927 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1928 write!(f, "{}", hex::encode(self.0))
1929 }
1930}
1931
1932impl<'a> TryFrom<&'a [u8]> for FileHash {
1933 type Error = anyhow::Error;
1934 fn try_from(value: &'a [u8]) -> Result<Self> {
1935 Ok(Self(value.try_into()?))
1936 }
1937}
1938
1939impl<'a> TryFrom<&'a str> for FileHash {
1940 type Error = anyhow::Error;
1941 fn try_from(value: &'a str) -> Result<Self> {
1942 Self::try_from(hex::decode(value)?.as_slice())
1943 }
1944}
1945
1946impl<'de> Deserialize<'de> for FileHash {
1947 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1948 where
1949 D: Deserializer<'de>,
1950 {
1951 let s = String::deserialize(deserializer)?;
1952 let bytes = hex::decode(&s).map_err(de::Error::custom)?;
1953 let arr = bytes.try_into().map_err(|bytes: Vec<u8>| {
1954 de::Error::custom(format!(
1955 "FileHash length mismatch: expected 32 bytes, got {}",
1956 bytes.len()
1957 ))
1958 })?;
1959 Ok(FileHash(arr))
1960 }
1961}
1962
1963impl Serialize for FileHash {
1964 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1965 where
1966 S: Serializer,
1967 {
1968 serializer.serialize_str(&hex::encode(self.0))
1969 }
1970}
1971
1972impl FileHash {
1973 fn to_string(&self) -> String {
1974 hex::encode(&self.0)
1975 }
1976}
1977
1978#[derive(Copy, Clone, PartialEq, Eq, Hash)]
1979struct PathHash(u64);
1980
1981impl std::fmt::Debug for PathHash {
1982 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1983 write!(f, "PathHash({:#x})", self.0)
1984 }
1985}
1986
1987impl std::fmt::Display for PathHash {
1988 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1989 write!(f, "{}", hex::encode(&self.0.to_be_bytes()))
1990 }
1991}
1992
1993impl<'de> Deserialize<'de> for PathHash {
1994 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1995 where
1996 D: Deserializer<'de>,
1997 {
1998 let s = String::deserialize(deserializer)?;
1999 let bytes = hex::decode(&s).map_err(de::Error::custom)?;
2000 let arr: [u8; 8] = bytes.try_into().map_err(|bytes: Vec<u8>| {
2001 de::Error::custom(format!(
2002 "PathHash length mismatch: expected 8 bytes, got {}",
2003 bytes.len()
2004 ))
2005 })?;
2006 Ok(PathHash(u64::from_be_bytes(arr)))
2007 }
2008}
2009
2010impl Serialize for PathHash {
2011 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2012 where
2013 S: Serializer,
2014 {
2015 serializer.serialize_str(&hex::encode(self.0.to_be_bytes()))
2016 }
2017}
2018
2019impl<'a> TryFrom<&'a [u8]> for PathHash {
2020 type Error = anyhow::Error;
2021 fn try_from(value: &'a [u8]) -> Result<Self> {
2022 let arr: [u8; 8] = value.try_into()?;
2023 Ok(PathHash(u64::from_be_bytes(arr)))
2024 }
2025}
2026
2027impl<'a> TryFrom<&'a str> for PathHash {
2028 type Error = anyhow::Error;
2029 fn try_from(value: &'a str) -> Result<Self> {
2030 Self::try_from(hex::decode(value)?.as_slice())
2031 }
2032}
2033
2034impl PathHash {
2035 fn to_string(&self) -> String {
2036 hex::encode(&self.0.to_be_bytes())
2037 }
2038}
2039
2040#[derive(Clone, Debug, Deserialize, Serialize)]
2041#[serde(rename_all = "camelCase")]
2042struct KeyData {
2043 boot_strap: String,
2044 warning: String,
2045 #[serde(with = "hex_vec")]
2046 params: Vec<u8>,
2047 archive_unique_key: String,
2048 #[serde(rename = "seed", with = "hex_vec_optional", default)]
2049 upper_key: Option<Vec<u8>>,
2050}
2051
2052mod hex_vec {
2053 use serde::{Deserialize, Deserializer, Serializer};
2054 pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
2055 where
2056 S: Serializer,
2057 {
2058 serializer.serialize_str(&hex::encode(bytes))
2059 }
2060 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
2061 where
2062 D: Deserializer<'de>,
2063 {
2064 let s = String::deserialize(deserializer)?;
2065 hex::decode(s).map_err(serde::de::Error::custom)
2066 }
2067}
2068
2069mod hex_vec_optional {
2070 use super::hex_vec;
2071 use serde::{Deserialize, Deserializer, Serializer};
2072 pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
2073 where
2074 S: Serializer,
2075 {
2076 match bytes {
2077 Some(b) => hex_vec::serialize(b, serializer),
2078 None => serializer.serialize_none(),
2079 }
2080 }
2081 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
2082 where
2083 D: Deserializer<'de>,
2084 {
2085 let s: Option<String> = Option::deserialize(deserializer)?;
2086 match s {
2087 Some(hex_str) => hex::decode(hex_str)
2088 .map(Some)
2089 .map_err(serde::de::Error::custom),
2090 None => Ok(None),
2091 }
2092 }
2093}
2094
2095#[derive(Clone, Debug, Deserialize, Serialize)]
2096pub struct KeyPackage {
2097 #[allow(unused)]
2098 description: String,
2099 key: KeyData,
2100 sku: String,
2101}
2102
2103#[derive(Clone, Deserialize, Serialize, msg_tool_macro::Default)]
2104#[serde(rename_all = "camelCase")]
2105struct CxdecDb {
2106 #[allow(unused)]
2107 #[default("xp3hnp".into())]
2108 file_hash_salt: String,
2109 file_list: HashMap<String, HashMap<PathHash, HashMap<FileHash, Option<String>>>>,
2111 #[serde(default)]
2112 key_packages: Vec<KeyPackage>,
2113 #[allow(unused)]
2114 #[default("xp3hnp".into())]
2115 path_hash_salt: String,
2116 path_mapping: HashMap<PathHash, Option<String>>,
2117 project_name: String,
2118}
2119
2120impl std::fmt::Debug for CxdecDb {
2121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2122 f.debug_struct("CxdecDb").finish_non_exhaustive()
2123 }
2124}
2125
2126#[derive(Debug)]
2127pub struct HxCrypt {
2128 base: CxEncryption,
2129 key1: IndexKey,
2130 key2: IndexKeys,
2131 filter_key: AtomicU64,
2132 file_mapping: Arc<HashMap<FileHash, String>>,
2133 path_mapping: Arc<HashMap<PathHash, String>>,
2134 info_map: Mutex<HashMap<String, HxEntry>>,
2135 file_hash: FileHashOption,
2136 path_hash: PathHashOption,
2137 dump_file_hash: Option<String>,
2138 filename: String,
2139}
2140
2141#[derive(Clone)]
2142pub struct IndexKey {
2143 key: [u8; 32],
2144 nonce: [u8; 16],
2145 filter_key: Option<u64>,
2146}
2147
2148impl std::fmt::Debug for IndexKey {
2149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2150 f.debug_struct("IndexKey")
2151 .field("key", &hex::encode(&self.key))
2152 .field("nonce", &hex::encode(&self.nonce))
2153 .field("filter_key", &self.filter_key)
2154 .finish()
2155 }
2156}
2157
2158#[derive(Deserialize)]
2159#[serde(rename_all = "PascalCase")]
2160struct IndexKeyTmp {
2161 key: String,
2162 nonce: String,
2163 #[serde(default)]
2164 filter_key: Option<u64>,
2165}
2166
2167impl<'de> Deserialize<'de> for IndexKey {
2168 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2169 where
2170 D: Deserializer<'de>,
2171 {
2172 use base64::{Engine, engine::general_purpose::STANDARD};
2173 let s = IndexKeyTmp::deserialize(deserializer)?;
2174 let bytes = STANDARD.decode(&s.key).map_err(de::Error::custom)?;
2175 let key: [u8; 32] = bytes.try_into().map_err(|bytes: Vec<u8>| {
2176 de::Error::custom(format!(
2177 "Index key length mismatch: expected 32 bytes, got {}",
2178 bytes.len()
2179 ))
2180 })?;
2181 let hbytes = STANDARD.decode(&s.nonce).map_err(de::Error::custom)?;
2182 let nonce: [u8; 16] = hbytes.try_into().map_err(|bytes: Vec<u8>| {
2183 de::Error::custom(format!(
2184 "Index key nonce length mismatch: expected 16 bytes, got {}",
2185 bytes.len()
2186 ))
2187 })?;
2188 Ok(Self {
2189 key,
2190 nonce,
2191 filter_key: s.filter_key,
2192 })
2193 }
2194}
2195
2196#[derive(Clone, Debug)]
2197pub struct IndexKeys(pub Vec<IndexKey>);
2198
2199impl Deref for IndexKeys {
2200 type Target = Vec<IndexKey>;
2201 fn deref(&self) -> &Self::Target {
2202 &self.0
2203 }
2204}
2205
2206impl DerefMut for IndexKeys {
2207 fn deref_mut(&mut self) -> &mut Self::Target {
2208 &mut self.0
2209 }
2210}
2211
2212#[derive(Deserialize)]
2213#[serde(untagged)]
2214enum IndexKeysTmp {
2215 List(Vec<IndexKey>),
2216 Single(IndexKey),
2217}
2218
2219impl<'de> Deserialize<'de> for IndexKeys {
2220 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2221 where
2222 D: Deserializer<'de>,
2223 {
2224 let tmp = IndexKeysTmp::deserialize(deserializer)?;
2225 Ok(match tmp {
2226 IndexKeysTmp::List(list) => Self(list),
2227 IndexKeysTmp::Single(one) => Self(vec![one]),
2228 })
2229 }
2230}
2231
2232impl HxCrypt {
2233 pub fn new(
2234 base: BaseSchema,
2235 cx: &CxSchema,
2236 index_key1: &IndexKey,
2237 index_key2: &IndexKeys,
2238 filter_key: u64,
2239 random_type: i32,
2240 file_list_name: Option<&str>,
2241 file_list_path: Option<&str>,
2242 filename: &str,
2243 config: &ExtraConfig,
2244 ) -> Result<Self> {
2245 let p = std::path::Path::new(filename);
2246 let b = p
2247 .file_name()
2248 .ok_or_else(|| anyhow::anyhow!("Failed to get file name from path."))?;
2249 let s: &str = &b.to_string_lossy();
2250 let (file_map, mut path_map) = if let Some(path) = file_list_path {
2251 let data = std::fs::read(path)?;
2252 let data = decode_to_string(Encoding::Utf8, &data, true)?;
2253 Self::read_names(&data, s)?
2254 } else if let Some(name) = file_list_name {
2255 let flist = query_filename_list(name)?;
2256 Self::read_names(&flist, s)?
2257 } else {
2258 let pdir = p.parent().map(|s| s.to_owned()).unwrap_or_default();
2259 if let Some(k) = Self::try_default_name(&pdir.join("filelist.json"), s)? {
2260 k
2261 } else if let Some(k) = Self::try_default_name(&pdir.join("filelist.lst"), s)? {
2262 k
2263 } else {
2264 (HashMap::new(), HashMap::new())
2265 }
2266 };
2267 let default_path_hash = calculate_path_hash("", "xp3hnp");
2268 if !path_map.contains_key(&default_path_hash) {
2269 path_map.insert(default_path_hash, String::new());
2270 }
2271 Ok(Self {
2272 base: CxEncryption::new_inner(
2273 base,
2274 cx,
2275 filename,
2276 Box::new(HxProgramBuilder::new(random_type)),
2277 )?,
2278 key1: index_key1.clone(),
2279 key2: index_key2.clone(),
2280 filter_key: AtomicU64::new(filter_key),
2281 file_mapping: Arc::new(file_map),
2282 path_mapping: Arc::new(path_map),
2283 info_map: Mutex::new(HashMap::new()),
2284 file_hash: config.xp3_cxdec_file_hash,
2285 path_hash: config.xp3_cxdec_path_hash,
2286 dump_file_hash: config.xp3_dump_file_hash_list.clone(),
2287 filename: filename.to_owned(),
2288 })
2289 }
2290
2291 fn new_inner(
2292 base: BaseSchema,
2293 cx: &CxSchema,
2294 index_key1: IndexKey,
2295 index_key2: IndexKeys,
2296 filter_key: u64,
2297 random_type: i32,
2298 config: &ExtraConfig,
2299 file_mapping: Arc<HashMap<FileHash, String>>,
2300 path_mapping: Arc<HashMap<PathHash, String>>,
2301 control_block: Vec<u32>,
2302 filename: &str,
2303 ) -> Result<Self> {
2304 Ok(Self {
2305 base: CxEncryption::new_inner2(
2306 base,
2307 cx,
2308 Box::new(HxProgramBuilder::new(random_type)),
2309 control_block,
2310 )?,
2311 key1: index_key1,
2312 key2: index_key2,
2313 filter_key: AtomicU64::new(filter_key),
2314 file_mapping,
2315 path_mapping,
2316 info_map: Mutex::new(HashMap::new()),
2317 file_hash: config.xp3_cxdec_file_hash,
2318 path_hash: config.xp3_cxdec_path_hash,
2319 dump_file_hash: config.xp3_dump_file_hash_list.clone(),
2320 filename: filename.to_owned(),
2321 })
2322 }
2323
2324 fn try_default_name<P: AsRef<std::path::Path>>(
2325 s: &P,
2326 b: &str,
2327 ) -> Result<Option<(HashMap<FileHash, String>, HashMap<PathHash, String>)>> {
2328 let n = match get_ignorecase_path(s) {
2329 Ok(s) => s,
2330 Err(_) => return Ok(None),
2331 };
2332 if !n.exists() {
2333 return Ok(None);
2334 }
2335 let s = std::fs::read(&n)?;
2336 let data = decode_to_string(Encoding::Utf8, &s, true)?;
2337 let names = Self::read_names(&data, b)?;
2338 eprintln!(
2339 "Read {} file entries and {} directory entries from filelist {}.",
2340 names.0.len(),
2341 names.1.len(),
2342 n.display()
2343 );
2344 Ok(Some(names))
2345 }
2346
2347 fn read_names(
2348 s: &str,
2349 basename: &str,
2350 ) -> Result<(HashMap<FileHash, String>, HashMap<PathHash, String>)> {
2351 if let Ok(s) = serde_json::from_str::<CxdecDb>(&s) {
2352 let path_map: HashMap<_, _> = s
2353 .path_mapping
2354 .iter()
2355 .filter_map(|(k, v)| match v {
2356 Some(v) => Some((k.clone(), v.clone())),
2357 None => None,
2358 })
2359 .collect();
2360 let file_map: HashMap<_, _> = if let Some(s) = s.file_list.get(basename) {
2361 s.iter()
2362 .map(|s| s.1)
2363 .flatten()
2364 .filter_map(|(k, v)| match v {
2365 Some(v) => Some((k.clone(), v.clone())),
2366 None => None,
2367 })
2368 .collect()
2369 } else {
2370 HashMap::new()
2371 };
2372 return Ok((file_map, path_map));
2373 }
2374 let mut file_map = HashMap::new();
2375 let mut path_map = HashMap::new();
2376 for line in s.lines() {
2377 let line = line.trim();
2378 if line.is_empty() {
2379 continue;
2380 }
2381 let mut iter = line.splitn(2, ':');
2382 let key = match iter.next() {
2383 Some(v) => v,
2384 None => continue,
2385 };
2386 let value = match iter.next() {
2387 Some(v) => v,
2388 None => continue,
2389 };
2390 if key.len() == 16 {
2391 let key = PathHash::try_from(key)?;
2392 path_map.insert(key, value.to_string());
2393 } else if key.len() == 64 {
2394 let key = FileHash::try_from(key)?;
2395 file_map.insert(key, value.to_string());
2396 }
2397 }
2398 Ok((file_map, path_map))
2399 }
2400
2401 fn create_chacha20_crypt(&self, flags: u16, key_index: usize) -> Result<ChaCha20Legacy> {
2402 use chacha20::{KeyIvInit, cipher::StreamCipherSeek};
2403 let key = match flags {
2404 0 => self
2405 .key2
2406 .get(key_index)
2407 .ok_or_else(|| anyhow::anyhow!("Index out of bound"))?,
2408 1 => &self.key1,
2409 _ => anyhow::bail!("Unknown hxv4 flags: {}", flags),
2410 };
2411 if let Some(filter_key) = &key.filter_key {
2412 self.filter_key.qsave(*filter_key);
2413 }
2414 let mut nonce = [0; 8];
2415 nonce.copy_from_slice(&key.nonce[..8]);
2416 let mut crypt = ChaCha20Legacy::new((&key.key).into(), (&nonce).into());
2417 crypt.try_seek(64)?;
2418 Ok(crypt)
2419 }
2420
2421 fn read_index_stream_internel<T: Read + Seek>(
2422 &self,
2423 stream: &mut T,
2424 flags: u16,
2425 key_index: usize,
2426 ) -> Result<MemReader> {
2427 use chacha20::cipher::StreamCipher;
2428 stream.rewind()?;
2429 let len = stream.stream_length()?;
2430 let mut crypt = self.create_chacha20_crypt(flags, key_index)?;
2431 let tlen = len as usize - 16;
2432 let mut buf = Vec::with_capacity(tlen);
2433 stream.seek(SeekFrom::Start(16))?;
2434 stream.read_to_end(&mut buf)?;
2435 crypt.try_apply_keystream(&mut buf)?;
2436 let mut stream = flate2::read::ZlibDecoder::new(MemReaderRef::new(&buf[4..]));
2437 let mut buf = Vec::new();
2438 stream.read_to_end(&mut buf)?;
2439 Ok(MemReader::new(buf))
2440 }
2441
2442 fn read_index_stream<T: Read + Seek>(&self, mut stream: T, flags: u16) -> Result<MemReader> {
2443 if flags != 0 {
2444 self.read_index_stream_internel(&mut stream, flags, 0)
2445 } else if self.key2.len() == 1 {
2446 self.read_index_stream_internel(&mut stream, flags, 0)
2447 } else {
2448 for i in 0..self.key2.len() {
2449 if let Ok(reader) = self.read_index_stream_internel(&mut stream, flags, i) {
2450 return Ok(reader);
2451 }
2452 }
2453 anyhow::bail!("All index key decrypt failed.")
2454 }
2455 }
2456
2457 fn read_manifest(mainfest_dir: &str) -> Result<CxdecDb> {
2458 Ok(serde_json::from_reader(std::fs::File::open(mainfest_dir)?)?)
2459 }
2460
2461 fn read_index<T: Read + Seek>(&self, stream: T, flags: u16) -> Result<()> {
2462 let mut reader = self.read_index_stream(stream, flags)?;
2463 let root_obj = TjsValue::unpack(&mut reader, true, Encoding::Utf16LE, &None)?;
2464 if !root_obj.is_array() {
2465 anyhow::bail!("Index object is not an array.");
2466 }
2467 let mut info_map = self.info_map.lock_blocking();
2468 info_map.clear();
2469 let set = create_garbage_filename_set("xp3hnp");
2470 let basename: &str = &std::path::Path::new(&self.filename)
2471 .file_name()
2472 .unwrap_or_default()
2473 .to_string_lossy();
2474 let mut manifest = if let Some(filename) = self.dump_file_hash.as_ref() {
2475 Some(match Self::read_manifest(filename) {
2476 Ok(manifest) => manifest,
2477 Err(_) => CxdecDb::default(),
2478 })
2479 } else {
2480 None
2481 };
2482 for i in (0..root_obj.len()).step_by(2) {
2483 let path_hash = PathHash::try_from(
2484 root_obj[i]
2485 .as_bytes()
2486 .ok_or_else(|| anyhow::anyhow!("path hash is not bytes."))?,
2487 )?;
2488 if let Some(m) = manifest.as_mut() {
2489 if !m.path_mapping.contains_key(&path_hash) {
2490 m.path_mapping.insert(path_hash, None);
2491 }
2492 }
2493 let dir_obj = &root_obj[i + 1];
2494 if !dir_obj.is_array() {
2495 anyhow::bail!("dir object at index {} is not array.", i + 1);
2496 }
2497 let (path_name, path_is_hash) = if let Some(n) = self.path_mapping.get(&path_hash) {
2498 (n.to_owned(), false)
2499 } else {
2500 (path_hash.to_string() + "/", true)
2501 };
2502 for j in (0..dir_obj.len()).step_by(2) {
2503 let entry_hash = FileHash::try_from(
2504 dir_obj[j]
2505 .as_bytes()
2506 .ok_or_else(|| anyhow::anyhow!("entry hash is not bytes."))?,
2507 )?;
2508 if let Some(m) = manifest.as_mut() {
2509 let xp3 = m.file_list.entry(basename.into()).or_default();
2510 let path = xp3.entry(path_hash).or_default();
2511 if !path.contains_key(&entry_hash) {
2512 path.insert(entry_hash, None);
2513 }
2514 }
2515 let entry_obj = &dir_obj[j + 1];
2516 if !entry_obj.is_array() {
2517 anyhow::bail!("Entry object at index {},{} is not array.", i + 1, j + 1);
2518 }
2519 if entry_obj.len() < 2 {
2520 anyhow::bail!("Entry object at index {},{} is too small.", i + 1, j + 1);
2521 }
2522 let entry_id = entry_obj[0]
2523 .as_u64()
2524 .ok_or_else(|| anyhow::anyhow!("Entry id is not int."))?;
2525 let entry_key = entry_obj[1]
2526 .as_u64()
2527 .ok_or_else(|| anyhow::anyhow!("Entry key is not int."))?;
2528 let (name, name_is_hash) = if let Some(n) = self.file_mapping.get(&entry_hash) {
2529 (n.to_owned(), false)
2530 } else {
2531 (entry_hash.to_string(), true)
2532 };
2533 let uname = Self::get_unicode_name(entry_id as u32);
2534 let entry = HxEntry {
2535 path: path_name.clone(),
2536 name,
2537 id: entry_id,
2538 key: entry_key,
2539 name_is_hash,
2540 path_is_hash,
2541 is_garbage: set.contains(&entry_hash),
2542 };
2543 info_map.insert(uname, entry);
2544 }
2545 }
2546 if let Some(filename) = &self.dump_file_hash
2547 && let Some(db) = manifest
2548 {
2549 serde_json::to_writer_pretty(std::fs::File::create(filename)?, &db)?;
2550 }
2551 Ok(())
2552 }
2553
2554 fn get_unicode_name(mut hash: u32) -> String {
2555 let mut buf = [0u16; 4];
2556 let mut i = 0;
2557 loop {
2558 buf[i] = ((hash & 0x3FFF) + 0x5000) as u16;
2559 hash >>= 14;
2560 i += 1;
2561 if hash == 0 {
2562 break;
2563 }
2564 }
2565 let s = String::from_utf16_lossy(&buf[..i]);
2566 s
2567 }
2568
2569 fn create_filter_key(&self, entry_key: u64, header_key_seed: u64) -> Result<HxFilterKey> {
2570 let key0 = entry_key as u32;
2571 let key1 = (entry_key >> 32) as u32;
2572 let k0 = self.base.execute_xcode(key0)?;
2573 let file_key_0 = (k0.0 as u64) | ((k0.1 as u64) << 32);
2574 let k1 = self.base.execute_xcode(key1)?;
2575 let file_key_1 = (k1.0 as u64) | ((k1.1 as u64) << 32);
2576 let split_position =
2577 (self.base.offset as u64 + ((entry_key >> 16) & self.base.mask as u64)) & 0xffffffff;
2578 let mut header_key = [0u8; 16];
2579 let k3 = self.base.execute_xcode(header_key_seed as u32)?;
2580 let mut v5 = (k3.0 as u64) | ((k3.1 as u64) << 32);
2581 v5 = !v5;
2582 let mut writer = MemWriterRef::new(&mut header_key);
2583 writer.write_u64_be(v5)?;
2584 let k3 = self.base.execute_xcode(v5 as u32)?;
2585 v5 = (k3.0 as u64) | ((k3.1 as u64) << 32);
2586 v5 = !v5;
2587 writer.write_u64_be(v5)?;
2588 Ok(HxFilterKey {
2589 key: [file_key_0, file_key_1],
2590 header_key,
2591 split_position,
2592 has_header_key: true,
2593 flag: false,
2594 })
2595 }
2596}
2597
2598impl AsRef<CxEncryption> for HxCrypt {
2599 fn as_ref(&self) -> &CxEncryption {
2600 &self.base
2601 }
2602}
2603
2604struct CopyStream<'a> {
2605 inner: Box<dyn Read + Send + Sync + 'a>,
2606}
2607
2608impl<'a> CopyStream<'a> {
2609 pub fn new(stream: Box<dyn Read + Send + Sync + 'a>) -> Self {
2610 Self { inner: stream }
2611 }
2612}
2613
2614impl<'a> std::fmt::Debug for CopyStream<'a> {
2615 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2616 f.debug_struct("CopyStream").finish_non_exhaustive()
2617 }
2618}
2619
2620impl<'a> Read for CopyStream<'a> {
2621 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2622 self.inner.read(buf)
2623 }
2624}
2625
2626impl Crypt for HxCrypt {
2627 base_schema_impl!();
2628 fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
2629 if let Some(hxv4) = archive.extras.iter().find(|x| x.tag == "Hxv4") {
2630 let mut reader = MemReaderRef::new(&hxv4.data);
2631 let offset = reader.read_u64()? + archive.base_offset;
2632 let size = reader.read_u32()?;
2633 let flags = reader.read_u16()?;
2634 let stream = StreamRegion::with_size(
2635 MutexWrapper::new(archive.inner.clone(), offset),
2636 size as u64,
2637 )?;
2638 self.read_index(stream, flags)?;
2639 let info_map = self.info_map.lock_blocking();
2640 for entry in archive.entries.iter_mut() {
2641 if let Some(info) = info_map.get(&entry.name) {
2642 if info.is_garbage {
2643 continue;
2644 }
2645 if self.path_hash == PathHashOption::Both || !info.path_is_hash {
2646 entry.name = format!("{}{}", info.path, info.name);
2647 } else {
2648 entry.name = info.name.clone();
2649 }
2650 let info = info.clone();
2651 entry.extra = Some(Arc::new(Box::new(info)))
2652 }
2653 }
2654 archive.entries.retain(|x| {
2655 x.extra.is_some() || !info_map.get(&x.name).is_some_and(|x| x.is_garbage)
2656 });
2657 if self.file_hash == FileHashOption::WithName {
2658 archive.entries.retain(|x| {
2659 !(x.extra.as_ref().is_some_and(|x| {
2660 x.as_any()
2661 .downcast_ref::<HxEntry>()
2662 .is_some_and(|x| x.name_is_hash)
2663 }))
2664 });
2665 } else if self.file_hash == FileHashOption::WithoutName {
2666 archive.entries.retain(|x| {
2667 x.extra.as_ref().is_some_and(|x| {
2668 x.as_any()
2669 .downcast_ref::<HxEntry>()
2670 .is_some_and(|x| x.name_is_hash)
2671 })
2672 });
2673 }
2674 }
2675 archive.extras.retain(|x| x.tag != "Hxv4");
2676 Ok(())
2677 }
2678 fn decrypt_supported(&self) -> bool {
2679 true
2680 }
2681 fn decrypt_seek_supported(&self) -> bool {
2682 true
2683 }
2684 fn decrypt<'a>(
2685 &self,
2686 entry: &Xp3Entry,
2687 cur_seg: &Segment,
2688 stream: Box<dyn Read + Send + Sync + 'a>,
2689 ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
2690 let info = match entry.extra.as_ref() {
2691 Some(info) => info,
2692 None => return Ok(Box::new(CopyStream::new(stream))),
2693 };
2694 let info = info
2695 .as_any()
2696 .downcast_ref::<HxEntry>()
2697 .ok_or_else(|| anyhow::anyhow!("extra info is not hx entry."))?;
2698 let mut entry_key = info.key;
2699 if (info.id & 0x100000000) == 0 {
2700 entry_key ^= self.filter_key.qload();
2701 }
2702 let header_key = !entry_key;
2703 let key = self.create_filter_key(entry_key, header_key)?;
2704 let filter = HxFilter::new(key);
2705 let key = (
2706 entry.file_hash,
2707 Box::new(filter) as Box<dyn ICxEncryption + Send + Sync + 'a>,
2708 );
2709 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
2710 }
2711 fn decrypt_with_seek<'a>(
2712 &self,
2713 entry: &Xp3Entry,
2714 cur_seg: &Segment,
2715 stream: Box<dyn ReadSeek + Send + Sync + 'a>,
2716 ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
2717 let info = match entry.extra.as_ref() {
2718 Some(info) => info,
2719 None => return Ok(stream),
2720 };
2721 let info = info
2722 .as_any()
2723 .downcast_ref::<HxEntry>()
2724 .ok_or_else(|| anyhow::anyhow!("extra info is not hx entry."))?;
2725 let mut entry_key = info.key;
2726 if (info.id & 0x100000000) == 0 {
2727 entry_key ^= self.filter_key.qload();
2728 }
2729 let header_key = !entry_key;
2730 let key = self.create_filter_key(entry_key, header_key)?;
2731 let filter = HxFilter::new(key);
2732 let key = (
2733 entry.file_hash,
2734 Box::new(filter) as Box<dyn ICxEncryption + Send + Sync + 'a>,
2735 );
2736 Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
2737 }
2738}
2739
2740#[derive(Debug)]
2741enum TjsValue {
2742 Void,
2743 Str(String),
2744 ByteArray(Vec<u8>),
2745 Int(i64),
2746 #[allow(unused)]
2747 Double(f64),
2748 Array(Vec<TjsValue>),
2749 Dict(HashMap<String, TjsValue>),
2750}
2751
2752impl TjsValue {
2753 fn is_array(&self) -> bool {
2754 matches!(self, Self::Array(_))
2755 }
2756
2757 fn len(&self) -> usize {
2758 match self {
2759 Self::Str(s) => s.len(),
2760 Self::ByteArray(arr) => arr.len(),
2761 Self::Array(arr) => arr.len(),
2762 Self::Dict(dict) => dict.len(),
2763 _ => 0,
2764 }
2765 }
2766
2767 fn as_bytes(&self) -> Option<&[u8]> {
2768 match self {
2769 Self::ByteArray(arr) => Some(arr),
2770 _ => None,
2771 }
2772 }
2773
2774 fn as_u64(&self) -> Option<u64> {
2775 match self {
2776 Self::Int(i) => Some(*i as u64),
2777 _ => None,
2778 }
2779 }
2780}
2781
2782const VOID: TjsValue = TjsValue::Void;
2783
2784impl Index<usize> for TjsValue {
2785 type Output = TjsValue;
2786 fn index(&self, index: usize) -> &Self::Output {
2787 match self {
2788 Self::Array(arr) => arr.get(index).unwrap_or(&VOID),
2789 _ => &VOID,
2790 }
2791 }
2792}
2793
2794fn unpack_string<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<String> {
2795 let len = u32::unpack(reader, big, encoding, &None)? as usize;
2796 let tlen = if encoding.is_utf16le() { len * 2 } else { len };
2797 let mut buf = vec![0u8; tlen];
2798 reader.read_exact(&mut buf)?;
2799 let s = decode_to_string(encoding, &buf, true)?;
2800 Ok(s)
2801}
2802
2803impl StructUnpack for TjsValue {
2804 fn unpack<R: Read + Seek>(
2805 reader: &mut R,
2806 big: bool,
2807 encoding: Encoding,
2808 info: &Option<Box<dyn std::any::Any>>,
2809 ) -> Result<Self> {
2810 let typ = u8::unpack(reader, big, encoding, info)?;
2811 Ok(match typ {
2812 0 => Self::Void,
2813 2 => Self::Str(unpack_string(reader, big, encoding)?),
2814 3 => {
2815 let len = u32::unpack(reader, big, encoding, info)?;
2816 let data = reader.read_exact_vec(len as usize)?;
2817 Self::ByteArray(data)
2818 }
2819 4 => {
2820 let num = i64::unpack(reader, big, encoding, info)?;
2821 Self::Int(num)
2822 }
2823 5 => {
2824 let num = f64::unpack(reader, big, encoding, info)?;
2825 Self::Double(num)
2826 }
2827 0x81 => {
2828 let len = u32::unpack(reader, big, encoding, info)?;
2829 let mut arr = Vec::with_capacity(len as usize);
2830 for _ in 0..len {
2831 arr.push(Self::unpack(reader, big, encoding, info)?);
2832 }
2833 Self::Array(arr)
2834 }
2835 0xC1 => {
2836 let len = u32::unpack(reader, big, encoding, info)?;
2837 let mut dict = HashMap::with_capacity(len as usize);
2838 for _ in 0..len {
2839 let name = unpack_string(reader, big, encoding)?;
2840 let obj = Self::unpack(reader, big, encoding, info)?;
2841 dict.insert(name, obj);
2842 }
2843 Self::Dict(dict)
2844 }
2845 _ => anyhow::bail!("Unknown type id: {typ:02x}."),
2846 })
2847 }
2848}
2849
2850#[derive(Clone, Debug)]
2851struct HxEntry {
2852 path: String,
2853 name: String,
2854 id: u64,
2855 key: u64,
2856 name_is_hash: bool,
2857 path_is_hash: bool,
2858 is_garbage: bool,
2859}
2860
2861impl AnyDebug for HxEntry {
2862 fn as_any(&self) -> &dyn std::any::Any {
2863 self
2864 }
2865}
2866
2867#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
2868struct HxSplitMix64 {
2869 state: u64,
2870}
2871
2872impl HxSplitMix64 {
2873 pub fn new(seed: u64) -> Self {
2874 Self { state: seed }
2875 }
2876}
2877
2878trait IRng: std::fmt::Debug {
2879 fn next(&mut self) -> u64;
2880}
2881
2882impl IRng for HxSplitMix64 {
2883 fn next(&mut self) -> u64 {
2884 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
2885 let mut z = self.state;
2886 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
2887 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
2888 z ^ (z >> 31)
2889 }
2890}
2891
2892#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2893struct Xoroshiro128PlusPlus {
2894 state: [u64; 2],
2895}
2896
2897impl Xoroshiro128PlusPlus {
2898 pub fn new(state: [u64; 2]) -> Self {
2899 assert!(
2900 state[0] != 0 || state[1] != 0,
2901 "Initial state cannot be all zeros."
2902 );
2903 Self { state }
2904 }
2905}
2906
2907impl IRng for Xoroshiro128PlusPlus {
2908 fn next(&mut self) -> u64 {
2909 let s0 = self.state[0];
2910 let mut s1 = self.state[1];
2911 let result = s0.wrapping_add(s1).rotate_left(17).wrapping_add(s0);
2912 s1 ^= s0;
2913 self.state[0] = s0.rotate_left(49) ^ s1 ^ (s1 << 21);
2914 self.state[1] = s1.rotate_left(28);
2915 result
2916 }
2917}
2918
2919#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2920struct Xoroshiro128StarStar {
2921 state: [u64; 2],
2922}
2923
2924impl Xoroshiro128StarStar {
2925 pub fn new(state: [u64; 2]) -> Self {
2926 assert!(
2927 state[0] != 0 || state[1] != 0,
2928 "Initial state cannot be all zeros."
2929 );
2930 Self { state }
2931 }
2932}
2933
2934impl IRng for Xoroshiro128StarStar {
2935 fn next(&mut self) -> u64 {
2936 let s0 = self.state[0];
2937 let mut s1 = self.state[1];
2938 let result = s0.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
2939 s1 ^= s0;
2940 self.state[0] = s0.rotate_left(24) ^ s1 ^ (s1 << 16);
2941 self.state[1] = s1.rotate_left(37);
2942 result
2943 }
2944}
2945
2946#[derive(Debug)]
2947struct HxProgram {
2948 base: CxProgram,
2949 rng: Box<dyn IRng + Send + Sync>,
2950}
2951
2952impl HxProgram {
2953 pub fn new(seed: u32, control_block: Weak<Vec<u32>>, random_method: i32) -> Self {
2954 let initial_seed = (seed as u64) | (!(seed as u64) << 32);
2955 let mut seeder = HxSplitMix64::new(initial_seed);
2956 let seed1 = seeder.next();
2957 let seed2 = seeder.next();
2958 let xoroshiro_seed = [seed1, seed2];
2959 Self {
2960 base: CxProgram {
2961 code: Vec::new(),
2962 control_block,
2963 length: 0,
2964 seed,
2965 },
2966 rng: if random_method == 0 {
2967 Box::new(Xoroshiro128PlusPlus::new(xoroshiro_seed))
2968 } else {
2969 Box::new(Xoroshiro128StarStar::new(xoroshiro_seed))
2970 },
2971 }
2972 }
2973}
2974
2975impl ICxProgram for HxProgram {
2976 fn execute(&self, hash: u32) -> Result<u32> {
2977 self.base.execute(hash)
2978 }
2979 fn clear(&mut self) {
2980 self.base.clear();
2981 }
2982 fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool {
2983 self.base.emit(bytecode, length)
2984 }
2985 fn emit_nop(&mut self, count: usize) -> bool {
2986 self.base.emit_nop(count)
2987 }
2988 fn emit_u32(&mut self, x: u32) -> bool {
2989 self.base.emit_u32(x)
2990 }
2991 fn get_random(&mut self) -> u32 {
2992 self.rng.next() as u32
2993 }
2994}
2995
2996#[derive(Debug)]
2997struct HxProgramBuilder {
2998 random_method: i32,
2999}
3000
3001impl HxProgramBuilder {
3002 pub fn new(random_method: i32) -> Self {
3003 Self { random_method }
3004 }
3005}
3006
3007impl ICxProgramBuilder for HxProgramBuilder {
3008 fn build(
3009 &self,
3010 seed: u32,
3011 control_blocks: Weak<Vec<u32>>,
3012 ) -> Box<dyn ICxProgram + Send + Sync> {
3013 Box::new(HxProgram::new(seed, control_blocks, self.random_method))
3014 }
3015}
3016
3017struct HxFilterKey {
3018 key: [u64; 2],
3019 header_key: [u8; 16],
3020 split_position: u64,
3021 has_header_key: bool,
3022 flag: bool,
3023}
3024
3025#[derive(Clone)]
3026struct HxFilterSpanDecryptor {
3027 first_decrypt_key: u32,
3028 key1: u8,
3029 key2: u8,
3030 span_position: [u64; 2],
3031}
3032
3033impl std::fmt::Debug for HxFilterSpanDecryptor {
3034 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3035 f.debug_struct("HxFilterSpanDecryptor")
3036 .field(
3037 "firstDecryptKey",
3038 &format_args!("{:#x}", &self.first_decrypt_key),
3039 )
3040 .field("key1", &format_args!("{:#x}", &self.key1))
3041 .field("key2", &format_args!("{:#x}", &self.key2))
3042 .field(
3043 "spanPosition",
3044 &format_args!("{:#x}, {:#x}", self.span_position[0], self.span_position[1]),
3045 )
3046 .finish()
3047 }
3048}
3049
3050impl HxFilterSpanDecryptor {
3051 pub fn new(key: u64, flag: bool) -> Self {
3052 let decrypt_key_bytes = ((key >> 8) & 0xFFFF) as u32;
3053 let mut first_decrypt_key = (key & 0xFF) as u32;
3054 let mut span_position = [(key >> 48) & 0xFFFF, (key >> 32) & 0xFFFF];
3055 if span_position[0] == span_position[1] {
3056 span_position[1] = span_position[1].wrapping_add(1);
3057 }
3058 if !flag && first_decrypt_key == 0 {
3059 first_decrypt_key = 0xA5;
3060 }
3061 first_decrypt_key = first_decrypt_key.wrapping_mul(0x01010101);
3062 let (key1, key2) = if flag {
3063 (0, 0)
3064 } else {
3065 (
3066 (decrypt_key_bytes & 0xFF) as u8,
3067 ((decrypt_key_bytes >> 8) & 0xFF) as u8,
3068 )
3069 };
3070 Self {
3071 first_decrypt_key,
3072 key1,
3073 key2,
3074 span_position,
3075 }
3076 }
3077
3078 pub fn decrypt(&self, position: u64, data: &mut [u8]) {
3079 if data.is_empty() {
3080 return;
3081 }
3082 let key_bytes = self.first_decrypt_key.to_le_bytes();
3083 for (i, byte) in data.iter_mut().enumerate() {
3084 let key_index = ((position as usize) + i) & 3;
3085 *byte ^= key_bytes[key_index];
3086 }
3087 let data_len = data.len() as u64;
3088 if self.key1 != 0 {
3089 let pos1 = self.span_position[0];
3090 if pos1 >= position && pos1 < position + data_len {
3091 let index = (pos1 - position) as usize;
3092 data[index] ^= self.key1;
3093 }
3094 }
3095 if self.key2 != 0 {
3096 let pos2 = self.span_position[1];
3097 if pos2 >= position && pos2 < position + data_len {
3098 let index = (pos2 - position) as usize;
3099 data[index] ^= self.key2;
3100 }
3101 }
3102 }
3103}
3104
3105struct HxFilter {
3106 span_decryptors: [HxFilterSpanDecryptor; 2],
3107 split_position: u64,
3108 header_key: [u8; 16],
3109 has_header_key: bool,
3110}
3111
3112impl HxFilter {
3113 pub fn new(key: HxFilterKey) -> HxFilter {
3114 HxFilter {
3115 span_decryptors: [
3116 HxFilterSpanDecryptor::new(key.key[0], key.flag),
3117 HxFilterSpanDecryptor::new(key.key[1], key.flag),
3118 ],
3119 split_position: key.split_position,
3120 header_key: key.header_key,
3121 has_header_key: key.has_header_key,
3122 }
3123 }
3124
3125 fn decrypt_header(&self, position: u64, buffer: &mut [u8]) {
3126 let header_len = self.header_key.len() as u64;
3127 let overlap_start = position;
3128 let overlap_end = (position + buffer.len() as u64).min(header_len);
3129 if overlap_start >= overlap_end {
3130 return;
3131 }
3132 for i in overlap_start..overlap_end {
3133 let buffer_index = (i - position) as usize;
3134 let key_index = i as usize;
3135 buffer[buffer_index] ^= self.header_key[key_index];
3136 }
3137 }
3138
3139 pub fn decrypt(&self, position: u64, buffer: &mut [u8]) {
3140 if buffer.is_empty() {
3141 return;
3142 }
3143 if self.has_header_key {
3144 self.decrypt_header(position, buffer);
3145 }
3146 let buffer_len = buffer.len() as u64;
3147 let buffer_end_pos = position + buffer_len;
3148 if buffer_end_pos <= self.split_position {
3149 self.span_decryptors[0].decrypt(position, buffer);
3150 } else if position >= self.split_position {
3151 self.span_decryptors[1].decrypt(position, buffer);
3152 } else {
3153 let split_index = (self.split_position - position) as usize;
3154 let (part1, part2) = buffer.split_at_mut(split_index);
3155 self.span_decryptors[0].decrypt(position, part1);
3156 self.span_decryptors[1].decrypt(self.split_position, part2);
3157 }
3158 }
3159}
3160
3161impl std::fmt::Debug for HxFilter {
3162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3163 f.debug_struct("HxFilter")
3164 .field("spanDecryptors", &self.span_decryptors)
3165 .field(
3166 "splitPosition",
3167 &format_args!("{:#x}", &self.split_position),
3168 )
3169 .field("hasHeaderKey", &self.has_header_key)
3170 .field("headerKey", &hex::encode(self.header_key))
3171 .finish()
3172 }
3173}
3174
3175impl ICxEncryption for HxFilter {
3176 fn get_base_offset(&self, _hash: u32) -> u32 {
3177 0
3178 }
3179 fn decode(
3180 &self,
3181 _key: u32,
3182 _offset: u64,
3183 _buffer: &mut [u8],
3184 _pos: usize,
3185 _count: usize,
3186 ) -> Result<()> {
3187 Ok(())
3188 }
3189 fn inner_decrypt(
3190 &self,
3191 _key: u32,
3192 offset: u64,
3193 buffer: &mut [u8],
3194 pos: usize,
3195 count: usize,
3196 ) -> Result<()> {
3197 self.decrypt(offset, &mut buffer[pos..pos + count]);
3198 Ok(())
3199 }
3200}
3201
3202fn calculate_file_hash(pathname: &str, file_hash_salt: &str) -> FileHash {
3203 use blake2::{Blake2s256, Digest};
3204 let mut hasher = Blake2s256::new();
3205 (pathname.to_lowercase() + file_hash_salt)
3206 .encode_utf16()
3207 .for_each(|b| {
3208 hasher.update(&b.to_le_bytes());
3209 });
3210 let result = hasher.finalize();
3211 let mut hash = [0u8; 32];
3212 hash.copy_from_slice(&result);
3213 FileHash(hash)
3214}
3215
3216fn create_garbage_filename_set(file_hash_salt: &str) -> HashSet<FileHash> {
3217 let mut set = HashSet::new();
3218 set.insert(calculate_file_hash("$$$ This is a protected archive. $$$ 著作者はこのアーカイブが正規の利用方法以外の方法で展開されることを望んでいません。 $$$ This is a protected archive. $$$ 著作者はこのアーカイブが正規の利用方法以外の方法で展開されることを望んでいません。 $$$ This is a protected archive. $$$ 著作者はこのアーカイブが正規の利用方法以外の方法で展開されることを望んでいません。 $$$ Warning! Extracting this archive may infringe on author's rights. 警告 このアーカイブを展開することにより、あなたは著作者の権利を侵害するおそれがあります。.txt", file_hash_salt));
3219 set
3220}
3221
3222fn calculate_path_hash(pathname: &str, path_hash_salt: &str) -> PathHash {
3223 use std::hash::Hasher;
3224 let mut hasher = siphasher::sip::SipHasher24::new();
3225 (pathname.to_lowercase() + path_hash_salt)
3226 .encode_utf16()
3227 .for_each(|b| {
3228 hasher.write(&b.to_le_bytes());
3229 });
3230 let data = hasher.finish().to_ne_bytes();
3231 PathHash(u64::from_be_bytes(data))
3232}
3233
3234fn triple32(mut v: u32) -> u32 {
3235 v ^= v >> 17;
3236 v = v.wrapping_mul(0xED5AD4BB);
3237 v ^= v >> 11;
3238 v = v.wrapping_mul(0xAC4C1B51);
3239 v ^= v >> 15;
3240 v = v.wrapping_mul(0x31848BAB);
3241 v ^= v >> 14;
3242
3243 v
3244}
3245
3246fn fnv_blake(data: &[u8], fnvbase: u32) -> Vec<u8> {
3247 use blake2::{Blake2s256, Digest};
3248 let fnv_value = (0x811C9DC5u32 ^ fnvbase).wrapping_mul(0x01000193);
3249 let mut hash_value = fnv_value;
3250 let mut out = [0u8; 32];
3251 for (i, &byte) in data.iter().enumerate() {
3252 hash_value = triple32(hash_value ^ (byte as u32));
3253 let offset = (i * 4) % 32;
3254 let hash_bytes = hash_value.to_le_bytes();
3255 for j in 0..4 {
3256 out[offset + j] ^= hash_bytes[j];
3257 }
3258 }
3259 let mut hasher = Blake2s256::new();
3260 hasher.update(data);
3261 hasher.update(out);
3262
3263 hasher.finalize().to_vec()
3264}
3265
3266#[derive(Clone, Copy, msg_tool_macro::Default)]
3268pub struct HxKeys {
3269 pub key: [u8; 32],
3270 pub nonce_a: [u8; 32],
3271 pub nonce_b: [u8; 32],
3272 pub filter: [u8; 8],
3274 #[default([0; 0x1000])]
3276 pub ctrlblk: [u8; 0x1000],
3277}
3278
3279#[repr(C)]
3285#[derive(Clone, Copy, Default, StructUnpack)]
3286pub struct CxParams {
3287 pub even_branch_perm: [u8; 8],
3290 pub odd_branch_perm: [u8; 6],
3293 pub prologue_perm: [u8; 3],
3296 pub flags: u8,
3301 pub mask: u16,
3303 pub offset: u16,
3305}
3306
3307impl CxParams {
3308 pub fn new(src: &[u8]) -> Result<CxParams> {
3309 let mut reader = MemReaderRef::new(src);
3310 Self::unpack(&mut reader, false, Encoding::Utf8, &None)
3311 }
3312}
3313
3314impl HxKeys {
3315 pub fn new(
3316 bootstrap: &[u8], warning: &[u8], param: &[u8], uniq: &[u8], upper_key: Option<[u8; 8]>, ) -> Result<(HxKeys, CxParams)> {
3322 use anyhow::Error;
3323 use argon2::{self, Algorithm, Argon2, Version};
3324 use sha3::Digest;
3325 use shake::digest::XofReader;
3326 const DEFAULT_SEED: [u8; 8] = [0xCE, 0xEA, 0xAF, 0x2C, 0xEF, 0xBE, 0xAD, 0xDE]; let upper_key = if let Some(upper_key) = upper_key {
3331 match u64::from_le_bytes(upper_key) == 0 {
3332 true => DEFAULT_SEED,
3333 false => upper_key,
3334 }
3335 } else {
3336 DEFAULT_SEED
3337 };
3338 let params = CxParams::new(param)?; let bootstrap_and_warning: Vec<u8> = [bootstrap, warning].concat();
3340 let mut hasher = Sha3_224::new();
3341 hasher.update(param);
3342 let params_hash = &hasher.finalize()[..16];
3343 let mut lower_key_full = vec![0u8; 64];
3344
3345 let argon2 = Argon2::new(
3347 Algorithm::Argon2i,
3348 Version::V0x13,
3349 argon2::Params::new(8, 3, 1, Some(64)).map_err(Error::msg)?,
3350 );
3351 argon2
3352 .hash_password_into(&bootstrap_and_warning, params_hash, &mut lower_key_full)
3353 .map_err(Error::msg)?;
3354 let lower_key = &lower_key_full[..32];
3355 let fnvbase_bytes: [u8; 4] = upper_key[0..4].try_into().map_err(Error::msg)?;
3356 let fnvbase = u32::from_le_bytes(fnvbase_bytes);
3357 let upper_key = fnv_blake(&upper_key, fnvbase);
3358 let b0 = fnv_blake(&bootstrap_and_warning, 0);
3359 let b1 = fnv_blake(param, 1);
3360 let b2 = fnv_blake(uniq, 2);
3361 let mut key_buffer: Vec<u8> = [b0, b1, b2].concat();
3362
3363 if key_buffer.len() < 96 {
3364 return Err(anyhow::anyhow!(
3365 "Concatenated fnv_blake buffers are less than 96 bytes"
3366 ));
3367 }
3368
3369 for i in 0..64 {
3370 key_buffer[i] ^= lower_key[i % 32];
3371 }
3372 for i in 0..32 {
3373 key_buffer[64 + i] ^= upper_key[i];
3374 }
3375
3376 let mut ctrlblk_pa = vec![0u8; 0x1000]; let mut ctrlblk_pb = vec![0u8; 0x1000]; let mut state = Shake256::default();
3380 shake::digest::Update::update(&mut state, &lower_key_full);
3381 let mut reader = shake::digest::ExtendableOutput::finalize_xof(state);
3382 reader.read(&mut ctrlblk_pa);
3383 reader.read(&mut ctrlblk_pb);
3384 if (params.flags & 1) == 1 {
3385 ctrlblk_pa
3386 .iter_mut()
3387 .zip(ctrlblk_pb.iter())
3388 .for_each(|(dst, src)| {
3389 *dst ^= *src;
3390 });
3391 }
3392
3393 Ok((
3394 HxKeys {
3395 key: key_buffer[0..32].try_into().map_err(Error::msg)?,
3396 nonce_a: key_buffer[32..64].try_into().map_err(Error::msg)?,
3397 nonce_b: key_buffer[64..96].try_into().map_err(Error::msg)?,
3398 filter: key_buffer[64..72].try_into().map_err(Error::msg)?,
3399 ctrlblk: ctrlblk_pa.try_into().unwrap(),
3400 },
3401 params,
3402 ))
3403 }
3404}
3405
3406const RANDOM_TYPE_FLAG: u8 = 0x80;
3407
3408fn hxkeys_new(
3409 bootstrap: &str,
3410 warning: &str,
3411 param: &[u8],
3412 uniq: &str,
3413 upper_key: Option<[u8; 8]>,
3414) -> Result<(HxKeys, CxParams)> {
3415 let bootstrap = encode_string(Encoding::Utf16LE, bootstrap, true)?;
3416 let warning = encode_string(Encoding::Utf16LE, warning, true)?;
3417 let uniq = encode_string(Encoding::Utf16LE, uniq, true)?;
3418 HxKeys::new(&bootstrap, &warning, param, &uniq, upper_key)
3419}
3420
3421fn map_key_to_garbro(cx: &mut CxParams) {
3422 const S3: [u8; 3] = [0, 1, 2];
3423 const S6: [u8; 6] = [2, 5, 3, 4, 1, 0];
3424 const S8: [u8; 8] = [0, 2, 3, 1, 5, 6, 7, 4];
3425 let mut o3 = [0; 3];
3426 let mut o6 = [0; 6];
3427 let mut o8 = [0; 8];
3428 for i in 0..3 {
3429 o3[cx.prologue_perm[i] as usize] = S3[i];
3430 }
3431 for i in 0..6 {
3432 o6[cx.odd_branch_perm[i] as usize] = S6[i];
3433 }
3434 for i in 0..8 {
3435 o8[cx.even_branch_perm[i] as usize] = S8[i];
3436 }
3437 cx.prologue_perm.copy_from_slice(&o3);
3438 cx.odd_branch_perm.copy_from_slice(&o6);
3439 cx.even_branch_perm.copy_from_slice(&o8);
3440}
3441
3442fn gen_index_keys(key: &HxKeys) -> Result<(IndexKey, IndexKey)> {
3443 let subkeya = hchacha::<chacha20::R20>(&key.key.into(), &Array::try_from(&key.nonce_a[0..16])?);
3444 let subkeyb = hchacha::<chacha20::R20>(&key.key.into(), &Array::try_from(&key.nonce_b[0..16])?);
3445 Ok((
3446 IndexKey {
3447 key: subkeya.into(),
3448 nonce: (&key.nonce_a[16..]).try_into()?,
3449 filter_key: None,
3450 },
3451 IndexKey {
3452 key: subkeyb.into(),
3453 nonce: (&key.nonce_b[16..]).try_into()?,
3454 filter_key: None,
3455 },
3456 ))
3457}
3458
3459#[derive(MyDebug)]
3460pub struct Hxv4Crypt {
3461 base: Mutex<Option<HxCrypt>>,
3462 file_mapping: Arc<HashMap<FileHash, String>>,
3463 path_mapping: Arc<HashMap<PathHash, String>>,
3464 key_packages: Vec<KeyPackage>,
3465 project: String,
3466 #[skip_fmt]
3467 config: ExtraConfig,
3468 filename: String,
3469}
3470
3471impl Hxv4Crypt {
3472 pub fn new(filename: &str, config: &ExtraConfig) -> Result<Self> {
3473 let p = std::path::Path::new(filename);
3474 let b = p
3475 .file_name()
3476 .ok_or_else(|| anyhow::anyhow!("Failed to get file name from path."))?;
3477 let s: &str = &b.to_string_lossy();
3478 let pdir = p
3479 .parent()
3480 .map(|s| s.to_owned())
3481 .filter(|s| !s.as_os_str().is_empty())
3482 .unwrap_or_else(|| PathBuf::from("."));
3483 let filep = get_ignorecase_path(&pdir.join("filelist.json"))?;
3484 let data = match std::fs::read(&filep) {
3485 Ok(data) => data,
3486 Err(err) => {
3487 let keys = load_key_packages_from_exe(&pdir);
3488 if keys.is_empty() {
3489 return Err(err.into());
3490 }
3491 eprintln!("Loaded {} key packages from game exe.", keys.len());
3492 return Self::new2(&keys, "Unknown", filename, config);
3493 }
3494 };
3495 let data = decode_to_string(Encoding::Utf8, &data, true)?;
3496 let mut manifest = serde_json::from_str::<CxdecDb>(&data)?;
3497 let mut path_map: HashMap<_, _> = manifest
3498 .path_mapping
3499 .iter()
3500 .filter_map(|(k, v)| match v {
3501 Some(v) => Some((k.clone(), v.clone())),
3502 None => None,
3503 })
3504 .collect();
3505 let file_map: HashMap<_, _> = if let Some(s) = manifest.file_list.get(s) {
3506 s.iter()
3507 .map(|s| s.1)
3508 .flatten()
3509 .filter_map(|(k, v)| match v {
3510 Some(v) => Some((k.clone(), v.clone())),
3511 None => None,
3512 })
3513 .collect()
3514 } else {
3515 HashMap::new()
3516 };
3517 eprintln!(
3518 "Read {} file entries, {} directory entries and {} key packages from filelist {}.",
3519 file_map.len(),
3520 path_map.len(),
3521 manifest.key_packages.len(),
3522 filep.display()
3523 );
3524 let more_keys = load_key_packages_from_exe(&pdir);
3525 if !more_keys.is_empty() {
3526 eprintln!("Loaded {} key packages from game exe.", more_keys.len());
3527 manifest.key_packages.extend_from_slice(&more_keys);
3528 }
3529 let default_path_hash = calculate_path_hash("", "xp3hnp");
3530 if !path_map.contains_key(&default_path_hash) {
3531 path_map.insert(default_path_hash, String::new());
3532 }
3533 Ok(Self {
3534 base: Mutex::new(None),
3535 file_mapping: Arc::new(file_map),
3536 path_mapping: Arc::new(path_map),
3537 key_packages: manifest.key_packages,
3538 project: manifest.project_name,
3539 config: config.clone(),
3540 filename: filename.to_owned(),
3541 })
3542 }
3543
3544 pub fn new2(
3545 key_packages: &[KeyPackage],
3546 project: &str,
3547 filename: &str,
3548 config: &ExtraConfig,
3549 ) -> Result<Self> {
3550 let p = std::path::Path::new(filename);
3551 let b = p
3552 .file_name()
3553 .ok_or_else(|| anyhow::anyhow!("Failed to get file name from path."))?;
3554 let s: &str = &b.to_string_lossy();
3555 let (file_map, mut path_map) = if let Some(path) = config.xp3_file_list_path.as_ref() {
3556 let data = std::fs::read(path)?;
3557 let data = decode_to_string(Encoding::Utf8, &data, true)?;
3558 HxCrypt::read_names(&data, s)?
3559 } else {
3560 let pdir = p.parent().map(|s| s.to_owned()).unwrap_or_default();
3561 if let Some(k) = HxCrypt::try_default_name(&pdir.join("filelist.json"), s)? {
3562 k
3563 } else if let Some(k) = HxCrypt::try_default_name(&pdir.join("filelist.lst"), s)? {
3564 k
3565 } else {
3566 (HashMap::new(), HashMap::new())
3567 }
3568 };
3569 let default_path_hash = calculate_path_hash("", "xp3hnp");
3570 if !path_map.contains_key(&default_path_hash) {
3571 path_map.insert(default_path_hash, String::new());
3572 }
3573 Ok(Self {
3574 base: Mutex::new(None),
3575 file_mapping: Arc::new(file_map),
3576 path_mapping: Arc::new(path_map),
3577 key_packages: key_packages.to_vec(),
3578 project: project.to_owned(),
3579 config: config.clone(),
3580 filename: filename.to_owned(),
3581 })
3582 }
3583
3584 fn load_package(&self, pack: &KeyPackage, archive: &mut Xp3Archive) -> Result<HxCrypt> {
3585 eprintln!("try key {} for {}", pack.sku, self.project);
3586 let upper_key = match &pack.key.upper_key {
3587 Some(key) => Some(key.as_slice().try_into()?),
3588 None => None,
3589 };
3590 let (key, mut params) = hxkeys_new(
3591 &pack.key.boot_strap,
3592 &pack.key.warning,
3593 &pack.key.params,
3594 &pack.key.archive_unique_key,
3595 upper_key,
3596 )?;
3597 map_key_to_garbro(&mut params);
3598 let (key1, key2) = gen_index_keys(&key)?;
3599 let base = BaseSchema {
3600 hash_after_crypt: false,
3601 startup_tjs_not_encrypted: false,
3602 obfuscated_index: false,
3603 };
3604 let cx = CxSchema {
3605 mask: params.mask as u32,
3606 offset: params.offset as u32,
3607 prolog_order: Base64Bytes {
3608 bytes: params.prologue_perm.to_vec(),
3609 },
3610 odd_branch_order: Base64Bytes {
3611 bytes: params.odd_branch_perm.to_vec(),
3612 },
3613 even_branch_order: Base64Bytes {
3614 bytes: params.even_branch_perm.to_vec(),
3615 },
3616 control_block_name: None,
3617 tpm_file_name: None,
3618 };
3619 let key2 = IndexKeys(vec![key2]);
3620 let filter_key = u64::from_le_bytes(key.filter);
3621 let random_type = if params.flags & RANDOM_TYPE_FLAG != 0 {
3622 1
3623 } else {
3624 0
3625 };
3626 let mut control_block = Vec::with_capacity(0x400);
3627 let mut reader = MemReaderRef::new(&key.ctrlblk);
3628 for _ in 0..0x400 {
3629 control_block.push(!reader.read_u32()?);
3630 }
3631 let crypt = HxCrypt::new_inner(
3632 base,
3633 &cx,
3634 key1,
3635 key2,
3636 filter_key,
3637 random_type,
3638 &self.config,
3639 self.file_mapping.clone(),
3640 self.path_mapping.clone(),
3641 control_block,
3642 &self.filename,
3643 )?;
3644 crypt.init(archive)?;
3645 Ok(crypt)
3646 }
3647}
3648
3649impl Crypt for Hxv4Crypt {
3650 fn startup_tjs_not_encrypted(&self) -> bool {
3651 false
3652 }
3653 fn obfuscated_index(&self) -> bool {
3654 false
3655 }
3656 fn hash_after_crypt(&self) -> bool {
3657 false
3658 }
3659 fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
3660 if self.key_packages.len() == 0 {
3661 eprintln!("WARNING: No key package specifed. Decrypt not works.");
3662 crate::COUNTER.inc_warning();
3663 return Ok(());
3664 }
3665 for package in self.key_packages.iter() {
3666 if let Ok(crypt) = self.load_package(package, archive) {
3667 let mut c = self.base.lock_blocking();
3668 c.replace(crypt);
3669 return Ok(());
3670 }
3671 }
3672 Err(anyhow::anyhow!("Failed to decrypt index."))
3673 }
3674 fn decrypt_supported(&self) -> bool {
3675 true
3676 }
3677 fn decrypt_seek_supported(&self) -> bool {
3678 true
3679 }
3680 fn decrypt<'a>(
3681 &self,
3682 entry: &Xp3Entry,
3683 cur_seg: &Segment,
3684 stream: Box<dyn Read + Send + Sync + 'a>,
3685 ) -> Result<Box<dyn ReadDebug + Send + Sync + 'a>> {
3686 if self.key_packages.len() == 0 {
3687 return Ok(Box::new(CopyStream::new(stream)));
3688 }
3689 let c = self.base.lock_blocking();
3690 let crypt = c
3691 .as_ref()
3692 .ok_or_else(|| anyhow::anyhow!("Archive not inited."))?;
3693 crypt.decrypt(entry, cur_seg, stream)
3694 }
3695 fn decrypt_with_seek<'a>(
3696 &self,
3697 entry: &Xp3Entry,
3698 cur_seg: &Segment,
3699 stream: Box<dyn ReadSeek + Send + Sync + 'a>,
3700 ) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
3701 if self.key_packages.len() == 0 {
3702 return Ok(stream);
3703 }
3704 let c = self.base.lock_blocking();
3705 let crypt = c
3706 .as_ref()
3707 .ok_or_else(|| anyhow::anyhow!("Archive not inited."))?;
3708 crypt.decrypt_with_seek(entry, cur_seg, stream)
3709 }
3710}
3711
3712fn load_key_packages_from_exe<S: AsRef<std::path::Path> + ?Sized>(path: &S) -> Vec<KeyPackage> {
3714 let mut packages = Vec::new();
3715 let (files, _) = match crate::utils::files::collect_ext_files(
3716 &path.as_ref().to_string_lossy(),
3717 false,
3718 &["exe"],
3719 ) {
3720 Ok(f) => f,
3721 Err(_) => {
3722 return packages;
3723 }
3724 };
3725 for file in files {
3726 match load_key_package_from_path(&file) {
3727 Ok(key) => packages.push(key),
3728 Err(_e) => {
3729 }
3731 }
3732 }
3733 packages
3734}
3735
3736fn load_key_package_from_path<S: AsRef<std::path::Path> + ?Sized>(path: &S) -> Result<KeyPackage> {
3737 let view = pelite::FileMap::open(path)?;
3738 let mut last_error = match load_key_package(&view, path) {
3739 Ok(key) => return Ok(key),
3740 Err(e) => e,
3741 };
3742 if libsteamless::is_steamstub(&view) {
3743 let options = libsteamless::SteamlessOptions::default();
3744 match libsteamless::process_data(view.as_ref().to_vec(), &options, &|_level, _message| {}) {
3745 Ok(unpacked) => match load_key_package(&unpacked, path) {
3746 Ok(key) => return Ok(key),
3747 Err(e) => {
3748 last_error = e;
3749 }
3750 },
3751 Err(err) => {
3752 last_error = err;
3753 }
3754 }
3755 }
3756 let v = view.as_ref();
3757 if let Some(pos) = memchr::memmem::find(&v[4..], &v[..4]) {
3759 let nv = &v[4 + pos..];
3760 match load_key_package(&nv, path) {
3761 Ok(key) => return Ok(key),
3762 Err(e) => {
3763 last_error = e;
3764 }
3765 }
3766 }
3767 Err(last_error)
3768}
3769
3770fn find_resource<
3771 'a,
3772 'b,
3773 D: Into<pelite::resources::Name<'b>>,
3774 N: Into<pelite::resources::Name<'b>>,
3775>(
3776 resources: &pelite::resources::Resources<'a>,
3777 dir: D,
3778 path: N,
3779) -> Result<&'a [u8]> {
3780 use pelite::resources::*;
3781 let base = resources.root()?.get_dir(dir.into())?;
3782 let ent = base.get(path.into())?;
3783 match ent {
3784 Entry::DataEntry(data) => Ok(data.bytes()?),
3785 Entry::Directory(dir) => Ok(dir.first_data()?.bytes()?),
3786 }
3787}
3788
3789struct PeSections {
3791 image_base: u64,
3792 sections: Vec<(u32, u32, u32, u32)>, }
3794
3795fn parse_pe_sections(data: &[u8]) -> Result<PeSections> {
3796 if data.len() < 0x40 {
3797 anyhow::bail!("PE file too small");
3798 }
3799 let pe_off = u32::from_le_bytes(data[0x3C..0x40].try_into()?) as usize;
3800 if pe_off + 4 > data.len() || &data[pe_off..pe_off + 4] != b"PE\0\0" {
3801 anyhow::bail!("Not a PE file");
3802 }
3803 let coff = pe_off + 4;
3804 let section_count = u16::from_le_bytes(data[coff + 2..coff + 4].try_into()?) as usize;
3805 let optional_size = u16::from_le_bytes(data[coff + 16..coff + 18].try_into()?) as usize;
3806 let optional = coff + 20;
3807 let magic = u16::from_le_bytes(data[optional..optional + 2].try_into()?);
3808 let image_base = if magic == 0x10B {
3809 u32::from_le_bytes(data[optional + 28..optional + 32].try_into()?) as u64
3811 } else if magic == 0x20B {
3812 u64::from_le_bytes(data[optional + 24..optional + 32].try_into()?)
3814 } else {
3815 anyhow::bail!("Unsupported PE optional header magic 0x{magic:x}");
3816 };
3817 let section_table = optional + optional_size;
3818 let mut sections = Vec::with_capacity(section_count);
3819 for i in 0..section_count {
3820 let off = section_table + i * 40;
3821 let va = u32::from_le_bytes(data[off + 12..off + 16].try_into()?);
3822 let virtual_size = u32::from_le_bytes(data[off + 8..off + 12].try_into()?);
3823 let raw_size = u32::from_le_bytes(data[off + 16..off + 20].try_into()?);
3824 let raw = u32::from_le_bytes(data[off + 20..off + 24].try_into()?);
3825 let size = virtual_size.max(raw_size);
3826 sections.push((va, size, raw, raw_size));
3827 }
3828 Ok(PeSections {
3829 image_base,
3830 sections,
3831 })
3832}
3833
3834fn rva_to_offset(ps: &PeSections, rva: u32) -> Option<u32> {
3835 for &(va, size, raw, raw_size) in &ps.sections {
3836 if va <= rva && rva < va + size {
3837 let offset = raw + (rva - va);
3838 if offset < raw + raw_size {
3839 return Some(offset);
3840 }
3841 }
3842 }
3843 None
3844}
3845
3846fn va_to_rva(ps: &PeSections, va: u64) -> Option<u32> {
3847 if va >= ps.image_base {
3848 Some((va - ps.image_base) as u32)
3849 } else {
3850 None
3851 }
3852}
3853
3854const BRES_SALT_SIZE: usize = 0x2000;
3855
3856fn iter_salt_assignment_candidates(data: &[u8], ps: &PeSections) -> Vec<(u32, Vec<u8>)> {
3860 let mut candidates = Vec::new();
3861 if data.len() < 20 {
3862 return candidates;
3863 }
3864 let mut off = 0usize;
3865 while off + 20 <= data.len() {
3866 if &data[off..off + 2] != b"\xC7\x05" {
3867 off += 1;
3868 continue;
3869 }
3870 if off + 10 > data.len() {
3871 break;
3872 }
3873 let salt_va = u32::from_le_bytes(data[off + 6..off + 10].try_into().unwrap());
3874 if (salt_va as u64) < ps.image_base {
3875 off += 1;
3876 continue;
3877 }
3878 let salt_rva = match va_to_rva(ps, salt_va as u64) {
3879 Some(rva) => rva,
3880 None => {
3881 off += 1;
3882 continue;
3883 }
3884 };
3885 let salt_file_off = match rva_to_offset(ps, salt_rva) {
3886 Some(o) => o as usize,
3887 None => {
3888 off += 1;
3889 continue;
3890 }
3891 };
3892 if salt_file_off + BRES_SALT_SIZE > data.len() {
3893 off += 1;
3894 continue;
3895 }
3896 let window_end = (off + 64).min(data.len().saturating_sub(10));
3898 for size_off in (off + 10..window_end).step_by(1) {
3899 if &data[size_off..size_off + 2] != b"\xC7\x05" {
3900 continue;
3901 }
3902 let size_val =
3903 u32::from_le_bytes(data[size_off + 6..size_off + 10].try_into().unwrap());
3904 if size_val != BRES_SALT_SIZE as u32 {
3905 continue;
3906 }
3907 let salt = data[salt_file_off..salt_file_off + BRES_SALT_SIZE].to_vec();
3908 candidates.push((salt_file_off as u32, salt));
3909 break;
3910 }
3911 off += 1;
3912 }
3913 candidates
3914}
3915
3916fn iter_packed_neighborhood_candidates(data: &[u8]) -> Vec<(u32, Vec<u8>)> {
3918 let mut candidates = Vec::new();
3919 let mut seen = std::collections::HashSet::new();
3920
3921 let marker_v2 = b"V2Link\x00\x00";
3923 let mut cursor = 0;
3924 while let Some(pos) = data[cursor..]
3925 .windows(marker_v2.len())
3926 .position(|w| w == marker_v2)
3927 {
3928 let anchor = cursor + pos;
3929 if anchor >= BRES_SALT_SIZE {
3930 let salt_off = anchor - BRES_SALT_SIZE;
3931 if salt_off + BRES_SALT_SIZE <= data.len() && seen.insert(salt_off) {
3932 candidates.push((
3933 salt_off as u32,
3934 data[salt_off..salt_off + BRES_SALT_SIZE].to_vec(),
3935 ));
3936 }
3937 }
3938 cursor = anchor + 1;
3939 }
3940
3941 let marker_fp3 = b"forcedataxp3\x00";
3943 cursor = 0;
3944 while let Some(pos) = data[cursor..]
3945 .windows(marker_fp3.len())
3946 .position(|w| w == marker_fp3)
3947 {
3948 let anchor = cursor + pos;
3949 let window_start = (anchor + marker_fp3.len() + 0xF) & !0xF;
3950 let window_end = (anchor + 0x100).min(data.len().saturating_sub(BRES_SALT_SIZE));
3951 for salt_off in (window_start..=window_end).step_by(0x10) {
3952 if salt_off + BRES_SALT_SIZE <= data.len() && seen.insert(salt_off) {
3953 candidates.push((
3954 salt_off as u32,
3955 data[salt_off..salt_off + BRES_SALT_SIZE].to_vec(),
3956 ));
3957 }
3958 }
3959 cursor = anchor + 1;
3960 }
3961
3962 candidates
3963}
3964
3965fn load_bres_salt<S: AsRef<[u8]> + ?Sized>(data: &S) -> Result<Vec<u8>> {
3970 let data = data.as_ref();
3971 let ps = parse_pe_sections(data)?;
3972
3973 let candidates = iter_salt_assignment_candidates(data, &ps);
3975 if !candidates.is_empty() {
3976 return Ok(candidates[0].1.clone());
3981 }
3982
3983 let candidates = iter_packed_neighborhood_candidates(data);
3985 if !candidates.is_empty() {
3986 return Ok(candidates[0].1.clone());
3991 }
3992
3993 anyhow::bail!("Could not locate 0x{BRES_SALT_SIZE:x}-byte bres salt in PE");
3994}
3995
3996fn decode_bres_root(text: &[u8]) -> Result<String> {
3997 let text = decode_to_string(Encoding::Utf16LE, text, true)?;
3998 let text = text.trim_end_matches('\0');
3999 if !text.starts_with("bres://./") {
4000 anyhow::bail!("Unexpected bres root: {text}");
4001 }
4002 Ok(text[9..].trim_end_matches('/').to_owned())
4003}
4004
4005fn decrypt_bres(data: &[u8], path_key: &str, salt: &[u8]) -> Result<Vec<u8>> {
4006 use chacha20::ChaCha8;
4008 use chacha20::cipher::{StreamCipher, StreamCipherSeek};
4009 use sha3::{Digest, Sha3_384};
4010 let mut h = Sha3_384::new();
4011 h.update(encode_string(Encoding::Utf16LE, path_key, true)?);
4012 h.update(salt);
4013 let mut digest = MemReader::new(h.finalize().to_vec());
4014 let mut key_bytes = [0u8; 32];
4015 let mut nonce = [0; 2];
4016 digest.read_exact(&mut key_bytes)?;
4017 for k in nonce.iter_mut() {
4018 *k = digest.read_u32()?;
4019 }
4020 let ctr_base = digest.read_u32()?;
4021 let ctr_high = digest.read_u32()?;
4022 let mut data = data.to_vec();
4023 let mut nonce_bytes = [0u8; 12];
4024 nonce_bytes[0..4].copy_from_slice(&ctr_high.to_le_bytes());
4025 for (i, &word) in nonce.iter().enumerate() {
4026 nonce_bytes[4 + i * 4..4 + (i + 1) * 4].copy_from_slice(&word.to_le_bytes());
4027 }
4028 let mut cipher = ChaCha8::new_from_slices(&key_bytes, &nonce_bytes)?;
4029 let chunk_size = 64;
4030 for (bn, chunk) in data.chunks_mut(chunk_size).enumerate() {
4031 let ctr_low = ctr_base ^ (bn as u32);
4032 cipher.seek((ctr_low as u64) * 64);
4033 cipher.apply_keystream(chunk);
4034 }
4035 Ok(data)
4036}
4037
4038fn parse_tjs_strings(data: &[u8]) -> Result<Vec<String>> {
4039 use super::super::super::super::tjs2::*;
4040 if !data.starts_with(b"TJS2100\0") {
4041 anyhow::bail!("invalid tjs2 compiled script.");
4042 }
4043 let mut reader = MemReaderRef::new(data);
4044 reader.pos = 0xC;
4045 let data = DataArea::unpack(&mut reader, false, Encoding::Utf16LE, &None)?;
4046 Ok(data.string_array)
4047}
4048
4049fn find_bootstrap_url<'a>(strings: &'a Vec<String>) -> Result<&'a str> {
4050 for value in strings.iter() {
4051 if value.starts_with("bres://./") && value.to_lowercase().ends_with("/bootstrap") {
4052 return Ok(value.as_str());
4053 }
4054 }
4055 anyhow::bail!("Could not find bootstrap bres URL in STARTUP.TJS strings")
4056}
4057
4058fn bres_key_from_url(url: &str) -> Result<String> {
4059 if !url.starts_with("bres://./") {
4060 anyhow::bail!("Not a local bres URL: {url}");
4061 }
4062 Ok(url[9..]
4063 .split('/')
4064 .next()
4065 .ok_or_else(|| anyhow::anyhow!("No data"))?
4066 .to_owned())
4067}
4068
4069const PARAMS_PAT: &[u8] = b"\0\0\0\0\0\0\0\0PARAMS";
4070const UPKEY_PAT: &[u8] = b"p\0t\0-\0-\0n\0o\0\0\0\0\0";
4071
4072fn parse_config_table(dll: &[u8]) -> Result<HashMap<String, Vec<u8>>> {
4073 let mut ofs =
4074 memchr::memmem::find(dll, PARAMS_PAT).ok_or_else(|| anyhow::anyhow!("No parmas in dll"))?;
4075 ofs += PARAMS_PAT.len() - 6;
4076 let mut reader = MemReaderRef::new(dll);
4077 reader.pos = ofs;
4078 let mut tags = HashMap::new();
4079 loop {
4080 let tag = reader.read_cstring()?;
4081 if tag.as_bytes().is_empty() {
4082 break;
4083 }
4084 let tag = decode_to_string(Encoding::Utf8, tag.as_bytes(), true)?;
4085 let length = reader.read_u16()?;
4086 let value = reader.read_exact_vec(length as usize)?;
4087 tags.insert(tag, value);
4088 }
4089 if let Some(mut ofs) = memchr::memmem::find(dll, UPKEY_PAT) {
4090 ofs += UPKEY_PAT.len();
4091 reader.pos = ofs;
4092 tags.insert("upperKey".into(), reader.read_exact_vec(8)?);
4093 }
4094 Ok(tags)
4095}
4096
4097struct TjsVM<'a> {
4098 file: &'a Tjs2File,
4099 obj: &'a Tjs2Object,
4100 i: usize,
4101 reg: HashMap<i32, String>,
4102}
4103
4104impl<'a> TjsVM<'a> {
4105 fn new(file: &'a Tjs2File, obj: &'a Tjs2Object) -> Self {
4106 Self {
4107 file,
4108 obj,
4109 i: 0,
4110 reg: HashMap::new(),
4111 }
4112 }
4113 fn run(&mut self) -> Result<String> {
4114 let len = self.obj.code.len();
4115 while self.i < len {
4116 if let Some(s) = self.run_step()? {
4117 return Ok(s);
4118 }
4119 }
4120 anyhow::bail!("No _bootStrap calld call invoked.");
4121 }
4122 fn run_step(&mut self) -> Result<Option<String>> {
4123 use tjs2dec::Variant;
4124 use tjs2dec::vmcodes::vm::*;
4125 let code = &self.obj.code;
4126 let op = code[self.i];
4127 const VM_INC1: i32 = VM_INC + 1;
4128 const VM_INC2: i32 = VM_INC + 2;
4129 const VM_INC3: i32 = VM_INC + 3;
4130 const VM_DEC1: i32 = VM_DEC + 1;
4131 const VM_DEC2: i32 = VM_DEC + 2;
4132 const VM_DEC3: i32 = VM_DEC + 3;
4133 match op {
4134 VM_CONST => {
4135 self.ensure(3)?;
4136 let dst_reg = code[self.i + 1];
4137 let src = code[self.i + 2];
4138 if let Some(v) = self.obj.data.get(src as usize) {
4139 match v {
4140 Variant::String(idx) => {
4141 if let Some(s) = self.file.const_pools.strings.get(*idx as usize) {
4142 self.reg.insert(dst_reg, s.to_owned());
4143 }
4144 }
4145 _ => {}
4146 }
4147 }
4148 self.i += 3;
4149 }
4150 VM_DELD | VM_TYPEOFD | VM_DELI | VM_TYPEOFI | VM_GPD | VM_GPDS | VM_SPD | VM_SPDE
4151 | VM_SPDEH | VM_SPDS | VM_GPI | VM_GPIS | VM_SPI | VM_SPIE | VM_SPIS | VM_INC1
4152 | VM_INC2 | VM_DEC1 | VM_DEC2 => {
4153 self.skip(4)?;
4154 }
4155 VM_CP | VM_CEQ | VM_CDEQ | VM_CLT | VM_CGT | VM_CHKINS | VM_ADDCI | VM_CHGTHIS
4156 | VM_CCL | VM_ENTRY | VM_SETP | VM_GETP | VM_INC3 | VM_DEC3 => {
4157 self.skip(3)?;
4158 }
4159 VM_CL | VM_SRV | VM_GLOBAL | VM_THROW | VM_TT | VM_TF | VM_SETF | VM_SETNF
4160 | VM_LNOT | VM_BNOT | VM_ASC | VM_CHR | VM_NUM | VM_CHS | VM_INV | VM_CHKINV
4161 | VM_TYPEOF | VM_EVAL | VM_EEXP | VM_INT | VM_REAL | VM_STR | VM_OCTET | VM_JF
4162 | VM_JNF | VM_JMP | VM_INC | VM_DEC => {
4163 self.skip(2)?;
4164 }
4165 VM_RET | VM_NOP | VM_NF | VM_EXTRY | VM_REGMEMBER | VM_DEBUGGER => {
4166 self.skip(1)?;
4167 }
4168 VM_CALL | VM_CALLD | VM_CALLI | VM_NEW => {
4169 let ns = match op {
4170 VM_CALL | VM_NEW => 4,
4171 VM_CALLD | VM_CALLI => 5,
4172 _ => unreachable!(),
4173 };
4174 self.ensure(ns)?;
4175 let i = self.i;
4176 let argc = code[i + ns - 1];
4177 self.i += ns;
4178 if argc == -1 {
4179 } else if argc == -2 {
4181 self.ensure(1)?;
4183 let num = code[i + ns] as usize;
4184 self.i += 1;
4185 self.ensure(num * 2)?;
4186 self.i += num * 2;
4187 } else {
4188 let argc = argc as usize;
4189 self.ensure(argc)?;
4190 if op == VM_CALLD {
4191 let obj = code[i + 2];
4192 let member = code[i + 3];
4193 if obj == -2 {
4195 if let Some(v) = self.obj.data.get(member as usize) {
4196 match v {
4197 Variant::String(idx) => {
4198 if let Some(s) =
4199 self.file.const_pools.strings.get(*idx as usize)
4200 {
4201 if s == "_bootStrap" {
4202 if argc >= 1 {
4203 let farg = code[i + 5];
4204 return Ok(Some(
4205 self.reg
4206 .get(&farg)
4207 .ok_or_else(|| {
4208 anyhow::anyhow!(
4209 "No string in %{}",
4210 farg
4211 )
4212 })?
4213 .to_owned(),
4214 ));
4215 }
4216 }
4217 }
4218 }
4219 _ => {}
4220 }
4221 }
4222 }
4223 }
4224 self.i += argc;
4225 }
4226 }
4227 _ => {
4228 for base in [
4229 VM_LOR, VM_LAND, VM_BOR, VM_BXOR, VM_BAND, VM_SAR, VM_SAL, VM_SR, VM_ADD,
4230 VM_SUB, VM_MOD, VM_DIV, VM_IDIV, VM_MUL,
4231 ] {
4232 match op - base {
4233 0 => {
4234 self.skip(3)?;
4235 return Ok(None);
4236 }
4237 1 | 2 => {
4238 self.skip(5)?;
4239 return Ok(None);
4240 }
4241 3 => {
4242 self.skip(4)?;
4243 return Ok(None);
4244 }
4245 _ => {}
4246 }
4247 }
4248 anyhow::bail!("unknown instruction {}", op);
4249 }
4250 }
4251 Ok(None)
4252 }
4253
4254 fn ensure(&self, need: usize) -> Result<()> {
4255 if self.i + need > self.obj.code.len() {
4256 anyhow::bail!(
4257 "truncated instruction at {}: need {}, code_len {}",
4258 self.i,
4259 need,
4260 self.obj.code.len()
4261 );
4262 }
4263 Ok(())
4264 }
4265
4266 fn skip(&mut self, size: usize) -> Result<()> {
4267 self.ensure(size)?;
4268 self.i += size;
4269 Ok(())
4270 }
4271}
4272
4273fn get_boot_strap(tjs: &[u8]) -> Result<String> {
4274 use tjs2dec::load_tjs2_bytecode;
4275 let file = load_tjs2_bytecode(tjs)?;
4276 let global_obj = file
4277 .objects
4278 .iter()
4279 .find(|s| s.name.as_ref().is_some_and(|s| s == "global"))
4280 .ok_or_else(|| anyhow::anyhow!("No global object."))?;
4281 let mut vm = TjsVM::new(&file, global_obj);
4282 vm.run()
4283}
4284
4285fn load_key_package<S: AsRef<[u8]> + ?Sized, P: AsRef<std::path::Path> + ?Sized>(
4286 data: &S,
4287 path: &P,
4288) -> Result<KeyPackage> {
4289 let bn = path
4290 .as_ref()
4291 .file_stem()
4292 .map(|s| (&s.to_string_lossy()).to_string())
4293 .unwrap_or_else(|| "TBD".into());
4294 let file = PeFile::from_bytes(data)?;
4295 let resources = file.resources()?;
4296 let bootstrap = find_resource(&resources, 10, "BOOTSTRAP")?;
4297 let startup_tjs = find_resource(&resources, 10, "STARTUP.TJS")?;
4298 let text = find_resource(&resources, "TEXT", 127)?;
4299 let salt = load_bres_salt(data)?;
4301 let text = decode_bres_root(text)?;
4306 let startup_tjs = decrypt_bres(startup_tjs, &text, &salt)?;
4308 let strings = parse_tjs_strings(&startup_tjs)?;
4309 let bootstrap_url = find_bootstrap_url(&strings)?;
4311 let bootstrap_key = bres_key_from_url(bootstrap_url)?;
4313 let bootstrap = decrypt_bres(bootstrap, &bootstrap_key, &salt)?;
4315 let mut reader = flate2::read::ZlibDecoder::new(MemReaderRef::new(&bootstrap[8..]));
4320 let mut dll = Vec::new();
4321 reader.read_to_end(&mut dll)?;
4322 if !dll.starts_with(b"MZ") {
4323 anyhow::bail!("Not a dll.");
4324 }
4325 let config = parse_config_table(&dll)?;
4326 let warning = config
4327 .get("WARNING")
4328 .ok_or_else(|| anyhow::anyhow!("WARNING not found"))?;
4329 let warning = decode_to_string(Encoding::Utf8, &warning, true)?;
4330 let params = config
4332 .get("PARAMS")
4333 .cloned()
4334 .ok_or_else(|| anyhow::anyhow!("PARAMS not found"))?;
4335 let archive_unique_key = config
4337 .get("UNIQUE")
4338 .ok_or_else(|| anyhow::anyhow!("UNIQUE not found"))?;
4339 let archive_unique_key = decode_to_string(Encoding::Utf16LE, &archive_unique_key, true)?;
4340 let upper_key = config.get("upperKey").cloned();
4342 let boot_strap = get_boot_strap(&startup_tjs)?;
4347 Ok(KeyPackage {
4349 description: "TBD".into(),
4350 key: KeyData {
4351 boot_strap,
4352 warning,
4353 params,
4354 archive_unique_key,
4355 upper_key,
4356 },
4357 sku: bn,
4358 })
4359}
4360
4361#[test]
4362fn test_triple32() {
4363 assert_eq!(triple32(0x281ff4b9), 0x3389ba89);
4364 assert_eq!(triple32(0x4899abb), 0xca5cb43b);
4365 assert_eq!(triple32(0x12fb3c7b), 0xe2855413);
4366 assert_eq!(triple32(0x275bdef0), 0x8f95ac52);
4367 assert_eq!(triple32(0x4e9302ee), 0x7b62fdd0);
4368 assert_eq!(triple32(0x4df4823b), 0xe7483578);
4369 assert_eq!(triple32(0x77b0cd89), 0x7d42d107);
4370 assert_eq!(triple32(0x312bebee), 0xa038d73f);
4371 assert_eq!(triple32(0x39203931), 0xf7b87c25);
4372 assert_eq!(triple32(0x633b66f4), 0x98ff988);
4373 assert_eq!(triple32(0x636d1fc1), 0x99897c6);
4374 assert_eq!(triple32(0xb7a114f), 0xef0b8bd3);
4375 assert_eq!(triple32(0x4c7d96c0), 0xc1ba0efe);
4376 assert_eq!(triple32(0x7f26226e), 0x7449b080);
4377 assert_eq!(triple32(0x1bd4bcc7), 0xea9264aa);
4378 assert_eq!(triple32(0x13afe6fd), 0x66396c69);
4379}
4380
4381#[test]
4382fn test_gen_keys() {
4383 let keys = hxkeys_new(
4384 "BOOTSTRAPbootstrap0123456789",
4385 "WARNINGwarning0123456789",
4386 b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a",
4387 "ArchiveUniqueKey0123456789",
4388 Some(*b"\x00\x11\x22\x33\x44\x55\x66\x77"),
4389 )
4390 .unwrap();
4391 let expected_key: [u8; 32] =
4392 hex::decode("4fb07f17eb1d7d0f14fba645e067d5d90a973494f4161da962ee49ccfc9ad237")
4393 .unwrap()
4394 .try_into()
4395 .unwrap();
4396 let expected_nonce_a: [u8; 24] =
4397 hex::decode("c84f47adef9093396d421105bd8893c925b3853aef22d346")
4398 .unwrap()
4399 .try_into()
4400 .unwrap();
4401 let expected_nonce_b: [u8; 24] =
4402 hex::decode("98d9fc0c47eb2684aad17ca33ee8cb1aed30812ee8990500")
4403 .unwrap()
4404 .try_into()
4405 .unwrap();
4406 assert_eq!(keys.0.key, expected_key);
4407 assert_eq!(&keys.0.nonce_a[..24], &expected_nonce_a);
4408 assert_eq!(&keys.0.nonce_b[..24], &expected_nonce_b);
4409}
4410
4411#[test]
4412fn test_real_keys() {
4413 let (key, mut params) = hxkeys_new(
4414 "LimeLightRemonadeJam (C)YUZUSOFT/JUNOS INC. All Rights Reserved.",
4415 "Warning! Extracting this game data may infringe on author's rights.",
4416 b"\x02\x00\x06\x05\x01\x04\x03\x07\x01\x05\x03\x02\x00\x04\x01\x00\x02\x01\xe2\x02\x83\x02",
4417 "{EnaAnjTukRirMikNay}",
4418 Some(*b"\xbf\x22\x36\x8a\x48\x21\x02\x06"),
4419 )
4420 .unwrap();
4421 map_key_to_garbro(&mut params);
4422 assert_eq!(params.mask, 738);
4423 assert_eq!(params.offset, 643);
4424 use base64::Engine;
4425 let b64 = base64::engine::general_purpose::STANDARD;
4426 assert_eq!(b64.encode(params.prologue_perm), "AQAC");
4427 assert_eq!(b64.encode(params.odd_branch_perm), "AQIEAwAF");
4428 assert_eq!(b64.encode(params.even_branch_perm), "AgUABwYBAwQ=");
4429 assert_eq!(u64::from_le_bytes(key.filter), 13089994567570788352);
4430 let (ind1, ind2) = gen_index_keys(&key).unwrap();
4431 assert_eq!(
4432 b64.encode(&ind1.key),
4433 "fMktWafCUSPGVDvR/8LUx9f+yh3Y+PIq90XmzJ6xZhI="
4434 );
4435 assert_eq!(b64.encode(&ind1.nonce), "aDnPYFowPzhVdOsfJweaYA==");
4436 assert_eq!(
4437 b64.encode(&ind2.key),
4438 "kHMdDweFjeDFVTwQA10XQAPUAOfXzKp2ukygeLzGzHg="
4439 );
4440 assert_eq!(b64.encode(&ind2.nonce), "CSBjzSXQNTioPhDp710WCQ==");
4441 assert!((params.flags & RANDOM_TYPE_FLAG) == 0);
4442}
4443
4444#[test]
4445fn test_filehash_deserialize() {
4446 assert_eq!(
4447 FileHash([
4448 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0,
4449 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
4450 ]),
4451 serde_json::from_str(
4452 "\"000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0F\""
4453 )
4454 .unwrap()
4455 );
4456}
4457
4458#[test]
4459fn test_calculate_path_hash() {
4460 assert_eq!(
4461 calculate_path_hash("", "xp3hnp"),
4462 PathHash::try_from("94d4a97c61498621").unwrap(),
4463 );
4464 assert_eq!(
4465 calculate_path_hash("scenario/scripts/", "xp3hnp"),
4466 PathHash::try_from("c81c19411c1a5e54").unwrap(),
4467 );
4468}