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