mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 12:58:45 +08:00
Add support to read embbed control block (tested game: https://vndb.org/v19829 )
This commit is contained in:
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1437,6 +1437,7 @@ dependencies = [
|
||||
"memchr",
|
||||
"mozjpeg",
|
||||
"msg-tool-jpegxl-sys",
|
||||
"msg_tool_build",
|
||||
"msg_tool_macro",
|
||||
"num_cpus",
|
||||
"overf",
|
||||
@@ -1461,6 +1462,14 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msg_tool_build"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"json",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msg_tool_macro"
|
||||
version = "0.3.0"
|
||||
|
||||
@@ -92,7 +92,7 @@ hexen-haus = ["memchr", "utils-str"]
|
||||
hexen-haus-arc = ["hexen-haus"]
|
||||
hexen-haus-img = ["hexen-haus", "image"]
|
||||
kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"]
|
||||
kirikiri-arc = ["kirikiri", "adler", "fastcdc", "flate2", "include-flate", "int-enum", "parse-size", "sha2", "utils-serde-base64bytes", "zopfli", "zstd"]
|
||||
kirikiri-arc = ["kirikiri", "adler", "fastcdc", "flate2", "include-flate", "int-enum", "msg_tool_build/kirikiri-arc", "parse-size", "sha2", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"]
|
||||
kirikiri-img = ["kirikiri", "image", "libtlg-rs"]
|
||||
musica = []
|
||||
musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"]
|
||||
@@ -127,6 +127,7 @@ utils-pcm = []
|
||||
utils-psd = ["image", "flate2", "utils-bit-stream"]
|
||||
utils-rc4 = []
|
||||
utils-serde-base64bytes = ["base64"]
|
||||
utils-simple-pack = ["zstd"]
|
||||
utils-str = []
|
||||
utils-xored-stream = []
|
||||
|
||||
@@ -134,4 +135,5 @@ utils-xored-stream = []
|
||||
windows-sys = { version = "0.61", features = ["Win32_Globalization", "Win32_System_Diagnostics_Debug"] }
|
||||
|
||||
[build-dependencies]
|
||||
msg_tool_build = { path = "./msg_tool_build", optional = true }
|
||||
parse-size = "1.1"
|
||||
|
||||
14
build.rs
14
build.rs
@@ -10,4 +10,18 @@ fn main() {
|
||||
println!("cargo:rustc-link-arg=/STACK:{}", stack_size);
|
||||
#[cfg(target_env = "gnu")]
|
||||
println!("cargo:rustc-link-arg=-Wl,-z,stack-size={}", stack_size);
|
||||
#[cfg(feature = "kirikiri-arc")]
|
||||
{
|
||||
let source_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let crypt_json_path = source_dir.join("src/scripts/kirikiri/archive/xp3/crypt.json");
|
||||
let outdir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
let level = std::env::var("MSG_TOOL_KIRIKIRI_ARC_GEN_LEVEL").unwrap_or("22".to_string());
|
||||
println!("cargo:rerun-if-env-changed=OUT_DIR");
|
||||
println!("cargo:rerun-if-changed={}", crypt_json_path.display());
|
||||
let level = level
|
||||
.parse::<i32>()
|
||||
.expect("MSG_TOOL_KIRIKIRI_ARC_GEN_LEVEL must be a valid integer");
|
||||
println!("cargo:rerun-if-env-changed=MSG_TOOL_KIRIKIRI_ARC_GEN_LEVEL");
|
||||
msg_tool_build::kr_arc::gen_cx_cb(&crypt_json_path, &outdir, level).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
2
msg_tool_build/.gitignore
vendored
Normal file
2
msg_tool_build/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Cargo.lock
|
||||
target/
|
||||
18
msg_tool_build/Cargo.toml
Normal file
18
msg_tool_build/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "msg_tool_build"
|
||||
version = "0.3.1"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/lifegpc/msg-tool"
|
||||
description = "Build time library for the msg-tool project."
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
json = { version = "0.12", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
|
||||
[features]
|
||||
kirikiri-arc = ["json", "simple-pack"]
|
||||
simple-pack = ["zstd"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
38
msg_tool_build/src/kr_arc.rs
Normal file
38
msg_tool_build/src/kr_arc.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use crate::simple_pack::SimplePack;
|
||||
use std::path::Path;
|
||||
|
||||
/// Pack all binary files in cx_cb into a single archive.
|
||||
pub fn gen_cx_cb<P: AsRef<Path> + ?Sized, D: AsRef<Path> + ?Sized>(
|
||||
json_path: &P,
|
||||
outdir: &D,
|
||||
level: i32,
|
||||
) -> std::io::Result<()> {
|
||||
let p = json_path.as_ref();
|
||||
let pb = p
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(""))
|
||||
.join("crypt")
|
||||
.join("cx_cb");
|
||||
let json_data = std::fs::read_to_string(p)?;
|
||||
let json = json::parse(&json_data)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||
let mut pack = SimplePack::new(&outdir.as_ref().join("cx_cb.pck"))?;
|
||||
for (_, obj) in json.entries() {
|
||||
if let Some(name) = obj["ControlBlockName"].as_str() {
|
||||
let file_path = pb.join(name);
|
||||
if !file_path.exists() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("File not found: {}", file_path.display()),
|
||||
));
|
||||
}
|
||||
let file = std::fs::File::open(file_path)?;
|
||||
let file = std::io::BufReader::new(file);
|
||||
pack.add_file(name, file)?;
|
||||
}
|
||||
}
|
||||
if level >= 0 && level <= 22 {
|
||||
pack.compress(level)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
4
msg_tool_build/src/lib.rs
Normal file
4
msg_tool_build/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[cfg(feature = "kirikiri-arc")]
|
||||
pub mod kr_arc;
|
||||
#[cfg(feature = "simple-pack")]
|
||||
mod simple_pack;
|
||||
66
msg_tool_build/src/simple_pack.rs
Normal file
66
msg_tool_build/src/simple_pack.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
//! A simple implementation of a pack file
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Read, Result, Seek, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct SimplePack {
|
||||
file: File,
|
||||
path: PathBuf,
|
||||
tmp_path: PathBuf,
|
||||
}
|
||||
|
||||
impl SimplePack {
|
||||
pub fn new<P: AsRef<Path> + ?Sized>(path: &P) -> Result<Self> {
|
||||
let mut file = File::create(path.as_ref())?;
|
||||
file.write_all(b"SPCK")?;
|
||||
file.write_all(&[0])?; // No compression
|
||||
Ok(Self {
|
||||
file,
|
||||
path: path.as_ref().to_path_buf(),
|
||||
tmp_path: path.as_ref().with_added_extension(".tmp"),
|
||||
})
|
||||
}
|
||||
pub fn add_file<R: Read>(&mut self, name: &str, mut data: R) -> Result<()> {
|
||||
let mut writer = BufWriter::new(&mut self.file);
|
||||
writer.write_all(name.as_bytes())?;
|
||||
writer.write_all(&[0])?; // Null terminator for the name
|
||||
let file_size_loc = writer.stream_position()?;
|
||||
writer.write_all(&0u64.to_le_bytes())?; // Placeholder for file size
|
||||
let size = std::io::copy(&mut data, &mut writer)?;
|
||||
let current_pos = writer.stream_position()?;
|
||||
writer.seek(std::io::SeekFrom::Start(file_size_loc))?;
|
||||
writer.write_all(&size.to_le_bytes())?; // Write the actual file size
|
||||
writer.seek(std::io::SeekFrom::Start(current_pos))?; // Move back to the end of the file
|
||||
writer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn compress(mut self, level: i32) -> Result<()> {
|
||||
self.file.flush()?;
|
||||
std::mem::drop(self.file); // Close the file before renaming
|
||||
// Move the file to a temporary location
|
||||
std::fs::rename(&self.path, &self.tmp_path)?;
|
||||
{
|
||||
let tmp_file = File::open(&self.tmp_path)?;
|
||||
let mut reader = std::io::BufReader::new(tmp_file);
|
||||
reader.seek_relative(5)?; // Skip header
|
||||
let original_size = reader.get_ref().metadata()?.len() - 5;
|
||||
let outfile = File::create(&self.path)?;
|
||||
let mut writer = std::io::BufWriter::new(outfile);
|
||||
writer.write_all(b"SPCK")?;
|
||||
writer.write_all(&[1])?; // Compression flag
|
||||
let compress_size_loc = writer.stream_position()?;
|
||||
writer.write_all(&0u64.to_le_bytes())?; // Placeholder for compressed size
|
||||
writer.write_all(&original_size.to_le_bytes())?;
|
||||
let cur_loc = writer.stream_position()?;
|
||||
let mut encoder = zstd::stream::write::Encoder::new(&mut writer, level)?;
|
||||
std::io::copy(&mut reader, &mut encoder)?;
|
||||
encoder.finish()?;
|
||||
writer.flush()?;
|
||||
let compressed_size = writer.stream_position()? - cur_loc;
|
||||
writer.seek(std::io::SeekFrom::Start(compress_size_loc))?;
|
||||
writer.write_all(&compressed_size.to_le_bytes())?; // Write the actual compressed size
|
||||
}
|
||||
std::fs::remove_file(&self.tmp_path)?; // Clean up the temporary file
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -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