serde_gff/
string.rs

1//! Содержит реализации структур, описывающих строки, хранящиеся в GFF файле
2use std::fmt;
3use std::mem::transmute;
4use std::collections::HashMap;
5
6/// Маска, определяющая идентификатор строки
7const USER_TLK_MASK: u32 = 0x8000_0000;
8
9/// Индекс в файле `dialog.tlk`, содержащий локализованный текст
10#[derive(Clone, Copy, PartialEq, Eq, Hash)]
11pub struct StrRef(pub(crate) u32);
12
13impl StrRef {
14  /// Определяет, является ли строка индексом не из основного TLK файла игры, а из TLK
15  /// файла модуля. Строка является строкой из TLK файла модуля, если старший бит в ее
16  /// идентификаторе взведен
17  #[inline]
18  pub fn is_user(&self) -> bool { self.0 & USER_TLK_MASK != 0 }
19
20  /// Определяет индекс строки в TLK файле
21  #[inline]
22  pub fn code(&self) -> u32 { self.0 & !USER_TLK_MASK }
23}
24
25impl fmt::Debug for StrRef {
26  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27    write!(f, "code: {}, user: {}", self.code(), self.is_user())
28  }
29}
30
31/// Виды языков, на которых могут храниться локализованные строки в объекте `LocString`
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33#[repr(u32)]
34pub enum Language {
35  /// Английский язык
36  English = 0,
37  /// Французский язык
38  French  = 1,
39  /// Немецкий язык
40  German  = 2,
41  /// Итальянский язык
42  Italian = 3,
43  /// Испанский язык
44  Spanish = 4,
45  /// Польский язык
46  Polish  = 5,
47  /// Корейский язык
48  Korean  = 128,
49  /// Традиционный китайский
50  ChineseTraditional = 129,
51  /// Упрощенный китайский
52  ChineseSimplified  = 130,
53  /// Японский
54  Japanese= 131,
55}
56
57/// Виды пола персонажа, на которых могут храниться локализованные строки в объекте `LocString`
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59#[repr(u32)]
60pub enum Gender {
61  /// Строка предназначена для персонажа мужского или неопределенного пола
62  Male = 0,
63  /// Строка предназначена для персонажа женского пола
64  Female = 1,
65}
66
67/// Ключ, используемый для индексации локализуемых строк во внутреннем представлении
68/// строк (когда строки внедрены в GFF файл, а не используются ссылки на строки в TLK
69/// файле).
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub struct StringKey(pub(crate) u32);
72impl StringKey {
73  /// Язык, на котором записан текст этой части многоязыковой строки
74  pub fn language(&self) -> Language { unsafe { transmute(self.0 >> 1) } }
75  /// Пол персонажа, для которого написан текст этой части многоязыковой строки
76  pub fn gender(&self) -> Gender { unsafe { transmute(self.0 % 2) } }
77}
78impl From<(Language, Gender)> for StringKey {
79  #[inline]
80  fn from(value: (Language, Gender)) -> Self {
81    StringKey(((value.0 as u32) << 1) | value.1 as u32)
82  }
83}
84/// Преобразует ключ в число, в котором он храниться в GFF файле по формуле:
85/// ```rust,ignore
86/// ((self.language() as u32) << 1) | self.gender() as u32
87/// ```
88impl Into<u32> for StringKey {
89  #[inline]
90  fn into(self) -> u32 { self.0 }
91}
92
93/// Часть локализованной строки, хранящая информацию для одного языка и пола
94#[derive(Debug, Clone, PartialEq, Eq, Hash)]
95pub struct SubString {
96  /// Язык, на котором записан текст этой части многоязыковой строки, и пол
97  /// персонажа, для которого он написан
98  pub key: StringKey,
99  /// Текст многоязыковой строки для указанного пола и языка
100  pub string: String,
101}
102impl From<(StringKey, String)> for SubString {
103  #[inline]
104  fn from(value: (StringKey, String)) -> Self {
105    SubString { key: value.0, string: value.1 }
106  }
107}
108impl Into<(StringKey, String)> for SubString {
109  #[inline]
110  fn into(self) -> (StringKey, String) {
111    (self.key, self.string)
112  }
113}
114
115/// Локализуемая строка, содержащая в себе все данные, которые могут храниться в GFF файле.
116/// Может содержать логически некорректные данные, поэтому, если не требуется анализировать
117/// непосредственное содержимое GFF файла без потерь, лучше сразу преобразовать ее в
118/// [`GffString`], используя `into()`, и работать с ней.
119///
120/// [`GffString`]: enum.GffString.html
121#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122pub struct LocString {
123  /// Индекс в TLK файле, содержащий локализованный текст
124  pub str_ref: StrRef,
125  /// Список локализованных строк для каждого языка и пола
126  pub strings: Vec<SubString>,
127}
128
129/// Локализуемая строка, представленная в виде, в котором некорректные значения
130/// непредставимы.
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub enum GffString {
133  /// Внешнее представление строки в виде индекса в TLK файле, содержащем локализованный
134  /// текст. В зависимости от локализации текст будет разным
135  External(StrRef),
136  /// Внутреннее представление строки, хранимое внутри самого файла -- по строке для каждого
137  /// языка и пола персонажа
138  Internal(HashMap<StringKey, String>),
139}
140impl From<LocString> for GffString {
141  /// Преобразует вариант строки, наиболее приближенный к хранимому в файле варианту (и, таким
142  /// образом, хранящий без потерь все содержимое файла) в вариант строки, в котором компилятор
143  /// Rust гарантирует корректность данных -- либо ссылка на внешнюю строку, либо список строк
144  /// для каждого языка и пола, причем для каждой пары существует лишь один вариант строки --
145  /// последний из `LocString.strings`, если их там окажется несколько.
146  ///
147  /// Метод возвращает внутреннее представление, если `LocString.str_ref == StrRef(0xFFFFFFFF)`,
148  /// в противном случае возвращается внешнее представление. Все строки из массива `LocString.strings`
149  /// в этом случае игнорируются.
150  fn from(value: LocString) -> Self {
151    use self::GffString::*;
152
153    match value.str_ref {
154      StrRef(0xFFFFFFFF) => Internal(value.strings.into_iter().map(Into::into).collect()),
155      _                  => External(value.str_ref),
156    }
157  }
158}
159impl From<GffString> for LocString {
160  /// Преобразует представление локализованной строки, корректность данных в котором гарантируется
161  /// компилятором Rust в представление, приближенное к хранимому в GFF файле.
162  ///
163  /// При преобразовании внешнего представления строки в `LocString.strings` записывается пустой массив.
164  /// При преобразовании внутреннего представления в `LocString.str_ref` записывается `StrRef(0xFFFFFFFF)`.
165  fn from(value: GffString) -> Self {
166    use self::GffString::*;
167
168    match value {
169      External(str_ref) => LocString { str_ref, strings: vec![] },
170      Internal(strings) => {
171        let strings = strings.into_iter().map(Into::into).collect();
172        LocString { str_ref: StrRef(0xFFFFFFFF), strings }
173      },
174    }
175  }
176}