use std::{
collections::BTreeMap,
io::{Seek, SeekFrom, Write},
};
use indexmap::IndexMap;
use crate::{Endian, Offset, Xc3Result, Xc3Write};
#[derive(Debug)]
pub struct WriteOptions {
pub start_alignment: u64,
pub start_padding_byte: u8,
pub string_alignment: u64,
pub string_padding_byte: u8,
}
impl Default for WriteOptions {
fn default() -> Self {
Self {
start_alignment: 1,
start_padding_byte: 0,
string_alignment: 1,
string_padding_byte: 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct StringSectionUniqueSorted {
name_to_offsets: BTreeMap<String, Vec<u64>>,
}
impl StringSectionUniqueSorted {
pub fn insert_offset64(&mut self, offset: &Offset<'_, u64, String>) {
self.name_to_offsets
.entry(offset.data.clone())
.or_default()
.push(offset.position);
}
pub fn write<W: Write + Seek>(
&self,
writer: &mut W,
data_ptr: &mut u64,
options: &WriteOptions,
endian: Endian,
) -> Xc3Result<()> {
let name_positions = write_strings(self.name_to_offsets.keys(), writer, data_ptr, options)?;
for (offsets, position) in self.name_to_offsets.values().zip(name_positions) {
update_offsets(writer, 0, endian, position as u64, offsets)?;
}
Ok(())
}
}
#[derive(Default)]
pub struct StringSectionUnique {
name_to_offsets: IndexMap<String, Vec<u64>>,
}
impl StringSectionUnique {
pub fn insert_offset32(&mut self, offset: &Offset<'_, u32, String>) {
self.name_to_offsets
.entry(offset.data.clone())
.or_default()
.push(offset.position);
}
pub fn write<W: Write + Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
options: &WriteOptions,
endian: Endian,
) -> Xc3Result<()> {
let name_positions = write_strings(self.name_to_offsets.keys(), writer, data_ptr, options)?;
for (offsets, position) in self.name_to_offsets.values().zip(name_positions) {
update_offsets(writer, base_offset, endian, position as u64, offsets)?;
}
Ok(())
}
}
#[derive(Default)]
pub struct StringSection {
name_to_offset: Vec<(String, u64)>,
}
impl StringSection {
pub fn insert_offset32(&mut self, offset: &Offset<'_, u32, String>) {
self.name_to_offset
.push((offset.data.clone(), offset.position));
}
pub fn write<W: std::io::Write + std::io::Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
options: &WriteOptions,
endian: Endian,
) -> Xc3Result<()> {
let name_positions = write_strings(
self.name_to_offset.iter().map(|(n, _)| n),
writer,
data_ptr,
options,
)?;
for ((_, offset), position) in self.name_to_offset.iter().zip(name_positions) {
update_offsets(writer, base_offset, endian, position as u64, &[*offset])?;
}
Ok(())
}
}
fn update_offsets<W: Write + Seek>(
writer: &mut W,
base_offset: u64,
endian: Endian,
position: u64,
offsets: &[u64],
) -> Result<(), std::io::Error> {
for offset in offsets {
writer.seek(SeekFrom::Start(*offset))?;
let final_offset = position - base_offset;
(final_offset as u32).xc3_write(writer, endian)?;
}
Ok(())
}
fn write_strings<'a, W: Write + Seek>(
names: impl Iterator<Item = &'a String>,
writer: &mut W,
data_ptr: &mut u64,
options: &WriteOptions,
) -> Xc3Result<Vec<u32>> {
let mut name_positions = Vec::new();
writer.seek(std::io::SeekFrom::Start(*data_ptr))?;
align(
writer,
*data_ptr,
options.start_alignment,
options.start_padding_byte,
)?;
for name in names {
let position = writer.stream_position()? as u32;
writer.write_all(name.as_bytes())?;
writer.write_all(&[0u8])?;
let position_after_write = writer.stream_position()?;
align(
writer,
position_after_write,
options.string_alignment,
options.string_padding_byte,
)?;
name_positions.push(position);
}
*data_ptr = (*data_ptr).max(writer.stream_position()?);
Ok(name_positions)
}
fn align<W: Write>(writer: &mut W, size: u64, align: u64, pad: u8) -> std::io::Result<()> {
let aligned_size = size.next_multiple_of(align);
let padding = aligned_size - size;
writer.write_all(&vec![pad; padding as usize])?;
Ok(())
}