1#![allow(dead_code)]
35
36use crate::parsing::*;
37use nom::multi::count;
38use nom::number::streaming::{le_f32, le_u32};
39use serde::{Deserialize, Serialize};
40
41pub use crate::parsing::FixedSizeString;
42
43pub const TLK_STRREF_USER_OFFSET: u32 = 0x0100_0000;
44
45#[derive(Debug, Deserialize, Clone)]
46#[serde(untagged)]
47enum TlkString {
48 #[serde(skip)]
49 Ptr {
50 offset: u32,
51 size: u32,
52 },
53 Owned(String),
54}
55
56#[derive(Debug, Default, Deserialize, Clone)]
60pub struct Entry {
61 pub sound_resref: Option<FixedSizeString<16>>,
63 pub _volume_variance: u32,
65 pub _pitch_variance: u32,
67 pub sound_length: Option<f32>,
69 text: Option<TlkString>,
70}
71impl Entry {
72 fn from_bytes(input: &[u8]) -> NWNParseResult<Self> {
73 let (input, flags) = le_u32(input)?;
74 let (input, sound_resref) = FixedSizeString::<16>::from_bytes(input)?;
75 let (input, _volume_variance) = le_u32(input)?;
76 let (input, _pitch_variance) = le_u32(input)?;
77 let (input, offset_to_string) = le_u32(input)?;
78 let (input, string_size) = le_u32(input)?;
79 let (input, sound_length) = le_f32(input)?;
80 Ok((
81 input,
82 Self {
83 sound_resref: if (flags & 2) > 0 {
84 Some(sound_resref)
85 } else {
86 None
87 },
88 _volume_variance,
89 _pitch_variance,
90 text: if (flags & 1) > 0 {
91 Some(TlkString::Ptr {
92 offset: offset_to_string,
93 size: string_size,
94 })
95 } else {
96 None
97 },
98 sound_length: if (flags & 4) > 0 {
99 Some(sound_length)
100 } else {
101 None
102 },
103 },
104 ))
105 }
106
107 pub fn is_empty(&self) -> bool {
109 self.sound_resref.is_none() && self.text.is_none() && self.sound_length.is_none()
110 }
111
112 fn get_flags(&self) -> u32 {
113 self.text.is_some() as u32
114 + self.sound_resref.is_some() as u32 * 2
115 + self.sound_length.is_some() as u32 * 4
116 }
117
118 pub fn get_str<'a>(&'a self, strings_data: &'a [u8]) -> Option<&'a str> {
123 if let Some(text) = &self.text {
124 match text {
125 TlkString::Owned(s) => Some(s),
126 TlkString::Ptr { offset, size } => {
127 let start = *offset as usize;
128 let end = start + *size as usize;
129
130 Some(
131 std::str::from_utf8(
132 strings_data
133 .get(start..end)
134 .expect("entry references out of bounds data"),
135 )
136 .expect("entry references invalid utf8 data"),
137 )
138 }
139 }
140 } else {
141 None
142 }
143 }
144
145 pub fn get_str_len(&self) -> usize {
147 if let Some(text) = &self.text {
148 match text {
149 TlkString::Owned(s) => s.len(),
150 TlkString::Ptr { offset: _, size } => *size as usize,
151 }
152 } else {
153 0
154 }
155 }
156
157 pub fn into_owned(mut self, strings_data: &[u8]) -> Self {
162 if let Some(TlkString::Ptr { offset: _, size: _ }) = &self.text {
163 self.text = Some(TlkString::Owned(
164 self.get_str(strings_data)
165 .expect("should not fail")
166 .to_string(),
167 ));
168 }
169 self
170 }
171
172 pub fn to_owned(&self, strings_data: &[u8]) -> Self {
177 self.clone().into_owned(strings_data)
178 }
179
180 pub fn to_string_pretty(
190 &self,
191 index: u32,
192 index_padding: usize,
193 user_indices: bool,
194 strings_data: &[u8],
195 ) -> String {
196 let mut ret = String::new();
197
198 let index = if user_indices {
199 index + TLK_STRREF_USER_OFFSET
200 } else {
201 index
202 };
203
204 ret += &format!(
205 "{0:1$}> sound_resref={2:?} _volume_variance={3} _pitch_variance={4} \
206 sound_length={5:?}",
207 index,
208 index_padding,
209 self.sound_resref,
210 self._volume_variance,
211 self._pitch_variance,
212 self.sound_length,
213 );
214 if let Some(text) = self.get_str(strings_data) {
215 ret += "\n";
216 ret += &text
217 .lines()
218 .map(|line| {
219 format!(
220 "{}|{}{}",
221 " ".repeat(index_padding),
222 if line.is_empty() { "" } else { " " },
223 line
224 )
225 })
226 .collect::<Vec<_>>()
227 .join("\n");
228 }
229 ret
230 }
231}
232struct EntrySerde<'a> {
234 entry: &'a Entry,
235 strings_data: &'a [u8],
236 strref: u32,
238}
239
240impl<'a> Serialize for EntrySerde<'a> {
241 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242 where
243 S: serde::Serializer,
244 {
245 use serde::ser::SerializeStruct;
246 let mut ser_struct = serializer.serialize_struct("Entry", 6)?;
247 ser_struct.serialize_field("__hint_strref", &self.strref)?;
248 ser_struct.serialize_field("sound_resref", &self.entry.sound_resref)?;
249 ser_struct.serialize_field("_volume_variance", &self.entry._volume_variance)?;
250 ser_struct.serialize_field("_pitch_variance", &self.entry._pitch_variance)?;
251 ser_struct.serialize_field("sound_length", &self.entry.sound_length)?;
252 ser_struct.serialize_field("text", &self.entry.get_str(self.strings_data))?;
253 ser_struct.end()
254 }
255}
256
257fn default_false() -> bool {
258 false
259}
260
261#[derive(Debug, Deserialize)]
270pub struct Tlk {
271 pub file_type: FixedSizeString<4>,
273 pub file_version: FixedSizeString<4>,
275 pub language_id: u32,
277 pub entries: Vec<Entry>,
279 #[serde(skip)]
281 pub strings_data: Vec<u8>,
282 #[serde(default = "default_false")]
284 pub hint_is_user: bool,
285}
286
287impl Tlk {
288 pub fn from_bytes(input: &[u8], repair: bool) -> NWNParseResult<Self> {
292 let (input, file_type) = FixedSizeString::<4>::from_bytes(input)?;
294 let (input, file_version) = FixedSizeString::<4>::from_bytes(input)?;
295 let (input, language_id) = le_u32(input)?;
296 let (input, string_count) = le_u32(input)?;
297 let (input, string_entries_offset) = le_u32(input)?;
298
299 let (input, mut entries) = nom_parse_context(
300 "while parsing tlk entries",
301 count(Entry::from_bytes, string_count as usize)(input),
302 )?;
303 let strings_data = input.to_vec();
304
305 for (strref, entry) in entries.iter_mut().enumerate() {
308 if let Some(ref mut text) = &mut entry.text {
309 if let TlkString::Ptr { offset, size } = text {
310 let start = *offset as usize;
311 let mut end = start + *size as usize;
312
313 if repair && end > strings_data.len() {
314 if start < strings_data.len() {
315 end = strings_data.len();
316 *size = (end - start) as u32;
317 } else {
318 entry.text = None;
319 continue;
320 }
321 }
322
323 if let Some(data) = strings_data.get(start..end) {
324 if let Err(e) = std::str::from_utf8(data) {
325 if repair {
326 entry.text = Some(TlkString::Owned(
327 String::from_utf8_lossy(data).to_string(),
328 ));
329 } else {
330 return Err(nom::Err::Error(NWNParseError::from(e)));
331 }
332 }
333 } else {
334 return Err(nom::Err::Error(
335 format!(
336 "TLK strref {} is pointing outside of strings_data: text \
337 address={}, size={}",
338 strref,
339 string_entries_offset + *offset,
340 size
341 )
342 .into(),
343 ));
344 }
345 } else {
346 panic!("unexpected owned string");
347 }
348 }
349 }
350
351 Ok((
352 input,
353 Tlk {
354 file_type,
355 file_version,
356 language_id,
357 entries,
358 strings_data,
359 hint_is_user: false,
360 },
361 ))
362 }
363
364 pub fn write_stream<T>(&self, output: &mut T) -> Result<(), Box<dyn std::error::Error>>
365 where
366 T: std::io::Write + std::io::Seek,
367 {
368 output.write_all(self.file_type.as_bytes())?;
369 output.write_all(self.file_version.as_bytes())?;
370 output.write_all(&self.language_id.to_le_bytes())?;
371 output.write_all(&self.len().to_le_bytes())?; output.write_all(&(20u32 + self.len() * 40u32).to_le_bytes())?; let mut strings_end_offset = 0u32;
375 for entry in &self.entries {
376 output.write_all(&entry.get_flags().to_le_bytes())?;
377 output.write_all(
378 entry
379 .sound_resref
380 .as_ref()
381 .unwrap_or(&Default::default())
382 .as_bytes(),
383 )?;
384 output.write_all(&entry._volume_variance.to_le_bytes())?;
385 output.write_all(&entry._pitch_variance.to_le_bytes())?;
386
387 let offset: u32 = strings_end_offset;
388 let size: u32 = entry.get_str_len() as u32;
389 output.write_all(&offset.to_le_bytes())?;
390 output.write_all(&size.to_le_bytes())?;
391
392 output.write_all(&entry.sound_length.unwrap_or(0f32).to_le_bytes())?;
393
394 strings_end_offset += size;
395 }
396
397 output.seek(std::io::SeekFrom::Current(strings_end_offset as i64))?;
399 output.write_all(&[])?;
400 output.seek(std::io::SeekFrom::Current(-(strings_end_offset as i64)))?;
401
402 for entry in &self.entries {
403 output.write_all(entry.get_str(&self.strings_data).unwrap_or("").as_bytes())?;
404 }
405
406 Ok(())
407 }
408
409 pub fn to_bytes(&self) -> Vec<u8> {
411 let mut buf = std::io::Cursor::new(vec![]);
412 self.write_stream(&mut buf)
413 .expect("failed to allocate memory?");
414 buf.into_inner()
415 }
416 pub fn len(&self) -> u32 {
418 self.entries.len() as u32
419 }
420 pub fn is_empty(&self) -> bool {
421 self.entries.is_empty()
422 }
423
424 pub fn get_entry(&self, strref: u32) -> Option<&Entry> {
426 self.entries.get(strref as usize)
427 }
428 pub fn get_entry_mut(&mut self, strref: u32) -> &mut Entry {
430 if strref as usize >= self.entries.len() {
431 self.entries
432 .resize_with(strref as usize + 1, Default::default);
433 }
434 &mut self.entries[strref as usize]
435 }
436 pub fn get_str(&self, strref: u32) -> Option<&str> {
438 self.get_entry(strref)?.get_str(&self.strings_data)
439 }
440 pub fn set_string(&mut self, strref: u32, text: Option<String>) {
442 let entry = &mut self.get_entry_mut(strref);
443 entry.text = text.map(TlkString::Owned);
444 }
445
446 pub fn to_string_pretty(&self) -> String {
448 let mut ret = String::new();
449
450 ret += "============================ Header ============================\n";
451 ret += &format!(
452 "file_type={:?} file_version={:?}\n",
453 self.file_type, self.file_version
454 );
455 ret += &format!("language_id={}\n", self.language_id);
456 ret += "============================ Entries ============================\n";
457
458 let max_index = if self.hint_is_user {
459 self.entries.len() + TLK_STRREF_USER_OFFSET as usize
460 } else {
461 self.entries.len()
462 };
463 let index_padding = (max_index.ilog10() + 1) as usize;
464
465 ret += &self
466 .entries
467 .iter()
468 .enumerate()
469 .map(|(index, entry)| {
470 entry.to_string_pretty(
471 index as u32,
472 index_padding,
473 self.hint_is_user,
474 &self.strings_data,
475 )
476 })
477 .collect::<Vec<_>>()
478 .join("\n");
479 ret
480 }
481
482 pub fn with_user_hint(mut self, hint: bool) -> Self {
484 self.hint_is_user = hint;
485 self
486 }
487
488 pub fn into_owned(mut self) -> Self {
493 self.entries = self
494 .entries
495 .into_iter()
496 .map(|e| e.into_owned(&self.strings_data))
497 .collect();
498 self.strings_data.clear();
499 self.strings_data.shrink_to_fit();
500 self
501 }
502
503 pub fn shrink_to_fit(&mut self) {
505 let mut new_size = self.entries.len();
506
507 for (index, entry) in self.entries.iter().enumerate().rev() {
508 if entry.is_empty() {
509 new_size = index;
510 } else {
511 break;
512 }
513 }
514
515 self.entries.truncate(new_size);
516 }
517
518 pub fn resize(&mut self, new_len: u32) {
520 self.entries.resize_with(new_len as usize, Default::default);
521 }
522}
523
524impl Serialize for Tlk {
525 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
526 where
527 S: serde::Serializer,
528 {
529 use serde::ser::SerializeStruct;
530 let mut ser_struct = serializer.serialize_struct("Tlk", 5)?;
531 ser_struct.serialize_field("file_type", &self.file_type)?;
532 ser_struct.serialize_field("file_version", &self.file_version)?;
533 ser_struct.serialize_field("language_id", &self.language_id)?;
534 ser_struct.serialize_field("__hint_is_user", &self.hint_is_user)?;
535 ser_struct.serialize_field(
536 "entries",
537 &self
538 .entries
539 .iter()
540 .enumerate()
541 .map(|(i, e)| EntrySerde {
542 entry: e,
543 strings_data: &self.strings_data,
544 strref: if !self.hint_is_user {
545 i as u32
546 } else {
547 i as u32 + TLK_STRREF_USER_OFFSET
548 },
549 })
550 .collect::<Vec<_>>(),
551 )?;
552 ser_struct.end()
553 }
554}
555
556#[derive(Debug, Default)]
558pub enum Gender {
559 #[default]
560 Male = 0,
561 Female = 1,
562}
563
564#[derive(Debug)]
566pub struct Resolver<'tlk> {
567 pub base: &'tlk Tlk,
568 pub base_f: Option<&'tlk Tlk>,
569 pub user: Option<&'tlk Tlk>,
570}
571impl<'tlk> Resolver<'tlk> {
572 pub fn new(base: &'tlk Tlk, base_f: Option<&'tlk Tlk>, user: Option<&'tlk Tlk>) -> Self {
574 Resolver { base, base_f, user }
575 }
576 pub fn get_tlk_for_strref(&self, strref: u32, gender: Gender) -> (Option<&Tlk>, u32) {
578 if strref < TLK_STRREF_USER_OFFSET {
579 match gender {
580 Gender::Male => (Some(self.base), strref),
581 Gender::Female => (Some(self.base_f.as_ref().unwrap_or(&self.base)), strref),
582 }
583 } else {
584 let strref = strref - TLK_STRREF_USER_OFFSET;
585 if let Some(tlk) = &self.user {
586 (Some(tlk), strref)
587 } else {
588 (None, strref)
589 }
590 }
591 }
592 pub fn get_entry(&self, strref: u32, gender: Gender) -> Option<&Entry> {
594 let (tlk, strref) = self.get_tlk_for_strref(strref, gender);
595 if let Some(tlk) = tlk {
596 if strref < tlk.len() {
597 tlk.get_entry(strref)
598 } else {
599 None
600 }
601 } else {
602 None
603 }
604 }
605 pub fn get_str(&self, strref: u32, gender: Gender) -> Option<&str> {
607 let (tlk, strref) = self.get_tlk_for_strref(strref, gender);
608 if let Some(tlk) = tlk {
609 tlk.get_str(strref)
610 } else {
611 None
612 }
613 }
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619
620 #[test]
621 fn test_user_tlk() {
622 let tlk_bytes = include_bytes!("../unittest/user.tlk");
623
624 let tlk = Tlk::from_bytes(tlk_bytes, false).unwrap().1;
625 assert_eq!(tlk.get_str(0), Some("Hello world"));
626 assert_eq!(tlk.get_str(1), Some("Café liégeois"));
627 assert_eq!(tlk.get_str(2), None);
628 assert_eq!(tlk.get_str(3), Some("Custom sound"));
629 assert_eq!(
630 tlk.get_entry(3)
631 .unwrap()
632 .sound_resref
633 .as_ref()
634 .unwrap()
635 .as_str(),
636 "snd_custom"
637 );
638 assert_eq!(tlk.get_str(10), None);
639 assert_eq!(tlk.len(), 4);
640
641 let tlk_bytes_ser = tlk.to_bytes();
642 assert_eq!(tlk_bytes_ser, tlk_bytes);
643
644 let mut tlk = Tlk::from_bytes(&tlk_bytes_ser, false).unwrap().1;
646 tlk.set_string(0, Some("Yolo".to_string()));
647 tlk.set_string(1, Some("Sôm€ ütf8 🐧".to_string()));
648 tlk.set_string(9, Some("Extend this tlk !".to_string()));
649
650 let tlk_bytes_ser = tlk.to_bytes();
651 let tlk = Tlk::from_bytes(&tlk_bytes_ser, false).unwrap().1;
652 assert_eq!(tlk.len(), 10);
653 assert_eq!(tlk.get_str(0), Some("Yolo"));
654 assert_eq!(tlk.get_str(1), Some("Sôm€ ütf8 🐧"));
655 assert_eq!(tlk.get_str(2), None);
656 assert_eq!(tlk.get_str(9), Some("Extend this tlk !"));
657 }
658
659 #[test]
660 fn test_dialog_tlk() {
661 let tlk_bytes = include_bytes!("../unittest/dialog.tlk");
662
663 let tlk = Tlk::from_bytes(tlk_bytes, false).unwrap().1;
664 assert_eq!(tlk.get_str(0), Some("Bad Strref"));
665 assert_eq!(tlk.get_str(1), Some("Barbares"));
666 assert_eq!(tlk.get_str(10), Some("Moine"));
667 assert_eq!(tlk.get_str(30), Some("Demi-elfe"));
668 assert_eq!(
669 tlk.get_str(36),
670 Some(concat!(
671 "La dernière tentative pour détecter les paramètres de votre ",
672 "système a échoué. Cela indique généralement une incompatibilité ",
673 "entre Neverwinter Nights 2 et vos pilotes vidéo actuels. Veuillez ",
674 "consultez le site internet du fabriquant de votre carte vidéo pour ",
675 "télécharger les pilotes les plus récents.\n\nVous pouvez :\n\n1. ",
676 "Cliquer sur \"Continuer\" si vous pensez que Neverwinter Nights 2 ",
677 "fonctionnera sur votre système. Si le jeu ne démarre pas, relancez ",
678 "le programme de configuration et essayez une autre option.\n\n2. ",
679 "Cliquez sur \"Assistance\" pour lancer votre navigateur Internet ",
680 "afin d'accéder au site web de l'assistance technique de ",
681 "Neverwinter Nights 2.\n\n3. Cliquez sur \"LisezMoi\" et consultez ",
682 "la section compatibilité pour savoir s'il n'y a pas de problèmes ",
683 "de compatibilité entre Neverwinter Nights 2 et votre ",
684 "système.\n\n4. Cliquez sur \"Quitter\" pour fermer le ",
685 "programme."
686 ))
687 );
688 assert_eq!(tlk.get_str(3000), None);
689
690 let tlk_bytes_ser = tlk.to_bytes();
691 assert_eq!(tlk_bytes_ser, tlk_bytes);
692 }
693
694 #[test]
695 fn test_repair_tlk() {
696 let tlk_bytes = include_bytes!("../unittest/user.broken.tlk");
697
698 let tlk = Tlk::from_bytes(tlk_bytes, true).unwrap().1;
699 assert_eq!(tlk.get_str(0), Some("Some in�(id UTF8 magic"));
700 assert_eq!(
701 tlk.get_str(1),
702 Some("Some text with bad sizeAnother text but with bad offset")
703 );
704 assert_eq!(tlk.get_str(2), None);
705 }
706
707 #[test]
708 fn test_tlk_resolver() {
709 let base = Tlk::from_bytes(include_bytes!("../unittest/dialog.tlk"), false)
710 .unwrap()
711 .1;
712 let user = Tlk::from_bytes(include_bytes!("../unittest/user.tlk"), false)
713 .unwrap()
714 .1;
715 let resolver = Resolver::new(&base, None, Some(&user));
716
717 assert_eq!(resolver.get_str(0, Gender::Male), Some("Bad Strref"));
718 assert_eq!(resolver.get_str(1, Gender::Male), Some("Barbares"));
719 assert_eq!(resolver.get_str(1_000_000, Gender::Male), None);
720 assert_eq!(
721 resolver.get_str(16_777_216, Gender::Male),
722 Some("Hello world")
723 );
724 assert_eq!(
725 resolver.get_str(16_777_217, Gender::Male),
726 Some("Café liégeois")
727 );
728 assert_eq!(resolver.get_str(17_777_216, Gender::Male), None);
729 }
730
731 }