#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
use super::document::{LsfAttribute, LsfDocument, LsfMetadataFormat, LsfNode};
use crate::error::{Error, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use std::fs::File;
use std::io::{Cursor, Read};
use std::path::Path;
const LSF_VER_INITIAL: u32 = 1;
const LSF_VER_EXTENDED_NODES: u32 = 3;
const LSF_VER_BG3_NODE_KEYS: u32 = 6;
pub fn read_lsf<P: AsRef<Path>>(path: P) -> Result<LsfDocument> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
parse_lsf_bytes(&buffer)
}
pub fn parse_lsf_bytes(data: &[u8]) -> Result<LsfDocument> {
let mut cursor = Cursor::new(data);
let mut magic = [0u8; 4];
cursor.read_exact(&mut magic)?;
if &magic != b"LSOF" {
return Err(Error::InvalidLsfMagic(magic));
}
let version = cursor.read_u32::<LittleEndian>()?;
if !(LSF_VER_INITIAL..=7).contains(&version) {
return Err(Error::UnsupportedLsfVersion { version });
}
let engine_version = cursor.read_u64::<LittleEndian>()?;
let strings_uncompressed = cursor.read_u32::<LittleEndian>()? as usize;
let strings_compressed = cursor.read_u32::<LittleEndian>()? as usize;
let (keys_uncompressed, keys_compressed) = if version >= LSF_VER_BG3_NODE_KEYS {
let u = cursor.read_u32::<LittleEndian>()? as usize;
let c = cursor.read_u32::<LittleEndian>()? as usize;
(u, c)
} else {
(0, 0)
};
let nodes_uncompressed = cursor.read_u32::<LittleEndian>()? as usize;
let nodes_compressed = cursor.read_u32::<LittleEndian>()? as usize;
let attributes_uncompressed = cursor.read_u32::<LittleEndian>()? as usize;
let attributes_compressed = cursor.read_u32::<LittleEndian>()? as usize;
let values_uncompressed = cursor.read_u32::<LittleEndian>()? as usize;
let values_compressed = cursor.read_u32::<LittleEndian>()? as usize;
let compression_flags = cursor.read_u32::<LittleEndian>()?;
let metadata_format_raw = cursor.read_u32::<LittleEndian>()?;
let metadata_format = LsfMetadataFormat::from(metadata_format_raw);
let compression_method = compression_flags & 0x0F;
let is_compressed = compression_method != 0;
let has_extended_nodes =
version >= LSF_VER_EXTENDED_NODES && metadata_format == LsfMetadataFormat::KeysAndAdjacency;
let names = read_names(
&mut cursor,
strings_uncompressed,
strings_compressed,
is_compressed,
)?;
let node_extended_format = detect_extended_format(nodes_uncompressed, has_extended_nodes);
let nodes = read_nodes(
&mut cursor,
nodes_uncompressed,
nodes_compressed,
is_compressed,
node_extended_format,
)?;
let attributes = read_attributes(
&mut cursor,
attributes_uncompressed,
attributes_compressed,
is_compressed,
node_extended_format,
&nodes,
)?;
let values = read_section(
&mut cursor,
values_uncompressed,
values_compressed,
is_compressed,
)?;
let node_keys = if version >= LSF_VER_BG3_NODE_KEYS && keys_uncompressed > 0 {
let keys_data = read_section(
&mut cursor,
keys_uncompressed,
keys_compressed,
is_compressed,
)?;
parse_keys(&keys_data, &names, nodes.len())?
} else {
vec![None; nodes.len()]
};
let has_keys_section = version >= LSF_VER_BG3_NODE_KEYS && keys_uncompressed > 0;
Ok(LsfDocument {
engine_version,
names,
nodes,
attributes,
values,
node_keys,
has_keys_section,
metadata_format,
})
}
fn detect_extended_format(data_size: usize, version_hint: bool) -> bool {
if data_size % 16 == 0 && data_size % 12 != 0 {
true } else if data_size % 12 == 0 && data_size % 16 != 0 {
false } else {
version_hint
}
}
fn read_section<R: Read>(
reader: &mut R,
uncompressed_size: usize,
compressed_size: usize,
is_compressed: bool,
) -> Result<Vec<u8>> {
if uncompressed_size == 0 {
return Ok(Vec::new());
}
let read_size = if is_compressed && compressed_size > 0 {
compressed_size
} else {
uncompressed_size
};
let mut buffer = vec![0u8; read_size];
reader.read_exact(&mut buffer)?;
if is_compressed && compressed_size > 0 {
let mut decoder = lz4_flex::frame::FrameDecoder::new(Cursor::new(&buffer));
let mut decompressed = Vec::new();
if decoder.read_to_end(&mut decompressed).is_ok() {
return Ok(decompressed);
}
lz4_flex::block::decompress(&buffer, uncompressed_size)
.map_err(|e| Error::DecompressionError(format!("LZ4: {e}")))
} else {
Ok(buffer)
}
}
fn read_names<R: Read>(
reader: &mut R,
uncompressed_size: usize,
compressed_size: usize,
is_compressed: bool,
) -> Result<Vec<Vec<String>>> {
let data = read_section(reader, uncompressed_size, compressed_size, is_compressed)?;
if data.is_empty() {
return Ok(Vec::new());
}
let mut cursor = Cursor::new(data);
let num_hash_entries = cursor.read_u32::<LittleEndian>()? as usize;
let mut names = Vec::with_capacity(num_hash_entries);
for _ in 0..num_hash_entries {
let num_strings = cursor.read_u16::<LittleEndian>()? as usize;
let mut string_list = Vec::with_capacity(num_strings);
for _ in 0..num_strings {
let string_len = cursor.read_u16::<LittleEndian>()? as usize;
let mut string_bytes = vec![0u8; string_len];
cursor.read_exact(&mut string_bytes)?;
string_list.push(String::from_utf8_lossy(&string_bytes).into_owned());
}
names.push(string_list);
}
Ok(names)
}
fn read_nodes<R: Read>(
reader: &mut R,
uncompressed_size: usize,
compressed_size: usize,
is_compressed: bool,
extended_format: bool,
) -> Result<Vec<LsfNode>> {
let data = read_section(reader, uncompressed_size, compressed_size, is_compressed)?;
if data.is_empty() {
return Ok(Vec::new());
}
let mut cursor = Cursor::new(&data);
let node_size = if extended_format { 16 } else { 12 };
let node_count = data.len() / node_size;
let mut nodes = Vec::with_capacity(node_count);
for _ in 0..node_count {
let name_hash_table_index = cursor.read_u32::<LittleEndian>()?;
let name_index_outer = (name_hash_table_index >> 16) as usize;
let name_index_inner = (name_hash_table_index & 0xFFFF) as usize;
if extended_format {
let parent_index = cursor.read_i32::<LittleEndian>()?;
let _next_sibling_index = cursor.read_i32::<LittleEndian>()?;
let first_attribute_index = cursor.read_i32::<LittleEndian>()?;
nodes.push(LsfNode {
name_index_outer,
name_index_inner,
parent_index,
first_attribute_index,
});
} else {
let first_attribute_index = cursor.read_i32::<LittleEndian>()?;
let parent_index = cursor.read_i32::<LittleEndian>()?;
nodes.push(LsfNode {
name_index_outer,
name_index_inner,
parent_index,
first_attribute_index,
});
}
}
Ok(nodes)
}
fn read_attributes<R: Read>(
reader: &mut R,
uncompressed_size: usize,
compressed_size: usize,
is_compressed: bool,
extended_format: bool,
_nodes: &[LsfNode],
) -> Result<Vec<LsfAttribute>> {
let data = read_section(reader, uncompressed_size, compressed_size, is_compressed)?;
if data.is_empty() {
return Ok(Vec::new());
}
let mut cursor = Cursor::new(&data);
let attr_size = if extended_format { 16 } else { 12 };
let attr_count = data.len() / attr_size;
let mut attributes = Vec::with_capacity(attr_count);
let mut current_data_offset: usize = 0;
for _ in 0..attr_count {
let name_hash_table_index = cursor.read_u32::<LittleEndian>()?;
let name_index_outer = (name_hash_table_index >> 16) as usize;
let name_index_inner = (name_hash_table_index & 0xFFFF) as usize;
let type_and_length = cursor.read_u32::<LittleEndian>()?;
let length = (type_and_length >> 6) as usize;
if extended_format {
let next_index = cursor.read_i32::<LittleEndian>()?;
let offset = cursor.read_u32::<LittleEndian>()? as usize;
attributes.push(LsfAttribute {
name_index_outer,
name_index_inner,
type_info: type_and_length,
next_index,
offset,
});
} else {
let node_index = cursor.read_i32::<LittleEndian>()?;
let offset = current_data_offset;
current_data_offset += length;
attributes.push(LsfAttribute {
name_index_outer,
name_index_inner,
type_info: type_and_length,
next_index: -(node_index + 1), offset,
});
}
}
if !extended_format {
let mut node_attrs: std::collections::HashMap<i32, Vec<usize>> =
std::collections::HashMap::new();
for (attr_idx, attr) in attributes.iter().enumerate() {
let node_index = -(attr.next_index + 1);
node_attrs.entry(node_index).or_default().push(attr_idx);
}
for attr_indices in node_attrs.values() {
for i in 0..attr_indices.len() {
let attr_idx = attr_indices[i];
if i + 1 < attr_indices.len() {
attributes[attr_idx].next_index = attr_indices[i + 1] as i32;
} else {
attributes[attr_idx].next_index = -1;
}
}
}
}
Ok(attributes)
}
fn parse_keys(
data: &[u8],
names: &[Vec<String>],
node_count: usize,
) -> Result<Vec<Option<String>>> {
if data.is_empty() {
return Ok(vec![None; node_count]);
}
let mut cursor = Cursor::new(data);
let mut keys = vec![None; node_count];
while cursor.position() < cursor.get_ref().len() as u64 {
let node_index = cursor.read_u32::<LittleEndian>()? as usize;
let name_hash_table_index = cursor.read_u32::<LittleEndian>()?;
let name_index_outer = (name_hash_table_index >> 16) as usize;
let name_index_inner = (name_hash_table_index & 0xFFFF) as usize;
if name_index_outer == 0xFFFF || name_index_inner == 0xFFFF {
continue;
}
if let Some(name_list) = names.get(name_index_outer)
&& let Some(key_name) = name_list.get(name_index_inner)
&& node_index < keys.len()
{
keys[node_index] = Some(key_name.clone());
}
}
Ok(keys)
}