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}