Implemented garglk_set_program_name(), garglk_set_program_info(), garglk_set_story_name()
[rodin/chimara.git] / interpreters / glulxe / string.c
1 /* string.c: Glulxe string and text functions.
2     Designed by Andrew Plotkin <erkyrath@eblong.com>
3     http://eblong.com/zarf/glulx/index.html
4 */
5
6 #include "glk.h"
7 #include "glulxe.h"
8
9 static glui32 iosys_mode;
10 static glui32 iosys_rock;
11 /* These constants are defined in the Glulx spec. */
12 #define iosys_None (0)
13 #define iosys_Filter (1)
14 #define iosys_Glk (2)
15
16 #define CACHEBITS (4)
17 #define CACHESIZE (1<<CACHEBITS) 
18 #define CACHEMASK (15)
19
20 typedef struct cacheblock_struct {
21   int depth; /* 1 to 4 */
22   int type;
23   union {
24     struct cacheblock_struct *branches;
25     unsigned char ch;
26     glui32 uch;
27     glui32 addr;
28   } u;
29 } cacheblock_t;
30
31 static int never_cache_stringtable = FALSE;
32
33 /* The current string-decoding tables, broken out into a fast and
34    easy-to-use form. */
35 static int tablecache_valid = FALSE;
36 static cacheblock_t tablecache;
37
38 static void stream_setup_unichar(void);
39
40 static void nopio_char_han(unsigned char ch);
41 static void filio_char_han(unsigned char ch);
42 static void nopio_unichar_han(glui32 ch);
43 static void filio_unichar_han(glui32 ch);
44 static void glkio_unichar_nouni_han(glui32 val);
45 static void (*glkio_unichar_han_ptr)(glui32 val) = NULL;
46
47 static void dropcache(cacheblock_t *cablist);
48 static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
49   int mask);
50 static void dumpcache(cacheblock_t *cablist, int count, int indent);
51
52 void stream_get_iosys(glui32 *mode, glui32 *rock)
53 {
54   *mode = iosys_mode;
55   *rock = iosys_rock;
56 }
57
58 static void stream_setup_unichar()
59 {
60 #ifdef GLK_MODULE_UNICODE
61
62   if (glk_gestalt(gestalt_Unicode, 0))
63     glkio_unichar_han_ptr = glk_put_char_uni;
64   else
65     glkio_unichar_han_ptr = glkio_unichar_nouni_han;
66
67 #else /* GLK_MODULE_UNICODE */
68
69   glkio_unichar_han_ptr = glkio_unichar_nouni_han;
70
71 #endif /* GLK_MODULE_UNICODE */
72 }
73
74 void stream_set_iosys(glui32 mode, glui32 rock)
75 {
76   switch (mode) {
77   default:
78     mode = 0;
79     /* ...and fall through to next case (no-op I/O). */
80   case iosys_None:
81     rock = 0;
82     stream_char_handler = nopio_char_han;
83     stream_unichar_handler = nopio_unichar_han;
84     break;
85   case iosys_Filter:
86     stream_char_handler = filio_char_han;
87     stream_unichar_handler = filio_unichar_han;
88     break;
89   case iosys_Glk:
90     if (!glkio_unichar_han_ptr)
91       stream_setup_unichar();
92     rock = 0;
93     stream_char_handler = glk_put_char;
94     stream_unichar_handler = glkio_unichar_han_ptr;
95     break;
96   }
97
98   iosys_mode = mode;
99   iosys_rock = rock;
100 }
101
102 static void nopio_char_han(unsigned char ch)
103 {
104 }
105
106 static void nopio_unichar_han(glui32 ch)
107 {
108 }
109
110 static void filio_char_han(unsigned char ch)
111 {
112   glui32 val = ch;
113   push_callstub(0, 0);
114   enter_function(iosys_rock, 1, &val);
115 }
116
117 static void filio_unichar_han(glui32 val)
118 {
119   push_callstub(0, 0);
120   enter_function(iosys_rock, 1, &val);
121 }
122
123 static void glkio_unichar_nouni_han(glui32 val)
124 {
125   /* Only used if the Glk library has no Unicode functions */
126   if (val > 0xFF)
127     val = '?';
128   glk_put_char(val);
129 }
130
131 /* stream_num():
132    Write a signed integer to the current output stream.
133 */
134 void stream_num(glsi32 val, int inmiddle, int charnum)
135 {
136   int ix = 0;
137   int res, jx;
138   char buf[16];
139   glui32 ival;
140
141   if (val == 0) {
142     buf[ix] = '0';
143     ix++;
144   }
145   else {
146     if (val < 0) 
147       ival = -val;
148     else 
149       ival = val;
150
151     while (ival != 0) {
152       buf[ix] = (ival % 10) + '0';
153       ix++;
154       ival /= 10;
155     }
156
157     if (val < 0) {
158       buf[ix] = '-';
159       ix++;
160     }
161   }
162
163   switch (iosys_mode) {
164
165   case iosys_Glk:
166     while (ix) {
167       ix--;
168       glk_put_char(buf[ix]);
169     }
170     break;
171
172   case iosys_Filter:
173     if (!inmiddle) {
174       push_callstub(0x11, 0);
175     }
176     if (charnum >= ix) {
177       res = pop_callstub_string(&jx);
178       if (res) 
179         fatal_error("String-on-string call stub while printing number.");
180     }
181     else {
182       ival = buf[(ix-1)-charnum] & 0xFF;
183       pc = val;
184       push_callstub(0x12, charnum+1);
185       enter_function(iosys_rock, 1, &ival);
186     }
187     break;
188
189   default:
190     break;
191
192   }
193 }
194
195 /* stream_string():
196    Write a Glulx string object to the current output stream.
197    inmiddle is zero if we are beginning a new string, or
198    nonzero if restarting one (E0/E1/E2, as appropriate for
199    the string type).
200 */
201 void stream_string(glui32 addr, int inmiddle, int bitnum)
202 {
203   int ch;
204   int type;
205   int alldone = FALSE;
206   int substring = (inmiddle != 0);
207   glui32 ival;
208
209   if (!addr)
210     fatal_error("Called stream_string with null address.");
211   
212   while (!alldone) {
213
214     if (inmiddle == 0) {
215       type = Mem1(addr);
216       if (type == 0xE2)
217         addr+=4;
218       else
219         addr++;
220       bitnum = 0;
221     }
222     else {
223       type = inmiddle;
224     }
225
226     if (type == 0xE1) {
227       if (tablecache_valid) {
228         int bits, numbits;
229         int readahead;
230         glui32 tmpaddr;
231         cacheblock_t *cablist;
232         int done = 0;
233
234         /* bitnum is already set right */
235         bits = Mem1(addr); 
236         if (bitnum)
237           bits >>= bitnum;
238         numbits = (8 - bitnum);
239         readahead = FALSE;
240
241         if (tablecache.type != 0) {
242           /* This is a bit of a cheat. If the top-level block is not
243              a branch, then it must be a string-terminator -- otherwise
244              the string would be an infinite repetition of that block.
245              We check for this case and bail immediately. */
246           done = 1;
247         }
248
249         cablist = tablecache.u.branches;
250         while (!done) {
251           cacheblock_t *cab;
252
253           if (numbits < CACHEBITS) {
254             /* readahead is certainly false */
255             int newbyte = Mem1(addr+1);
256             bits |= (newbyte << numbits);
257             numbits += 8;
258             readahead = TRUE;
259           }
260
261           cab = &(cablist[bits & CACHEMASK]);
262           numbits -= cab->depth;
263           bits >>= cab->depth;
264           bitnum += cab->depth;
265           if (bitnum >= 8) {
266             addr += 1;
267             bitnum -= 8;
268             if (readahead) {
269               readahead = FALSE;
270             }
271             else {
272               int newbyte = Mem1(addr);
273               bits |= (newbyte << numbits);
274               numbits += 8;
275             }
276           }
277
278           switch (cab->type) {
279           case 0x00: /* non-leaf node */
280             cablist = cab->u.branches;
281             break;
282           case 0x01: /* string terminator */
283             done = 1;
284             break;
285           case 0x02: /* single character */
286             switch (iosys_mode) {
287             case iosys_Glk:
288               glk_put_char(cab->u.ch);
289               break;
290             case iosys_Filter: 
291               ival = cab->u.ch & 0xFF;
292               if (!substring) {
293                 push_callstub(0x11, 0);
294                 substring = TRUE;
295               }
296               pc = addr;
297               push_callstub(0x10, bitnum);
298               enter_function(iosys_rock, 1, &ival);
299               return;
300             }
301             cablist = tablecache.u.branches;
302             break;
303           case 0x04: /* single Unicode character */
304             switch (iosys_mode) {
305             case iosys_Glk:
306               glkio_unichar_han_ptr(cab->u.uch);
307               break;
308             case iosys_Filter: 
309               ival = cab->u.uch;
310               if (!substring) {
311                 push_callstub(0x11, 0);
312                 substring = TRUE;
313               }
314               pc = addr;
315               push_callstub(0x10, bitnum);
316               enter_function(iosys_rock, 1, &ival);
317               return;
318             }
319             cablist = tablecache.u.branches;
320             break;
321           case 0x03: /* C string */
322             switch (iosys_mode) {
323             case iosys_Glk:
324               for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++) 
325                 glk_put_char(ch);
326               cablist = tablecache.u.branches; 
327               break;
328             case iosys_Filter:
329               if (!substring) {
330                 push_callstub(0x11, 0);
331                 substring = TRUE;
332               }
333               pc = addr;
334               push_callstub(0x10, bitnum);
335               inmiddle = 0xE0;
336               addr = cab->u.addr;
337               done = 2;
338               break;
339             default:
340               cablist = tablecache.u.branches; 
341               break;
342             }
343             break;
344           case 0x05: /* C Unicode string */
345             switch (iosys_mode) {
346             case iosys_Glk:
347               for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4) 
348                 glkio_unichar_han_ptr(ival);
349               cablist = tablecache.u.branches; 
350               break;
351             case iosys_Filter:
352               if (!substring) {
353                 push_callstub(0x11, 0);
354                 substring = TRUE;
355               }
356               pc = addr;
357               push_callstub(0x10, bitnum);
358               inmiddle = 0xE2;
359               addr = cab->u.addr;
360               done = 2;
361               break;
362             default:
363               cablist = tablecache.u.branches; 
364               break;
365             }
366             break;
367           case 0x08:
368           case 0x09:
369           case 0x0A:
370           case 0x0B: 
371             {
372               glui32 oaddr;
373               int otype;
374               oaddr = cab->u.addr;
375               if (cab->type >= 0x09)
376                 oaddr = Mem4(oaddr);
377               if (cab->type == 0x0B)
378                 oaddr = Mem4(oaddr);
379               otype = Mem1(oaddr);
380               if (!substring) {
381                 push_callstub(0x11, 0);
382                 substring = TRUE;
383               }
384               if (otype >= 0xE0 && otype <= 0xFF) {
385                 pc = addr;
386                 push_callstub(0x10, bitnum);
387                 inmiddle = 0;
388                 addr = oaddr;
389                 done = 2;
390               }
391               else if (otype >= 0xC0 && otype <= 0xDF) {
392                 glui32 argc;
393                 glui32 *argv;
394                 if (cab->type == 0x0A || cab->type == 0x0B) {
395                   argc = Mem4(cab->u.addr+4);
396                   argv = pop_arguments(argc, cab->u.addr+8);
397                 }
398                 else {
399                   argc = 0;
400                   argv = NULL;
401                 }
402                 pc = addr;
403                 push_callstub(0x10, bitnum);
404                 enter_function(oaddr, argc, argv);
405                 return;
406               }
407               else {
408                 fatal_error("Unknown object while decoding string indirect reference.");
409               }
410             }
411             break;
412           default:
413             fatal_error("Unknown entity in string decoding (cached).");
414             break;
415           }
416         }
417         if (done > 1) {
418           continue; /* restart the top-level loop */
419         }
420       }
421       else { /* tablecache not valid */
422         glui32 node;
423         int byte;
424         int nodetype;
425         int done = 0;
426
427         if (!stringtable)
428           fatal_error("Attempted to print a compressed string with no table set.");
429         /* bitnum is already set right */
430         byte = Mem1(addr); 
431         if (bitnum)
432           byte >>= bitnum;
433         node = Mem4(stringtable+8);
434         while (!done) {
435           nodetype = Mem1(node);
436           node++;
437           switch (nodetype) {
438           case 0x00: /* non-leaf node */
439             if (byte & 1) 
440               node = Mem4(node+4);
441             else
442               node = Mem4(node+0);
443             if (bitnum == 7) {
444               bitnum = 0;
445               addr++;
446               byte = Mem1(addr);
447             }
448             else {
449               bitnum++;
450               byte >>= 1;
451             }
452             break;
453           case 0x01: /* string terminator */
454             done = 1;
455             break;
456           case 0x02: /* single character */
457             ch = Mem1(node);
458             switch (iosys_mode) {
459             case iosys_Glk:
460               glk_put_char(ch);
461               break;
462             case iosys_Filter: 
463               ival = ch & 0xFF;
464               if (!substring) {
465                 push_callstub(0x11, 0);
466                 substring = TRUE;
467               }
468               pc = addr;
469               push_callstub(0x10, bitnum);
470               enter_function(iosys_rock, 1, &ival);
471               return;
472             }
473             node = Mem4(stringtable+8);
474             break;
475           case 0x04: /* single Unicode character */
476             ival = Mem4(node);
477             switch (iosys_mode) {
478             case iosys_Glk:
479               glkio_unichar_han_ptr(ival);
480               break;
481             case iosys_Filter: 
482               if (!substring) {
483                 push_callstub(0x11, 0);
484                 substring = TRUE;
485               }
486               pc = addr;
487               push_callstub(0x10, bitnum);
488               enter_function(iosys_rock, 1, &ival);
489               return;
490             }
491             node = Mem4(stringtable+8);
492             break;
493           case 0x03: /* C string */
494             switch (iosys_mode) {
495             case iosys_Glk:
496               for (; (ch=Mem1(node)) != '\0'; node++) 
497                 glk_put_char(ch);
498               node = Mem4(stringtable+8);
499               break;
500             case iosys_Filter:
501               if (!substring) {
502                 push_callstub(0x11, 0);
503                 substring = TRUE;
504               }
505               pc = addr;
506               push_callstub(0x10, bitnum);
507               inmiddle = 0xE0;
508               addr = node;
509               done = 2;
510               break;
511             default:
512               node = Mem4(stringtable+8);
513               break;
514             }
515             break;
516           case 0x05: /* C Unicode string */
517             switch (iosys_mode) {
518             case iosys_Glk:
519               for (; (ival=Mem4(node)) != 0; node+=4) 
520                 glkio_unichar_han_ptr(ival);
521               node = Mem4(stringtable+8);
522               break;
523             case iosys_Filter:
524               if (!substring) {
525                 push_callstub(0x11, 0);
526                 substring = TRUE;
527               }
528               pc = addr;
529               push_callstub(0x10, bitnum);
530               inmiddle = 0xE2;
531               addr = node;
532               done = 2;
533               break;
534             default:
535               node = Mem4(stringtable+8);
536               break;
537             }
538             break;
539           case 0x08:
540           case 0x09:
541           case 0x0A:
542           case 0x0B: 
543             {
544               glui32 oaddr;
545               int otype;
546               oaddr = Mem4(node);
547               if (nodetype == 0x09 || nodetype == 0x0B)
548                 oaddr = Mem4(oaddr);
549               otype = Mem1(oaddr);
550               if (!substring) {
551                 push_callstub(0x11, 0);
552                 substring = TRUE;
553               }
554               if (otype >= 0xE0 && otype <= 0xFF) {
555                 pc = addr;
556                 push_callstub(0x10, bitnum);
557                 inmiddle = 0;
558                 addr = oaddr;
559                 done = 2;
560               }
561               else if (otype >= 0xC0 && otype <= 0xDF) {
562                 glui32 argc;
563                 glui32 *argv;
564                 if (nodetype == 0x0A || nodetype == 0x0B) {
565                   argc = Mem4(node+4);
566                   argv = pop_arguments(argc, node+8);
567                 }
568                 else {
569                   argc = 0;
570                   argv = NULL;
571                 }
572                 pc = addr;
573                 push_callstub(0x10, bitnum);
574                 enter_function(oaddr, argc, argv);
575                 return;
576               }
577               else {
578                 fatal_error("Unknown object while decoding string indirect reference.");
579               }
580             }
581             break;
582           default:
583             fatal_error("Unknown entity in string decoding.");
584             break;
585           }
586         }
587         if (done > 1) {
588           continue; /* restart the top-level loop */
589         }
590       }
591     }
592     else if (type == 0xE0) {
593       switch (iosys_mode) {
594       case iosys_Glk:
595         while (1) {
596           ch = Mem1(addr);
597           addr++;
598           if (ch == '\0')
599             break;
600           glk_put_char(ch);
601         }
602         break;
603       case iosys_Filter:
604         if (!substring) {
605           push_callstub(0x11, 0);
606           substring = TRUE;
607         }
608         ch = Mem1(addr);
609         addr++;
610         if (ch != '\0') {
611           ival = ch & 0xFF;
612           pc = addr;
613           push_callstub(0x13, 0);
614           enter_function(iosys_rock, 1, &ival);
615           return;
616         }
617         break;
618       }
619     }
620     else if (type == 0xE2) {
621       switch (iosys_mode) {
622       case iosys_Glk:
623         while (1) {
624           ival = Mem4(addr);
625           addr+=4;
626           if (ival == 0)
627             break;
628           glkio_unichar_han_ptr(ival);
629         }
630         break;
631       case iosys_Filter:
632         if (!substring) {
633           push_callstub(0x11, 0);
634           substring = TRUE;
635         }
636         ival = Mem4(addr);
637         addr+=4;
638         if (ival != 0) {
639           pc = addr;
640           push_callstub(0x14, 0);
641           enter_function(iosys_rock, 1, &ival);
642           return;
643         }
644         break;
645       }
646     }
647     else if (type >= 0xE0 && type <= 0xFF) {
648       fatal_error("Attempt to print unknown type of string.");
649     }
650     else {
651       fatal_error("Attempt to print non-string.");
652     }
653
654     if (!substring) {
655       /* Just get straight out. */
656       alldone = TRUE;
657     }
658     else {
659       /* Pop a stub and see what's to be done. */
660       addr = pop_callstub_string(&bitnum);
661       if (addr == 0) {
662         alldone = TRUE;
663       }
664       else {
665         inmiddle = 0xE1;
666       }
667     }
668   }
669 }
670
671 /* stream_get_table():
672    Get the current table address. 
673 */
674 glui32 stream_get_table()
675 {
676   return stringtable;
677 }
678
679 /* stream_set_table():
680    Set the current table address, and rebuild decoding cache. 
681 */
682 void stream_set_table(glui32 addr)
683 {
684   if (stringtable == addr)
685     return;
686
687   /* Drop cache. */
688   if (tablecache_valid) {
689     if (tablecache.type == 0)
690       dropcache(tablecache.u.branches);
691     tablecache.u.branches = NULL;
692     tablecache_valid = FALSE;
693   }
694
695   stringtable = addr;
696
697   if (stringtable) {
698     /* Build cache. We can only do this if the table is entirely in ROM. */
699     glui32 tablelen = Mem4(stringtable);
700     glui32 rootaddr = Mem4(stringtable+8);
701     if (stringtable+tablelen <= ramstart && !never_cache_stringtable) {
702       buildcache(&tablecache, rootaddr, CACHEBITS, 0);
703       /* dumpcache(&tablecache, 1, 0); */
704       tablecache_valid = TRUE;
705     }
706   }
707 }
708
709 static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
710   int mask)
711 {
712   int ix, type;
713
714   type = Mem1(nodeaddr);
715
716   if (type == 0 && depth == CACHEBITS) {
717     cacheblock_t *list, *cab;
718     list = (cacheblock_t *)glulx_malloc(sizeof(cacheblock_t) * CACHESIZE);
719     buildcache(list, nodeaddr, 0, 0);
720     cab = &(cablist[mask]);
721     cab->type = 0;
722     cab->depth = CACHEBITS;
723     cab->u.branches = list;
724     return;
725   }
726
727   if (type == 0) {
728     glui32 leftaddr  = Mem4(nodeaddr+1);
729     glui32 rightaddr = Mem4(nodeaddr+5);
730     buildcache(cablist, leftaddr, depth+1, mask);
731     buildcache(cablist, rightaddr, depth+1, (mask | (1 << depth)));
732     return;
733   }
734
735   /* Leaf node. */
736   nodeaddr++;
737   for (ix = mask; ix < CACHESIZE; ix += (1 << depth)) {
738     cacheblock_t *cab = &(cablist[ix]);
739     cab->type = type;
740     cab->depth = depth;
741     switch (type) {
742     case 0x02:
743       cab->u.ch = Mem1(nodeaddr);
744       break;
745     case 0x04:
746       cab->u.uch = Mem4(nodeaddr);
747       break;
748     case 0x03:
749     case 0x05:
750     case 0x0A:
751     case 0x0B:
752       cab->u.addr = nodeaddr;
753       break;
754     case 0x08:
755     case 0x09:
756       cab->u.addr = Mem4(nodeaddr);
757       break;
758     }
759   }
760 }
761
762 #if 0
763 #include <stdio.h>
764 static void dumpcache(cacheblock_t *cablist, int count, int indent)
765 {
766   int ix, jx;
767
768   for (ix=0; ix<count; ix++) {
769     cacheblock_t *cab = &(cablist[ix]); 
770     for (jx=0; jx<indent; jx++)
771       printf("  ");
772     printf("%X: ", ix);
773     switch (cab->type) {
774     case 0:
775       printf("...\n");
776       dumpcache(cab->u.branches, CACHESIZE, indent+1);
777       break;
778     case 1:
779       printf("<EOS>\n");
780       break;
781     case 2:
782       printf("0x%02X", cab->u.ch);
783       if (cab->u.ch < 32)
784         printf(" ''\n");
785       else
786         printf(" '%c'\n", cab->u.ch);
787       break;
788     default:
789       printf("type %02X, address %06lX\n", cab->type, cab->u.addr);
790       break;
791     }
792   }
793 }
794 #endif /* 0 */
795
796 static void dropcache(cacheblock_t *cablist)
797 {
798   int ix;
799   for (ix=0; ix<CACHESIZE; ix++) {
800     cacheblock_t *cab = &(cablist[ix]);
801     if (cab->type == 0) {
802       dropcache(cab->u.branches);
803       cab->u.branches = NULL;
804     }
805   }
806   glulx_free(cablist);
807 }
808
809 /* This misbehaves if a Glk function has more than one S argument. */
810
811 #define STATIC_TEMP_BUFSIZE (127)
812 static char temp_buf[STATIC_TEMP_BUFSIZE+1];
813
814 char *make_temp_string(glui32 addr)
815 {
816   int ix, len;
817   glui32 addr2;
818   char *res, *cx;
819
820   if (Mem1(addr) != 0xE0)
821     fatal_error("String argument to a Glk call must be unencoded.");
822   addr++;
823
824   for (addr2=addr; Mem1(addr2); addr2++) { };
825   len = (addr2 - addr);
826   if (len < STATIC_TEMP_BUFSIZE) {
827     res = temp_buf;
828   }
829   else {
830     res = (char *)glulx_malloc(len+1);
831     if (!res) 
832       fatal_error("Unable to allocate space for string argument to Glk call.");
833   }
834   
835   for (ix=0, addr2=addr; ix<len; ix++, addr2++) {
836     res[ix] = Mem1(addr2);
837   }
838   res[len] = '\0';
839
840   return res;
841 }
842
843 glui32 *make_temp_ustring(glui32 addr)
844 {
845   int ix, len;
846   glui32 addr2;
847   glui32 *res, *cx;
848
849   if (Mem1(addr) != 0xE2)
850     fatal_error("Ustring argument to a Glk call must be unencoded.");
851   addr+=4;
852
853   for (addr2=addr; Mem4(addr2); addr2+=4) { };
854   len = (addr2 - addr) / 4;
855   if ((len+1)*4 < STATIC_TEMP_BUFSIZE) {
856     res = (glui32 *)temp_buf;
857   }
858   else {
859     res = (glui32 *)glulx_malloc((len+1)*4);
860     if (!res) 
861       fatal_error("Unable to allocate space for ustring argument to Glk call.");
862   }
863   
864   for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
865     res[ix] = Mem4(addr2);
866   }
867   res[len] = 0;
868
869   return res;
870 }
871
872 void free_temp_string(char *str)
873 {
874   if (str && str != temp_buf) 
875     glulx_free(str);
876 }
877
878 void free_temp_ustring(glui32 *str)
879 {
880   if (str && str != (glui32 *)temp_buf) 
881     glulx_free(str);
882 }
883