#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::wrong_self_convention
)]
use crate::converter::{ConvertPhase, ConvertProgress, ConvertProgressCallback};
use crate::error::{Error, Result};
use crate::formats::common::{
hash_string_lslib, serialize_translated_string, serialize_value, type_name_to_id,
};
use crate::formats::lsf::{self, LsfAttribute, LsfDocument, LsfMetadataFormat, LsfNode};
use quick_xml::Reader;
use quick_xml::events::Event;
use std::collections::HashMap;
use std::path::Path;
pub fn convert_lsx_to_lsf<P: AsRef<Path>>(source: P, dest: P) -> Result<()> {
convert_lsx_to_lsf_with_progress(source, dest, &|_| {})
}
pub fn convert_lsx_to_lsf_with_progress<P: AsRef<Path>>(
source: P,
dest: P,
progress: ConvertProgressCallback,
) -> Result<()> {
progress(&ConvertProgress::with_file(
ConvertPhase::ReadingSource,
0,
3,
"Reading LSX file",
));
tracing::info!(
"Converting LSX→LSF: {:?} → {:?}",
source.as_ref(),
dest.as_ref()
);
let content = std::fs::read_to_string(&source)?;
progress(&ConvertProgress::with_file(
ConvertPhase::Parsing,
1,
3,
"Parsing XML",
));
let lsf_doc = from_lsx(&content)?;
progress(&ConvertProgress::with_file(
ConvertPhase::WritingOutput,
2,
3,
"Writing LSF file",
));
lsf::write_lsf(&lsf_doc, dest)?;
progress(&ConvertProgress::new(ConvertPhase::Complete, 3, 3));
tracing::info!("Conversion complete");
Ok(())
}
pub fn from_lsx(content: &str) -> Result<LsfDocument> {
let mut reader = Reader::from_str(content);
reader.trim_text(true);
let mut buf = Vec::new();
let mut engine_version: u64 = 0;
let mut metadata_format = LsfMetadataFormat::None;
let mut string_table = StringTable::new();
let mut nodes = Vec::new();
let mut attributes: Vec<LsfAttribute> = Vec::new();
let mut values_buffer = Vec::new();
let mut node_keys = Vec::new();
let mut node_stack: Vec<usize> = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => match e.name().as_ref() {
b"version" => {
let (ver, meta) = parse_version(&e)?;
engine_version = ver;
metadata_format = meta;
}
b"node" => {
let node_idx = parse_and_create_node(
&e,
&mut string_table,
&mut nodes,
&mut node_keys,
&node_stack,
)?;
node_stack.push(node_idx);
}
b"attribute" => {
parse_and_create_attribute(
&e,
&mut string_table,
&mut attributes,
&mut values_buffer,
&mut nodes,
&node_stack,
)?;
}
_ => {}
},
Ok(Event::Empty(e)) => {
match e.name().as_ref() {
b"version" => {
let (ver, meta) = parse_version(&e)?;
engine_version = ver;
metadata_format = meta;
}
b"node" => {
parse_and_create_node(
&e,
&mut string_table,
&mut nodes,
&mut node_keys,
&node_stack,
)?;
}
b"attribute" => {
parse_and_create_attribute(
&e,
&mut string_table,
&mut attributes,
&mut values_buffer,
&mut nodes,
&node_stack,
)?;
}
_ => {}
}
}
Ok(Event::End(e)) => {
if e.name().as_ref() == b"node" {
node_stack.pop();
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(Error::XmlError(e)),
_ => {}
}
buf.clear();
}
let has_keys_section = !node_keys.iter().all(std::option::Option::is_none);
Ok(LsfDocument {
engine_version,
names: string_table.to_name_lists(),
nodes,
attributes,
values: values_buffer,
node_keys,
has_keys_section,
metadata_format,
})
}
fn parse_version(e: &quick_xml::events::BytesStart) -> Result<(u64, LsfMetadataFormat)> {
let mut major = 0u32;
let mut minor = 0u32;
let mut revision = 0u32;
let mut build = 0u32;
let mut metadata_format = LsfMetadataFormat::None;
for attr in e.attributes() {
let attr = attr?;
let value = String::from_utf8_lossy(&attr.value);
match attr.key.as_ref() {
b"major" => major = value.parse().unwrap_or(4),
b"minor" => minor = value.parse().unwrap_or(0),
b"revision" => revision = value.parse().unwrap_or(0),
b"build" => build = value.parse().unwrap_or(0),
b"lslib_meta" => {
if value.contains("lsf_keys_adjacency") {
metadata_format = LsfMetadataFormat::KeysAndAdjacency;
} else if value.contains("lsf_adjacency") {
metadata_format = LsfMetadataFormat::None2;
}
}
_ => {}
}
}
let engine_version = ((u64::from(major) & 0x7F) << 55)
| ((u64::from(minor) & 0xFF) << 47)
| ((u64::from(revision) & 0xFFFF) << 31)
| (u64::from(build) & 0x7FFFFFFF);
Ok((engine_version, metadata_format))
}
fn parse_and_create_node(
e: &quick_xml::events::BytesStart,
string_table: &mut StringTable,
nodes: &mut Vec<LsfNode>,
node_keys: &mut Vec<Option<String>>,
node_stack: &[usize],
) -> Result<usize> {
let mut node_id = String::new();
let mut node_key: Option<String> = None;
for attr in e.attributes() {
let attr = attr?;
let value = String::from_utf8_lossy(&attr.value).into_owned();
match attr.key.as_ref() {
b"id" => node_id = value,
b"key" => node_key = Some(value),
_ => {}
}
}
let (name_outer, name_inner) = string_table.get_or_insert(&node_id);
let parent_index = node_stack.last().map_or(-1, |&idx| idx as i32);
let node = LsfNode {
name_index_outer: name_outer,
name_index_inner: name_inner,
parent_index,
first_attribute_index: -1,
};
let node_idx = nodes.len();
nodes.push(node);
node_keys.push(node_key);
Ok(node_idx)
}
fn parse_and_create_attribute(
e: &quick_xml::events::BytesStart,
string_table: &mut StringTable,
attributes: &mut Vec<LsfAttribute>,
values_buffer: &mut Vec<u8>,
nodes: &mut [LsfNode],
node_stack: &[usize],
) -> Result<()> {
let mut attr_id = String::new();
let mut attr_type = String::new();
let mut attr_value = String::new();
let mut handle = String::new();
let mut version: u16 = 0;
for attr in e.attributes() {
let attr = attr?;
let value = String::from_utf8_lossy(&attr.value).into_owned();
match attr.key.as_ref() {
b"id" => attr_id = value,
b"type" => attr_type = value,
b"value" => attr_value = value,
b"handle" => handle = value,
b"version" => version = value.parse().unwrap_or(0),
_ => {}
}
}
if let Some(current_node_idx) = node_stack.last() {
let type_id = type_name_to_id(&attr_type);
let (name_outer, name_inner) = string_table.get_or_insert(&attr_id);
let value_offset = values_buffer.len();
let value_length = if type_id == 28 {
serialize_translated_string(values_buffer, &handle, version, &attr_value)?
} else {
serialize_value(values_buffer, type_id, &attr_value)?
};
let type_info = type_id | ((value_length as u32) << 6);
let attr = LsfAttribute {
name_index_outer: name_outer,
name_index_inner: name_inner,
type_info,
next_index: -1,
offset: value_offset,
};
let attr_idx = attributes.len();
let node = &mut nodes[*current_node_idx];
if node.first_attribute_index == -1 {
node.first_attribute_index = attr_idx as i32;
} else {
let mut last_idx = node.first_attribute_index as usize;
while attributes[last_idx].next_index != -1 {
last_idx = attributes[last_idx].next_index as usize;
}
attributes[last_idx].next_index = attr_idx as i32;
}
attributes.push(attr);
}
Ok(())
}
const STRING_HASH_MAP_SIZE: usize = 0x200;
struct StringTable {
string_map: HashMap<String, (usize, usize)>,
name_lists: Vec<Vec<String>>,
}
impl StringTable {
fn new() -> Self {
let mut name_lists = Vec::with_capacity(STRING_HASH_MAP_SIZE);
for _ in 0..STRING_HASH_MAP_SIZE {
name_lists.push(Vec::new());
}
Self {
string_map: HashMap::new(),
name_lists,
}
}
fn get_or_insert(&mut self, s: &str) -> (usize, usize) {
if let Some(&indices) = self.string_map.get(s) {
return indices;
}
let hash = hash_string_lslib(s);
let bucket = ((hash & 0x1ff)
^ ((hash >> 9) & 0x1ff)
^ ((hash >> 18) & 0x1ff)
^ ((hash >> 27) & 0x1ff)) as usize;
let outer = bucket;
let inner = self.name_lists[outer].len();
self.name_lists[outer].push(s.to_string());
self.string_map.insert(s.to_string(), (outer, inner));
(outer, inner)
}
fn to_name_lists(self) -> Vec<Vec<String>> {
self.name_lists
}
}