mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-08 05:48:46 +08:00
Add support to read embbed control block (tested game: https://vndb.org/v19829 )
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use super::consts::*;
|
||||
use super::crypt::Crypt;
|
||||
use crate::scripts::base::ReadSeek;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -50,6 +51,12 @@ pub struct ExtraProp {
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ExtraProp {
|
||||
pub fn is_filename_hash(&self) -> bool {
|
||||
&self.tag == CHUNK_HNFN
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the entire XP3 archive
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -6,6 +6,7 @@ pub const CHUNK_FILE: &[u8; 4] = b"File";
|
||||
pub const CHUNK_INFO: &[u8; 4] = b"info";
|
||||
pub const CHUNK_SEGM: &[u8; 4] = b"segm";
|
||||
pub const CHUNK_ADLR: &[u8; 4] = b"adlr";
|
||||
pub const CHUNK_HNFN: &[u8; 4] = b"hnfn";
|
||||
|
||||
// Index entry flags
|
||||
pub const TVP_XP3_INDEX_ENCODE_METHOD_MASK: u8 = 0x07;
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
"$type": "HashCrypt",
|
||||
"Title": "25c -その箱は少女の悲鳴を漏らさない-"
|
||||
},
|
||||
"9 -Nine- Kokonotsu Kokonoka Kokonoiro": {
|
||||
"$type": "CxEncryption",
|
||||
"Mask": 518,
|
||||
"Offset": 77,
|
||||
"PrologOrder": "AgEA",
|
||||
"OddBranchOrder": "AQIDBAAF",
|
||||
"EvenBranchOrder": "BQMEBgAHAgE=",
|
||||
"ControlBlockName": "9nine_ep1.bin",
|
||||
"Title": "9-nine-ここのつここのかここのいろ | 9-nine-九次九日九重色 | 9-nine-九次九日九色"
|
||||
},
|
||||
"9 -Nine-:Episode 1": {
|
||||
"$type": "CxEncryption",
|
||||
"Mask": 299,
|
||||
"Offset": 1963,
|
||||
"PrologOrder": "AAIB",
|
||||
"OddBranchOrder": "BAUAAgED",
|
||||
"EvenBranchOrder": "BQcBBgMCAAQ=",
|
||||
"ControlBlockName": "9nine_ep1_sekai.bin"
|
||||
},
|
||||
"Aibo Nyuujoku": {
|
||||
"$type": "FlyingShineCrypt",
|
||||
"Title": "愛母乳辱~妄執の巨乳責めザンマイ~"
|
||||
|
||||
@@ -29,8 +29,20 @@ impl CxEncryption {
|
||||
}
|
||||
let control_block = if let Some(tpm_path) = &schema.tpm_file_name {
|
||||
Self::read_tpm(tpm_path, filename)?
|
||||
} else if let Some(control_block_name) = &schema.control_block_name {
|
||||
CX_CB_TABLE
|
||||
.get(control_block_name)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Control block not found in cx_cb.pck: {}",
|
||||
control_block_name
|
||||
)
|
||||
})?
|
||||
.clone()
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("TPM file name is required in schema"));
|
||||
return Err(anyhow::anyhow!(
|
||||
"TPM file name or control block is required in schema"
|
||||
));
|
||||
};
|
||||
let control_block = Arc::new(control_block);
|
||||
let programs = Vec::with_capacity(0x80);
|
||||
|
||||
18
src/scripts/kirikiri/archive/xp3/crypt/cx_cb/9nine_ep1.bin
Normal file
18
src/scripts/kirikiri/archive/xp3/crypt/cx_cb/9nine_ep1.bin
Normal file
@@ -0,0 +1,18 @@
|
||||
w��\�ɨ��i7�ED�x� Z��Vw�y0Hk��#",����0K�q�e�5��2�����
|
||||
�f@l�셷G�F#�liS����J��C�[c��n�z����5eh����� ��&3��U&�+$:��5�6�Lxcm���rg]��y[uè2t��e�I��j$
|
||||
:_cF�� �f��6���i�+��b:9}*�\v<��
|
||||
����;���N����!�7�Oד*���9������["�,m 6H$�z�2�E7<���)8?j��.�����kgTf��7�V��HWC�m��1b 7�����\�� ��.h����c+�3�*+,ڇ�����P�\����l��P&���I#ps|*�w���nd|Y
|
||||
�G�����0��8=m�Nc!���JW��d�biT�F�Ū��x��֜Ҹ%��c�9���W}Q>�s�D�36,q&��u�j9$� �iK�]J�����\�j�>�$��K17���#yi־E�s{J��}��dPc*���2�� t�����Ǣb7mҤr�W�'�ZYq��U��I�v�o�V�b˕&@Y{N+xA���1|(�I>���VC;��w���]�<�k6��s�u��fU�ma=�S�'���L<h0�L�S��u
|
||||
4l�3Dk�=�������È�ߪ�ƫ9�`«_���zY�O�ư���a��>�q]�^b�����I\8)�P?��8F߽2V��#\�5q�&�z�������S�W
|
||||
��IC��\���Ayv�2 w{d���^?S��ʓF�l�vP��}����o���wK����D��ڣA��,Ji���,�,0-�.�H-��L@Τ��0S)�� �W���k���;]Ny�4�k�T#�o��K��G/�ѩa|���uU�ص��5\>����9� @�p�+����):��5
|
||||
S-��)Wc[(��!�̰�^q�"C�:U��Ա�$��w�
|
||||
���������"d�e(�]p���ٚ�W�ZZg���7�Ky9[ w@R�6pj�*���x����N�QC�� ���O�C&6ӋA�W���+����E�<�����<�d����Yj��0k��Ql���KVh5��<�������iԣ2�g�B�PN�ǃ�� N��mߣ�h�QCApt�nF���ɂa���Y��bŃ g6#^G��!$��Mj��!&N
|
||||
�^ZV��ha��[yW���z��3���l��2X�4�r���j�T�O\"�W~��h{�F� ���}����IGc�p9��أ*��� ��'rZ��oR��c���v�l��\Z��<�'�����r3�KI��k ���O���
|
||||
�4V�����P�t�"�`?N�''��
|
||||
zC?
|
||||
��������� �G�����}��'�>�hr�6�YT
|
||||
趩v��Z��}��k��d6n�%�'-��}+��˒TK��;4ݱ)��|T��z�J�m�Gdo�g2@P\!h����%���eqO}
|
||||
y �m
|
||||
��:�M<�"Kn����F!E�����[����r��A�'KSyY v���Y��"�3I{\�R��🎦�0�c="e��&rᔆ�ݚ�EH�ۄ��QUdm.�V�5z�]o�b��3���F,��TW���7�[��#�Ό��b37�U^�t�;�]��gxb��)�'>�����K����6Zφ�K�[��H���?�ńY����2�����{���m��3#v&x���H�,�kI���*b�U����rK
|
||||
.b����bU�k��o��id�7C= ����CQ�����'f�m�M7uΩˆ
|
||||
G���:#���^}��tKǻ����5`I��WJf����x����m�'�7�M����g"��2%� �l�]�!հ���s�i��M*[����R���,2
|
||||
@@ -0,0 +1,15 @@
|
||||
v��WB6����I٬`ɥ� -
|
||||
Ti���m:'�I�����s��)���F��$����H���J����|���
|
||||
;�x�Q8C��u���V�d=���9[yE�}O��H�~i�&wW�=.�{)R��[���2>�7l�X���I�m�n#2�ؤG���7^�����WO6n�Pf�k�Թy�zr�1aL�����N��^�����N�q�Ԕ ��<E���.�j��f�u]VЭ���pt����23J�6.�$�Ɩ1�U�\����>*ւ���X���w�b8X�ѣ���n
|
||||
0FE�b��f�>�����,ܵ��_�H���ZN��mҌHoإ�/BH�̙����1r��*���v{9��IC��J`�S�#�����.t�E��x��~ˉ9�^3D��y`(Cq���Ѵ����QI��3�O
|
||||
a�Š������tC�+�jA�Ч &1�s�83f��:�����M$��%�/pJ�`�%%�v��߉�/��;~S��o��A0_Y�[�iD@?��zK=3��xآ���A����.%0TQ6'*�r|}��0��Iv���m�䥀�*<GTcr�T���c��|U�y,�pҎ��G�a��_�ܟ��}Em�g��"�=��;]n#&
|
||||
?�繻>���'�a��:�&������P�ִ�����}�ò�G�#JĄ
|
||||
��±�%� �O��1��݉�!s0X��V%LU7`�3���n'�>����UL)���Ḅ��5�y�����5�Tr��V �JM�8��<w|�]��Տa���Ws#�`�a���1à�O^�致���1�2�*\ʕ�rOS`0�[������ul����]ߥJ��N�pP贪;�s��'�vEF1+����Q���Kz�ɫ8�{ 79H(�b�0��M�"e���g�n�`t<���s�M�)���%L��Q����AxBԳI��%b[��F�UR�X3�&<F�(�{+���^V��u���sik�in�5|��t�i,�}�n��ʘ����_�h��f� 4Z[��ɬl2�ㄼ��o7
|
||||
���t���|�c�e�!��XE��I�g#c�3oo��� =w��9msv�+pQ 7w*�S�s��t
|
||||
����v-��Y��,�&u�6�YH�+L2�p�-��rc��h�C�~�'Uh�����<
|
||||
�Ֆ�/�u4��Up]D�b��X_ϣ����Iј:�x6-A��[;����E� )O5fY8�s��b���9] f�DcG�ܛ�����,t68��k�K,�de��;����_Kr�G��I�~>�z�Г8�8L��Zw�-/@���*2
|
||||
�Έ��q�ʧ|��pv?J���+����Vc�̅g�ayS��e�)���Ҫ[����LG�J\�v��������F�����@��bx� R��.�����r��qv�z;:/~��ưu+83J�+Q��eN ����M$~{��Q LG��;4m������y��ߦoo,$t�Ý��(w��� 0�o:hW�D ��9-Hp��d����+F�&Ͱ.��:X'��<u�m+�ʸf�&���F�jhTn ������Uk�Q�J���X�9E�����'wj��%J3��;/�CZ[[�,�Ԋ��',�R�Y�_\�����%E�'�߿C�(��yF����l��/�>A]E+�����1�ɝ�f^\$!�5�=;6��.�Z�\V�OA;���߆�B�� J7;�x�T��js�-�i��������>OX�������X����h.r�PUb ���=�
|
||||
H��zq�5Mg�k�!���J��]��o�l�~CL�I��J���͌��"�Pd�՝�Q��r�[��4���l K�
|
||||
�S>�5Z��N͛� �k��Lw�z��'�����O���G7R)u���T�b�'@���p[���*�����k˹�2v�#[��C�R�#����i�XZ#�S�� ��wi�`�O���bQ�'6�-x$�ȄM�$��ҙ�T:*��hΒ�(m�w��g�F*����`4�'�A�hw�N]�)�"c.^���p��g&�Wk(�i�~Z�x�~����:����b������/����7|SLԀ�i�ݻqx�,d=������o����n�o��h��U���Z0e�뭇�7B��&��"�Q-���}�?�>�9`X���G�eQ����"mI}=�q)\��g��⧅�6�|�[�&�ک�4������B<������|�-FR�PdC�<�-�q~������f�)yRэ���~
|
||||
̓<�?�a��!��
|
||||
��y�zzY��{�=�������@��ӥ�-�E����~���G���r/��=�͂���|���e:�e,�N��T��J��bP�n ĺs[
|
||||
@@ -6,16 +6,39 @@ use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::*;
|
||||
use crate::utils::serde_base64bytes::*;
|
||||
use crate::utils::simple_pack::*;
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn default_init_crypt(archive: &mut Xp3Archive) -> Result<()> {
|
||||
if archive.extras.iter().any(|extra| extra.is_filename_hash()) {
|
||||
let mut filename_map = HashMap::new();
|
||||
for extra in &archive.extras {
|
||||
if extra.is_filename_hash() {
|
||||
let mut reader = MemReaderRef::new(&extra.data);
|
||||
let hash = reader.read_u32()?;
|
||||
let name_length = reader.read_u16()?;
|
||||
let name = reader.read_exact_vec(name_length as usize * 2)?;
|
||||
let name = decode_to_string(Encoding::Utf16LE, &name, true)?;
|
||||
filename_map.insert(hash, name);
|
||||
}
|
||||
}
|
||||
for entry in &mut archive.entries {
|
||||
if let Some(name) = filename_map.get(&entry.file_hash) {
|
||||
entry.name = name.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait Crypt: std::fmt::Debug {
|
||||
/// Initializes the cryptographic context for the archive.
|
||||
fn init(&self, _archive: &mut Xp3Archive) -> Result<()> {
|
||||
Ok(())
|
||||
fn init(&self, archive: &mut Xp3Archive) -> Result<()> {
|
||||
default_init_crypt(archive)
|
||||
}
|
||||
|
||||
/// Read a entry name from archive index
|
||||
@@ -111,6 +134,7 @@ impl Schema {
|
||||
}
|
||||
|
||||
include_flate::flate!(static CRYPT_DATA: str from "src/scripts/kirikiri/archive/xp3/crypt.json" with zstd);
|
||||
const CX_CB_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/cx_cb.pck"));
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CRYPT_SCHEMA: BTreeMap<String, Schema> = {
|
||||
@@ -134,6 +158,20 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
table
|
||||
};
|
||||
static ref CX_CB_TABLE: HashMap<String, Vec<u32>> = {
|
||||
let reader = MemReaderRef::new(CX_CB_DATA);
|
||||
let mut pack = read_simple_pack(reader).expect("Failed to read cx_cb.pck");
|
||||
let mut table = HashMap::new();
|
||||
while let Some(mut entry) = pack.next().expect("Failed to read entry in cx_cb.pck") {
|
||||
let mut list = Vec::with_capacity(0x400);
|
||||
let errmsg = format!("Failed to read u32 in cx_cb.pck entry {}", entry.name);
|
||||
for _ in 0..0x400 {
|
||||
list.push(entry.read_u32().expect(&errmsg));
|
||||
}
|
||||
table.insert(entry.name.clone(), list);
|
||||
}
|
||||
table
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the supported game titles for encrypted xp3 archives.
|
||||
@@ -510,3 +548,10 @@ fn test_deserialize_crypt() {
|
||||
println!("Title: {}, Schema: {:?}", key, schema);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cx_cb_table() {
|
||||
for (key, list) in CX_CB_TABLE.iter() {
|
||||
println!("Key: {}, List length: {}", key, list.len());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ pub mod psd;
|
||||
pub mod rc4;
|
||||
#[cfg(feature = "utils-serde-base64bytes")]
|
||||
pub mod serde_base64bytes;
|
||||
#[cfg(feature = "utils-simple-pack")]
|
||||
pub mod simple_pack;
|
||||
#[cfg(feature = "utils-str")]
|
||||
pub mod str;
|
||||
pub mod struct_pack;
|
||||
|
||||
100
src/utils/simple_pack.rs
Normal file
100
src/utils/simple_pack.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::ext::io::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::*;
|
||||
use anyhow::Result;
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
pub struct SimplePack<T: Read> {
|
||||
inner: T,
|
||||
total: u64,
|
||||
current: u64,
|
||||
}
|
||||
|
||||
impl<T: Read> SimplePack<T> {
|
||||
pub fn next<'a>(&'a mut self) -> Result<Option<SimplePackEntry<'a, T>>> {
|
||||
if self.current >= self.total {
|
||||
return Ok(None);
|
||||
}
|
||||
let name = self.read_cstring()?;
|
||||
let name = decode_to_string(Encoding::Utf8, name.as_bytes(), true)?;
|
||||
let entry_size = self.read_u64()?;
|
||||
Ok(Some(SimplePackEntry {
|
||||
pack: self,
|
||||
total: entry_size,
|
||||
current: 0,
|
||||
name,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read> Read for SimplePack<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let remaining = self.total - self.current;
|
||||
if remaining == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
let to_read = std::cmp::min(remaining, buf.len() as u64) as usize;
|
||||
let bytes_read = self.inner.read(&mut buf[..to_read])?;
|
||||
self.current += bytes_read as u64;
|
||||
Ok(bytes_read)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SimplePackEntry<'a, T: Read> {
|
||||
pub pack: &'a mut SimplePack<T>,
|
||||
total: u64,
|
||||
current: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl<'a, T: Read> Drop for SimplePackEntry<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let to_skip = self.total - self.current;
|
||||
if to_skip > 0 {
|
||||
if let Err(e) = self.pack.skip(to_skip) {
|
||||
eprintln!("Failed to skip remaining bytes in SimplePackEntry: {}", e);
|
||||
crate::COUNTER.inc_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Read> Read for SimplePackEntry<'a, T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let remaining = self.total - self.current;
|
||||
if remaining == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
let to_read = std::cmp::min(remaining, buf.len() as u64) as usize;
|
||||
let bytes_read = self.pack.read(&mut buf[..to_read])?;
|
||||
self.current += bytes_read as u64;
|
||||
Ok(bytes_read)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_simple_pack<'a, T: Read + Seek + 'a>(
|
||||
mut reader: T,
|
||||
) -> Result<SimplePack<Box<dyn Read + 'a>>> {
|
||||
reader.read_and_equal(b"SPCK")?;
|
||||
let flags = reader.read_u8()?;
|
||||
// not compressed
|
||||
if flags == 0 {
|
||||
let pos = reader.stream_position()?;
|
||||
let total = reader.stream_length()? - pos;
|
||||
Ok(SimplePack {
|
||||
inner: Box::new(reader),
|
||||
total,
|
||||
current: 0,
|
||||
})
|
||||
} else {
|
||||
let compressed_size = reader.read_u64()?;
|
||||
let uncompressed_size = reader.read_u64()?;
|
||||
let compressed = reader.take(compressed_size);
|
||||
let decompressed = zstd::stream::read::Decoder::new(compressed)?;
|
||||
Ok(SimplePack {
|
||||
inner: Box::new(decompressed),
|
||||
total: uncompressed_size,
|
||||
current: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user