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/glulxe/glulxe.h"
24
25 namespace Glk {
26 namespace Glulxe {
27
28 /* This code is actually very general; it could work for almost any
29 32-bit VM which remotely resembles Glulxe or the Z-machine in design.
30
31 To be precise, we make the following assumptions:
32
33 - An argument list is an array of 32-bit values, which can represent
34 either integers or addresses.
35 - We can read or write to a 32-bit integer in VM memory using the macros
36 ReadMemory(addr) and WriteMemory(addr), where addr is an address
37 taken from the argument list.
38 - A character array is a sequence of bytes somewhere in VM memory.
39 The array can be turned into a C char array by the macro
40 CaptureCArray(addr, len), and released by ReleaseCArray().
41 The passin, passout hints may be used to avoid unnecessary copying.
42 - An integer array is a sequence of integers somewhere in VM memory.
43 The array can be turned into a C integer array by the macro
44 CaptureIArray(addr, len), and released by ReleaseIArray().
45 These macros are responsible for fixing byte-order and alignment
46 (if the C ABI does not match the VM's). The passin, passout hints
47 may be used to avoid unnecessary copying.
48 - A Glk object array is a sequence of integers in VM memory. It is
49 turned into a C pointer array (remember that C pointers may be more
50 than 4 bytes!) The pointer array is allocated by
51 CapturePtrArray(addr, len, objclass) and released by ReleasePtrArray().
52 Again, the macros handle the conversion.
53 - A Glk structure (such as event_t) is a set of integers somewhere
54 in VM memory, which can be read and written with the macros
55 ReadStructField(addr, fieldnum) and WriteStructField(addr, fieldnum).
56 The fieldnum is an integer (from 0 to 3, for event_t.)
57 - A VM string can be turned into a C-style string with the macro
58 ptr = DecodeVMString(addr). After the string is used, this code
59 calls ReleaseVMString(ptr), which should free any memory that
60 DecodeVMString allocates.
61 - A VM Unicode string can be turned into a zero-terminated array
62 of 32-bit integers, in the same way, with DecodeVMUstring
63 and ReleaseVMUstring.
64
65 To work this code over for a new VM, just diddle the macros.
66 */
67
classtable_register(void * obj,uint objclass)68 static gidispatch_rock_t classtable_register(void *obj, uint objclass) {
69 return g_vm->glulxe_classtable_register(obj, objclass);
70 }
71
classtable_unregister(void * obj,uint objclass,gidispatch_rock_t objrock)72 static void classtable_unregister(void *obj, uint objclass, gidispatch_rock_t objrock) {
73 g_vm->glulxe_classtable_unregister(obj, objclass, objrock);
74 }
75
retained_register(void * array,uint len,const char * typecode)76 static gidispatch_rock_t retained_register(void *array, uint len, const char *typecode) {
77 return g_vm->glulxe_retained_register(array, len, typecode);
78 }
79
retained_unregister(void * array,uint len,const char * typecode,gidispatch_rock_t objrock)80 static void retained_unregister(void *array, uint len, const char *typecode, gidispatch_rock_t objrock) {
81 g_vm->glulxe_retained_unregister(array, len, typecode, objrock);
82 }
83
glkopInit()84 void Glulxe::glkopInit() {
85 library_select_hook = nullptr;
86 arrays = nullptr;
87 num_classes = 0;
88 classes = nullptr;
89 }
90
init_dispatch()91 bool Glulxe::init_dispatch() {
92 int ix;
93
94 /* Set up the game-ID hook. (This is ifdeffed because not all Glk
95 libraries have this call.) */
96 #ifdef GI_DISPA_GAME_ID_AVAILABLE
97 gidispatch_set_game_id_hook(&get_game_id);
98 #endif /* GI_DISPA_GAME_ID_AVAILABLE */
99
100 /* Allocate the class hash tables. */
101 num_classes = gidispatch_count_classes();
102 classes = (classtable_t **)glulx_malloc(num_classes * sizeof(classtable_t *));
103 if (!classes)
104 return false;
105
106 for (ix = 0; ix < num_classes; ix++) {
107 classes[ix] = new_classtable((glulx_random() % (uint)(101)) + 1);
108 if (!classes[ix])
109 return false;
110 }
111
112 /* Set up the two callbacks. */
113 gidispatch_set_object_registry(&classtable_register, &classtable_unregister);
114 gidispatch_set_retained_registry(&retained_register, &retained_unregister);
115
116 /* If the library supports autorestore callbacks, set those up too.
117 (These are only used in iosglk, currently.) */
118 #ifdef GIDISPATCH_AUTORESTORE_REGISTRY
119 gidispatch_set_autorestore_registry(&glulxe_array_locate, &glulxe_array_restore);
120 #endif /* GIDISPATCH_AUTORESTORE_REGISTRY */
121
122 return true;
123 }
124
perform_glk(uint funcnum,uint numargs,uint * arglist)125 uint Glulxe::perform_glk(uint funcnum, uint numargs, uint *arglist) {
126 uint retval = 0;
127
128 switch (funcnum) {
129 /* To speed life up, we implement commonly-used Glk functions
130 directly -- instead of bothering with the whole prototype
131 mess. */
132
133 case 0x0047: /* stream_set_current */
134 if (numargs != 1)
135 goto WrongArgNum;
136 glk_stream_set_current(find_stream_by_id(arglist[0]));
137 break;
138 case 0x0048: /* stream_get_current */
139 if (numargs != 0)
140 goto WrongArgNum;
141 retval = find_id_for_stream(glk_stream_get_current());
142 break;
143 case 0x0080: /* put_char */
144 if (numargs != 1)
145 goto WrongArgNum;
146 glk_put_char(arglist[0] & 0xFF);
147 break;
148 case 0x0081: /* put_char_stream */
149 if (numargs != 2)
150 goto WrongArgNum;
151 glk_put_char_stream(find_stream_by_id(arglist[0]), arglist[1] & 0xFF);
152 break;
153 case 0x00C0: /* select */
154 /* call a library hook on every glk_select() */
155 if (library_select_hook)
156 library_select_hook(arglist[0]);
157 /* but then fall through to full dispatcher, because there's no real
158 need for speed here */
159 goto FullDispatcher;
160 case 0x00A0: /* char_to_lower */
161 if (numargs != 1)
162 goto WrongArgNum;
163 retval = glk_char_to_lower(arglist[0] & 0xFF);
164 break;
165 case 0x00A1: /* char_to_upper */
166 if (numargs != 1)
167 goto WrongArgNum;
168 retval = glk_char_to_upper(arglist[0] & 0xFF);
169 break;
170 case 0x0128: /* put_char_uni */
171 if (numargs != 1)
172 goto WrongArgNum;
173 glk_put_char_uni(arglist[0]);
174 break;
175 case 0x012B: /* put_char_stream_uni */
176 if (numargs != 2)
177 goto WrongArgNum;
178 glk_put_char_stream_uni(find_stream_by_id(arglist[0]), arglist[1]);
179 break;
180
181 WrongArgNum:
182 error("Wrong number of arguments to Glk function.");
183 break;
184
185 FullDispatcher:
186 default: {
187 /* Go through the full dispatcher prototype foo. */
188 const char *proto, *cx;
189 dispatch_splot_t splot;
190 int argnum, argnum2;
191
192 /* Grab the string. */
193 proto = gidispatch_prototype(funcnum);
194 if (!proto)
195 error("Unknown Glk function.");
196
197 splot.varglist = arglist;
198 splot.numvargs = numargs;
199 splot.retval = &retval;
200
201 /* The work goes in four phases. First, we figure out how many
202 arguments we want, and allocate space for the Glk argument
203 list. Then we go through the Glulxe arguments and load them
204 into the Glk list. Then we call. Then we go through the
205 arguments again, unloading the data back into Glulx memory. */
206
207 /* Phase 0. */
208 prepare_glk_args(proto, &splot);
209
210 /* Phase 1. */
211 argnum = 0;
212 cx = proto;
213 parse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
214
215 /* Phase 2. */
216 gidispatch_call(funcnum, argnum, splot.garglist);
217
218 /* Phase 3. */
219 argnum2 = 0;
220 cx = proto;
221 unparse_glk_args(&splot, &cx, 0, &argnum2, 0, 0);
222 if (argnum != argnum2)
223 error("Argument counts did not match.");
224
225 break;
226 }
227 }
228
229 return retval;
230 }
231
read_prefix(const char * cx,int * isref,int * isarray,int * passin,int * passout,int * nullok,int * isretained,int * isreturn)232 const char *Glulxe::read_prefix(const char *cx, int *isref, int *isarray, int *passin, int *passout,
233 int *nullok, int *isretained, int *isreturn) {
234 *isref = false;
235 *passin = false;
236 *passout = false;
237 *nullok = true;
238 *isarray = false;
239 *isretained = false;
240 *isreturn = false;
241 while (1) {
242 if (*cx == '<') {
243 *isref = true;
244 *passout = true;
245 } else if (*cx == '>') {
246 *isref = true;
247 *passin = true;
248 } else if (*cx == '&') {
249 *isref = true;
250 *passout = true;
251 *passin = true;
252 } else if (*cx == '+') {
253 *nullok = false;
254 } else if (*cx == ':') {
255 *isref = true;
256 *passout = true;
257 *nullok = false;
258 *isreturn = true;
259 } else if (*cx == '#') {
260 *isarray = true;
261 } else if (*cx == '!') {
262 *isretained = true;
263 } else {
264 break;
265 }
266 cx++;
267 }
268 return cx;
269 }
270
prepare_glk_args(const char * proto,dispatch_splot_t * splot)271 void Glulxe::prepare_glk_args(const char *proto, dispatch_splot_t *splot) {
272 static gluniversal_t *garglist = nullptr;
273 static int garglist_size = 0;
274
275 int ix;
276 int numwanted, numvargswanted, maxargs;
277 const char *cx;
278
279 cx = proto;
280 numwanted = 0;
281 while (*cx >= '0' && *cx <= '9') {
282 numwanted = 10 * numwanted + (*cx - '0');
283 cx++;
284 }
285 splot->numwanted = numwanted;
286
287 maxargs = 0;
288 numvargswanted = 0;
289 for (ix = 0; ix < numwanted; ix++) {
290 int isref, passin, passout, nullok, isarray, isretained, isreturn;
291 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
292 &isretained, &isreturn);
293 if (isref) {
294 maxargs += 2;
295 } else {
296 maxargs += 1;
297 }
298 if (!isreturn) {
299 if (isarray) {
300 numvargswanted += 2;
301 } else {
302 numvargswanted += 1;
303 }
304 }
305
306 if (*cx == 'I' || *cx == 'C') {
307 cx += 2;
308 } else if (*cx == 'Q') {
309 cx += 2;
310 } else if (*cx == 'S' || *cx == 'U') {
311 cx += 1;
312 } else if (*cx == '[') {
313 int refdepth, nwx;
314 cx++;
315 nwx = 0;
316 while (*cx >= '0' && *cx <= '9') {
317 nwx = 10 * nwx + (*cx - '0');
318 cx++;
319 }
320 maxargs += nwx; /* This is *only* correct because all structs contain
321 plain values. */
322 refdepth = 1;
323 while (refdepth > 0) {
324 if (*cx == '[')
325 refdepth++;
326 else if (*cx == ']')
327 refdepth--;
328 cx++;
329 }
330 } else {
331 error("Illegal format string.");
332 }
333 }
334
335 if (*cx != ':' && *cx != '\0')
336 error("Illegal format string.");
337
338 splot->maxargs = maxargs;
339
340 if (splot->numvargs != numvargswanted)
341 error("Wrong number of arguments to Glk function.");
342
343 if (garglist && garglist_size < maxargs) {
344 glulx_free(garglist);
345 garglist = nullptr;
346 garglist_size = 0;
347 }
348 if (!garglist) {
349 garglist_size = maxargs + 16;
350 garglist = (gluniversal_t *)glulx_malloc(garglist_size
351 * sizeof(gluniversal_t));
352 }
353 if (!garglist)
354 error("Unable to allocate storage for Glk arguments.");
355
356 splot->garglist = garglist;
357 }
358
parse_glk_args(dispatch_splot_t * splot,const char ** proto,int depth,int * argnumptr,uint subaddress,int subpassin)359 void Glulxe::parse_glk_args(dispatch_splot_t *splot, const char **proto, int depth, int *argnumptr,
360 uint subaddress, int subpassin) {
361 const char *cx;
362 int ix, argx;
363 int gargnum, numwanted;
364 void *opref;
365 gluniversal_t *garglist;
366 uint *varglist;
367
368 garglist = splot->garglist;
369 varglist = splot->varglist;
370 gargnum = *argnumptr;
371 cx = *proto;
372
373 numwanted = 0;
374 while (*cx >= '0' && *cx <= '9') {
375 numwanted = 10 * numwanted + (*cx - '0');
376 cx++;
377 }
378
379 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
380 char typeclass;
381 int skipval;
382 int isref, passin, passout, nullok, isarray, isretained, isreturn;
383 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
384 &isretained, &isreturn);
385
386 typeclass = *cx;
387 cx++;
388
389 skipval = false;
390 if (isref) {
391 if (!isreturn && varglist[ix] == 0) {
392 if (!nullok)
393 error("Zero passed invalidly to Glk function.");
394 garglist[gargnum]._ptrflag = false;
395 gargnum++;
396 skipval = true;
397 } else {
398 garglist[gargnum]._ptrflag = true;
399 gargnum++;
400 }
401 }
402 if (!skipval) {
403 uint thisval;
404
405 if (typeclass == '[') {
406
407 parse_glk_args(splot, &cx, depth + 1, &gargnum, varglist[ix], passin);
408
409 } else if (isarray) {
410 /* definitely isref */
411
412 switch (typeclass) {
413 case 'C':
414 /* This test checks for a giant array length, which is
415 deprecated. It displays a warning and cuts it down to
416 something reasonable. Future releases of this interpreter
417 may remove this test and go on to verify_array_addresses(),
418 which treats this case as a fatal error. */
419 if (varglist[ix + 1] > endmem
420 || varglist[ix] + varglist[ix + 1] > endmem) {
421 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix + 1]);
422 varglist[ix + 1] = endmem - varglist[ix];
423 }
424 verify_array_addresses(varglist[ix], varglist[ix + 1], 1);
425 garglist[gargnum]._array = CaptureCArray(varglist[ix], varglist[ix + 1], passin);
426 gargnum++;
427 ix++;
428 garglist[gargnum]._uint = varglist[ix];
429 gargnum++;
430 cx++;
431 break;
432 case 'I':
433 /* See comment above. */
434 if (varglist[ix + 1] > endmem / 4
435 || varglist[ix + 1] > (endmem - varglist[ix]) / 4) {
436 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix + 1]);
437 varglist[ix + 1] = (endmem - varglist[ix]) / 4;
438 }
439 verify_array_addresses(varglist[ix], varglist[ix + 1], 4);
440 garglist[gargnum]._array = CaptureIArray(varglist[ix], varglist[ix + 1], passin);
441 gargnum++;
442 ix++;
443 garglist[gargnum]._uint = varglist[ix];
444 gargnum++;
445 cx++;
446 break;
447 case 'Q':
448 /* This case was added after the giant arrays were deprecated,
449 so we don't bother to allow for that case. We just verify
450 the length. */
451 verify_array_addresses(varglist[ix], varglist[ix + 1], 4);
452 garglist[gargnum]._array = CapturePtrArray(varglist[ix], varglist[ix + 1], (*cx - 'a'), passin);
453 gargnum++;
454 ix++;
455 garglist[gargnum]._uint = varglist[ix];
456 gargnum++;
457 cx++;
458 break;
459 default:
460 error("Illegal format string.");
461 break;
462 }
463 } else {
464 /* a plain value or a reference to one. */
465
466 if (isreturn) {
467 thisval = 0;
468 } else if (depth > 0) {
469 /* Definitely not isref or isarray. */
470 if (subpassin)
471 thisval = ReadStructField(subaddress, ix);
472 else
473 thisval = 0;
474 } else if (isref) {
475 if (passin)
476 thisval = ReadMemory(varglist[ix]);
477 else
478 thisval = 0;
479 } else {
480 thisval = varglist[ix];
481 }
482
483 switch (typeclass) {
484 case 'I':
485 if (*cx == 'u')
486 garglist[gargnum]._uint = (uint)(thisval);
487 else if (*cx == 's')
488 garglist[gargnum]._sint = (int)(thisval);
489 else
490 error("Illegal format string.");
491 gargnum++;
492 cx++;
493 break;
494 case 'Q':
495 if (thisval) {
496 opref = classes_get(*cx - 'a', thisval);
497 if (!opref) {
498 error("Reference to nonexistent Glk object.");
499 }
500 } else {
501 opref = nullptr;
502 }
503 garglist[gargnum]._opaqueref = opref;
504 gargnum++;
505 cx++;
506 break;
507 case 'C':
508 if (*cx == 'u')
509 garglist[gargnum]._uch = (unsigned char)(thisval);
510 else if (*cx == 's')
511 garglist[gargnum]._sch = (signed char)(thisval);
512 else if (*cx == 'n')
513 garglist[gargnum]._ch = (char)(thisval);
514 else
515 error("Illegal format string.");
516 gargnum++;
517 cx++;
518 break;
519 case 'S':
520 garglist[gargnum]._charstr = DecodeVMString(thisval);
521 gargnum++;
522 break;
523 #ifdef GLK_MODULE_UNICODE
524 case 'U':
525 garglist[gargnum]._unicharstr = DecodeVMUstring(thisval);
526 gargnum++;
527 break;
528 #endif /* GLK_MODULE_UNICODE */
529 default:
530 error("Illegal format string.");
531 break;
532 }
533 }
534 } else {
535 /* We got a null reference, so we have to skip the format element. */
536 if (typeclass == '[') {
537 int numsubwanted, refdepth;
538 numsubwanted = 0;
539 while (*cx >= '0' && *cx <= '9') {
540 numsubwanted = 10 * numsubwanted + (*cx - '0');
541 cx++;
542 }
543 refdepth = 1;
544 while (refdepth > 0) {
545 if (*cx == '[')
546 refdepth++;
547 else if (*cx == ']')
548 refdepth--;
549 cx++;
550 }
551 } else if (typeclass == 'S' || typeclass == 'U') {
552 /* leave it */
553 } else {
554 cx++;
555 if (isarray)
556 ix++;
557 }
558 }
559 }
560
561 if (depth > 0) {
562 if (*cx != ']')
563 error("Illegal format string.");
564 cx++;
565 } else {
566 if (*cx != ':' && *cx != '\0')
567 error("Illegal format string.");
568 }
569
570 *proto = cx;
571 *argnumptr = gargnum;
572 }
573
unparse_glk_args(dispatch_splot_t * splot,const char ** proto,int depth,int * argnumptr,uint subaddress,int subpassout)574 void Glulxe::unparse_glk_args(dispatch_splot_t *splot, const char **proto, int depth,
575 int *argnumptr, uint subaddress, int subpassout) {
576 const char *cx;
577 int ix, argx;
578 int gargnum, numwanted;
579 void *opref;
580 gluniversal_t *garglist;
581 uint *varglist;
582
583 garglist = splot->garglist;
584 varglist = splot->varglist;
585 gargnum = *argnumptr;
586 cx = *proto;
587
588 numwanted = 0;
589 while (*cx >= '0' && *cx <= '9') {
590 numwanted = 10 * numwanted + (*cx - '0');
591 cx++;
592 }
593
594 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
595 char typeclass;
596 int skipval;
597 int isref, passin, passout, nullok, isarray, isretained, isreturn;
598 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
599 &isretained, &isreturn);
600
601 typeclass = *cx;
602 cx++;
603
604 skipval = false;
605 if (isref) {
606 if (!isreturn && varglist[ix] == 0) {
607 if (!nullok)
608 error("Zero passed invalidly to Glk function.");
609 garglist[gargnum]._ptrflag = false;
610 gargnum++;
611 skipval = true;
612 } else {
613 garglist[gargnum]._ptrflag = true;
614 gargnum++;
615 }
616 }
617 if (!skipval) {
618 uint thisval = 0;
619
620 if (typeclass == '[') {
621
622 unparse_glk_args(splot, &cx, depth + 1, &gargnum, varglist[ix], passout);
623
624 } else if (isarray) {
625 /* definitely isref */
626
627 switch (typeclass) {
628 case 'C':
629 ReleaseCArray((char *)garglist[gargnum]._array, varglist[ix], varglist[ix + 1], passout);
630 gargnum++;
631 ix++;
632 gargnum++;
633 cx++;
634 break;
635 case 'I':
636 ReleaseIArray((uint *)garglist[gargnum]._array, varglist[ix], varglist[ix + 1], passout);
637 gargnum++;
638 ix++;
639 gargnum++;
640 cx++;
641 break;
642 case 'Q':
643 ReleasePtrArray((void **)garglist[gargnum]._array, varglist[ix], varglist[ix + 1], (*cx - 'a'), passout);
644 gargnum++;
645 ix++;
646 gargnum++;
647 cx++;
648 break;
649 default:
650 error("Illegal format string.");
651 break;
652 }
653 } else {
654 /* a plain value or a reference to one. */
655
656 if (isreturn || (depth > 0 && subpassout) || (isref && passout)) {
657 skipval = false;
658 } else {
659 skipval = true;
660 }
661
662 switch (typeclass) {
663 case 'I':
664 if (!skipval) {
665 if (*cx == 'u')
666 thisval = (uint)garglist[gargnum]._uint;
667 else if (*cx == 's')
668 thisval = (uint)garglist[gargnum]._sint;
669 else
670 error("Illegal format string.");
671 }
672 gargnum++;
673 cx++;
674 break;
675 case 'Q':
676 if (!skipval) {
677 opref = garglist[gargnum]._opaqueref;
678 if (opref) {
679 gidispatch_rock_t objrock = gidispatch_get_objrock(opref, *cx - 'a');
680 assert(objrock.ptr);
681 thisval = ((classref_t *)objrock.ptr)->id;
682 } else {
683 thisval = 0;
684 }
685 }
686 gargnum++;
687 cx++;
688 break;
689 case 'C':
690 if (!skipval) {
691 if (*cx == 'u')
692 thisval = (uint)garglist[gargnum]._uch;
693 else if (*cx == 's')
694 thisval = (uint)garglist[gargnum]._sch;
695 else if (*cx == 'n')
696 thisval = (uint)garglist[gargnum]._ch;
697 else
698 error("Illegal format string.");
699 }
700 gargnum++;
701 cx++;
702 break;
703 case 'S':
704 if (garglist[gargnum]._charstr)
705 ReleaseVMString(garglist[gargnum]._charstr);
706 gargnum++;
707 break;
708 #ifdef GLK_MODULE_UNICODE
709 case 'U':
710 if (garglist[gargnum]._unicharstr)
711 ReleaseVMUstring(garglist[gargnum]._unicharstr);
712 gargnum++;
713 break;
714 #endif /* GLK_MODULE_UNICODE */
715 default:
716 error("Illegal format string.");
717 break;
718 }
719
720 if (isreturn) {
721 *(splot->retval) = thisval;
722 } else if (depth > 0) {
723 /* Definitely not isref or isarray. */
724 if (subpassout)
725 WriteStructField(subaddress, ix, thisval);
726 } else if (isref) {
727 if (passout)
728 WriteMemory(varglist[ix], thisval);
729 }
730 }
731 } else {
732 /* We got a null reference, so we have to skip the format element. */
733 if (typeclass == '[') {
734 int numsubwanted, refdepth;
735 numsubwanted = 0;
736 while (*cx >= '0' && *cx <= '9') {
737 numsubwanted = 10 * numsubwanted + (*cx - '0');
738 cx++;
739 }
740 refdepth = 1;
741 while (refdepth > 0) {
742 if (*cx == '[')
743 refdepth++;
744 else if (*cx == ']')
745 refdepth--;
746 cx++;
747 }
748 } else if (typeclass == 'S' || typeclass == 'U') {
749 /* leave it */
750 } else {
751 cx++;
752 if (isarray)
753 ix++;
754 }
755 }
756 }
757
758 if (depth > 0) {
759 if (*cx != ']')
760 error("Illegal format string.");
761 cx++;
762 } else {
763 if (*cx != ':' && *cx != '\0')
764 error("Illegal format string.");
765 }
766
767 *proto = cx;
768 *argnumptr = gargnum;
769 }
770
find_stream_by_id(uint objid)771 strid_t Glulxe::find_stream_by_id(uint objid) {
772 if (!objid)
773 return nullptr;
774
775 // Recall that class 1 ("b") is streams
776 return (strid_t)classes_get(gidisp_Class_Stream, objid);
777 }
778
find_id_for_window(winid_t win)779 uint Glulxe::find_id_for_window(winid_t win) {
780 gidispatch_rock_t objrock;
781
782 if (!win)
783 return 0;
784
785 objrock = gidispatch_get_objrock(win, gidisp_Class_Window);
786 if (!objrock.ptr)
787 return 0;
788 return ((classref_t *)objrock.ptr)->id;
789 }
790
find_id_for_stream(strid_t str)791 uint Glulxe::find_id_for_stream(strid_t str) {
792 gidispatch_rock_t objrock;
793
794 if (!str)
795 return 0;
796
797 objrock = gidispatch_get_objrock(str, gidisp_Class_Stream);
798 if (!objrock.ptr)
799 return 0;
800 return ((classref_t *)objrock.ptr)->id;
801 }
802
find_id_for_fileref(frefid_t fref)803 uint Glulxe::find_id_for_fileref(frefid_t fref) {
804 gidispatch_rock_t objrock;
805
806 if (!fref)
807 return 0;
808
809 objrock = gidispatch_get_objrock(fref, gidisp_Class_Fileref);
810 if (!objrock.ptr)
811 return 0;
812 return ((classref_t *)objrock.ptr)->id;
813 }
814
find_id_for_schannel(schanid_t schan)815 uint Glulxe::find_id_for_schannel(schanid_t schan) {
816 gidispatch_rock_t objrock;
817
818 if (!schan)
819 return 0;
820
821 objrock = gidispatch_get_objrock(schan, gidisp_Class_Schannel);
822 if (!objrock.ptr)
823 return 0;
824 return ((classref_t *)objrock.ptr)->id;
825 }
826
new_classtable(uint firstid)827 classtable_t *Glulxe::new_classtable(uint firstid) {
828 int ix;
829 classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t));
830 if (!ctab)
831 return nullptr;
832
833 for (ix = 0; ix < CLASSHASH_SIZE; ix++)
834 ctab->bucket[ix] = nullptr;
835
836 ctab->lastid = firstid;
837
838 return ctab;
839 }
840
classes_get(int classid,uint objid)841 void *Glulxe::classes_get(int classid, uint objid) {
842 classtable_t *ctab;
843 classref_t *cref;
844 if (classid < 0 || classid >= num_classes)
845 return nullptr;
846 ctab = classes[classid];
847 cref = ctab->bucket[objid % CLASSHASH_SIZE];
848 for (; cref; cref = cref->next) {
849 if (cref->id == objid)
850 return cref->obj;
851 }
852 return nullptr;
853 }
854
classes_put(int classid,void * obj,uint origid)855 classref_t *Glulxe::classes_put(int classid, void *obj, uint origid) {
856 int bucknum;
857 classtable_t *ctab;
858 classref_t *cref;
859 if (classid < 0 || classid >= num_classes)
860 return nullptr;
861 ctab = classes[classid];
862 cref = (classref_t *)glulx_malloc(sizeof(classref_t));
863 if (!cref)
864 return nullptr;
865 cref->obj = obj;
866 if (!origid) {
867 cref->id = ctab->lastid;
868 ctab->lastid++;
869 } else {
870 cref->id = origid;
871 if (ctab->lastid <= origid)
872 ctab->lastid = origid + 1;
873 }
874 bucknum = cref->id % CLASSHASH_SIZE;
875 cref->bucknum = bucknum;
876 cref->next = ctab->bucket[bucknum];
877 ctab->bucket[bucknum] = cref;
878 return cref;
879 }
880
classes_remove(int classid,void * obj)881 void Glulxe::classes_remove(int classid, void *obj) {
882 classtable_t *ctab;
883 classref_t *cref;
884 classref_t **crefp;
885 gidispatch_rock_t objrock;
886 if (classid < 0 || classid >= num_classes)
887 return;
888 ctab = classes[classid];
889 objrock = gidispatch_get_objrock(obj, classid);
890 cref = (classref_t *)objrock.ptr;
891 if (!cref)
892 return;
893 crefp = &(ctab->bucket[cref->bucknum]);
894 for (; *crefp; crefp = &((*crefp)->next)) {
895 if ((*crefp) == cref) {
896 *crefp = cref->next;
897 if (!cref->obj) {
898 nonfatal_warning("attempt to free nullptr object!");
899 }
900 cref->obj = nullptr;
901 cref->id = 0;
902 cref->next = nullptr;
903 glulx_free(cref);
904 return;
905 }
906 }
907 return;
908 }
909
glulxe_classtable_register(void * obj,uint objclass)910 gidispatch_rock_t Glulxe::glulxe_classtable_register(void *obj, uint objclass) {
911 classref_t *cref;
912 gidispatch_rock_t objrock;
913 cref = classes_put(objclass, obj, 0);
914 objrock.ptr = cref;
915 return objrock;
916 }
917
glulxe_classtable_unregister(void * obj,uint objclass,gidispatch_rock_t objrock)918 void Glulxe::glulxe_classtable_unregister(void *obj, uint objclass,
919 gidispatch_rock_t objrock) {
920 classes_remove(objclass, obj);
921 }
922
glulxe_classtable_register_existing(void * obj,uint objclass,uint dispid)923 gidispatch_rock_t Glulxe::glulxe_classtable_register_existing(void *obj, uint objclass, uint dispid) {
924 classref_t *cref;
925 gidispatch_rock_t objrock;
926 cref = classes_put(objclass, obj, dispid);
927 objrock.ptr = cref;
928 return objrock;
929 }
930
grab_temp_c_array(uint addr,uint len,int passin)931 char *Glulxe::grab_temp_c_array(uint addr, uint len, int passin) {
932 arrayref_t *arref = nullptr;
933 char *arr = nullptr;
934 uint ix, addr2;
935
936 if (len) {
937 arr = (char *)glulx_malloc(len * sizeof(char));
938 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
939 if (!arr || !arref)
940 error("Unable to allocate space for array argument to Glk call.");
941
942 arref->array = arr;
943 arref->addr = addr;
944 arref->elemsize = 1;
945 arref->retained = false;
946 arref->len = len;
947 arref->next = arrays;
948 arrays = arref;
949
950 if (passin) {
951 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 1) {
952 arr[ix] = Mem1(addr2);
953 }
954 }
955 }
956
957 return arr;
958 }
959
release_temp_c_array(char * arr,uint addr,uint len,int passout)960 void Glulxe::release_temp_c_array(char *arr, uint addr, uint len, int passout) {
961 arrayref_t *arref = nullptr;
962 arrayref_t **aptr;
963 uint ix, val, addr2;
964
965 if (arr) {
966 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
967 if ((*aptr)->array == arr)
968 break;
969 }
970 arref = *aptr;
971 if (!arref)
972 error("Unable to re-find array argument in Glk call.");
973 if (arref->addr != addr || arref->len != len)
974 error("Mismatched array argument in Glk call.");
975
976 if (arref->retained) {
977 return;
978 }
979
980 *aptr = arref->next;
981 arref->next = nullptr;
982
983 if (passout) {
984 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 1) {
985 val = arr[ix];
986 MemW1(addr2, val);
987 }
988 }
989 glulx_free(arr);
990 glulx_free(arref);
991 }
992 }
993
grab_temp_i_array(uint addr,uint len,int passin)994 uint *Glulxe::grab_temp_i_array(uint addr, uint len, int passin) {
995 arrayref_t *arref = nullptr;
996 uint *arr = nullptr;
997 uint ix, addr2;
998
999 if (len) {
1000 arr = (uint *)glulx_malloc(len * sizeof(uint));
1001 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
1002 if (!arr || !arref)
1003 error("Unable to allocate space for array argument to Glk call.");
1004
1005 arref->array = arr;
1006 arref->addr = addr;
1007 arref->elemsize = 4;
1008 arref->retained = false;
1009 arref->len = len;
1010 arref->next = arrays;
1011 arrays = arref;
1012
1013 if (passin) {
1014 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1015 arr[ix] = Mem4(addr2);
1016 }
1017 }
1018 }
1019
1020 return arr;
1021 }
1022
release_temp_i_array(uint * arr,uint addr,uint len,int passout)1023 void Glulxe::release_temp_i_array(uint *arr, uint addr, uint len, int passout) {
1024 arrayref_t *arref = nullptr;
1025 arrayref_t **aptr;
1026 uint ix, val, addr2;
1027
1028 if (arr) {
1029 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1030 if ((*aptr)->array == arr)
1031 break;
1032 }
1033 arref = *aptr;
1034 if (!arref)
1035 error("Unable to re-find array argument in Glk call.");
1036 if (arref->addr != addr || arref->len != len)
1037 error("Mismatched array argument in Glk call.");
1038
1039 if (arref->retained) {
1040 return;
1041 }
1042
1043 *aptr = arref->next;
1044 arref->next = nullptr;
1045
1046 if (passout) {
1047 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1048 val = arr[ix];
1049 MemW4(addr2, val);
1050 }
1051 }
1052 glulx_free(arr);
1053 glulx_free(arref);
1054 }
1055 }
1056
grab_temp_ptr_array(uint addr,uint len,int objclass,int passin)1057 void **Glulxe::grab_temp_ptr_array(uint addr, uint len, int objclass, int passin) {
1058 arrayref_t *arref = nullptr;
1059 void **arr = nullptr;
1060 uint ix, addr2;
1061
1062 if (len) {
1063 arr = (void **)glulx_malloc(len * sizeof(void *));
1064 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
1065 if (!arr || !arref)
1066 error("Unable to allocate space for array argument to Glk call.");
1067
1068 arref->array = arr;
1069 arref->addr = addr;
1070 arref->elemsize = sizeof(void *);
1071 arref->retained = false;
1072 arref->len = len;
1073 arref->next = arrays;
1074 arrays = arref;
1075
1076 if (passin) {
1077 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1078 uint thisval = Mem4(addr2);
1079 if (thisval)
1080 arr[ix] = classes_get(objclass, thisval);
1081 else
1082 arr[ix] = nullptr;
1083 }
1084 }
1085 }
1086
1087 return arr;
1088 }
1089
release_temp_ptr_array(void ** arr,uint addr,uint len,int objclass,int passout)1090 void Glulxe::release_temp_ptr_array(void **arr, uint addr, uint len, int objclass, int passout) {
1091 arrayref_t *arref = nullptr;
1092 arrayref_t **aptr;
1093 uint ix, val, addr2;
1094
1095 if (arr) {
1096 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1097 if ((*aptr)->array == arr)
1098 break;
1099 }
1100 arref = *aptr;
1101 if (!arref)
1102 error("Unable to re-find array argument in Glk call.");
1103 if (arref->addr != addr || arref->len != len)
1104 error("Mismatched array argument in Glk call.");
1105
1106 if (arref->retained) {
1107 return;
1108 }
1109
1110 *aptr = arref->next;
1111 arref->next = nullptr;
1112
1113 if (passout) {
1114 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1115 void *opref = arr[ix];
1116 if (opref) {
1117 gidispatch_rock_t objrock =
1118 gidispatch_get_objrock(opref, objclass);
1119 val = ((classref_t *)objrock.ptr)->id;
1120 } else {
1121 val = 0;
1122 }
1123 MemW4(addr2, val);
1124 }
1125 }
1126 glulx_free(arr);
1127 glulx_free(arref);
1128 }
1129 }
1130
glulxe_retained_register(void * array,uint len,const char * typecode)1131 gidispatch_rock_t Glulxe::glulxe_retained_register(void *array, uint len, const char *typecode) {
1132 gidispatch_rock_t rock;
1133 arrayref_t *arref = nullptr;
1134 arrayref_t **aptr;
1135 uint elemsize = 0;
1136
1137 if (typecode[4] == 'C')
1138 elemsize = 1;
1139 else if (typecode[4] == 'I')
1140 elemsize = 4;
1141
1142 if (!elemsize || array == nullptr) {
1143 rock.ptr = nullptr;
1144 return rock;
1145 }
1146
1147 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1148 if ((*aptr)->array == array)
1149 break;
1150 }
1151 arref = *aptr;
1152 if (!arref)
1153 error("Unable to re-find array argument in Glk call.");
1154 if (arref->elemsize != elemsize || arref->len != len)
1155 error("Mismatched array argument in Glk call.");
1156
1157 arref->retained = true;
1158
1159 rock.ptr = arref;
1160 return rock;
1161 }
1162
glulxe_retained_unregister(void * array,uint len,const char * typecode,gidispatch_rock_t objrock)1163 void Glulxe::glulxe_retained_unregister(void *array, uint len, const char *typecode, gidispatch_rock_t objrock) {
1164 arrayref_t *arref = nullptr;
1165 arrayref_t **aptr;
1166 uint ix, addr2, val;
1167 uint elemsize = 0;
1168
1169 // TODO: See if original GLULXE has code I'm overlooking to cleanly close everything before freeing memmap
1170 if (!memmap)
1171 return;
1172
1173 if (typecode[4] == 'C')
1174 elemsize = 1;
1175 else if (typecode[4] == 'I')
1176 elemsize = 4;
1177
1178 if (!elemsize || array == nullptr) {
1179 return;
1180 }
1181
1182 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1183 if ((*aptr)->array == array)
1184 break;
1185 }
1186 arref = *aptr;
1187 if (!arref)
1188 error("Unable to re-find array argument in Glk call.");
1189 if (arref != objrock.ptr)
1190 error("Mismatched array reference in Glk call.");
1191 if (!arref->retained)
1192 error("Unretained array reference in Glk call.");
1193 if (arref->elemsize != elemsize || arref->len != len)
1194 error("Mismatched array argument in Glk call.");
1195
1196 *aptr = arref->next;
1197 arref->next = nullptr;
1198
1199 if (elemsize == 1) {
1200 for (ix = 0, addr2 = arref->addr; ix < arref->len; ix++, addr2 += 1) {
1201 val = ((char *)array)[ix];
1202 MemW1(addr2, val);
1203 }
1204 } else if (elemsize == 4) {
1205 for (ix = 0, addr2 = arref->addr; ix < arref->len; ix++, addr2 += 4) {
1206 val = ((uint *)array)[ix];
1207 MemW4(addr2, val);
1208 }
1209 }
1210
1211 glulx_free(array);
1212 glulx_free(arref);
1213 }
1214
glulxe_array_locate(void * array,uint len,char * typecode,gidispatch_rock_t objrock,int * elemsizeref)1215 long Glulxe::glulxe_array_locate(void *array, uint len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref) {
1216 arrayref_t *arref = nullptr;
1217 arrayref_t **aptr;
1218 uint elemsize = 0;
1219
1220 if (typecode[4] == 'C')
1221 elemsize = 1;
1222 else if (typecode[4] == 'I')
1223 elemsize = 4;
1224
1225 if (!elemsize || array == nullptr) {
1226 *elemsizeref = 0; /* No need to save the array separately */
1227 return (unsigned char *)array - memmap;
1228 }
1229
1230 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1231 if ((*aptr)->array == array)
1232 break;
1233 }
1234 arref = *aptr;
1235 if (!arref)
1236 error("Unable to re-find array argument in array_locate.");
1237 if (arref != objrock.ptr)
1238 error("Mismatched array reference in array_locate.");
1239 if (!arref->retained)
1240 error("Unretained array reference in array_locate.");
1241 if (arref->elemsize != elemsize || arref->len != len)
1242 error("Mismatched array argument in array_locate.");
1243
1244 *elemsizeref = arref->elemsize;
1245 return arref->addr;
1246 }
1247
glulxe_array_restore(long bufkey,uint len,char * typecode,void ** arrayref)1248 gidispatch_rock_t Glulxe::glulxe_array_restore(long bufkey, uint len, char *typecode, void **arrayref) {
1249 gidispatch_rock_t rock;
1250 int elemsize = 0;
1251
1252 if (typecode[4] == 'C')
1253 elemsize = 1;
1254 else if (typecode[4] == 'I')
1255 elemsize = 4;
1256
1257 if (!elemsize) {
1258 unsigned char *buf = memmap + bufkey;
1259 *arrayref = buf;
1260 rock.ptr = nullptr;
1261 return rock;
1262 }
1263
1264 if (elemsize == 1) {
1265 char *cbuf = grab_temp_c_array(bufkey, len, false);
1266 rock = glulxe_retained_register(cbuf, len, typecode);
1267 *arrayref = cbuf;
1268 } else {
1269 uint *ubuf = grab_temp_i_array(bufkey, len, false);
1270 rock = glulxe_retained_register(ubuf, len, typecode);
1271 *arrayref = ubuf;
1272 }
1273 return rock;
1274 }
1275
set_library_select_hook(void (* func)(uint))1276 void Glulxe::set_library_select_hook(void (*func)(uint)) {
1277 library_select_hook = func;
1278 }
1279
get_game_id()1280 char *Glulxe::get_game_id() {
1281 /* This buffer gets rewritten on every call, but that's okay -- the caller
1282 is supposed to copy out the result. */
1283 static char buf[2 * 64 + 2];
1284 int ix, jx;
1285
1286 if (!memmap)
1287 return nullptr;
1288
1289 for (ix = 0, jx = 0; ix < 64; ix++) {
1290 char ch = memmap[ix];
1291 int val = ((ch >> 4) & 0x0F);
1292 buf[jx++] = ((val < 10) ? (val + '0') : (val + 'A' - 10));
1293 val = (ch & 0x0F);
1294 buf[jx++] = ((val < 10) ? (val + '0') : (val + 'A' - 10));
1295 }
1296 buf[jx++] = '\0';
1297
1298 return buf;
1299 }
1300
ReadMemory(uint addr)1301 uint Glulxe::ReadMemory(uint addr) {
1302 if (addr == 0xffffffff) {
1303 stackptr -= 4;
1304 return Stk4(stackptr);
1305 } else {
1306 return Mem4(addr);
1307 }
1308 }
1309
WriteMemory(uint addr,uint val)1310 void Glulxe::WriteMemory(uint addr, uint val) {
1311 if (addr == 0xffffffff) {
1312 StkW4(stackptr, (val));
1313 stackptr += 4;
1314 } else {
1315 MemW4(addr, val);
1316 }
1317 }
1318
CaptureCArray(uint addr,uint len,int passin)1319 char *Glulxe::CaptureCArray(uint addr, uint len, int passin) {
1320 return grab_temp_c_array(addr, len, passin);
1321 }
1322
ReleaseCArray(char * ptr,uint addr,uint len,int passout)1323 void Glulxe::ReleaseCArray(char *ptr, uint addr, uint len, int passout) {
1324 release_temp_c_array(ptr, addr, len, passout);
1325 }
1326
CaptureIArray(uint addr,uint len,int passin)1327 uint *Glulxe::CaptureIArray(uint addr, uint len, int passin) {
1328 return grab_temp_i_array(addr, len, passin);
1329 }
1330
ReleaseIArray(uint * ptr,uint addr,uint len,int passout)1331 void Glulxe::ReleaseIArray(uint *ptr, uint addr, uint len, int passout) {
1332 release_temp_i_array(ptr, addr, len, passout);
1333 }
1334
CapturePtrArray(uint addr,uint len,int objclass,int passin)1335 void **Glulxe::CapturePtrArray(uint addr, uint len, int objclass, int passin) {
1336 return grab_temp_ptr_array(addr, len, objclass, passin);
1337 }
1338
ReleasePtrArray(void ** ptr,uint addr,uint len,int objclass,int passout)1339 void Glulxe::ReleasePtrArray(void **ptr, uint addr, uint len, int objclass, int passout) {
1340 return release_temp_ptr_array(ptr, addr, len, objclass, passout);
1341 }
1342
ReadStructField(uint addr,uint fieldnum)1343 uint Glulxe::ReadStructField(uint addr, uint fieldnum) {
1344 if (addr == 0xffffffff) {
1345 stackptr -= 4;
1346 return Stk4(stackptr);
1347 } else {
1348 return Mem4(addr + (fieldnum * 4));
1349 }
1350 }
1351
WriteStructField(uint addr,uint fieldnum,uint val)1352 void Glulxe::WriteStructField(uint addr, uint fieldnum, uint val) {
1353 if (addr == 0xffffffff) {
1354 StkW4(stackptr, val);
1355 stackptr += 4;
1356 } else {
1357 MemW4(addr + (fieldnum * 4), val);
1358 }
1359 }
1360
DecodeVMString(uint addr)1361 char *Glulxe::DecodeVMString(uint addr) {
1362 return make_temp_string(addr);
1363 }
1364
ReleaseVMString(char * ptr)1365 void Glulxe::ReleaseVMString(char *ptr) {
1366 free_temp_string(ptr);
1367 }
1368
DecodeVMUstring(uint addr)1369 uint32 *Glulxe::DecodeVMUstring(uint addr) {
1370 return make_temp_ustring(addr);
1371 }
1372
ReleaseVMUstring(uint32 * ptr)1373 void Glulxe::ReleaseVMUstring(uint32 *ptr) {
1374 free_temp_ustring(ptr);
1375 }
1376
1377 } // End of namespace Glulxe
1378 } // End of namespace Glk
1379