Add SenrenCxCrypt

This commit is contained in:
2026-04-08 15:32:11 +08:00
parent 36ee257776
commit a8ed40db2c
7 changed files with 417 additions and 52 deletions

7
Cargo.lock generated
View File

@@ -1345,6 +1345,12 @@ dependencies = [
"xml5ever", "xml5ever",
] ]
[[package]]
name = "md5"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.0"
@@ -1435,6 +1441,7 @@ dependencies = [
"lz4", "lz4",
"markup5ever", "markup5ever",
"markup5ever_rcdom", "markup5ever_rcdom",
"md5",
"memchr", "memchr",
"mozjpeg", "mozjpeg",
"msg-tool-jpegxl-sys", "msg-tool-jpegxl-sys",

View File

@@ -35,6 +35,7 @@ libtlg-rs = { version = "0.2", optional = true, features = ["encode"] }
lz4 = { version = "1.28", optional = true } lz4 = { version = "1.28", optional = true }
markup5ever = { version = "0.38", optional = true } markup5ever = { version = "0.38", optional = true }
markup5ever_rcdom = { version = "0.38", optional = true } markup5ever_rcdom = { version = "0.38", optional = true }
md5 = { version = "0.8", optional = true }
memchr = { version = "2.7", optional = true } memchr = { version = "2.7", optional = true }
mozjpeg = { version = "0.10", optional = true } mozjpeg = { version = "0.10", optional = true }
msg_tool_macro = { path = "./msg_tool_macro" } msg_tool_macro = { path = "./msg_tool_macro" }
@@ -94,7 +95,7 @@ hexen-haus = ["memchr", "utils-str"]
hexen-haus-arc = ["hexen-haus"] hexen-haus-arc = ["hexen-haus"]
hexen-haus-img = ["hexen-haus", "image"] hexen-haus-img = ["hexen-haus", "image"]
kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"] kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"]
kirikiri-arc = ["kirikiri", "adler", "bytes", "fastcdc", "flate2", "int-enum", "msg_tool_xp3data", "parse-size", "sha2", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"] kirikiri-arc = ["kirikiri", "adler", "bytes", "fastcdc", "flate2", "int-enum", "md5", "msg_tool_xp3data", "parse-size", "sha2", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"]
kirikiri-img = ["kirikiri", "image", "libtlg-rs"] kirikiri-img = ["kirikiri", "image", "libtlg-rs"]
musica = [] musica = []
musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"] musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"]

View File

@@ -1378,6 +1378,17 @@
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "閃電の守護騎士ニィナ" "Title": "閃電の守護騎士ニィナ"
}, },
"Senren*Banka": {
"$type": "SenrenCxCrypt",
"Mask": 308,
"Offset": 1846,
"PrologOrder": "AQAC",
"OddBranchOrder": "AwECBAAF",
"EvenBranchOrder": "BwQDBgIAAQU=",
"NamesSectionId": "sen:",
"ControlBlockName": "senren_banka.bin",
"Title": "千恋*万花 | 千戀*萬花"
},
"Shikijou Kyoudan": { "Shikijou Kyoudan": {
"$type": "HashCrypt", "$type": "HashCrypt",
"Title": "色情教団 | 妃神会秘史" "Title": "色情教団 | 妃神会秘史"

View File

@@ -0,0 +1,15 @@
©%Wœ—±Zoï*ª£î�ôóò4@xà‘VEŠ‘©÷oö-€Ô—?õ²ŒŽ-^ ›cL]2Ø7û„msŽ=Å@20}ŒÕ�Eaå{«˜ŒóœðqõdC(¨°Cù Ñ¿Õj¤"›g¹\é.Á'ì³;ÃFÂLµZ™Ü½——æ_+ïIóu¼ `û?Æ‘MŒ\ÙÜ‹ø¹æ¹o“[ÉÚ{e'Fƒ•W÷åô‡Q”nrì_?hŠÜi&°4</¸¤ó{DŠ”ù¦J.ã¿€ƒ’¼ùp¦«‡�H�Ámf�xs-!„òs£ô–r³Á;fËšþ'¤`ï‰ê"L"=ÅŒ³¿“Ÿtä䞣2Žq ^�_¿”´áê•zªmÒ¨—“`¾äL=Ĥf•&BÛLÉ)´ßÅФ¨Ü@,^Ô mù:/û?^)p¼üº´Ú�²°6ÇÙù?aÕC«0n•�Kù°[ŽÍ‡:$ íÚo™Ânî›ý€³Ì„CÊýèF§€Ç´ë[Á¬\³OͪDQ>…öºH¶Ì=Rv.mùíÁYÿžDð‰Ü¥²Á¸µçŸFb>µf“Ó1X˜aÈc«;ÙSÁ„�€Aäø…‚…�ŸgŠá†Õ©T»ð¢ÏŒ ß18"°ö¬Ï#÷vàTê ^æíüõ>�V«OÌgvÙ›/\*énÝ–gzuæeB=éI€³þdšBãÂI‰xÅÄfq…+Å„[’íÞêÄÝÖjwÛ
¼„;ùŽâÌ`\D�Ðø-é`(Æït=7Å ŽfF`©Ç?ãPÏ7IÓ%ÔÇî† øxN¬ÍÊz\¹5èK¿OÈY•ïã¬h«âˆ…J�™Í‹S�bøZaAÙnê»Xp_ÝÌ‚(#ÑkQ¦æ¦bv·~_mìÈœew�'Þl‚×»?h\‹u4F]•¨N;;‡„­Ø†G;··mq—vèÿ+N[ë>æEþ¨~NX|’ú×löeå(gÅ´N¸m\Ÿ¼Iù#n…
×°Ž�q}ŒLVÜHFë2Ê¿…æ•t�Qi!Ô·ôPñq MÆùyèÅ¢×^;ðr(GbIÒûëÄwR×#1I·h+bøÀÄôw_\€¼+J iÖ.x²O!uq�H÷ݨI‘ª¬ÿ¨½cº^”o­ë ü¼G˜ûÂ[£�2‹&“5íÙ¦Úõø9½%NëÁßDäK&þE‡çþ¸ ןe§7L\�[î,ëŠ=¯WßóðK¥Z?iP&´6?²ýKjøÖJ¶ê=L§øÙû#d 9ªýk º”¢÷­¹â|å«Z¥ïˆÔp1¶ú/òÞw¢Tp›OŠÙ'ªÇþ%C¤PƒðÂLî :SSýòÛEpÙh�i®Ä$jİü'ÞÙYÇÿp.•€¯à÷hµ�¢ìfÂÑžßnp(Dÿ~/j2´Ì„™Ú‹§“c):‹]š$“w-9Óóä¼·“^Y _yh.¹©Î:̦d“£;^£�Wô;^¸s"2›vXó$ü¨ïòdáÒåþX.”3¡ Ñè)½/ö”ë­óîbæ°jÌPÞ X›ÃqÏKoSŽY6»äŒ¸œ‰y�˜²'4Gb¬€f†»à L_YuÚV°ô9ZH¾£Gîÿ‰3ÀòGäbw ¾ÖvžX,
d ‡˜«xïekýÌbêE:ë-„s]Ç‹¦3Ë·W¼È‡Ûܦ¥,^™Ïe‚ut×v¾æf0-st>í÷îÛã»y—ÁgÅŒL�sgh±üï6�/è÷iЩŸñ
”r4‘"ÍÃòy%vSLõïÔAÊœ70ö„ªýÜf<\¦^‹õ?%ÃÖ‡¸° ñ–Îõ,œûèÞ-ÂV"æâ° ¨‘ö~©ñ€EÍ­b#Ηé £+Þa_ÊËrakki¯Ù½Áýa12´ÏFÔõ12KÎgâ•J†Æœ;žGu1øN±YL? ¬ s®X�cƒ…'Ïaˆ‚ìX®>P��BË)¸ðdg�PBD*_9¥¶ú¡Ö³3;»Á2 ´”ÕÐ&Ÿ ÷ †W€ÎUØG0ƒô)_½G>‰L%£Dõ>qÚkÆÔ˜†©ÇÜÜ(\u&¤˜¹«rˆ6z¡°íÈÉ�|¬­Ìé‰�&F ™ø{¨5ÃtnGo³Âž%ŸæÌgšX´Î—/‘‘‡0p¦ÉæÍ™¹z²bz ^pD�ÆÈ*Wš3óh„ø¶× ­\õ\2 8bìËÚ“x4/ÎÃZ¥«ùÚ�Îà ƒÁ¥W¨>�šâbu x‡-¢™Ì9ÀvÊaIq¹ŽrÍ´œ&�°fŒÖs@Fþ €ß® ݨ?”™²´o{䮦Ò2“0 ³´–!\üxš¹ áçì¿~—;‹ŽÈ‰Æfn'õ»¾µ8áöäÕ.s½ÿzU¯ÂÃα(•b÷ð eù n¤ß{Á–)z!†‚±ÛñèhÔ¨|Ó�oþŸ¾M®à?ÿŠqbÞêznÕøÐÜ„-�©tº†HnPw¢4ü)_| 2(àcÞLYsaªjŶþ
U°ÉÅ)ø
=‡à[¥dÞtýäb¯¨Œ¼èRó Ð&†ßQÆVð3�òÏ/T*®˜ý ÜÔ7*ëܨ͟æ®ôð91À¥'öŠòŒMü�Ûœ¡¦ô>s�ÓˆÐY¯%
eä;I�cE–P,žŒg—°üÍÛ(ÝS2z›“†Õúáˆÿ¬*^<Ž1± v˜ëARÙ©“Þ5t8Äë|b�¨ù¥5{У1”ã76»Ùë
ñ.tåªÑÞ¾ëŒé^=AÐ1ŸiXEó^Ô-ñwîC˜EW�N@†Á�< ’2pD`ŠÅjæµ§µ€3Ü€Š.ã.Ï,®Õ�1î1�åÛéÞÒ6&ØŸÀf6ý½„JY3@Ñ÷ïŠ-eÂë¹þGà™ jGíâ@é@î_/¾’Z“Æ]q’£Œ}ÀÞ|o0âîgÃÓ•Òì˲: A 6áïÄŽ,ƒÎ‰g) à¬Ãn!¨ú,ÉQ{årþþ‰(ñØër7[e¦»Ï{²ÏŠ·&p¦5Œ\!�¼C©˜¡Õº xÝïrU!d`¨Ý- Bý„ÊlRð@ßnu Øé'-ÕÝæQd ‡Š/”£;a}uü¼kïû-Ò½ªoa_jha&­0 ¡QìîdȠߤQ{–*’o‡åœÃON÷�¶�¼ûÔŠ]Üj×åù<‡¦Á’8Z‹×¶'�B+ß(JfÀÁï&z6þ üU2Ãt�Ùå <aK^`sœÚ¨”©ÚûÃÈ ¥ëY›>_ëÝp!E�»ÃUþ(¸ü�7vè·âssî÷ ’–Ço†aÓ KßMeŠ£™^žŒ‚$È�[«Ðþ¤ÕtIY¬YAÝöò0p8 ãZ°¦„­µ ^úO`¬~Ï*`‰”¢Séücmc8ñ¯æã“’âK}ªîË_
ëé°*�µeÖhn OÌ›õ¼g µv~,µà”¸¯&Å’�Ò-Ⱦ@ÏZþU•Óje2ŒôGØÊëZF]2ÍÓkº.Lõ 3ÎyÐXîïnezpì!:™½í·õÇv'˜“XéàfÉëežPû‹iå°¸\}ºÎÎ+‹ãoÇÚ ´°¿k‡)„ÔÞ7K÷±R<ñÁ!úûܬ}ÕÃ"ÉÁ³~ˆÌXÉù ©åI‹%_Oü;EÏÅ�ïAuu�ø%™t&º¨èÂø†ºÖ3úŽÜ9r÷*‚ªx…hJšGàƒVQâôæ Yñy5(ÃT쯔ÓA[FYæiभQ…mŸ ŽJŒ¡æ¹î6¢/œ�Âå¬7CA²¡Juæg)W(_–>¨À!?Qè¨ Ù¦vr¶€Eù šË¹{_xë/�ƒ¹¯Qc‘ÊŠÞ-¹¬0D¿ÎŠC<`€÷áÍ䦩BrÄ~5¨ï"ÅcYRë 3-ÊDžm‹`V]©PtÌ.Øqh®"x·wñ¾…#li㬹…1å]ìÙzÌD£P= „f.$ýYÆå¿ao“¤žýŸ…B’sVX»ÏVꊈ˜¤Üî0O¹pÍ msUIUªÞĽ0äÄ»ÃK¤ÁÞ¯¼°?Û�w_…î¶HHϰ”z�zåD2üÂROs ±ÕMÑÒŒ•°[+Ìhû¤:ׂyõÁ 7#{uÞOÇüƒèùZÝQÉvá­–{#™žÏôGÂCÕ;)6Pl\ì/¸@ê ;)ڽʬ)³ã‡œ…Ø›ÿhŒ—e*‰ÿóeE e�ñʉCç×n{ƒ3) {‰ÏÈZZ-ûS�4Ác�Žˆ� L9¥3b ïV‹ŽŒ~OÉÜÌ6hbå)VY‚ù;Ë?…À›É]ëõ/™s“öÅž/4å#Á}Ø€8x{É”GF 3Ö›DK�F·‹»Ê‹T5[ܨ©ó+×)à#ðAP@*jÉ/Œ‘¦ùÐ�½B[«v²ïèoHæTòêK,®Gî×:ÿÍj,ÜžõIa_âÏf¡½‹éú2)ÞD`J0¯Ô!ÓÓ·\÷Q†uïέÛ‘Ë!j4ÖCìçÚßéàBÃ4«¿tßë½,Q7rzíü>BñDÃ\­4ó!n߸·@=Ñ,¬ P ¸Ÿ#«}vÉ"¶ázB{¨à»£È“EÃ�÷
¥®âלë‚ÏÆiªr×ùC&&«)ãÃÐ}ÕQºùäÈÒ½÷\¸×ÜRÈð,˜Ù±A_C®ìò�ƒÃÒáSIÇ€³\ê î¡dB«Þbäz�Q&êûÑ”»iJÅ~�|^þÂYœz* çb¯ ˵Tó±Èëºby:˜ÌtÂ�ÖÅYÙ8�5ž ò¼ö汘ÿ[‚Gfïa[úlËnaoQý8ÿÊÿŠ2Yeê¡$+òŠâ=ø³&Óîf1!ÿ.wAÀj#÷¨‚`šæBY[è>Cî¨T>àt×PÝDH[ÒÇ�Ø}³|1dñÞ�y£ƒ%•m8'COtWz·G‹rÏa¯¢C³%Œï²K ¹/p:¤,²ò2�vY˸—(�Ö[ðr7wE=7ÅÝÖd³¢¡€/‡È«`'Òdj×»³†§Øƒ
Ìþg
½Â1hÑtÃ'ò‚Ågª5›C�™.-A•Ó H¸4Q«Cæ‘QŠ6Š�ˆN‚²ò°VWi5WC�hÐvól·ø je­Ô?«”˜
eg6R¡œ§óÒßö3‹^ËJÍÁ¥‰n{´“39qÑäý_�½L6Ž^6§Ÿó•<Lƒ‰-,apj =‹\`ÿ*#KÆ|‰?ŒþVÿN•ƒ;G¦Ä¢¸� C�à¥ÝyúlÏ»ÎGüYÎêd/§'æt$øÍ%…1J çô×ÑB§4õ²• <
¿Ê �­BÇášÜ›Sላ?

View File

@@ -90,6 +90,18 @@ impl PartialEq<&[u8; 4]> for PropTag {
} }
} }
impl PartialEq<String> for PropTag {
fn eq(&self, other: &String) -> bool {
self.tag == other.as_bytes()
}
}
impl PartialEq<&str> for PropTag {
fn eq(&self, other: &&str) -> bool {
self.tag == other.as_bytes()
}
}
impl ExtraProp { impl ExtraProp {
pub fn is_filename_hash(&self) -> bool { pub fn is_filename_hash(&self) -> bool {
self.tag == CHUNK_HNFN self.tag == CHUNK_HNFN

View File

@@ -8,13 +8,13 @@ const S_CTL_BLOCK_SIGNATURE: &[u8] = b" Encryption control block";
macro_rules! base_schema_impl { macro_rules! base_schema_impl {
() => { () => {
fn hash_after_crypt(&self) -> bool { fn hash_after_crypt(&self) -> bool {
self.base.hash_after_crypt AsRef::<BaseSchema>::as_ref(self.as_ref()).hash_after_crypt
} }
fn startup_tjs_not_encrypted(&self) -> bool { fn startup_tjs_not_encrypted(&self) -> bool {
self.base.startup_tjs_not_encrypted AsRef::<BaseSchema>::as_ref(self.as_ref()).startup_tjs_not_encrypted
} }
fn obfuscated_index(&self) -> bool { fn obfuscated_index(&self) -> bool {
self.base.obfuscated_index AsRef::<BaseSchema>::as_ref(self.as_ref()).obfuscated_index
} }
}; };
} }
@@ -27,12 +27,60 @@ pub struct CxEncryption {
odd_branch_order: Vec<u8>, odd_branch_order: Vec<u8>,
even_branch_order: Vec<u8>, even_branch_order: Vec<u8>,
control_block: Arc<Vec<u32>>, control_block: Arc<Vec<u32>>,
programs: Vec<CxProgram>, programs: Vec<Box<dyn ICxProgram>>,
program_builder: Box<dyn ICxProgramBuilder>,
base: BaseSchema, base: BaseSchema,
} }
trait ICxEncryption: std::fmt::Debug {
fn get_base_offset(&self, hash: u32) -> u32;
fn inner_decrypt(
&self,
mut key: u32,
mut offset: u64,
buffer: &mut [u8],
mut pos: usize,
mut count: usize,
) -> Result<()> {
let base_offset = self.get_base_offset(key);
if offset < base_offset as u64 {
let base_length = ((base_offset as u64 - offset) as usize).min(count);
self.decode(key, offset, buffer, pos, base_length)?;
offset += base_length as u64;
pos += base_length;
count -= base_length;
}
if count > 0 {
key = (key >> 16) ^ key;
self.decode(key, offset, buffer, pos, count)?;
}
Ok(())
}
fn decode(
&self,
key: u32,
offset: u64,
buffer: &mut [u8],
pos: usize,
count: usize,
) -> Result<()>;
}
impl CxEncryption { impl CxEncryption {
pub fn new(base: BaseSchema, schema: &CxSchema, filename: &str) -> Result<Arc<Self>> { pub fn new(base: BaseSchema, schema: &CxSchema, filename: &str) -> Result<Arc<Self>> {
Ok(Arc::new(Self::new_inner(
base,
schema,
filename,
Box::new(CxProgramBuilder::default()),
)?))
}
fn new_inner(
base: BaseSchema,
schema: &CxSchema,
filename: &str,
program_builder: Box<dyn ICxProgramBuilder>,
) -> Result<Self> {
if schema.prolog_order.len() != 3 { if schema.prolog_order.len() != 3 {
return Err(anyhow::anyhow!("Prolog order must have 3 elements")); return Err(anyhow::anyhow!("Prolog order must have 3 elements"));
} }
@@ -70,23 +118,20 @@ impl CxEncryption {
even_branch_order: schema.even_branch_order.bytes.clone(), even_branch_order: schema.even_branch_order.bytes.clone(),
control_block: control_block, control_block: control_block,
programs, programs,
program_builder,
}; };
for seed in 0..0x80 { for seed in 0..0x80 {
obj.programs.push(obj.generate_program(seed)?); obj.programs.push(obj.generate_program(seed)?);
} }
Ok(Arc::new(obj)) Ok(obj)
} }
fn new_program(&self, seed: u32) -> CxProgram { fn new_program(&self, seed: u32) -> Box<dyn ICxProgram> {
CxProgram { self.program_builder
code: Vec::with_capacity(CX_PROGRAM_SIZE), .build(seed, Arc::downgrade(&self.control_block))
control_block: Arc::downgrade(&self.control_block),
length: 0,
seed,
}
} }
fn generate_program(&self, seed: u32) -> Result<CxProgram> { fn generate_program(&self, seed: u32) -> Result<Box<dyn ICxProgram>> {
let mut program = self.new_program(seed); let mut program = self.new_program(seed);
for stage in (1..=5).rev() { for stage in (1..=5).rev() {
if self.emit_code(&mut program, stage) { if self.emit_code(&mut program, stage) {
@@ -135,7 +180,7 @@ impl CxEncryption {
Err(anyhow::anyhow!("TPM file not found: {}", tpm_path)) Err(anyhow::anyhow!("TPM file not found: {}", tpm_path))
} }
fn emit_code(&self, program: &mut CxProgram, stage: i32) -> bool { fn emit_code(&self, program: &mut Box<dyn ICxProgram>, stage: i32) -> bool {
program.emit_nop(5) program.emit_nop(5)
&& program.emit(MovEdiArg, 4) && program.emit(MovEdiArg, 4)
&& self.emit_body(program, stage) && self.emit_body(program, stage)
@@ -143,7 +188,7 @@ impl CxEncryption {
&& program.emit(Retn, 1) && program.emit(Retn, 1)
} }
fn emit_body(&self, program: &mut CxProgram, stage: i32) -> bool { fn emit_body(&self, program: &mut Box<dyn ICxProgram>, stage: i32) -> bool {
if stage == 1 { if stage == 1 {
return self.emit_prolog(program); return self.emit_prolog(program);
} }
@@ -174,7 +219,7 @@ impl CxEncryption {
self.emit_odd_branch(program) && program.emit(PopEbx, 1) self.emit_odd_branch(program) && program.emit(PopEbx, 1)
} }
fn emit_body2(&self, program: &mut CxProgram, stage: i32) -> bool { fn emit_body2(&self, program: &mut Box<dyn ICxProgram>, stage: i32) -> bool {
if stage == 1 { if stage == 1 {
return self.emit_prolog(program); return self.emit_prolog(program);
} }
@@ -185,7 +230,7 @@ impl CxEncryption {
}; };
r && self.emit_even_branch(program) r && self.emit_even_branch(program)
} }
fn emit_prolog(&self, program: &mut CxProgram) -> bool { fn emit_prolog(&self, program: &mut Box<dyn ICxProgram>) -> bool {
match self.prolog_order[(program.get_random() % 3) as usize] { match self.prolog_order[(program.get_random() % 3) as usize] {
2 => { 2 => {
program.emit_nop(5) program.emit_nop(5)
@@ -202,7 +247,7 @@ impl CxEncryption {
} }
} }
fn emit_even_branch(&self, program: &mut CxProgram) -> bool { fn emit_even_branch(&self, program: &mut Box<dyn ICxProgram>) -> bool {
match self.even_branch_order[(program.get_random() & 7) as usize] { match self.even_branch_order[(program.get_random() & 7) as usize] {
0 => program.emit(NotEax, 2), 0 => program.emit(NotEax, 2),
1 => program.emit(DecEax, 1), 1 => program.emit(DecEax, 1),
@@ -240,7 +285,7 @@ impl CxEncryption {
} }
} }
fn emit_odd_branch(&self, program: &mut CxProgram) -> bool { fn emit_odd_branch(&self, program: &mut Box<dyn ICxProgram>) -> bool {
match self.odd_branch_order[(program.get_random() % 6) as usize] { match self.odd_branch_order[(program.get_random() % 6) as usize] {
0 => { 0 => {
program.emit(PushEcx, 1) program.emit(PushEcx, 1)
@@ -264,10 +309,6 @@ impl CxEncryption {
} }
} }
fn get_base_offset(&self, hash: u32) -> u32 {
(hash & self.mask).wrapping_add(self.offset)
}
fn execute_xcode(&self, mut hash: u32) -> Result<(u32, u32)> { fn execute_xcode(&self, mut hash: u32) -> Result<(u32, u32)> {
let seed = hash & 0x7f; let seed = hash & 0x7f;
hash >>= 7; hash >>= 7;
@@ -276,28 +317,17 @@ impl CxEncryption {
let ret2 = program.execute(!hash)?; let ret2 = program.execute(!hash)?;
Ok((ret1, ret2)) Ok((ret1, ret2))
} }
}
fn inner_decrypt( impl AsRef<BaseSchema> for CxEncryption {
&self, fn as_ref(&self) -> &BaseSchema {
mut key: u32, &self.base
mut offset: u64, }
buffer: &mut [u8], }
mut pos: usize,
mut count: usize, impl ICxEncryption for CxEncryption {
) -> Result<()> { fn get_base_offset(&self, hash: u32) -> u32 {
let base_offset = self.get_base_offset(key); (hash & self.mask).wrapping_add(self.offset)
if offset < base_offset as u64 {
let base_length = ((base_offset as u64 - offset) as usize).min(count);
self.decode(key, offset, buffer, pos, base_length)?;
offset += base_length as u64;
pos += base_length;
count -= base_length;
}
if count > 0 {
key = (key >> 16) ^ key;
self.decode(key, offset, buffer, pos, count)?;
}
Ok(())
} }
fn decode( fn decode(
@@ -331,6 +361,68 @@ impl CxEncryption {
} }
} }
macro_rules! icx_enc_arc_impl {
($t:ident) => {
impl ICxEncryption for Arc<$t> {
fn get_base_offset(&self, hash: u32) -> u32 {
self.as_ref().get_base_offset(hash)
}
fn inner_decrypt(
&self,
key: u32,
offset: u64,
buffer: &mut [u8],
pos: usize,
count: usize,
) -> Result<()> {
self.as_ref().inner_decrypt(key, offset, buffer, pos, count)
}
fn decode(
&self,
key: u32,
offset: u64,
buffer: &mut [u8],
pos: usize,
count: usize,
) -> Result<()> {
self.as_ref().decode(key, offset, buffer, pos, count)
}
}
};
}
macro_rules! icx_enc_impl {
($t:ident) => {
impl ICxEncryption for $t {
fn get_base_offset(&self, hash: u32) -> u32 {
self.base.get_base_offset(hash)
}
fn inner_decrypt(
&self,
key: u32,
offset: u64,
buffer: &mut [u8],
pos: usize,
count: usize,
) -> Result<()> {
self.base.inner_decrypt(key, offset, buffer, pos, count)
}
fn decode(
&self,
key: u32,
offset: u64,
buffer: &mut [u8],
pos: usize,
count: usize,
) -> Result<()> {
self.base.decode(key, offset, buffer, pos, count)
}
}
};
}
icx_enc_arc_impl!(CxEncryption);
impl Crypt for Arc<CxEncryption> { impl Crypt for Arc<CxEncryption> {
base_schema_impl!(); base_schema_impl!();
fn decrypt_supported(&self) -> bool { fn decrypt_supported(&self) -> bool {
@@ -345,7 +437,10 @@ impl Crypt for Arc<CxEncryption> {
cur_seg: &Segment, cur_seg: &Segment,
stream: Box<dyn Read + 'a>, stream: Box<dyn Read + 'a>,
) -> Result<Box<dyn ReadDebug + 'a>> { ) -> Result<Box<dyn ReadDebug + 'a>> {
let key = (entry.file_hash, self.clone()); let key = (
entry.file_hash,
Box::new(self.clone()) as Box<dyn ICxEncryption + 'a>,
);
Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key))) Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
} }
fn decrypt_with_seek<'a>( fn decrypt_with_seek<'a>(
@@ -354,7 +449,10 @@ impl Crypt for Arc<CxEncryption> {
cur_seg: &Segment, cur_seg: &Segment,
stream: Box<dyn ReadSeek + 'a>, stream: Box<dyn ReadSeek + 'a>,
) -> Result<Box<dyn ReadSeek + 'a>> { ) -> Result<Box<dyn ReadSeek + 'a>> {
let key = (entry.file_hash, self.clone()); let key = (
entry.file_hash,
Box::new(self.clone()) as Box<dyn ICxEncryption + 'a>,
);
Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key))) Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
} }
} }
@@ -418,7 +516,41 @@ struct Context {
stack: Vec<u32>, stack: Vec<u32>,
} }
impl CxProgram { #[derive(Debug)]
struct CxProgramBuilder {}
impl Default for CxProgramBuilder {
fn default() -> Self {
Self {}
}
}
trait ICxProgramBuilder: std::fmt::Debug {
fn build(&self, seed: u32, control_blocks: Weak<Vec<u32>>) -> Box<dyn ICxProgram>;
}
impl ICxProgramBuilder for CxProgramBuilder {
fn build(&self, seed: u32, control_blocks: Weak<Vec<u32>>) -> Box<dyn ICxProgram> {
Box::new(CxProgram {
code: Vec::with_capacity(CX_PROGRAM_SIZE),
control_block: control_blocks,
length: 0,
seed,
})
}
}
trait ICxProgram: std::fmt::Debug {
fn execute(&self, hash: u32) -> Result<u32>;
fn clear(&mut self);
fn emit_nop(&mut self, count: usize) -> bool;
fn emit(&mut self, bytecode: CxByteCode, length: usize) -> bool;
fn emit_u32(&mut self, x: u32) -> bool;
fn emit_random(&mut self) -> bool;
fn get_random(&mut self) -> u32;
}
impl ICxProgram for CxProgram {
fn execute(&self, hash: u32) -> Result<u32> { fn execute(&self, hash: u32) -> Result<u32> {
let mut context = Context::default(); let mut context = Context::default();
let mut iterator = self.code.iter(); let mut iterator = self.code.iter();
@@ -602,7 +734,45 @@ impl CxProgram {
} }
} }
impl<R: Read> Read for CxEncryptionReader<R> { #[derive(msg_tool_macro::MyDebug)]
struct CxEncryptionReader<'a, T> {
#[skip_fmt]
inner: T,
seg_start: u64,
seg_size: u64,
pos: u64,
key: (u32, Box<dyn ICxEncryption + 'a>),
}
impl<'a, T: Read> CxEncryptionReader<'a, T> {
pub fn new(inner: T, seg: &Segment, key: (u32, Box<dyn ICxEncryption + 'a>)) -> Self {
Self {
inner,
seg_start: seg.offset_in_file,
seg_size: seg.original_size,
pos: 0,
key,
}
}
}
impl<'a, T: Read + Seek> Seek for CxEncryptionReader<'a, T> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
let new_pos: i64 = match pos {
SeekFrom::Start(offset) => offset as i64,
SeekFrom::End(offset) => self.seg_size as i64 + offset,
SeekFrom::Current(offset) => self.pos as i64 + offset,
};
let offset = new_pos - self.pos as i64;
if offset != 0 {
self.inner.seek(SeekFrom::Current(offset))?;
self.pos = new_pos as u64;
}
Ok(self.pos)
}
}
impl<'a, R: Read> Read for CxEncryptionReader<'a, R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let offset = self.seg_start + self.pos; let offset = self.seg_start + self.pos;
let count = self.inner.read(buf)?; let count = self.inner.read(buf)?;
@@ -618,3 +788,140 @@ impl<R: Read> Read for CxEncryptionReader<R> {
Ok(count) Ok(count)
} }
} }
#[derive(Debug)]
pub struct SenrenCxCrypt {
base: CxEncryption,
names_section_id: String,
}
impl AsRef<BaseSchema> for SenrenCxCrypt {
fn as_ref(&self) -> &BaseSchema {
self.base.as_ref()
}
}
impl SenrenCxCrypt {
pub fn new(
base: BaseSchema,
schema: &CxSchema,
filename: &str,
names_section_id: String,
) -> Result<Arc<Self>> {
Ok(Arc::new(Self::new_inner(
base,
schema,
filename,
Box::new(CxProgramBuilder::default()),
names_section_id,
)?))
}
fn new_inner(
base: BaseSchema,
schema: &CxSchema,
filename: &str,
program_builder: Box<dyn ICxProgramBuilder>,
names_section_id: String,
) -> Result<Self> {
let cx = CxEncryption::new_inner(base, schema, filename, program_builder)?;
Ok(Self {
base: cx,
names_section_id,
})
}
fn read_yuzu_names(&self, archive: &mut Xp3Archive) -> Result<()> {
if let Some(section) = archive
.extras
.iter()
.find(|s| s.tag == self.names_section_id)
{
let mut sreader = MemReaderRef::new(&section.data);
let offset = sreader.read_u64()? + archive.base_offset;
let unpacked_size = sreader.read_u32()?;
let packed_size = sreader.read_u32()?;
let index_stream =
MutexWrapper::new(archive.inner.clone(), offset).take(packed_size as u64);
let mut decoded = MemWriter::from_vec(Vec::with_capacity(unpacked_size as usize));
{
let mut decoder = flate2::read::ZlibDecoder::new(index_stream);
std::io::copy(&mut decoder, &mut decoded)?;
}
let decoded = decoded.into_inner();
let mut reader = MemReader::new(decoded);
let mut hash_map = HashMap::new();
let mut md5_map = HashMap::new();
let mut dir_offset = 0u64;
while !reader.is_eof() {
let _entry_sign = reader.read_u32()?;
let mut entry_size = reader.read_u64()?;
dir_offset += 12 + entry_size;
let hash = reader.read_u32()?;
let name_len = reader.read_u16()?;
entry_size -= 6;
if (name_len as u64) * 2 <= entry_size {
let name = reader.read_exact_vec((name_len) as usize * 2)?;
let name = decode_to_string(Encoding::Utf16LE, &name, true)?;
if !hash_map.contains_key(&hash) {
hash_map.insert(hash, name.clone());
}
let encoded =
encode_string(Encoding::Utf16LE, &name.to_ascii_lowercase(), true)?;
let md5 = format!("{:x}", md5::compute(encoded));
md5_map.insert(md5, name);
}
reader.pos = dir_offset as usize;
md5_map.insert("$".into(), "startup.tjs".into());
}
for entry in archive.entries.iter_mut() {
if let Some(name) = hash_map.get(&entry.file_hash) {
entry.name = name.clone();
} else if let Some(name) = md5_map.get(&entry.name) {
entry.name = name.clone();
}
}
}
archive.extras.retain(|s| s.tag != self.names_section_id);
Ok(())
}
}
icx_enc_impl!(SenrenCxCrypt);
icx_enc_arc_impl!(SenrenCxCrypt);
impl Crypt for Arc<SenrenCxCrypt> {
base_schema_impl!();
fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
default_init_crypt(archive)?;
self.read_yuzu_names(archive)
}
fn decrypt_supported(&self) -> bool {
true
}
fn decrypt_seek_supported(&self) -> bool {
true
}
fn decrypt<'a>(
&self,
entry: &Xp3Entry,
cur_seg: &Segment,
stream: Box<dyn Read + 'a>,
) -> Result<Box<dyn ReadDebug + 'a>> {
let key = (
entry.file_hash,
Box::new(self.clone()) as Box<dyn ICxEncryption + 'a>,
);
Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
}
fn decrypt_with_seek<'a>(
&self,
entry: &Xp3Entry,
cur_seg: &Segment,
stream: Box<dyn ReadSeek + 'a>,
) -> Result<Box<dyn ReadSeek + 'a>> {
let key = (
entry.file_hash,
Box::new(self.clone()) as Box<dyn ICxEncryption + 'a>,
);
Ok(Box::new(CxEncryptionReader::new(stream, cur_seg, key)))
}
}

View File

@@ -124,6 +124,12 @@ enum CryptType {
}, },
FlyingShineCrypt, FlyingShineCrypt,
CxEncryption(CxSchema), CxEncryption(CxSchema),
#[serde(rename_all = "PascalCase")]
SenrenCxCrypt {
#[serde(flatten)]
cx: CxSchema,
names_section_id: String,
},
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@@ -159,6 +165,15 @@ impl Schema {
CryptType::CxEncryption(schema) => { CryptType::CxEncryption(schema) => {
Box::new(cx::CxEncryption::new(self.base.clone(), &schema, filename)?) Box::new(cx::CxEncryption::new(self.base.clone(), &schema, filename)?)
} }
CryptType::SenrenCxCrypt {
cx,
names_section_id,
} => Box::new(cx::SenrenCxCrypt::new(
self.base.clone(),
cx,
filename,
names_section_id.clone(),
)?),
}) })
} }
} }
@@ -601,9 +616,6 @@ impl<R: Read> Read for FlyingShineCryptReader<R> {
} }
} }
// extended in cx.rs
seek_reader_key_impl!(CxEncryptionReader<T>, (u32, Arc<cx::CxEncryption>));
#[test] #[test]
fn test_deserialize_crypt() { fn test_deserialize_crypt() {
for (key, schema) in CRYPT_SCHEMA.iter() { for (key, schema) in CRYPT_SCHEMA.iter() {