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