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