mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-08 13:58:50 +08:00
Add StartupTjsNotEncrypted support
This commit is contained in:
@@ -310,7 +310,8 @@
|
||||
"ControlBlockName": "fate_hollow.bin"
|
||||
},
|
||||
"Fate/stay night": {
|
||||
"$type": "FateCrypt"
|
||||
"$type": "FateCrypt",
|
||||
"HashAfterCrypt": true
|
||||
},
|
||||
"Fukashi na Aijou": {
|
||||
"$type": "HashCrypt",
|
||||
@@ -738,7 +739,8 @@
|
||||
"OddBranchOrder": "BAABBQID",
|
||||
"EvenBranchOrder": "BwUCAwYBBAA=",
|
||||
"TpmFileName": "plugin/lavender.tpm",
|
||||
"Title": "光輪の町、ラベンダーの少女"
|
||||
"Title": "光輪の町、ラベンダーの少女",
|
||||
"StartupTjsNotEncrypted": true
|
||||
},
|
||||
"Kurenai no Tsuki": {
|
||||
"$type": "CxEncryption",
|
||||
@@ -833,7 +835,8 @@
|
||||
},
|
||||
"Mizu no Kakera ~Once Summer of Islet~": {
|
||||
"$type": "MizukakeCrypt",
|
||||
"Title": "みずのかけら -once summer of islet-"
|
||||
"Title": "みずのかけら -once summer of islet-",
|
||||
"HashAfterCrypt": true
|
||||
},
|
||||
"Mizu no Miyako no Patisserie": {
|
||||
"$type": "CxEncryption",
|
||||
@@ -1035,7 +1038,8 @@
|
||||
"OddBranchOrder": "AAIEAQUD",
|
||||
"EvenBranchOrder": "BQMGAgEHBAA=",
|
||||
"ControlBlockName": "riajuu.bin",
|
||||
"Title": "リア充催眠~リアルが充実する催眠生活はじめました。~"
|
||||
"Title": "リア充催眠~リアルが充実する催眠生活はじめました。~",
|
||||
"ObfuscatedIndex": true
|
||||
},
|
||||
"Rui wa Tomo o Yobu": {
|
||||
"$type": "CxEncryption",
|
||||
@@ -1195,7 +1199,8 @@
|
||||
},
|
||||
"Sorairo no Shizuku": {
|
||||
"$type": "MizukakeCrypt",
|
||||
"Title": "そらいろの雫"
|
||||
"Title": "そらいろの雫",
|
||||
"HashAfterCrypt": true
|
||||
},
|
||||
"Soukan Chiryou Byoutou": {
|
||||
"$type": "FlyingShineCrypt",
|
||||
@@ -1429,7 +1434,8 @@
|
||||
"OddBranchOrder": "AAQDAQIF",
|
||||
"EvenBranchOrder": "AwQABQcCAQY=",
|
||||
"ControlBlockName": "zecchou.bin",
|
||||
"Title": "ぜっちょースパイラル!! ~下宿人はわがままエッチな女の子ばかり~"
|
||||
"Title": "ぜっちょースパイラル!! ~下宿人はわがままエッチな女の子ばかり~",
|
||||
"ObfuscatedIndex": true
|
||||
},
|
||||
"Zettai Karen! Ojou-sama": {
|
||||
"$type": "CxEncryption",
|
||||
|
||||
@@ -5,6 +5,20 @@ use std::sync::Weak;
|
||||
|
||||
const S_CTL_BLOCK_SIGNATURE: &[u8] = b" Encryption control block";
|
||||
|
||||
macro_rules! base_schema_impl {
|
||||
() => {
|
||||
fn hash_after_crypt(&self) -> bool {
|
||||
self.base.hash_after_crypt
|
||||
}
|
||||
fn startup_tjs_not_encrypted(&self) -> bool {
|
||||
self.base.startup_tjs_not_encrypted
|
||||
}
|
||||
fn obfuscated_index(&self) -> bool {
|
||||
self.base.obfuscated_index
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CxEncryption {
|
||||
mask: u32,
|
||||
@@ -14,10 +28,11 @@ pub struct CxEncryption {
|
||||
even_branch_order: Vec<u8>,
|
||||
control_block: Arc<Vec<u32>>,
|
||||
programs: Vec<CxProgram>,
|
||||
base: BaseSchema,
|
||||
}
|
||||
|
||||
impl CxEncryption {
|
||||
pub fn new(schema: &CxSchema, filename: &str) -> Result<Arc<Self>> {
|
||||
pub fn new(base: BaseSchema, schema: &CxSchema, filename: &str) -> Result<Arc<Self>> {
|
||||
if schema.prolog_order.len() != 3 {
|
||||
return Err(anyhow::anyhow!("Prolog order must have 3 elements"));
|
||||
}
|
||||
@@ -47,6 +62,7 @@ impl CxEncryption {
|
||||
let control_block = Arc::new(control_block);
|
||||
let programs = Vec::with_capacity(0x80);
|
||||
let mut obj = Self {
|
||||
base,
|
||||
mask: schema.mask,
|
||||
offset: schema.offset,
|
||||
prolog_order: schema.prolog_order.bytes.clone(),
|
||||
@@ -316,6 +332,7 @@ impl CxEncryption {
|
||||
}
|
||||
|
||||
impl Crypt for Arc<CxEncryption> {
|
||||
base_schema_impl!();
|
||||
fn decrypt_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -37,6 +37,19 @@ pub fn default_init_crypt(archive: &mut Xp3Archive) -> Result<()> {
|
||||
}
|
||||
|
||||
pub trait Crypt: std::fmt::Debug {
|
||||
#[allow(dead_code)]
|
||||
/// whether Adler32 checksum should be calculated after contents have been encrypted.
|
||||
fn hash_after_crypt(&self) -> bool;
|
||||
|
||||
/// whether the startup.tjs script is not encrypted even when the archive is encrypted.
|
||||
fn startup_tjs_not_encrypted(&self) -> bool;
|
||||
|
||||
/// whether XP3 index is obfuscated:
|
||||
/// - duplicate entries
|
||||
/// - entries have additional dummy segments
|
||||
#[allow(dead_code)]
|
||||
fn obfuscated_index(&self) -> bool;
|
||||
|
||||
/// Initializes the cryptographic context for the archive.
|
||||
fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
|
||||
default_init_crypt(archive)
|
||||
@@ -112,24 +125,36 @@ enum CryptType {
|
||||
CxEncryption(CxSchema),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct BaseSchema {
|
||||
hash_after_crypt: bool,
|
||||
startup_tjs_not_encrypted: bool,
|
||||
obfuscated_index: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Schema {
|
||||
#[serde(flatten)]
|
||||
crypt: CryptType,
|
||||
title: Option<String>,
|
||||
#[serde(flatten)]
|
||||
base: BaseSchema,
|
||||
}
|
||||
|
||||
impl Schema {
|
||||
pub fn create_crypt(&self, filename: &str) -> Result<Box<dyn Crypt>> {
|
||||
Ok(match &self.crypt {
|
||||
CryptType::NoCrypt => Box::new(NoCrypt::new()),
|
||||
CryptType::FateCrypt => Box::new(FateCrypt::new()),
|
||||
CryptType::MizukakeCrypt => Box::new(MizukakeCrypt::new()),
|
||||
CryptType::HashCrypt => Box::new(HashCrypt::new()),
|
||||
CryptType::XorCrypt { key } => Box::new(XorCrypt::new(*key)),
|
||||
CryptType::FlyingShineCrypt => Box::new(FlyingShineCrypt::new()),
|
||||
CryptType::CxEncryption(schema) => Box::new(cx::CxEncryption::new(&schema, filename)?),
|
||||
CryptType::FateCrypt => Box::new(FateCrypt::new(self.base.clone())),
|
||||
CryptType::MizukakeCrypt => Box::new(MizukakeCrypt::new(self.base.clone())),
|
||||
CryptType::HashCrypt => Box::new(HashCrypt::new(self.base.clone())),
|
||||
CryptType::XorCrypt { key } => Box::new(XorCrypt::new(self.base.clone(), *key)),
|
||||
CryptType::FlyingShineCrypt => Box::new(FlyingShineCrypt::new(self.base.clone())),
|
||||
CryptType::CxEncryption(schema) => {
|
||||
Box::new(cx::CxEncryption::new(self.base.clone(), &schema, filename)?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -205,7 +230,17 @@ impl NoCrypt {
|
||||
}
|
||||
}
|
||||
|
||||
impl Crypt for NoCrypt {}
|
||||
impl Crypt for NoCrypt {
|
||||
fn hash_after_crypt(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn startup_tjs_not_encrypted(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn obfuscated_index(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! seek_impl {
|
||||
($reader:ident<$t:ident>) => {
|
||||
@@ -280,16 +315,33 @@ macro_rules! seek_reader_key_impl {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! base_schema_impl {
|
||||
() => {
|
||||
fn hash_after_crypt(&self) -> bool {
|
||||
self.base.hash_after_crypt
|
||||
}
|
||||
fn startup_tjs_not_encrypted(&self) -> bool {
|
||||
self.base.startup_tjs_not_encrypted
|
||||
}
|
||||
fn obfuscated_index(&self) -> bool {
|
||||
self.base.obfuscated_index
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! seek_crypt_base_impl {
|
||||
($crypt:ident, $reader:ident) => {
|
||||
#[derive(Debug)]
|
||||
pub struct $crypt {}
|
||||
pub struct $crypt {
|
||||
base: BaseSchema,
|
||||
}
|
||||
impl $crypt {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new(base: BaseSchema) -> Self {
|
||||
Self { base }
|
||||
}
|
||||
}
|
||||
impl Crypt for $crypt {
|
||||
base_schema_impl!();
|
||||
fn decrypt_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -368,15 +420,18 @@ impl<R: Read> Read for MizukakeCryptReader<R> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HashCrypt {}
|
||||
pub struct HashCrypt {
|
||||
base: BaseSchema,
|
||||
}
|
||||
|
||||
impl HashCrypt {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new(base: BaseSchema) -> Self {
|
||||
Self { base }
|
||||
}
|
||||
}
|
||||
|
||||
impl Crypt for HashCrypt {
|
||||
base_schema_impl!();
|
||||
fn decrypt_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -424,16 +479,18 @@ impl<R: Read> Read for HashCryptReader<R> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XorCrypt {
|
||||
base: BaseSchema,
|
||||
key: u8,
|
||||
}
|
||||
|
||||
impl XorCrypt {
|
||||
pub fn new(key: u8) -> Self {
|
||||
Self { key }
|
||||
pub fn new(base: BaseSchema, key: u8) -> Self {
|
||||
Self { base, key }
|
||||
}
|
||||
}
|
||||
|
||||
impl Crypt for XorCrypt {
|
||||
base_schema_impl!();
|
||||
fn decrypt_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -472,11 +529,13 @@ impl<R: Read> Read for XorCryptReader<R> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FlyingShineCrypt {}
|
||||
pub struct FlyingShineCrypt {
|
||||
base: BaseSchema,
|
||||
}
|
||||
|
||||
impl FlyingShineCrypt {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new(base: BaseSchema) -> Self {
|
||||
Self { base }
|
||||
}
|
||||
|
||||
fn adjust(&self, hash: u32) -> (u8, u32) {
|
||||
@@ -493,6 +552,7 @@ impl FlyingShineCrypt {
|
||||
}
|
||||
|
||||
impl Crypt for FlyingShineCrypt {
|
||||
base_schema_impl!();
|
||||
fn decrypt_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ impl Xp3Archive {
|
||||
index_stream.skip(chunk_size)?;
|
||||
}
|
||||
}
|
||||
entries.push(Xp3Entry {
|
||||
let mut entry = Xp3Entry {
|
||||
name: name
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing name chunk in file entry"))?,
|
||||
flags: flags
|
||||
@@ -149,7 +149,14 @@ impl Xp3Archive {
|
||||
timestamp,
|
||||
segments,
|
||||
extras: entry_extras,
|
||||
});
|
||||
};
|
||||
if entry.name == "startup.tjs"
|
||||
&& entry.flags != 0
|
||||
&& crypt.startup_tjs_not_encrypted()
|
||||
{
|
||||
entry.flags = 0;
|
||||
}
|
||||
entries.push(entry);
|
||||
} else {
|
||||
let data = index_stream.read_exact_vec(size as usize)?;
|
||||
extras.push(ExtraProp {
|
||||
|
||||
Reference in New Issue
Block a user