1 /*
2 ** stringtable.cpp
3 ** Implements the FStringTable class
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 #include <string.h>
36 #include <stddef.h>
37
38 #include "stringtable.h"
39 #include "cmdlib.h"
40 #include "m_swap.h"
41 #include "w_wad.h"
42 #include "i_system.h"
43 #include "sc_man.h"
44 #include "c_dispatch.h"
45 #include "v_text.h"
46 #include "gi.h"
47
48 // PassNum identifies which language pass this string is from.
49 // PassNum 0 is for DeHacked.
50 // PassNum 1 is for * strings.
51 // PassNum 2+ are for specific locales.
52
53 struct FStringTable::StringEntry
54 {
55 StringEntry *Next;
56 char *Name;
57 BYTE PassNum;
58 char String[];
59 };
60
FStringTable()61 FStringTable::FStringTable ()
62 {
63 for (int i = 0; i < HASH_SIZE; ++i)
64 {
65 Buckets[i] = NULL;
66 }
67 }
68
~FStringTable()69 FStringTable::~FStringTable ()
70 {
71 FreeData ();
72 }
73
FreeData()74 void FStringTable::FreeData ()
75 {
76 for (int i = 0; i < HASH_SIZE; ++i)
77 {
78 StringEntry *entry = Buckets[i], *next;
79 Buckets[i] = NULL;
80 while (entry != NULL)
81 {
82 next = entry->Next;
83 M_Free (entry);
84 entry = next;
85 }
86 }
87 }
88
FreeNonDehackedStrings()89 void FStringTable::FreeNonDehackedStrings ()
90 {
91 for (int i = 0; i < HASH_SIZE; ++i)
92 {
93 StringEntry *entry, *next, **pentry;
94
95 for (pentry = &Buckets[i], entry = *pentry; entry != NULL; )
96 {
97 next = entry->Next;
98 if (entry->PassNum != 0)
99 {
100 *pentry = next;
101 M_Free (entry);
102 }
103 else
104 {
105 pentry = &entry->Next;
106 }
107 entry = next;
108 }
109 }
110 }
111
112 #include "doomerrors.h"
LoadStrings(bool enuOnly)113 void FStringTable::LoadStrings (bool enuOnly)
114 {
115 int lastlump, lump;
116 int i, j;
117
118 FreeNonDehackedStrings ();
119
120 lastlump = 0;
121
122 while ((lump = Wads.FindLump ("LANGUAGE", &lastlump)) != -1)
123 {
124 j = 0;
125 if (!enuOnly)
126 {
127 LoadLanguage (lump, MAKE_ID('*',0,0,0), true, ++j);
128 for (i = 0; i < 4; ++i)
129 {
130 LoadLanguage (lump, LanguageIDs[i], true, ++j);
131 LoadLanguage (lump, LanguageIDs[i] & MAKE_ID(0xff,0xff,0,0), true, ++j);
132 LoadLanguage (lump, LanguageIDs[i], false, ++j);
133 }
134 }
135
136 // Fill in any missing strings with the default language
137 LoadLanguage (lump, MAKE_ID('*','*',0,0), true, ++j);
138 }
139 }
140
LoadLanguage(int lumpnum,DWORD code,bool exactMatch,int passnum)141 void FStringTable::LoadLanguage (int lumpnum, DWORD code, bool exactMatch, int passnum)
142 {
143 static bool errordone = false;
144 const DWORD orMask = exactMatch ? 0 : MAKE_ID(0,0,0xff,0);
145 DWORD inCode = 0;
146 StringEntry *entry, **pentry;
147 DWORD bucket;
148 int cmpval;
149 bool skip = true;
150
151 code |= orMask;
152
153 FScanner sc(lumpnum);
154 sc.SetCMode (true);
155 while (sc.GetString ())
156 {
157 if (sc.Compare ("["))
158 { // Process language identifiers
159 bool donot = false;
160 bool forceskip = false;
161 skip = true;
162 sc.MustGetString ();
163 do
164 {
165 size_t len = sc.StringLen;
166 if (len != 2 && len != 3)
167 {
168 if (len == 1 && sc.String[0] == '~')
169 {
170 donot = true;
171 sc.MustGetString ();
172 continue;
173 }
174 if (len == 1 && sc.String[0] == '*')
175 {
176 inCode = MAKE_ID('*',0,0,0);
177 }
178 else if (len == 7 && stricmp (sc.String, "default") == 0)
179 {
180 inCode = MAKE_ID('*','*',0,0);
181 }
182 else
183 {
184 sc.ScriptError ("The language code must be 2 or 3 characters long.\n'%s' is %lu characters long.",
185 sc.String, len);
186 }
187 }
188 else
189 {
190 inCode = MAKE_ID(tolower(sc.String[0]), tolower(sc.String[1]), tolower(sc.String[2]), 0);
191 }
192 if ((inCode | orMask) == code)
193 {
194 if (donot)
195 {
196 forceskip = true;
197 donot = false;
198 }
199 else
200 {
201 skip = false;
202 }
203 }
204 sc.MustGetString ();
205 } while (!sc.Compare ("]"));
206 if (donot)
207 {
208 sc.ScriptError ("You must specify a language after ~");
209 }
210 skip |= forceskip;
211 }
212 else
213 { // Process string definitions.
214 if (inCode == 0)
215 {
216 // LANGUAGE lump is bad. We need to check if this is an old binary
217 // lump and if so just skip it to allow old WADs to run which contain
218 // such a lump.
219 if (!sc.isText())
220 {
221 if (!errordone) Printf("Skipping binary 'LANGUAGE' lump.\n");
222 errordone = true;
223 return;
224 }
225 sc.ScriptError ("Found a string without a language specified.");
226 }
227
228 bool savedskip = skip;
229 if (sc.Compare("$"))
230 {
231 sc.MustGetStringName("ifgame");
232 sc.MustGetStringName("(");
233 sc.MustGetString();
234 skip |= !sc.Compare(GameTypeName());
235 sc.MustGetStringName(")");
236 sc.MustGetString();
237
238 }
239
240 if (skip)
241 { // We're not interested in this language, so skip the string.
242 sc.MustGetStringName ("=");
243 sc.MustGetString ();
244 do
245 {
246 sc.MustGetString ();
247 }
248 while (!sc.Compare (";"));
249 skip = savedskip;
250 continue;
251 }
252
253 FString strName (sc.String);
254 sc.MustGetStringName ("=");
255 sc.MustGetString ();
256 FString strText (sc.String, ProcessEscapes (sc.String));
257 sc.MustGetString ();
258 while (!sc.Compare (";"))
259 {
260 ProcessEscapes (sc.String);
261 strText += sc.String;
262 sc.MustGetString ();
263 }
264
265 // Does this string exist? If so, should we overwrite it?
266 bucket = MakeKey (strName.GetChars()) & (HASH_SIZE-1);
267 pentry = &Buckets[bucket];
268 entry = *pentry;
269 cmpval = 1;
270 while (entry != NULL)
271 {
272 cmpval = stricmp (entry->Name, strName.GetChars());
273 if (cmpval >= 0)
274 break;
275 pentry = &entry->Next;
276 entry = *pentry;
277 }
278 if (cmpval == 0 && entry->PassNum >= passnum)
279 {
280 *pentry = entry->Next;
281 M_Free (entry);
282 entry = NULL;
283 }
284 if (entry == NULL || cmpval > 0)
285 {
286 entry = (StringEntry *)M_Malloc (sizeof(*entry) + strText.Len() + strName.Len() + 2);
287 entry->Next = *pentry;
288 *pentry = entry;
289 strcpy (entry->String, strText.GetChars());
290 strcpy (entry->Name = entry->String + strText.Len() + 1, strName.GetChars());
291 entry->PassNum = passnum;
292 }
293 }
294 }
295 }
296
297 // Replace \ escape sequences in a string with the escaped characters.
ProcessEscapes(char * iptr)298 size_t FStringTable::ProcessEscapes (char *iptr)
299 {
300 char *sptr = iptr, *optr = iptr, c;
301
302 while ((c = *iptr++) != '\0')
303 {
304 if (c == '\\')
305 {
306 c = *iptr++;
307 if (c == 'n')
308 c = '\n';
309 else if (c == 'c')
310 c = TEXTCOLOR_ESCAPE;
311 else if (c == 'r')
312 c = '\r';
313 else if (c == 't')
314 c = '\t';
315 else if (c == '\n')
316 continue;
317 }
318 *optr++ = c;
319 }
320 *optr = '\0';
321 return optr - sptr;
322 }
323
324 // Finds a string by name and returns its value
operator [](const char * name) const325 const char *FStringTable::operator[] (const char *name) const
326 {
327 if (name == NULL)
328 {
329 return NULL;
330 }
331 DWORD bucket = MakeKey (name) & (HASH_SIZE - 1);
332 StringEntry *entry = Buckets[bucket];
333
334 while (entry != NULL)
335 {
336 int cmpval = stricmp (entry->Name, name);
337 if (cmpval == 0)
338 {
339 return entry->String;
340 }
341 if (cmpval == 1)
342 {
343 return NULL;
344 }
345 entry = entry->Next;
346 }
347 return NULL;
348 }
349
350 // Finds a string by name and returns its value. If the string does
351 // not exist, returns the passed name instead.
operator ()(const char * name) const352 const char *FStringTable::operator() (const char *name) const
353 {
354 const char *str = operator[] (name);
355 return str ? str : name;
356 }
357
358 // Find a string by name. pentry1 is a pointer to a pointer to it, and entry1 is a
359 // pointer to it. Return NULL for entry1 if it wasn't found.
FindString(const char * name,StringEntry ** & pentry1,StringEntry * & entry1)360 void FStringTable::FindString (const char *name, StringEntry **&pentry1, StringEntry *&entry1)
361 {
362 DWORD bucket = MakeKey (name) & (HASH_SIZE - 1);
363 StringEntry **pentry = &Buckets[bucket], *entry = *pentry;
364
365 while (entry != NULL)
366 {
367 int cmpval = stricmp (entry->Name, name);
368 if (cmpval == 0)
369 {
370 pentry1 = pentry;
371 entry1 = entry;
372 return;
373 }
374 if (cmpval == 1)
375 {
376 pentry1 = pentry;
377 entry1 = NULL;
378 return;
379 }
380 pentry = &entry->Next;
381 entry = *pentry;
382 }
383 pentry1 = pentry;
384 entry1 = entry;
385 }
386
387 // Find a string with the same exact text. Returns its name.
MatchString(const char * string) const388 const char *FStringTable::MatchString (const char *string) const
389 {
390 for (int i = 0; i < HASH_SIZE; ++i)
391 {
392 for (StringEntry *entry = Buckets[i]; entry != NULL; entry = entry->Next)
393 {
394 if (strcmp (entry->String, string) == 0)
395 {
396 return entry->Name;
397 }
398 }
399 }
400 return NULL;
401 }
402
SetString(const char * name,const char * newString)403 void FStringTable::SetString (const char *name, const char *newString)
404 {
405 StringEntry **pentry, *oentry;
406 FindString (name, pentry, oentry);
407
408 size_t newlen = strlen (newString);
409 size_t namelen = strlen (name);
410
411 // Create a new string entry
412 StringEntry *entry = (StringEntry *)M_Malloc (sizeof(*entry) + newlen + namelen + 2);
413 strcpy (entry->String, newString);
414 strcpy (entry->Name = entry->String + newlen + 1, name);
415 entry->PassNum = 0;
416
417 // If this is a new string, insert it. Otherwise, replace the old one.
418 if (oentry == NULL)
419 {
420 entry->Next = *pentry;
421 *pentry = entry;
422 }
423 else
424 {
425 *pentry = entry;
426 entry->Next = oentry->Next;
427 M_Free (oentry);
428 }
429 }
430