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