engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / language.cpp
bloba5fd41b03a04927f31c37e1e038470ad9025c002
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "gamedefs.h"
27 #include "language.h"
30 // ////////////////////////////////////////////////////////////////////////// //
31 VLanguage GLanguage;
34 //==========================================================================
36 // VLanguage::VLanguage
38 //==========================================================================
39 VLanguage::VLanguage () noexcept {
43 //==========================================================================
45 // VLanguage::~VLanguage
47 //==========================================================================
48 VLanguage::~VLanguage () noexcept {
52 //==========================================================================
54 // VLanguage::FreeNonDehackedStrings
56 //==========================================================================
57 void VLanguage::FreeNonDehackedStrings () {
58 for (auto &&it : table.first()) {
59 if (it.getValue().PassNum != 0) it.removeCurrentNoAdvance();
61 table.compact();
65 //==========================================================================
67 // VLanguage::LoadStrings
69 //==========================================================================
70 void VLanguage::LoadStrings (const char *LangId) {
71 FreeNonDehackedStrings();
72 for (auto &&it : WadNSNameIterator(NAME_language, WADNS_Global)) {
73 const int Lump = it.lump;
74 int j = 1;
75 if (VStr::Cmp(LangId, "**") != 0) {
76 ParseLanguageScript(Lump, "*", true, j++);
77 ParseLanguageScript(Lump, LangId, true, j++);
78 ParseLanguageScript(Lump, LangId, false, j++);
80 ParseLanguageScript(Lump, "**", true, j++);
85 //==========================================================================
87 // VLanguage::ParseLanguageScript
89 //==========================================================================
90 void VLanguage::ParseLanguageScript (vint32 Lump, const char *InCode, bool ExactMatch, vint32 PassNum) {
91 char Code[4];
92 Code[0] = VStr::ToLower(InCode[0]);
93 Code[1] = VStr::ToLower(InCode[0] ? InCode[1] : 0);
94 Code[2] = (ExactMatch && InCode[0] && InCode[1] ? VStr::ToLower(InCode[2]) : 0);
95 Code[3] = 0;
97 VScriptParser *sc = VScriptParser::NewWithLump(Lump);
98 sc->SetCMode(true);
100 bool GotLanguageCode = false;
101 bool Skip = false;
102 bool Finished = false;
104 while (!sc->AtEnd()) {
105 if (sc->Check("[")) {
106 // language identifiers
107 Skip = true;
108 while (!sc->Check("]")) {
109 sc->ExpectString();
110 size_t Len = sc->String.Length();
111 char CurCode[4];
112 if (Len != 2 && Len != 3) {
113 if (Len == 1 && sc->String[0] == '*') {
114 CurCode[0] = '*';
115 CurCode[1] = 0;
116 CurCode[2] = 0;
117 } else if (Len == 7 && !sc->String.ICmp("default")) {
118 CurCode[0] = '*';
119 CurCode[1] = '*';
120 CurCode[2] = 0;
121 } else {
122 sc->Error(va("Language code must be 2 or 3 characters long, %s is %u characters long", *sc->String, (unsigned)Len));
123 // shut up compiler
124 CurCode[0] = 0;
125 CurCode[1] = 0;
126 CurCode[2] = 0;
128 } else {
129 CurCode[0] = VStr::ToLower(sc->String[0]);
130 CurCode[1] = VStr::ToLower(sc->String[1]);
131 CurCode[2] = (ExactMatch ? VStr::ToLower(sc->String[2]) : 0);
132 CurCode[3] = 0;
134 if (Code[0] == CurCode[0] && Code[1] == CurCode[1] && Code[2] == CurCode[2]) {
135 Skip = false;
137 if (!GotLanguageCode && !Skip) {
138 GCon->Logf(NAME_Dev, "parsing language script '%s' for language '%s'", *W_FullLumpName(Lump), Code);
140 GotLanguageCode = true;
142 } else {
143 if (!GotLanguageCode) {
144 // skip old binary LANGUAGE lumps
145 if (!sc->IsText()) {
146 if (!Finished) GCon->Logf("Skipping binary LANGUAGE lump");
147 Finished = true;
148 return;
150 sc->Error("Found a string without language specified");
153 // parse string definitions
154 if (Skip) {
155 // we are skipping this language
156 sc->ExpectString();
157 //sc->Expect("=");
158 //sc->ExpectString();
159 while (!sc->Check(";")) sc->ExpectString();
160 continue;
163 sc->ExpectString();
165 if (sc->String == "$") {
166 //GCon->Logf(NAME_Warning, "%s: conditionals in language script is not supported yet", *sc->GetLoc().toStringNoCol());
167 sc->Expect("ifgame");
168 sc->Expect("(");
169 VStr gmname;
170 if (!sc->Check(")")) {
171 sc->ExpectString();
172 gmname = sc->String.xstrip();
173 if (!sc->Check(")")) {
174 GCon->Logf(NAME_Error, "%s: invalid conditional in language script", *sc->GetLoc().toStringNoCol());
175 while (!sc->Check(")")) sc->ExpectString();
177 } else {
178 GCon->Logf(NAME_Warning, "%s: empty conditionals in language script", *sc->GetLoc().toStringNoCol());
180 if (!gmname.isEmpty() && !game_name.asStr().startsWithCI(gmname)) {
181 //if (!gmname.isEmpty()) GCon->Logf(NAME_Init, "%s: skipped conditional for game '%s'", *sc->GetLoc().toStringNoCol(), *gmname);
182 sc->ExpectString();
183 sc->Expect("=");
184 while (!sc->Check(";")) sc->ExpectString();
185 continue;
187 sc->ExpectString();
190 VName Key(*sc->String, VName::AddLower);
191 sc->Expect("=");
192 sc->ExpectString();
193 VStr Value = HandleEscapes(sc->String);
194 while (!sc->Check(";")) {
195 sc->ExpectString();
196 Value += HandleEscapes(sc->String);
199 // check for replacement
200 VLangEntry *vep = table.get(Key);
201 if (!vep || vep->PassNum >= PassNum) {
202 VLangEntry Entry;
203 Entry.Value = Value;
204 Entry.PassNum = PassNum;
205 table.put(Key, Entry);
206 //GCon->Logf(NAME_Dev, " [%s]=[%s]", *VStr(Key).quote(), *Value.quote());
211 delete sc;
215 //==========================================================================
217 // VLanguage::HandleEscapes
219 //==========================================================================
220 VStr VLanguage::HandleEscapes (VStr Src) {
221 bool hasWork = false;
222 for (size_t i = Src.Length(); i > 0; --i) if (Src[i-1] == '\\') { hasWork = true; break; }
223 if (!hasWork) return VStr(Src);
224 VStr Ret;
225 for (int i = 0; i < Src.Length(); ++i) {
226 char c = Src[i];
227 if (c == '\\') {
228 ++i;
229 c = Src[i];
230 if (c == 'n') c = '\n';
231 else if (c == 'r') c = '\r';
232 else if (c == 't') c = '\t';
233 else if (c == 'c') c = -127;
234 else if (c == '\n') continue;
236 Ret += c;
238 return Ret;
242 //==========================================================================
244 // VLanguage::Find
246 //==========================================================================
247 VStr VLanguage::Find (VName Key, bool *found) const {
248 if (Key == NAME_None) {
249 if (found) *found = true;
250 return VStr();
252 const VLangEntry *vep = table.get(Key);
253 if (vep) {
254 if (found) *found = true;
255 return vep->Value;
257 // try lowercase
258 for (const char *s = *Key; *s; ++s) {
259 if (*s >= 'A' && *s <= 'Z') {
260 // found uppercase letter, try lowercase name
261 VName loname = VName(*Key, VName::FindLower);
262 if (loname != NAME_None) {
263 vep = table.get(loname);
264 if (vep) {
265 if (found) *found = true;
266 return vep->Value;
269 break;
272 if (found) *found = false;
273 return VStr();
277 //==========================================================================
279 // VLanguage::Find
281 //==========================================================================
282 VStr VLanguage::Find (const char *s, bool *found) const {
283 if (!s) s = "";
284 if (!s[0]) {
285 if (found) *found = true;
286 return VStr();
288 VName loname = VName(s, VName::FindLower);
289 if (loname != NAME_None) {
290 const VLangEntry *vep = table.get(loname);
291 if (vep) {
292 if (found) *found = true;
293 return vep->Value;
296 if (found) *found = false;
297 return VStr();
301 //==========================================================================
303 // VLanguage::operator[]
305 //==========================================================================
306 VStr VLanguage::operator [] (VName Key) const {
307 bool found = false;
308 VStr res = Find(Key, &found);
309 if (found) return res;
310 return VStr(Key);
314 //==========================================================================
316 // VLanguage::operator[]
318 //==========================================================================
319 VStr VLanguage::operator [] (const char *s) const {
320 bool found = false;
321 VStr res = Find(s, &found);
322 if (found) return res;
323 return (s ? VStr(s) : VStr::EmptyString);
327 //==========================================================================
329 // VLanguage::Translate
331 // WITH '$'!
333 //==========================================================================
334 VStr VLanguage::Translate (const VStr &s) const {
335 if (s.length() < 2 || s[0] != '$') return s;
336 bool found = false;
337 VStr res = Find(*s+1, &found);
338 if (!found) res = s;
339 return res;
343 //==========================================================================
345 // VLanguage::HasTranslation
347 //==========================================================================
348 bool VLanguage::HasTranslation (VName s) const {
349 bool found = false;
350 (void)Find(s, &found);
351 return found;
355 //==========================================================================
357 // VLanguage::HasTranslation
359 //==========================================================================
360 bool VLanguage::HasTranslation (const char *s) const {
361 bool found = false;
362 (void)Find(s, &found);
363 return found;
367 //==========================================================================
369 // VLanguage::GetStringId
371 //==========================================================================
372 VName VLanguage::GetStringId (VStr Str) {
373 for (auto it = table.first(); it; ++it) {
374 if (it.getValue().Value == Str) return it.GetKey();
376 return NAME_None;
380 //==========================================================================
382 // VLanguage::GetStringIdCI
384 //==========================================================================
385 VName VLanguage::GetStringIdCI (VStr Str) {
386 for (auto it = table.first(); it; ++it) {
387 if (it.getValue().Value.strEquCI(Str)) return it.GetKey();
389 return NAME_None;
393 //==========================================================================
395 // VLanguage::ReplaceString
397 //==========================================================================
398 void VLanguage::ReplaceString (VName Key, VStr Value) {
399 VLangEntry Entry;
400 Entry.Value = Value;
401 Entry.PassNum = 0;
402 table.put(Key, Entry);