#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
use super::document::LsfDocument;
use crate::error::Result;
use byteorder::{LittleEndian, WriteBytesExt};
use std::io::Write;
use std::path::Path;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum LsfFormat {
#[default]
V2,
V3,
}
fn compress_lz4_block(data: &[u8]) -> Vec<u8> {
if data.is_empty() {
return Vec::new();
}
lz4_flex::block::compress(data)
}
fn compress_lz4_frame(data: &[u8]) -> std::io::Result<Vec<u8>> {
if data.is_empty() {
return Ok(Vec::new());
}
let mut encoder = lz4_flex::frame::FrameEncoder::new(Vec::new());
encoder.write_all(data)?;
Ok(encoder.finish()?)
}
pub fn write_lsf<P: AsRef<Path>>(doc: &LsfDocument, path: P) -> Result<()> {
write_lsf_with_format(doc, path, LsfFormat::V2)
}
pub fn write_lsf_with_format<P: AsRef<Path>>(
doc: &LsfDocument,
path: P,
format: LsfFormat,
) -> Result<()> {
let bytes = serialize_lsf_with_format(doc, format)?;
std::fs::write(path, bytes)?;
Ok(())
}
pub fn serialize_lsf(doc: &LsfDocument) -> Result<Vec<u8>> {
serialize_lsf_with_format(doc, LsfFormat::V2)
}
pub fn serialize_lsf_with_format(doc: &LsfDocument, format: LsfFormat) -> Result<Vec<u8>> {
let mut output = Vec::new();
output.extend_from_slice(b"LSOF");
output.write_u32::<LittleEndian>(6)?;
output.write_u64::<LittleEndian>(doc.engine_version)?;
let names_data = write_names(doc)?;
let keys_data = write_keys(doc)?;
let nodes_data = write_nodes(doc, format)?;
let attributes_data = write_attributes(doc, format)?;
let values_data = &doc.values;
let names_compressed = compress_lz4_block(&names_data);
let keys_compressed = compress_lz4_frame(&keys_data)?;
let nodes_compressed = compress_lz4_frame(&nodes_data)?;
let attributes_compressed = compress_lz4_frame(&attributes_data)?;
let values_compressed = compress_lz4_frame(values_data)?;
output.write_u32::<LittleEndian>(names_data.len() as u32)?;
output.write_u32::<LittleEndian>(names_compressed.len() as u32)?;
output.write_u32::<LittleEndian>(keys_data.len() as u32)?;
output.write_u32::<LittleEndian>(keys_compressed.len() as u32)?;
output.write_u32::<LittleEndian>(nodes_data.len() as u32)?;
output.write_u32::<LittleEndian>(nodes_compressed.len() as u32)?;
output.write_u32::<LittleEndian>(attributes_data.len() as u32)?;
output.write_u32::<LittleEndian>(attributes_compressed.len() as u32)?;
output.write_u32::<LittleEndian>(values_data.len() as u32)?;
output.write_u32::<LittleEndian>(values_compressed.len() as u32)?;
output.write_u32::<LittleEndian>(0x22)?;
let metadata_format = match format {
LsfFormat::V3 => 1u32, LsfFormat::V2 => 0u32, };
output.write_u32::<LittleEndian>(metadata_format)?;
output.extend_from_slice(&names_compressed);
output.extend_from_slice(&nodes_compressed);
output.extend_from_slice(&attributes_compressed);
output.extend_from_slice(&values_compressed);
output.extend_from_slice(&keys_compressed);
Ok(output)
}
fn write_names(doc: &LsfDocument) -> Result<Vec<u8>> {
let mut buffer = Vec::new();
buffer.write_u32::<LittleEndian>(doc.names.len() as u32)?;
for name_list in &doc.names {
buffer.write_u16::<LittleEndian>(name_list.len() as u16)?;
for name in name_list {
buffer.write_u16::<LittleEndian>(name.len() as u16)?;
buffer.extend_from_slice(name.as_bytes());
}
}
Ok(buffer)
}
fn write_keys(doc: &LsfDocument) -> Result<Vec<u8>> {
let mut buffer = Vec::new();
for (node_idx, key_opt) in doc.node_keys.iter().enumerate() {
if let Some(key) = key_opt
&& let Some((outer, inner)) = doc.find_name_indices(key)
{
buffer.write_u32::<LittleEndian>(node_idx as u32)?;
let packed_name = ((outer as u32) << 16) | (inner as u32);
buffer.write_u32::<LittleEndian>(packed_name)?;
}
}
Ok(buffer)
}
fn write_nodes(doc: &LsfDocument, format: LsfFormat) -> Result<Vec<u8>> {
let mut buffer = Vec::new();
for node in &doc.nodes {
buffer.write_u16::<LittleEndian>(node.name_index_inner as u16)?;
buffer.write_u16::<LittleEndian>(node.name_index_outer as u16)?;
match format {
LsfFormat::V2 => {
buffer.write_i32::<LittleEndian>(node.first_attribute_index)?;
buffer.write_i32::<LittleEndian>(node.parent_index)?;
}
LsfFormat::V3 => {
buffer.write_i32::<LittleEndian>(node.parent_index)?;
buffer.write_i32::<LittleEndian>(-1)?; buffer.write_i32::<LittleEndian>(node.first_attribute_index)?;
}
}
}
Ok(buffer)
}
fn write_attributes(doc: &LsfDocument, format: LsfFormat) -> Result<Vec<u8>> {
let mut buffer = Vec::new();
match format {
LsfFormat::V2 => {
let attr_to_node = build_attr_to_node_map(doc);
for (attr_idx, attr) in doc.attributes.iter().enumerate() {
buffer.write_u16::<LittleEndian>(attr.name_index_inner as u16)?;
buffer.write_u16::<LittleEndian>(attr.name_index_outer as u16)?;
buffer.write_u32::<LittleEndian>(attr.type_info)?;
let node_index = attr_to_node.get(&attr_idx).copied().unwrap_or(-1);
buffer.write_i32::<LittleEndian>(node_index)?;
}
}
LsfFormat::V3 => {
for attr in &doc.attributes {
buffer.write_u16::<LittleEndian>(attr.name_index_inner as u16)?;
buffer.write_u16::<LittleEndian>(attr.name_index_outer as u16)?;
buffer.write_u32::<LittleEndian>(attr.type_info)?;
buffer.write_i32::<LittleEndian>(attr.next_index)?;
buffer.write_u32::<LittleEndian>(attr.offset as u32)?;
}
}
}
Ok(buffer)
}
fn build_attr_to_node_map(doc: &LsfDocument) -> std::collections::HashMap<usize, i32> {
let mut map = std::collections::HashMap::new();
for (node_idx, node) in doc.nodes.iter().enumerate() {
let mut attr_idx = node.first_attribute_index;
while attr_idx >= 0 {
map.insert(attr_idx as usize, node_idx as i32);
if let Some(attr) = doc.attributes.get(attr_idx as usize) {
attr_idx = attr.next_index;
} else {
break;
}
}
}
map
}