Skip to main content

xc3_write/
strings.rs

1use std::{
2    collections::BTreeMap,
3    io::{Seek, SeekFrom, Write},
4};
5
6use indexmap::IndexMap;
7
8use crate::{Endian, Offset, Xc3Result, Xc3Write};
9
10// TODO: support 32 and 64 bit offsets.
11
12#[derive(Debug)]
13pub struct WriteOptions {
14    /// Alignment of the start of the string section in bytes.
15    pub start_alignment: u64,
16
17    /// The padding byte for aligning the start of the string section.
18    pub start_padding_byte: u8,
19
20    /// Alignment in bytes applied after writing each string.
21    pub string_alignment: u64,
22
23    /// The padding byte used for aligning strings.
24    pub string_padding_byte: u8,
25}
26
27impl Default for WriteOptions {
28    fn default() -> Self {
29        Self {
30            start_alignment: 1,
31            start_padding_byte: 0,
32            string_alignment: 1,
33            string_padding_byte: 0,
34        }
35    }
36}
37
38/// Offsets to unique strings in alphabetical order.
39#[derive(Debug, Clone, Default)]
40pub struct StringSectionUniqueSorted {
41    // Unique strings are stored in alphabetical order.
42    name_to_offsets: BTreeMap<String, Vec<u64>>,
43}
44
45impl StringSectionUniqueSorted {
46    /// Insert a 64-bit offset to update later.
47    pub fn insert_offset64(&mut self, offset: &Offset<'_, u64, String>) {
48        self.name_to_offsets
49            .entry(offset.data.clone())
50            .or_default()
51            .push(offset.position);
52    }
53
54    /// Write the strings at `data_ptr` and update all stored offsets.
55    pub fn write<W: Write + Seek>(
56        &self,
57        writer: &mut W,
58        data_ptr: &mut u64,
59        options: &WriteOptions,
60        endian: Endian,
61    ) -> Xc3Result<()> {
62        let name_positions = write_strings(self.name_to_offsets.keys(), writer, data_ptr, options)?;
63
64        for (offsets, position) in self.name_to_offsets.values().zip(name_positions) {
65            update_offsets(writer, 0, endian, position as u64, offsets)?;
66        }
67
68        Ok(())
69    }
70}
71
72/// Offsets to unique strings in insertion order.
73#[derive(Default)]
74pub struct StringSectionUnique {
75    name_to_offsets: IndexMap<String, Vec<u64>>,
76}
77
78impl StringSectionUnique {
79    /// Insert a 32-bit offset to update later.
80    pub fn insert_offset32(&mut self, offset: &Offset<'_, u32, String>) {
81        self.name_to_offsets
82            .entry(offset.data.clone())
83            .or_default()
84            .push(offset.position);
85    }
86
87    /// Write the strings at `data_ptr` and update all stored offsets.
88    pub fn write<W: Write + Seek>(
89        &self,
90        writer: &mut W,
91        base_offset: u64,
92        data_ptr: &mut u64,
93        options: &WriteOptions,
94        endian: Endian,
95    ) -> Xc3Result<()> {
96        let name_positions = write_strings(self.name_to_offsets.keys(), writer, data_ptr, options)?;
97
98        for (offsets, position) in self.name_to_offsets.values().zip(name_positions) {
99            update_offsets(writer, base_offset, endian, position as u64, offsets)?;
100        }
101
102        Ok(())
103    }
104}
105
106/// Offsets to strings in insertion order.
107#[derive(Default)]
108pub struct StringSection {
109    name_to_offset: Vec<(String, u64)>,
110}
111
112impl StringSection {
113    /// Insert a 32-bit offset to update later.
114    pub fn insert_offset32(&mut self, offset: &Offset<'_, u32, String>) {
115        self.name_to_offset
116            .push((offset.data.clone(), offset.position));
117    }
118
119    /// Write the strings at `data_ptr` and update all stored offsets.
120    pub fn write<W: std::io::Write + std::io::Seek>(
121        &self,
122        writer: &mut W,
123        base_offset: u64,
124        data_ptr: &mut u64,
125        options: &WriteOptions,
126        endian: Endian,
127    ) -> Xc3Result<()> {
128        let name_positions = write_strings(
129            self.name_to_offset.iter().map(|(n, _)| n),
130            writer,
131            data_ptr,
132            options,
133        )?;
134
135        // TODO: make base offset an argument or force it to first string?
136        for ((_, offset), position) in self.name_to_offset.iter().zip(name_positions) {
137            update_offsets(writer, base_offset, endian, position as u64, &[*offset])?;
138        }
139
140        Ok(())
141    }
142}
143
144fn update_offsets<W: Write + Seek>(
145    writer: &mut W,
146    base_offset: u64,
147    endian: Endian,
148    position: u64,
149    offsets: &[u64],
150) -> Result<(), std::io::Error> {
151    for offset in offsets {
152        // Assume all string pointers are 4 bytes.
153        writer.seek(SeekFrom::Start(*offset))?;
154        let final_offset = position - base_offset;
155        (final_offset as u32).xc3_write(writer, endian)?;
156    }
157    Ok(())
158}
159
160fn write_strings<'a, W: Write + Seek>(
161    names: impl Iterator<Item = &'a String>,
162    writer: &mut W,
163    data_ptr: &mut u64,
164    options: &WriteOptions,
165) -> Xc3Result<Vec<u32>> {
166    let mut name_positions = Vec::new();
167    writer.seek(std::io::SeekFrom::Start(*data_ptr))?;
168    align(
169        writer,
170        *data_ptr,
171        options.start_alignment,
172        options.start_padding_byte,
173    )?;
174
175    for name in names {
176        // Assume all string pointers are 4 bytes.
177        let position = writer.stream_position()? as u32;
178
179        writer.write_all(name.as_bytes())?;
180        writer.write_all(&[0u8])?;
181
182        // Apply alignment to each string.
183        let position_after_write = writer.stream_position()?;
184        align(
185            writer,
186            position_after_write,
187            options.string_alignment,
188            options.string_padding_byte,
189        )?;
190
191        name_positions.push(position);
192    }
193    *data_ptr = (*data_ptr).max(writer.stream_position()?);
194
195    Ok(name_positions)
196}
197
198fn align<W: Write>(writer: &mut W, size: u64, align: u64, pad: u8) -> std::io::Result<()> {
199    let aligned_size = size.next_multiple_of(align);
200    let padding = aligned_size - size;
201    writer.write_all(&vec![pad; padding as usize])?;
202    Ok(())
203}