rakata_formats/tlk/
mod.rs1mod reader;
45mod writer;
46
47pub use reader::{read_tlk, read_tlk_from_bytes};
48pub use writer::{write_tlk, write_tlk_to_vec};
49
50use thiserror::Error;
51
52#[cfg(feature = "serde")]
53use serde::{Deserialize, Serialize};
54
55use rakata_core::{DecodeTextError, EncodeTextError, LanguageId, ResRef, ResRefError};
56
57use crate::binary::{self, DecodeBinary, EncodeBinary};
58
59const TLK_MAGIC: [u8; 4] = *b"TLK ";
61const TLK_VERSION_V3: [u8; 4] = *b"V3.0";
63const FILE_HEADER_SIZE: usize = 20;
65const ENTRY_SIZE: usize = 40;
67
68#[derive(Debug, Clone, PartialEq)]
70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
71pub struct Tlk {
72 pub language_id: LanguageId,
74 pub entries: Vec<TlkEntry>,
76}
77
78impl Tlk {
79 pub fn new(language_id: impl Into<LanguageId>) -> Self {
81 Self {
82 language_id: language_id.into(),
83 entries: Vec::new(),
84 }
85 }
86}
87
88impl DecodeBinary for Tlk {
89 type Error = TlkBinaryError;
90
91 fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
92 read_tlk_from_bytes(bytes)
93 }
94}
95
96impl EncodeBinary for Tlk {
97 type Error = TlkBinaryError;
98
99 fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
100 write_tlk_to_vec(self)
101 }
102}
103
104#[derive(Debug, Clone, PartialEq)]
106#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
107pub struct TlkEntry {
108 pub text: String,
110 pub voiceover: ResRef,
112 pub text_present: bool,
114 pub sound_present: bool,
116 pub sound_length_present: bool,
118 pub sound_length: f32,
120 pub volume_var: u32,
122 pub pitch_var: u32,
124}
125
126impl TlkEntry {
127 pub fn new(text: impl Into<String>, voiceover: ResRef) -> Self {
129 let text = text.into();
130 Self {
131 text_present: !text.is_empty(),
132 sound_present: !voiceover.is_blank(),
133 sound_length_present: false,
134 sound_length: 0.0,
135 volume_var: 0,
136 pitch_var: 0,
137 text,
138 voiceover,
139 }
140 }
141
142 pub fn normalized(&self) -> Self {
144 let mut normalized = self.clone();
145 normalized.text_present = !normalized.text.is_empty();
146 normalized.sound_present = !normalized.voiceover.is_blank();
147 if !normalized.sound_present {
148 normalized.sound_length_present = false;
149 normalized.sound_length = 0.0;
150 } else {
151 normalized.sound_length_present =
152 normalized.sound_length_present || normalized.sound_length != 0.0;
153 }
154 normalized
155 }
156}
157
158#[derive(Debug, Error)]
160pub enum TlkBinaryError {
161 #[error(transparent)]
163 Io(#[from] std::io::Error),
164 #[error("invalid TLK magic: {0:?}")]
166 InvalidMagic([u8; 4]),
167 #[error("invalid TLK version: {0:?}")]
169 InvalidVersion([u8; 4]),
170 #[error("invalid TLK header: {0}")]
172 InvalidHeader(String),
173 #[error("value overflow while writing field `{0}`")]
175 ValueOverflow(&'static str),
176 #[error("unsupported TLK language id {0} for text encoding")]
178 UnsupportedLanguageEncoding(u32),
179 #[error("entry {entry_index} text encoding failed: {source}")]
181 TextEncoding {
182 entry_index: usize,
184 #[source]
186 source: EncodeTextError,
187 },
188 #[error("entry {entry_index} text decoding failed: {source}")]
190 TextDecoding {
191 entry_index: usize,
193 #[source]
195 source: DecodeTextError,
196 },
197 #[error("entry {entry_index} sound resref `{value}` failed validation: {source}")]
199 InvalidSoundResRef {
200 entry_index: usize,
202 value: String,
204 #[source]
206 source: ResRefError,
207 },
208}
209
210impl From<binary::BinaryLayoutError> for TlkBinaryError {
211 fn from(error: binary::BinaryLayoutError) -> Self {
212 Self::InvalidHeader(error.to_string())
213 }
214}
215
216#[cfg(feature = "serde")]
221mod serde_impl {
222 use super::*;
223 use serde_json::{from_slice, from_str, to_string_pretty, to_vec};
224
225 pub fn write_tlk_to_json(tlk: &Tlk) -> Result<String, TlkBinaryError> {
227 to_string_pretty(tlk).map_err(|e| TlkBinaryError::Io(std::io::Error::other(e)))
228 }
229
230 pub fn write_tlk_to_json_vec(tlk: &Tlk) -> Result<Vec<u8>, TlkBinaryError> {
232 to_vec(tlk).map_err(|e| TlkBinaryError::Io(std::io::Error::other(e)))
233 }
234
235 pub fn read_tlk_from_json(json: &str) -> Result<Tlk, TlkBinaryError> {
237 from_str(json).map_err(|e| TlkBinaryError::Io(std::io::Error::other(e)))
238 }
239
240 pub fn read_tlk_from_json_bytes(bytes: &[u8]) -> Result<Tlk, TlkBinaryError> {
242 from_slice(bytes).map_err(|e| TlkBinaryError::Io(std::io::Error::other(e)))
243 }
244}
245
246#[cfg(feature = "serde")]
247pub use serde_impl::{
248 read_tlk_from_json, read_tlk_from_json_bytes, write_tlk_to_json, write_tlk_to_json_vec,
249};