rakata_formats/tlk/
writer.rs

1//! TLK binary writer.
2
3use std::io::{Cursor, Write};
4
5use rakata_core::{encode_text, text_encoding_for_language, ResRef};
6
7use crate::binary::{write_f32, write_u32};
8
9use super::{Tlk, TlkBinaryError, ENTRY_SIZE, FILE_HEADER_SIZE, TLK_MAGIC, TLK_VERSION_V3};
10
11/// Writes a TLK to a writer in KotOR TLK V3.0 binary format.
12#[cfg_attr(
13    feature = "tracing",
14    tracing::instrument(level = "debug", skip(writer, tlk))
15)]
16pub fn write_tlk<W: Write>(writer: &mut W, tlk: &Tlk) -> Result<(), TlkBinaryError> {
17    let text_encoding = text_encoding_for_language(tlk.language_id)
18        .map_err(|err| TlkBinaryError::UnsupportedLanguageEncoding(err.language_id.raw()))?;
19    let entry_count = u32::try_from(tlk.entries.len())
20        .map_err(|_| TlkBinaryError::ValueOverflow("entry_count"))?;
21    let entries_offset = u32::try_from(
22        FILE_HEADER_SIZE
23            .checked_add(
24                tlk.entries
25                    .len()
26                    .checked_mul(ENTRY_SIZE)
27                    .ok_or(TlkBinaryError::ValueOverflow("entries_offset"))?,
28            )
29            .ok_or(TlkBinaryError::ValueOverflow("entries_offset"))?,
30    )
31    .map_err(|_| TlkBinaryError::ValueOverflow("entries_offset"))?;
32
33    writer.write_all(&TLK_MAGIC)?;
34    writer.write_all(&TLK_VERSION_V3)?;
35    write_u32(writer, tlk.language_id.raw())?;
36    write_u32(writer, entry_count)?;
37    write_u32(writer, entries_offset)?;
38
39    let mut text_blob = Vec::new();
40    for (entry_index, entry) in tlk.entries.iter().enumerate() {
41        let normalized = entry.normalized();
42        let text_bytes = encode_text(&normalized.text, text_encoding).map_err(|source| {
43            TlkBinaryError::TextEncoding {
44                entry_index,
45                source,
46            }
47        })?;
48        let text_offset = u32::try_from(text_blob.len())
49            .map_err(|_| TlkBinaryError::ValueOverflow("text_offset"))?;
50        let text_length = u32::try_from(text_bytes.len())
51            .map_err(|_| TlkBinaryError::ValueOverflow("text_length"))?;
52
53        let mut flags = 0_u32;
54        if normalized.text_present {
55            flags |= 0x0001;
56        }
57        if normalized.sound_present {
58            flags |= 0x0002;
59        }
60        if normalized.sound_length_present {
61            flags |= 0x0004;
62        }
63
64        write_u32(writer, flags)?;
65        write_resref_field(writer, &normalized.voiceover)?;
66        write_u32(writer, normalized.volume_var)?;
67        write_u32(writer, normalized.pitch_var)?;
68        write_u32(writer, text_offset)?;
69        write_u32(writer, text_length)?;
70        write_f32(writer, normalized.sound_length)?;
71
72        text_blob.extend_from_slice(&text_bytes);
73    }
74
75    writer.write_all(&text_blob)?;
76    Ok(())
77}
78
79/// Serializes a TLK to a byte vector in KotOR TLK V3.0 format.
80#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(tlk)))]
81pub fn write_tlk_to_vec(tlk: &Tlk) -> Result<Vec<u8>, TlkBinaryError> {
82    let mut cursor = Cursor::new(Vec::new());
83    write_tlk(&mut cursor, tlk)?;
84    Ok(cursor.into_inner())
85}
86
87/// Writes a fixed 16-byte TLK sound resref field.
88fn write_resref_field<W: Write>(writer: &mut W, resref: &ResRef) -> Result<(), TlkBinaryError> {
89    let mut field = [0_u8; 16];
90    let bytes = resref.as_str().as_bytes();
91    if bytes.len() > field.len() {
92        return Err(TlkBinaryError::ValueOverflow("sound_resref"));
93    }
94    field[..bytes.len()].copy_from_slice(bytes);
95    writer.write_all(&field)?;
96    Ok(())
97}