1 //Copyright Paul Reiche, Fred Ford. 1992-2002
2 
3 /*
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (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  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #include "resintrn.h"
20 #include "libs/memlib.h"
21 #include "options.h"
22 #include "types.h"
23 #include "libs/log.h"
24 #include "libs/gfxlib.h"
25 #include "libs/reslib.h"
26 #include "libs/sndlib.h"
27 #include "libs/vidlib.h"
28 #include "propfile.h"
29 #include <ctype.h>
30 #include <stdlib.h>
31 // XXX: we should not include anything from uqm/ inside libs/
32 #include "uqm/coderes.h"
33 
34 static RESOURCE_INDEX
allocResourceIndex(void)35 allocResourceIndex (void) {
36 	RESOURCE_INDEX ndx = HMalloc (sizeof (RESOURCE_INDEX_DESC));
37 	ndx->map = CharHashTable_newHashTable (NULL, NULL, NULL, NULL, NULL,
38 			0, 0.85, 0.9);
39 	return ndx;
40 }
41 
42 static void
freeResourceIndex(RESOURCE_INDEX h)43 freeResourceIndex (RESOURCE_INDEX h) {
44 	if (h != NULL)
45 	{
46 		/* TODO: This leaks the contents of h->map */
47 		CharHashTable_deleteHashTable (h->map);
48 		HFree (h);
49 	}
50 }
51 
52 #define TYPESIZ 32
53 
54 static ResourceDesc *
newResourceDesc(const char * res_id,const char * resval)55 newResourceDesc (const char *res_id, const char *resval)
56 {
57 	const char *path;
58 	int pathlen;
59 	ResourceHandlers *vtable;
60 	ResourceDesc *result, *handlerdesc;
61 	RESOURCE_INDEX idx = _get_current_index_header ();
62 	char typestr[TYPESIZ];
63 
64 	path = strchr (resval, ':');
65 	if (path == NULL)
66 	{
67 		log_add (log_Warning, "Could not find type information for resource '%s'", res_id);
68 		strncpy(typestr, "sys.UNKNOWNRES", TYPESIZ);
69 		path = resval;
70 	}
71 	else
72 	{
73 		int n = path - resval;
74 
75 		if (n >= TYPESIZ - 4)
76 		{
77 			n = TYPESIZ - 5;
78 		}
79 		strncpy (typestr, "sys.", TYPESIZ);
80 		strncat (typestr+1, resval, n);
81 		typestr[n+4] = '\0';
82 		path++;
83 	}
84 	pathlen = strlen (path);
85 
86 	handlerdesc = lookupResourceDesc(idx, typestr);
87 	if (handlerdesc == NULL) {
88 		path = resval;
89 		log_add (log_Warning, "Illegal type '%s' for resource '%s'; treating as UNKNOWNRES", typestr, res_id);
90 		handlerdesc = lookupResourceDesc(idx, "sys.UNKNOWNRES");
91 	}
92 
93 	vtable = (ResourceHandlers *)handlerdesc->resdata.ptr;
94 
95 	if (vtable->loadFun == NULL)
96 	{
97 		log_add (log_Warning, "Warning: Unable to load '%s'; no handler "
98 				"for type %s defined.", res_id, typestr);
99 		return NULL;
100 	}
101 
102 	result = HMalloc (sizeof (ResourceDesc));
103 	if (result == NULL)
104 		return NULL;
105 
106 	result->fname = HMalloc (pathlen + 1);
107 	strncpy (result->fname, path, pathlen);
108 	result->fname[pathlen] = '\0';
109 	result->vtable = vtable;
110 	result->refcount = 0;
111 
112 	if (vtable->freeFun == NULL)
113 	{
114 		/* Non-heap resources are raw values. Work those out at load time. */
115 		vtable->loadFun (result->fname, &result->resdata);
116 	}
117 	else
118 	{
119 		result->resdata.ptr = NULL;
120 	}
121 	return result;
122 }
123 
124 static void
process_resource_desc(const char * key,const char * value)125 process_resource_desc (const char *key, const char *value)
126 {
127 	CharHashTable_HashTable *map = _get_current_index_header ()->map;
128 	ResourceDesc *newDesc = newResourceDesc (key, value);
129 	if (newDesc != NULL)
130 	{
131 		if (!CharHashTable_add (map, key, newDesc))
132 		{
133 			res_Remove (key);
134 			CharHashTable_add (map, key, newDesc);
135 		}
136 	}
137 }
138 
139 static void
UseDescriptorAsRes(const char * descriptor,RESOURCE_DATA * resdata)140 UseDescriptorAsRes (const char *descriptor, RESOURCE_DATA *resdata)
141 {
142 	resdata->str = descriptor;
143 }
144 
145 static void
DescriptorToInt(const char * descriptor,RESOURCE_DATA * resdata)146 DescriptorToInt (const char *descriptor, RESOURCE_DATA *resdata)
147 {
148 	resdata->num = atoi (descriptor);
149 }
150 
151 static void
DescriptorToBoolean(const char * descriptor,RESOURCE_DATA * resdata)152 DescriptorToBoolean (const char *descriptor, RESOURCE_DATA *resdata)
153 {
154 	if (!strcasecmp (descriptor, "true"))
155 	{
156 		resdata->num = TRUE;
157 	}
158 	else
159 	{
160 		resdata->num = FALSE;
161 	}
162 }
163 
164 static inline size_t
skipWhiteSpace(const char * start)165 skipWhiteSpace (const char *start)
166 {
167 	const char *ptr = start;
168 	while (isspace (*ptr))
169 		ptr++;
170 	return (ptr - start);
171 }
172 
173 // On success, resdata->num will be filled with a 32-bits RGBA value.
174 static void
DescriptorToColor(const char * descriptor,RESOURCE_DATA * resdata)175 DescriptorToColor (const char *descriptor, RESOURCE_DATA *resdata)
176 {
177 	int bytesParsed;
178 	int componentBits;
179 	int maxComponentValue;
180 	size_t componentCount;
181 	size_t compI;
182 	int comps[4];
183 			// One element for each of r, g, b, a.
184 
185 	descriptor += skipWhiteSpace (descriptor);
186 
187 #if 0
188 	// Can't use this; '#' starts a comment.
189 	if (*descriptor == '#')
190 	{
191 		// "#rrggbb"
192 		int i;
193 		DWORD value = 0;
194 
195 		descriptor++;
196 		for (i = 0; i < 6; i++)
197 		{
198 			BYTE nibbleValue;
199 			if (*descriptor >= '0' && *descriptor <= '9')
200 				nibbleValue = *descriptor - '0';
201 			else if (*descriptor >= 'a' && *descriptor <= 'f')
202 				nibbleValue = 0xa + *descriptor - 'a';
203 			else if (*descriptor >= 'A' && *descriptor <= 'F')
204 				nibbleValue = 0xa + *descriptor - 'A';
205 			else
206 				goto fail;
207 
208 			value = (value * 16) + nibbleValue;
209 			descriptor++;
210 		}
211 
212 		descriptor += skipWhiteSpace (descriptor);
213 
214 		if (*descriptor != '\0')
215 			log_add (log_Warning, "Junk after color resource string.");
216 
217 		resdata->num = (value << 8) | 0xff;
218 		return;
219 	}
220 #endif
221 
222 	// Color is of the form "rgb(r, g, b)", "rgba(r, g, b, a)",
223 	// or "rgb15(r, g, b)".
224 
225 	if (sscanf (descriptor, "rgb ( %i , %i , %i ) %n",
226 				&comps[0], &comps[1], &comps[2], &bytesParsed) >= 3)
227 	{
228 		componentBits = 8;
229 		componentCount = 3;
230 		comps[3] = 0xff;
231 	}
232 	else if (sscanf (descriptor, "rgba ( %i , %i , %i , %i ) %n",
233 			&comps[0], &comps[1], &comps[2], &comps[3], &bytesParsed) >= 4)
234 	{
235 		componentBits = 8;
236 		componentCount = 4;
237 	}
238 	else if (sscanf (descriptor, "rgb15 ( %i , %i , %i ) %n",
239 				&comps[0], &comps[1], &comps[2], &bytesParsed) >= 3)
240 	{
241 		componentBits = 5;
242 		componentCount = 3;
243 		comps[3] = 0xff;
244 	}
245 	else
246 		goto fail;
247 
248 	if (descriptor[bytesParsed] != '\0')
249 		log_add (log_Warning, "Junk after color resource string.");
250 
251 	maxComponentValue = (1 << componentBits) - 1;
252 
253 	// Check the range of the components.
254 	for (compI = 0; compI < componentCount; compI++)
255 	{
256 		if (comps[compI] < 0)
257 		{
258 			comps[compI] = 0;
259 			log_add (log_Warning, "Color component value too small; "
260 					"value clipped.");
261 		}
262 
263 		if (comps[compI] > (long) maxComponentValue)
264 		{
265 			comps[compI] = maxComponentValue;
266 			log_add (log_Warning, "Color component value too large; "
267 					"value clipped.");
268 		}
269 	}
270 
271 	if (componentBits == 5)
272 		resdata->num = ((CC5TO8 (comps[0]) << 24) |
273 				(CC5TO8 (comps[1]) << 16) | (CC5TO8 (comps[2]) << 8) |
274 				comps[3]);
275 	else
276 		resdata->num = ((comps[0] << 24) | (comps[1] << 16) |
277 				(comps[2] << 8) | comps[3]);
278 
279 	return;
280 
281 fail:
282 	log_add (log_Error, "Invalid color description string for resource.\n");
283 	resdata->num = 0x00000000;
284 }
285 
286 static void
RawDescriptor(RESOURCE_DATA * resdata,char * buf,unsigned int size)287 RawDescriptor (RESOURCE_DATA *resdata, char *buf, unsigned int size)
288 {
289 	snprintf (buf, size, "%s", resdata->str);
290 }
291 
292 static void
IntToString(RESOURCE_DATA * resdata,char * buf,unsigned int size)293 IntToString (RESOURCE_DATA *resdata, char *buf, unsigned int size)
294 {
295 	snprintf (buf, size, "%d", resdata->num);
296 }
297 
298 
299 static void
BooleanToString(RESOURCE_DATA * resdata,char * buf,unsigned int size)300 BooleanToString (RESOURCE_DATA *resdata, char *buf, unsigned int size)
301 {
302 	snprintf (buf, size, "%s", resdata->num ? "true" : "false");
303 }
304 
305 static void
ColorToString(RESOURCE_DATA * resdata,char * buf,unsigned int size)306 ColorToString (RESOURCE_DATA *resdata, char *buf, unsigned int size)
307 {
308 	if ((resdata->num & 0xff) == 0xff)
309 	{
310 		// Opaque color, save as "rgb".
311 		snprintf (buf, size, "rgb(0x%02x, 0x%02x, 0x%02x)",
312 				(resdata->num >> 24), (resdata->num >> 16) & 0xff,
313 				(resdata->num >> 8) & 0xff);
314 	}
315 	else
316 	{
317 		// (Partially) transparent color, save as "rgba".
318 		snprintf (buf, size, "rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x)",
319 				(resdata->num >> 24), (resdata->num >> 16) & 0xff,
320 				(resdata->num >> 8) & 0xff, resdata->num & 0xff);
321 	}
322 }
323 
324 static RESOURCE_INDEX curResourceIndex;
325 
326 void
_set_current_index_header(RESOURCE_INDEX newResourceIndex)327 _set_current_index_header (RESOURCE_INDEX newResourceIndex)
328 {
329 	curResourceIndex = newResourceIndex;
330 }
331 
332 RESOURCE_INDEX
InitResourceSystem(void)333 InitResourceSystem (void)
334 {
335 	RESOURCE_INDEX ndx;
336 	if (curResourceIndex) {
337 		return curResourceIndex;
338 	}
339 	ndx = allocResourceIndex ();
340 
341 	_set_current_index_header (ndx);
342 
343 	InstallResTypeVectors ("UNKNOWNRES", UseDescriptorAsRes, NULL, NULL);
344 	InstallResTypeVectors ("STRING", UseDescriptorAsRes, NULL, RawDescriptor);
345 	InstallResTypeVectors ("INT32", DescriptorToInt, NULL, IntToString);
346 	InstallResTypeVectors ("BOOLEAN", DescriptorToBoolean, NULL,
347 			BooleanToString);
348 	InstallResTypeVectors ("COLOR", DescriptorToColor, NULL, ColorToString);
349 	InstallGraphicResTypes ();
350 	InstallStringTableResType ();
351 	InstallAudioResTypes ();
352 	InstallVideoResType ();
353 	InstallCodeResType ();
354 
355 	return ndx;
356 }
357 
358 RESOURCE_INDEX
_get_current_index_header(void)359 _get_current_index_header (void)
360 {
361 	if (!curResourceIndex) {
362 		InitResourceSystem ();
363 	}
364 	return curResourceIndex;
365 }
366 
367 void
LoadResourceIndex(uio_DirHandle * dir,const char * rmpfile,const char * prefix)368 LoadResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *prefix)
369 {
370 	PropFile_from_filename (dir, rmpfile, process_resource_desc, prefix);
371 }
372 
373 void
SaveResourceIndex(uio_DirHandle * dir,const char * rmpfile,const char * root,BOOLEAN strip_root)374 SaveResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *root, BOOLEAN strip_root)
375 {
376 	uio_Stream *f;
377 	CharHashTable_Iterator *it;
378 	unsigned int prefix_len;
379 
380 	f = res_OpenResFile (dir, rmpfile, "wb");
381 	if (!f) {
382 		/* TODO: Warning message */
383 		return;
384 	}
385 	prefix_len = root ? strlen (root) : 0;
386 	for (it = CharHashTable_getIterator (_get_current_index_header ()->map);
387 	     !CharHashTable_iteratorDone (it);
388 	     it = CharHashTable_iteratorNext (it)) {
389 		char *key = CharHashTable_iteratorKey (it);
390 		if (!root || !strncmp (root, key, prefix_len)) {
391 			ResourceDesc *value = CharHashTable_iteratorValue (it);
392 			if (!value) {
393 				log_add(log_Warning, "Resource %s had no value", key);
394 			} else if (!value->vtable) {
395 				log_add(log_Warning, "Resource %s had no type", key);
396 			} else if (value->vtable->toString) {
397 				char buf[256];
398 				value->vtable->toString (&value->resdata, buf, 256);
399 				buf[255]=0;
400 				if (root && strip_root) {
401 					WriteResFile (key+prefix_len, 1, strlen (key) - prefix_len, f);
402 				} else {
403 					WriteResFile (key, 1, strlen (key), f);
404 				}
405 				PutResFileChar(' ', f);
406 				PutResFileChar('=', f);
407 				PutResFileChar(' ', f);
408 				WriteResFile (value->vtable->resType, 1, strlen (value->vtable->resType), f);
409 				PutResFileChar(':', f);
410 				WriteResFile (buf, 1, strlen (buf), f);
411 				PutResFileNewline(f);
412 			}
413 		}
414 	}
415 	res_CloseResFile (f);
416 	CharHashTable_freeIterator (it);
417 }
418 
419 void
UninitResourceSystem(void)420 UninitResourceSystem (void)
421 {
422 	freeResourceIndex (_get_current_index_header ());
423 	_set_current_index_header (NULL);
424 }
425 
426 BOOLEAN
InstallResTypeVectors(const char * resType,ResourceLoadFun * loadFun,ResourceFreeFun * freeFun,ResourceStringFun * stringFun)427 InstallResTypeVectors (const char *resType, ResourceLoadFun *loadFun,
428 		ResourceFreeFun *freeFun, ResourceStringFun *stringFun)
429 {
430 	ResourceHandlers *handlers;
431 	ResourceDesc *result;
432 	char key[TYPESIZ];
433 	int typelen;
434 	CharHashTable_HashTable *map;
435 
436 	snprintf(key, TYPESIZ, "sys.%s", resType);
437 	key[TYPESIZ-1] = '\0';
438 	typelen = strlen(resType);
439 
440 	handlers = HMalloc (sizeof (ResourceHandlers));
441 	if (handlers == NULL)
442 	{
443 		return FALSE;
444 	}
445 	handlers->loadFun = loadFun;
446 	handlers->freeFun = freeFun;
447 	handlers->toString = stringFun;
448 	handlers->resType = resType;
449 
450 	result = HMalloc (sizeof (ResourceDesc));
451 	if (result == NULL)
452 		return FALSE;
453 
454 	result->fname = HMalloc (strlen(resType) + 1);
455 	strncpy (result->fname, resType, typelen);
456 	result->fname[typelen] = '\0';
457 	result->vtable = NULL;
458 	result->resdata.ptr = handlers;
459 
460 	map = _get_current_index_header ()->map;
461 	return CharHashTable_add (map, key, result) != 0;
462 }
463 
464 /* These replace the mapres.c calls and probably should be split out at some point. */
465 BOOLEAN
res_IsString(const char * key)466 res_IsString (const char *key)
467 {
468 	RESOURCE_INDEX idx = _get_current_index_header ();
469 	ResourceDesc *desc = lookupResourceDesc (idx, key);
470 	return desc && !strcmp(desc->vtable->resType, "STRING");
471 }
472 
473 const char *
res_GetString(const char * key)474 res_GetString (const char *key)
475 {
476 	RESOURCE_INDEX idx = _get_current_index_header ();
477 	ResourceDesc *desc = lookupResourceDesc (idx, key);
478 	if (!desc || !desc->resdata.str || strcmp(desc->vtable->resType, "STRING"))
479 		return "";
480 	/* TODO: Work out exact STRING semantics, specifically, the lifetime of
481 	 *   the returned value. If caller is allowed to reference the returned
482 	 *   value forever, STRING has to be ref-counted. */
483 	return desc->resdata.str;
484 }
485 
486 void
res_PutString(const char * key,const char * value)487 res_PutString (const char *key, const char *value)
488 {
489 	RESOURCE_INDEX idx = _get_current_index_header ();
490 	ResourceDesc *desc = lookupResourceDesc (idx, key);
491 	int srclen, dstlen;
492 	if (!desc || !desc->resdata.str || strcmp(desc->vtable->resType, "STRING"))
493 	{
494 		/* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */
495 		process_resource_desc(key, "STRING:undefined");
496 		desc = lookupResourceDesc (idx, key);
497 	}
498 	srclen = strlen (value);
499 	dstlen = strlen (desc->fname);
500 	if (srclen > dstlen) {
501 		char *newValue = HMalloc(srclen + 1);
502 		char *oldValue = desc->fname;
503 		log_add(log_Warning, "Reallocating string space for '%s'", key);
504 		strncpy (newValue, value, srclen + 1);
505 		desc->resdata.str = newValue;
506 		desc->fname = newValue;
507 		HFree (oldValue);
508 	} else {
509 		strncpy (desc->fname, value, dstlen + 1);
510 	}
511 }
512 
513 BOOLEAN
res_IsInteger(const char * key)514 res_IsInteger (const char *key)
515 {
516 	RESOURCE_INDEX idx = _get_current_index_header ();
517 	ResourceDesc *desc = lookupResourceDesc (idx, key);
518 	return desc && !strcmp(desc->vtable->resType, "INT32");
519 }
520 
521 int
res_GetInteger(const char * key)522 res_GetInteger (const char *key)
523 {
524 	RESOURCE_INDEX idx = _get_current_index_header ();
525 	ResourceDesc *desc = lookupResourceDesc (idx, key);
526 	if (!desc || strcmp(desc->vtable->resType, "INT32"))
527 	{
528 		// TODO: Better error handling
529 		return 0;
530 	}
531 	return desc->resdata.num;
532 }
533 
534 void
res_PutInteger(const char * key,int value)535 res_PutInteger (const char *key, int value)
536 {
537 	RESOURCE_INDEX idx = _get_current_index_header ();
538 	ResourceDesc *desc = lookupResourceDesc (idx, key);
539 	if (!desc || strcmp(desc->vtable->resType, "INT32"))
540 	{
541 		/* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */
542 		process_resource_desc(key, "INT32:0");
543 		desc = lookupResourceDesc (idx, key);
544 	}
545 	desc->resdata.num = value;
546 }
547 
548 BOOLEAN
res_IsBoolean(const char * key)549 res_IsBoolean (const char *key)
550 {
551 	RESOURCE_INDEX idx = _get_current_index_header ();
552 	ResourceDesc *desc = lookupResourceDesc (idx, key);
553 	return desc && !strcmp(desc->vtable->resType, "BOOLEAN");
554 }
555 
556 BOOLEAN
res_GetBoolean(const char * key)557 res_GetBoolean (const char *key)
558 {
559 	RESOURCE_INDEX idx = _get_current_index_header ();
560 	ResourceDesc *desc = lookupResourceDesc (idx, key);
561 	if (!desc || strcmp(desc->vtable->resType, "BOOLEAN"))
562 	{
563 		// TODO: Better error handling
564 		return FALSE;
565 	}
566 	return desc->resdata.num ? TRUE : FALSE;
567 }
568 
569 void
res_PutBoolean(const char * key,BOOLEAN value)570 res_PutBoolean (const char *key, BOOLEAN value)
571 {
572 	RESOURCE_INDEX idx = _get_current_index_header ();
573 	ResourceDesc *desc = lookupResourceDesc (idx, key);
574 	if (!desc || strcmp(desc->vtable->resType, "BOOLEAN"))
575 	{
576 		/* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */
577 		process_resource_desc(key, "BOOLEAN:false");
578 		desc = lookupResourceDesc (idx, key);
579 	}
580 	desc->resdata.num = value;
581 }
582 
583 BOOLEAN
res_IsColor(const char * key)584 res_IsColor (const char *key)
585 {
586 	RESOURCE_INDEX idx = _get_current_index_header ();
587 	ResourceDesc *desc = lookupResourceDesc (idx, key);
588 	return desc && !strcmp(desc->vtable->resType, "COLOR");
589 }
590 
591 Color
res_GetColor(const char * key)592 res_GetColor (const char *key)
593 {
594 	RESOURCE_INDEX idx = _get_current_index_header ();
595 	ResourceDesc *desc = lookupResourceDesc (idx, key);
596 	DWORD num;
597 	if (!desc || strcmp(desc->vtable->resType, "COLOR"))
598 	{
599 		// TODO: Better error handling
600 		return buildColorRgba (0, 0, 0, 0);
601 	}
602 
603 	num = desc->resdata.num;
604 	return buildColorRgba (num >> 24, (num >> 16) & 0xff,
605 			(desc->resdata.num >> 8) & 0xff, num & 0xff);
606 }
607 
608 void
res_PutColor(const char * key,Color value)609 res_PutColor (const char *key, Color value)
610 {
611 	RESOURCE_INDEX idx = _get_current_index_header ();
612 	ResourceDesc *desc = lookupResourceDesc (idx, key);
613 	if (!desc || strcmp(desc->vtable->resType, "COLOR"))
614 	{
615 		/* TODO: This is kind of roundabout. We can do better by refactoring
616 		 * newResourceDesc */
617 		process_resource_desc(key, "COLOR:rgb(0, 0, 0)");
618 		desc = lookupResourceDesc (idx, key);
619 	}
620 	desc->resdata.num =
621 			(value.r << 24) | (value.g << 16) | (value.b << 8) | value.a;
622 }
623 
624 BOOLEAN
res_HasKey(const char * key)625 res_HasKey (const char *key)
626 {
627 	RESOURCE_INDEX idx = _get_current_index_header ();
628 	return (lookupResourceDesc(idx, key) != NULL);
629 }
630 
631 BOOLEAN
res_Remove(const char * key)632 res_Remove (const char *key)
633 {
634 	CharHashTable_HashTable *map = _get_current_index_header ()->map;
635 	ResourceDesc *oldDesc = (ResourceDesc *)CharHashTable_find (map, key);
636 	if (oldDesc != NULL)
637 	{
638 		if (oldDesc->resdata.ptr != NULL)
639 		{
640 			if (oldDesc->refcount > 0)
641 				log_add (log_Warning, "WARNING: Replacing '%s' while it is live", key);
642 			if (oldDesc->vtable && oldDesc->vtable->freeFun)
643 			{
644 				oldDesc->vtable->freeFun(oldDesc->resdata.ptr);
645 			}
646 		}
647 		HFree (oldDesc->fname);
648 		HFree (oldDesc);
649 	}
650 	return CharHashTable_remove (map, key);
651 }
652