Add support to read embbed control block (tested game: https://vndb.org/v19829 )

This commit is contained in:
2026-04-07 12:01:27 +08:00
parent 80150784ab
commit 7b0de4468b
17 changed files with 376 additions and 4 deletions

9
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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
View File

@@ -0,0 +1,2 @@
Cargo.lock
target/

18
msg_tool_build/Cargo.toml Normal file
View 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

View 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(())
}

View File

@@ -0,0 +1,4 @@
#[cfg(feature = "kirikiri-arc")]
pub mod kr_arc;
#[cfg(feature = "simple-pack")]
mod simple_pack;

View 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(())
}
}

View File

@@ -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)]

View File

@@ -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;

View File

@@ -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": "愛母乳辱~妄執の巨乳責めザンマイ~"

View File

@@ -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);

View 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��[y W���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

View File

@@ -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[

View File

@@ -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());
}
}

View File

@@ -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
View 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,
})
}
}