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