1 //
2 // Copyright(C) 2005-2014 Simon Howard
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 //
15 // Parses Text substitution sections in dehacked files
16 //
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <stdarg.h>
22 
23 #include "doomtype.h"
24 #include "deh_str.h"
25 #include "m_misc.h"
26 
27 #include "z_zone.h"
28 
29 typedef struct
30 {
31     char *from_text;
32     char *to_text;
33 } deh_substitution_t;
34 
35 static deh_substitution_t **hash_table = NULL;
36 static int hash_table_entries;
37 static int hash_table_length = -1;
38 
39 // This is the algorithm used by glib
40 
strhash(const char * s)41 static unsigned int strhash(const char *s)
42 {
43     const char *p = s;
44     unsigned int h = *p;
45 
46     if (h)
47     {
48         for (p += 1; *p; p++)
49             h = (h << 5) - h + *p;
50     }
51 
52     return h;
53 }
54 
SubstitutionForString(const char * s)55 static deh_substitution_t *SubstitutionForString(const char *s)
56 {
57     int entry;
58 
59     // Fallback if we have not initialized the hash table yet
60     if (hash_table_length < 0)
61 	return NULL;
62 
63     entry = strhash(s) % hash_table_length;
64 
65     while (hash_table[entry] != NULL)
66     {
67         if (!strcmp(hash_table[entry]->from_text, s))
68         {
69             // substitution found!
70             return hash_table[entry];
71         }
72 
73         entry = (entry + 1) % hash_table_length;
74     }
75 
76     // no substitution found
77     return NULL;
78 }
79 
80 // Look up a string to see if it has been replaced with something else
81 // This will be used throughout the program to substitute text
82 
DEH_String(const char * s)83 const char *DEH_String(const char *s)
84 {
85     deh_substitution_t *subst;
86 
87     subst = SubstitutionForString(s);
88 
89     if (subst != NULL)
90     {
91         return subst->to_text;
92     }
93     else
94     {
95         return s;
96     }
97 }
98 
99 // [crispy] returns true if a string has been substituted
100 
DEH_HasStringReplacement(const char * s)101 boolean DEH_HasStringReplacement(const char *s)
102 {
103     return DEH_String(s) != s;
104 }
105 
InitHashTable(void)106 static void InitHashTable(void)
107 {
108     // init hash table
109 
110     hash_table_entries = 0;
111     hash_table_length = 16;
112     hash_table = Z_Malloc(sizeof(deh_substitution_t *) * hash_table_length,
113                           PU_STATIC, NULL);
114     memset(hash_table, 0, sizeof(deh_substitution_t *) * hash_table_length);
115 }
116 
117 static void DEH_AddToHashtable(deh_substitution_t *sub);
118 
IncreaseHashtable(void)119 static void IncreaseHashtable(void)
120 {
121     deh_substitution_t **old_table;
122     int old_table_length;
123     int i;
124 
125     // save the old table
126 
127     old_table = hash_table;
128     old_table_length = hash_table_length;
129 
130     // double the size
131 
132     hash_table_length *= 2;
133     hash_table = Z_Malloc(sizeof(deh_substitution_t *) * hash_table_length,
134                           PU_STATIC, NULL);
135     memset(hash_table, 0, sizeof(deh_substitution_t *) * hash_table_length);
136 
137     // go through the old table and insert all the old entries
138 
139     for (i=0; i<old_table_length; ++i)
140     {
141         if (old_table[i] != NULL)
142         {
143             DEH_AddToHashtable(old_table[i]);
144         }
145     }
146 
147     // free the old table
148 
149     Z_Free(old_table);
150 }
151 
DEH_AddToHashtable(deh_substitution_t * sub)152 static void DEH_AddToHashtable(deh_substitution_t *sub)
153 {
154     int entry;
155 
156     // if the hash table is more than 60% full, increase its size
157 
158     if ((hash_table_entries * 10) / hash_table_length > 6)
159     {
160         IncreaseHashtable();
161     }
162 
163     // find where to insert it
164     entry = strhash(sub->from_text) % hash_table_length;
165 
166     while (hash_table[entry] != NULL)
167     {
168         entry = (entry + 1) % hash_table_length;
169     }
170 
171     hash_table[entry] = sub;
172     ++hash_table_entries;
173 }
174 
DEH_AddStringReplacement(const char * from_text,const char * to_text)175 void DEH_AddStringReplacement(const char *from_text, const char *to_text)
176 {
177     deh_substitution_t *sub;
178     size_t len;
179 
180     // Initialize the hash table if this is the first time
181     if (hash_table_length < 0)
182     {
183         InitHashTable();
184     }
185 
186     // Check to see if there is an existing substitution already in place.
187     sub = SubstitutionForString(from_text);
188 
189     if (sub != NULL)
190     {
191         Z_Free(sub->to_text);
192 
193         len = strlen(to_text) + 1;
194         sub->to_text = Z_Malloc(len, PU_STATIC, NULL);
195         memcpy(sub->to_text, to_text, len);
196     }
197     else
198     {
199         // We need to allocate a new substitution.
200         sub = Z_Malloc(sizeof(*sub), PU_STATIC, 0);
201 
202         // We need to create our own duplicates of the provided strings.
203         len = strlen(from_text) + 1;
204         sub->from_text = Z_Malloc(len, PU_STATIC, NULL);
205         memcpy(sub->from_text, from_text, len);
206 
207         len = strlen(to_text) + 1;
208         sub->to_text = Z_Malloc(len, PU_STATIC, NULL);
209         memcpy(sub->to_text, to_text, len);
210 
211         DEH_AddToHashtable(sub);
212     }
213 }
214 
215 typedef enum
216 {
217     FORMAT_ARG_INVALID,
218     FORMAT_ARG_INT,
219     FORMAT_ARG_FLOAT,
220     FORMAT_ARG_CHAR,
221     FORMAT_ARG_STRING,
222     FORMAT_ARG_PTR,
223     FORMAT_ARG_SAVE_POS
224 } format_arg_t;
225 
226 // Get the type of a format argument.
227 // We can mix-and-match different format arguments as long as they
228 // are for the same data type.
229 
FormatArgumentType(char c)230 static format_arg_t FormatArgumentType(char c)
231 {
232     switch (c)
233     {
234         case 'd': case 'i': case 'o': case 'u': case 'x': case 'X':
235             return FORMAT_ARG_INT;
236 
237         case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
238         case 'a': case 'A':
239             return FORMAT_ARG_FLOAT;
240 
241         case 'c': case 'C':
242             return FORMAT_ARG_CHAR;
243 
244         case 's': case 'S':
245             return FORMAT_ARG_STRING;
246 
247         case 'p':
248             return FORMAT_ARG_PTR;
249 
250         case 'n':
251             return FORMAT_ARG_SAVE_POS;
252 
253         default:
254             return FORMAT_ARG_INVALID;
255     }
256 }
257 
258 // Given the specified string, get the type of the first format
259 // string encountered.
260 
NextFormatArgument(const char ** str)261 static format_arg_t NextFormatArgument(const char **str)
262 {
263     format_arg_t argtype;
264 
265     // Search for the '%' starting the next string.
266 
267     while (**str != '\0')
268     {
269         if (**str == '%')
270         {
271             ++*str;
272 
273             // Don't stop for double-%s.
274 
275             if (**str != '%')
276             {
277                 break;
278             }
279         }
280 
281         ++*str;
282     }
283 
284     // Find the type of the format string.
285 
286     while (**str != '\0')
287     {
288         argtype = FormatArgumentType(**str);
289 
290         if (argtype != FORMAT_ARG_INVALID)
291         {
292             ++*str;
293 
294             return argtype;
295         }
296 
297         ++*str;
298     }
299 
300     // Stop searching, we have reached the end.
301 
302     *str = NULL;
303 
304     return FORMAT_ARG_INVALID;
305 }
306 
307 // Check if the specified argument type is a valid replacement for
308 // the original.
309 
ValidArgumentReplacement(format_arg_t original,format_arg_t replacement)310 static boolean ValidArgumentReplacement(format_arg_t original,
311                                         format_arg_t replacement)
312 {
313     // In general, the original and replacement types should be
314     // identical.  However, there are some cases where the replacement
315     // is valid and the types don't match.
316 
317     // Characters can be represented as ints.
318 
319     if (original == FORMAT_ARG_CHAR && replacement == FORMAT_ARG_INT)
320     {
321         return true;
322     }
323 
324     // Strings are pointers.
325 
326     if (original == FORMAT_ARG_STRING && replacement == FORMAT_ARG_PTR)
327     {
328         return true;
329     }
330 
331     return original == replacement;
332 }
333 
334 // Return true if the specified string contains no format arguments.
335 
ValidFormatReplacement(const char * original,const char * replacement)336 static boolean ValidFormatReplacement(const char *original, const char *replacement)
337 {
338     const char *rover1;
339     const char *rover2;
340     int argtype1, argtype2;
341 
342     // Check each argument in turn and compare types.
343 
344     rover1 = original; rover2 = replacement;
345 
346     for (;;)
347     {
348         argtype1 = NextFormatArgument(&rover1);
349         argtype2 = NextFormatArgument(&rover2);
350 
351         if (argtype2 == FORMAT_ARG_INVALID)
352         {
353             // No more arguments left to read from the replacement string.
354 
355             break;
356         }
357         else if (argtype1 == FORMAT_ARG_INVALID)
358         {
359             // Replacement string has more arguments than the original.
360 
361             return false;
362         }
363         else if (!ValidArgumentReplacement(argtype1, argtype2))
364         {
365             // Not a valid replacement argument.
366 
367             return false;
368         }
369     }
370 
371     return true;
372 }
373 
374 // Get replacement format string, checking arguments.
375 
FormatStringReplacement(const char * s)376 static const char *FormatStringReplacement(const char *s)
377 {
378     const char *repl;
379 
380     repl = DEH_String(s);
381 
382     if (!ValidFormatReplacement(s, repl))
383     {
384         printf("WARNING: Unsafe dehacked replacement provided for "
385                "printf format string: %s\n", s);
386 
387         return s;
388     }
389 
390     return repl;
391 }
392 
393 // printf(), performing a replacement on the format string.
394 
DEH_printf(const char * fmt,...)395 void DEH_printf(const char *fmt, ...)
396 {
397     va_list args;
398     const char *repl;
399 
400     repl = FormatStringReplacement(fmt);
401 
402     va_start(args, fmt);
403 
404     vprintf(repl, args);
405 
406     va_end(args);
407 }
408 
409 // fprintf(), performing a replacement on the format string.
410 
DEH_fprintf(FILE * fstream,const char * fmt,...)411 void DEH_fprintf(FILE *fstream, const char *fmt, ...)
412 {
413     va_list args;
414     const char *repl;
415 
416     repl = FormatStringReplacement(fmt);
417 
418     va_start(args, fmt);
419 
420     vfprintf(fstream, repl, args);
421 
422     va_end(args);
423 }
424 
425 // snprintf(), performing a replacement on the format string.
426 
DEH_snprintf(char * buffer,size_t len,const char * fmt,...)427 void DEH_snprintf(char *buffer, size_t len, const char *fmt, ...)
428 {
429     va_list args;
430     const char *repl;
431 
432     repl = FormatStringReplacement(fmt);
433 
434     va_start(args, fmt);
435 
436     M_vsnprintf(buffer, len, repl, args);
437 
438     va_end(args);
439 }
440 
441