use crate::Result;
use crate::format::property_id;
use crate::format::reader::write_variable_u64;
use super::encoding_utils::encode_bool_vector;
use super::options::WriteOptions;
use super::{FilteredFolderInfo, PendingEntry, StreamInfo};
enum PlannedCoder<'a> {
#[cfg(feature = "aes")]
Aes(&'a [u8]),
Filter(&'a FilteredFolderInfo),
Compression,
Bcj2,
Bcj2Stream {
method: crate::codec::CodecMethod,
properties: &'a [u8],
},
}
struct FolderPlan<'a> {
coders: Vec<(PlannedCoder<'a>, u64)>,
bind_pairs: Vec<(u64, u64)>,
packed_indices: Vec<u64>,
}
pub(crate) struct HeaderModel<'a> {
pub(crate) stream_info: &'a StreamInfo,
pub(crate) entries: &'a [PendingEntry],
pub(crate) options: &'a WriteOptions,
}
impl HeaderModel<'_> {
pub(crate) fn encode_header(&self) -> Result<Vec<u8>> {
let mut header = Vec::new();
header.push(property_id::HEADER);
let has_bcj2 = self
.stream_info
.bcj2_folder_info
.iter()
.any(|f| f.is_some());
let total_pack_streams: usize = self
.stream_info
.bcj2_folder_info
.iter()
.map(|f| if f.is_some() { 4 } else { 1 })
.sum();
let has_streams = !self.stream_info.pack_sizes.is_empty() || has_bcj2;
if has_streams {
header.push(property_id::MAIN_STREAMS_INFO);
header.push(property_id::PACK_INFO);
write_variable_u64(&mut header, 0)?; write_variable_u64(&mut header, total_pack_streams as u64)?;
header.push(property_id::SIZE);
let mut non_bcj2_pack_idx = 0;
for bcj2_info in self.stream_info.bcj2_folder_info.iter() {
if let Some(info) = bcj2_info {
for &size in &info.pack_sizes {
write_variable_u64(&mut header, size)?;
}
} else {
if let Some(&size) = self.stream_info.pack_sizes.get(non_bcj2_pack_idx) {
write_variable_u64(&mut header, size)?;
}
non_bcj2_pack_idx += 1;
}
}
header.push(property_id::END);
header.push(property_id::UNPACK_INFO);
header.push(property_id::FOLDER);
write_variable_u64(&mut header, self.stream_info.unpack_sizes.len() as u64)?;
header.push(0);
for (i, &unpack_size) in self.stream_info.unpack_sizes.iter().enumerate() {
self.encode_folder(&mut header, i, unpack_size)?;
}
header.push(property_id::CODERS_UNPACK_SIZE);
for (i, &unpack_size) in self.stream_info.unpack_sizes.iter().enumerate() {
self.encode_unpack_sizes(&mut header, i, unpack_size)?;
}
let defined: Vec<bool> = self.stream_info.crcs.iter().map(Option::is_some).collect();
if defined.iter().any(|d| *d) {
header.push(property_id::CRC);
if defined.iter().all(|d| *d) {
header.push(1); } else {
header.push(0);
header.extend_from_slice(&encode_bool_vector(&defined));
}
for crc in self.stream_info.crcs.iter().flatten() {
header.extend_from_slice(&crc.to_le_bytes());
}
}
header.push(property_id::END);
self.encode_substreams_info(&mut header)?;
header.push(property_id::END); }
self.encode_files_info(&mut header)?;
header.push(property_id::END);
Ok(header)
}
fn plan_folder(&self, folder_idx: usize, unpack_size: u64) -> Option<FolderPlan<'_>> {
if let Some(bcj2) = self
.stream_info
.bcj2_folder_info
.get(folder_idx)
.and_then(|f| f.as_ref())
{
return Some(Self::plan_bcj2_folder(bcj2, unpack_size));
}
let filter_info = self
.stream_info
.filter_info
.get(folder_idx)
.and_then(|f| f.as_ref());
#[cfg(feature = "aes")]
if let Some(enc_info) = self
.stream_info
.encryption_info
.get(folder_idx)
.and_then(|e| e.as_ref())
{
let aes = (
PlannedCoder::Aes(&enc_info.aes_properties),
enc_info.compressed_size,
);
return Some(match filter_info {
Some(flt_info) => FolderPlan {
coders: vec![
aes,
(PlannedCoder::Filter(flt_info), unpack_size),
(PlannedCoder::Compression, flt_info.filtered_size),
],
bind_pairs: vec![(2, 0), (1, 2)],
packed_indices: Vec::new(),
},
None => FolderPlan {
coders: vec![aes, (PlannedCoder::Compression, unpack_size)],
bind_pairs: vec![(1, 0)],
packed_indices: Vec::new(),
},
});
}
Some(match filter_info {
Some(flt_info) => FolderPlan {
coders: vec![
(PlannedCoder::Filter(flt_info), unpack_size),
(PlannedCoder::Compression, flt_info.filtered_size),
],
bind_pairs: vec![(0, 1)],
packed_indices: Vec::new(),
},
None => FolderPlan {
coders: vec![(PlannedCoder::Compression, unpack_size)],
bind_pairs: Vec::new(),
packed_indices: Vec::new(),
},
})
}
fn plan_bcj2_folder(bcj2: &super::Bcj2FolderInfo, unpack_size: u64) -> FolderPlan<'_> {
let stream = |index: usize| {
(
PlannedCoder::Bcj2Stream {
method: bcj2.method,
properties: &bcj2.properties[index],
},
bcj2.stream_sizes[index],
)
};
FolderPlan {
coders: vec![
(PlannedCoder::Bcj2, unpack_size),
stream(0),
stream(1),
stream(2),
],
bind_pairs: vec![(0, 1), (1, 2), (2, 3)],
packed_indices: vec![4, 5, 6, 3],
}
}
fn encode_folder(
&self,
header: &mut Vec<u8>,
folder_idx: usize,
unpack_size: u64,
) -> Result<()> {
let plan = self
.plan_folder(folder_idx, unpack_size)
.expect("every folder has a chain");
write_variable_u64(header, plan.coders.len() as u64)?;
for (coder, _) in &plan.coders {
match coder {
#[cfg(feature = "aes")]
PlannedCoder::Aes(properties) => self.write_aes_coder(header, properties)?,
PlannedCoder::Filter(info) => self.write_filter_coder(header, info)?,
PlannedCoder::Compression => self.write_compression_coder(header, folder_idx)?,
PlannedCoder::Bcj2 => write_bcj2_coder(header)?,
PlannedCoder::Bcj2Stream { method, properties } => {
write_coder(header, method.method_id(), properties)?
}
}
}
for &(in_index, out_index) in &plan.bind_pairs {
write_variable_u64(header, in_index)?;
write_variable_u64(header, out_index)?;
}
for &index in &plan.packed_indices {
write_variable_u64(header, index)?;
}
Ok(())
}
fn encode_unpack_sizes(
&self,
header: &mut Vec<u8>,
folder_idx: usize,
unpack_size: u64,
) -> Result<()> {
let plan = self
.plan_folder(folder_idx, unpack_size)
.expect("every folder has a chain");
for (_, output_size) in &plan.coders {
write_variable_u64(header, *output_size)?;
}
Ok(())
}
fn encode_substreams_info(&self, header: &mut Vec<u8>) -> Result<()> {
if self.stream_info.substream_crcs.is_empty() {
return Ok(());
}
header.push(property_id::SUBSTREAMS_INFO);
let has_multi = self
.stream_info
.num_unpack_streams_per_folder
.iter()
.any(|&n| n > 1);
if has_multi {
header.push(property_id::NUM_UNPACK_STREAM);
for &count in &self.stream_info.num_unpack_streams_per_folder {
write_variable_u64(header, count)?;
}
}
if has_multi {
header.push(property_id::SIZE);
let mut stream_idx = 0;
for &count in &self.stream_info.num_unpack_streams_per_folder {
for i in 0..(count as usize).saturating_sub(1) {
if let Some(&size) = self.stream_info.substream_sizes.get(stream_idx + i) {
write_variable_u64(header, size)?;
}
}
stream_idx += count as usize;
}
}
header.push(property_id::CRC);
header.push(1); for &crc in &self.stream_info.substream_crcs {
header.extend_from_slice(&crc.to_le_bytes());
}
header.push(property_id::END);
Ok(())
}
fn encode_files_info(&self, header: &mut Vec<u8>) -> Result<()> {
if self.entries.is_empty() {
return Ok(());
}
header.push(property_id::FILES_INFO);
write_variable_u64(header, self.entries.len() as u64)?;
let empty_entries: Vec<_> = self
.entries
.iter()
.map(|e| e.meta.is_directory || e.uncompressed_size == 0)
.collect();
if empty_entries.iter().any(|&x| x) {
header.push(property_id::EMPTY_STREAM);
let bool_vec = encode_bool_vector(&empty_entries);
write_variable_u64(header, bool_vec.len() as u64)?;
header.extend_from_slice(&bool_vec);
let empty_files: Vec<_> = self
.entries
.iter()
.filter(|e| e.meta.is_directory || e.uncompressed_size == 0)
.map(|e| !e.meta.is_directory)
.collect();
if empty_files.iter().any(|&x| x) {
header.push(property_id::EMPTY_FILE);
let bool_vec = encode_bool_vector(&empty_files);
write_variable_u64(header, bool_vec.len() as u64)?;
header.extend_from_slice(&bool_vec);
}
let anti_items: Vec<_> = self
.entries
.iter()
.filter(|e| e.meta.is_directory || e.uncompressed_size == 0)
.map(|e| e.meta.is_anti)
.collect();
if anti_items.iter().any(|&x| x) {
header.push(property_id::ANTI);
let bool_vec = encode_bool_vector(&anti_items);
write_variable_u64(header, bool_vec.len() as u64)?;
header.extend_from_slice(&bool_vec);
}
}
header.push(property_id::NAME);
let names_data = self.encode_names();
write_variable_u64(header, names_data.len() as u64 + 1)?; header.push(0); header.extend_from_slice(&names_data);
let has_mtime: Vec<_> = self
.entries
.iter()
.map(|e| e.meta.modification_time.is_some())
.collect();
if has_mtime.iter().any(|&x| x) {
header.push(property_id::MTIME);
let mtime_data = self.encode_times(&has_mtime, |e| e.meta.modification_time);
write_variable_u64(header, mtime_data.len() as u64)?;
header.extend_from_slice(&mtime_data);
}
if let Some(ref comment) = self.options.comment {
header.push(property_id::COMMENT);
let comment_data = self.encode_comment(comment);
write_variable_u64(header, comment_data.len() as u64)?;
header.extend_from_slice(&comment_data);
}
header.push(property_id::END);
Ok(())
}
pub(crate) fn coder_properties(&self, folder_idx: usize) -> &[u8] {
self.stream_info
.coder_properties
.get(folder_idx)
.map_or(&[], Vec::as_slice)
}
pub(crate) fn write_compression_coder(
&self,
header: &mut Vec<u8>,
folder_idx: usize,
) -> Result<()> {
use super::encoding_utils::encode_method_id;
let method = self
.stream_info
.coder_methods
.get(folder_idx)
.copied()
.unwrap_or(self.options.method);
let method_bytes = encode_method_id(method.method_id());
let props = self.coder_properties(folder_idx);
let flags = (method_bytes.len() as u8) | if props.is_empty() { 0 } else { 0x20 };
header.push(flags);
header.extend_from_slice(&method_bytes);
if !props.is_empty() {
write_variable_u64(header, props.len() as u64)?;
header.extend_from_slice(props);
}
Ok(())
}
#[cfg(feature = "aes")]
pub(crate) fn write_aes_coder(&self, header: &mut Vec<u8>, properties: &[u8]) -> Result<()> {
use crate::codec::method;
let flags = (method::AES.len() as u8) | 0x20; header.push(flags);
header.extend_from_slice(method::AES);
write_variable_u64(header, properties.len() as u64)?;
header.extend_from_slice(properties);
Ok(())
}
pub(crate) fn write_filter_coder(
&self,
header: &mut Vec<u8>,
info: &FilteredFolderInfo,
) -> Result<()> {
let method_id = &info.filter_method;
let has_props = info.filter_properties.is_some();
let flags = (method_id.len() as u8) | if has_props { 0x20 } else { 0 };
header.push(flags);
header.extend_from_slice(method_id);
if let Some(props) = &info.filter_properties {
write_variable_u64(header, props.len() as u64)?;
header.extend_from_slice(props);
}
Ok(())
}
}
fn write_coder(header: &mut Vec<u8>, method_id: u64, properties: &[u8]) -> Result<()> {
use super::encoding_utils::encode_method_id;
let method_bytes = encode_method_id(method_id);
let flags = (method_bytes.len() as u8) | if properties.is_empty() { 0 } else { 0x20 };
header.push(flags);
header.extend_from_slice(&method_bytes);
if !properties.is_empty() {
write_variable_u64(header, properties.len() as u64)?;
header.extend_from_slice(properties);
}
Ok(())
}
fn write_bcj2_coder(header: &mut Vec<u8>) -> Result<()> {
let method_id = crate::codec::method::BCJ2;
let flags = (method_id.len() as u8) | 0x10;
header.push(flags);
header.extend_from_slice(method_id);
write_variable_u64(header, 4)?;
write_variable_u64(header, 1)?;
Ok(())
}