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
28 /* This code is actually very general; it could work for almost any
29 32-bit VM which remotely resembles Glulx 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 Glulx::glkopInit() {
85 library_select_hook = nullptr;
86 arrays = nullptr;
87 num_classes = 0;
88 classes = nullptr;
89 }
90
init_dispatch()91 bool Glulx::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 Glulx::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 Glulx 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 // WORKAROUND: For stream_open_file calls, for savegame handling
219 // we need to store a copy of what the savegame description was,
220 // for use when we actually generate the savefile contents
221 if (funcnum == 0x42) {
222 frefid_t fref = (frefid_t)splot.garglist[0]._opaqueref;
223 _savegameDescription = fref->_description;
224 }
225
226 /* Phase 3. */
227 argnum2 = 0;
228 cx = proto;
229 unparse_glk_args(&splot, &cx, 0, &argnum2, 0, 0);
230 if (argnum != argnum2)
231 error("Argument counts did not match.");
232
233 break;
234 }
235 }
236
237 return retval;
238 }
239
read_prefix(const char * cx,int * isref,int * isarray,int * passin,int * passout,int * nullok,int * isretained,int * isreturn)240 const char *Glulx::read_prefix(const char *cx, int *isref, int *isarray, int *passin, int *passout,
241 int *nullok, int *isretained, int *isreturn) {
242 *isref = false;
243 *passin = false;
244 *passout = false;
245 *nullok = true;
246 *isarray = false;
247 *isretained = false;
248 *isreturn = false;
249 while (1) {
250 if (*cx == '<') {
251 *isref = true;
252 *passout = true;
253 } else if (*cx == '>') {
254 *isref = true;
255 *passin = true;
256 } else if (*cx == '&') {
257 *isref = true;
258 *passout = true;
259 *passin = true;
260 } else if (*cx == '+') {
261 *nullok = false;
262 } else if (*cx == ':') {
263 *isref = true;
264 *passout = true;
265 *nullok = false;
266 *isreturn = true;
267 } else if (*cx == '#') {
268 *isarray = true;
269 } else if (*cx == '!') {
270 *isretained = true;
271 } else {
272 break;
273 }
274 cx++;
275 }
276 return cx;
277 }
278
prepare_glk_args(const char * proto,dispatch_splot_t * splot)279 void Glulx::prepare_glk_args(const char *proto, dispatch_splot_t *splot) {
280 static gluniversal_t *garglist = nullptr;
281 static int garglist_size = 0;
282
283 int ix;
284 int numwanted, numvargswanted, maxargs;
285 const char *cx;
286
287 cx = proto;
288 numwanted = 0;
289 while (*cx >= '0' && *cx <= '9') {
290 numwanted = 10 * numwanted + (*cx - '0');
291 cx++;
292 }
293 splot->numwanted = numwanted;
294
295 maxargs = 0;
296 numvargswanted = 0;
297 for (ix = 0; ix < numwanted; ix++) {
298 int isref, passin, passout, nullok, isarray, isretained, isreturn;
299 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
300 &isretained, &isreturn);
301 if (isref) {
302 maxargs += 2;
303 } else {
304 maxargs += 1;
305 }
306 if (!isreturn) {
307 if (isarray) {
308 numvargswanted += 2;
309 } else {
310 numvargswanted += 1;
311 }
312 }
313
314 if (*cx == 'I' || *cx == 'C') {
315 cx += 2;
316 } else if (*cx == 'Q') {
317 cx += 2;
318 } else if (*cx == 'S' || *cx == 'U') {
319 cx += 1;
320 } else if (*cx == '[') {
321 int refdepth, nwx;
322 cx++;
323 nwx = 0;
324 while (*cx >= '0' && *cx <= '9') {
325 nwx = 10 * nwx + (*cx - '0');
326 cx++;
327 }
328 maxargs += nwx; /* This is *only* correct because all structs contain
329 plain values. */
330 refdepth = 1;
331 while (refdepth > 0) {
332 if (*cx == '[')
333 refdepth++;
334 else if (*cx == ']')
335 refdepth--;
336 cx++;
337 }
338 } else {
339 error("Illegal format string.");
340 }
341 }
342
343 if (*cx != ':' && *cx != '\0')
344 error("Illegal format string.");
345
346 splot->maxargs = maxargs;
347
348 if (splot->numvargs != numvargswanted)
349 error("Wrong number of arguments to Glk function.");
350
351 if (garglist && garglist_size < maxargs) {
352 glulx_free(garglist);
353 garglist = nullptr;
354 garglist_size = 0;
355 }
356 if (!garglist) {
357 garglist_size = maxargs + 16;
358 garglist = (gluniversal_t *)glulx_malloc(garglist_size
359 * sizeof(gluniversal_t));
360 }
361 if (!garglist)
362 error("Unable to allocate storage for Glk arguments.");
363
364 splot->garglist = garglist;
365 }
366
parse_glk_args(dispatch_splot_t * splot,const char ** proto,int depth,int * argnumptr,uint subaddress,int subpassin)367 void Glulx::parse_glk_args(dispatch_splot_t *splot, const char **proto, int depth, int *argnumptr,
368 uint subaddress, int subpassin) {
369 const char *cx;
370 int ix, argx;
371 int gargnum, numwanted;
372 void *opref;
373 gluniversal_t *garglist;
374 uint *varglist;
375
376 garglist = splot->garglist;
377 varglist = splot->varglist;
378 gargnum = *argnumptr;
379 cx = *proto;
380
381 numwanted = 0;
382 while (*cx >= '0' && *cx <= '9') {
383 numwanted = 10 * numwanted + (*cx - '0');
384 cx++;
385 }
386
387 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
388 char typeclass;
389 int skipval;
390 int isref, passin, passout, nullok, isarray, isretained, isreturn;
391 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
392 &isretained, &isreturn);
393
394 typeclass = *cx;
395 cx++;
396
397 skipval = false;
398 if (isref) {
399 if (!isreturn && varglist[ix] == 0) {
400 if (!nullok)
401 error("Zero passed invalidly to Glk function.");
402 garglist[gargnum]._ptrflag = false;
403 gargnum++;
404 skipval = true;
405 } else {
406 garglist[gargnum]._ptrflag = true;
407 gargnum++;
408 }
409 }
410 if (!skipval) {
411 uint thisval;
412
413 if (typeclass == '[') {
414
415 parse_glk_args(splot, &cx, depth + 1, &gargnum, varglist[ix], passin);
416
417 } else if (isarray) {
418 /* definitely isref */
419
420 switch (typeclass) {
421 case 'C':
422 /* This test checks for a giant array length, which is
423 deprecated. It displays a warning and cuts it down to
424 something reasonable. Future releases of this interpreter
425 may remove this test and go on to verify_array_addresses(),
426 which treats this case as a fatal error. */
427 if (varglist[ix + 1] > endmem
428 || varglist[ix] + varglist[ix + 1] > endmem) {
429 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix + 1]);
430 varglist[ix + 1] = endmem - varglist[ix];
431 }
432 verify_array_addresses(varglist[ix], varglist[ix + 1], 1);
433 garglist[gargnum]._array = CaptureCArray(varglist[ix], varglist[ix + 1], passin);
434 gargnum++;
435 ix++;
436 garglist[gargnum]._uint = varglist[ix];
437 gargnum++;
438 cx++;
439 break;
440 case 'I':
441 /* See comment above. */
442 if (varglist[ix + 1] > endmem / 4
443 || varglist[ix + 1] > (endmem - varglist[ix]) / 4) {
444 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix + 1]);
445 varglist[ix + 1] = (endmem - varglist[ix]) / 4;
446 }
447 verify_array_addresses(varglist[ix], varglist[ix + 1], 4);
448 garglist[gargnum]._array = CaptureIArray(varglist[ix], varglist[ix + 1], passin);
449 gargnum++;
450 ix++;
451 garglist[gargnum]._uint = varglist[ix];
452 gargnum++;
453 cx++;
454 break;
455 case 'Q':
456 /* This case was added after the giant arrays were deprecated,
457 so we don't bother to allow for that case. We just verify
458 the length. */
459 verify_array_addresses(varglist[ix], varglist[ix + 1], 4);
460 garglist[gargnum]._array = CapturePtrArray(varglist[ix], varglist[ix + 1], (*cx - 'a'), passin);
461 gargnum++;
462 ix++;
463 garglist[gargnum]._uint = varglist[ix];
464 gargnum++;
465 cx++;
466 break;
467 default:
468 error("Illegal format string.");
469 break;
470 }
471 } else {
472 /* a plain value or a reference to one. */
473
474 if (isreturn) {
475 thisval = 0;
476 } else if (depth > 0) {
477 /* Definitely not isref or isarray. */
478 if (subpassin)
479 thisval = ReadStructField(subaddress, ix);
480 else
481 thisval = 0;
482 } else if (isref) {
483 if (passin)
484 thisval = ReadMemory(varglist[ix]);
485 else
486 thisval = 0;
487 } else {
488 thisval = varglist[ix];
489 }
490
491 switch (typeclass) {
492 case 'I':
493 if (*cx == 'u')
494 garglist[gargnum]._uint = (uint)(thisval);
495 else if (*cx == 's')
496 garglist[gargnum]._sint = (int)(thisval);
497 else
498 error("Illegal format string.");
499 gargnum++;
500 cx++;
501 break;
502 case 'Q':
503 if (thisval) {
504 opref = classes_get(*cx - 'a', thisval);
505 if (!opref) {
506 error("Reference to nonexistent Glk object.");
507 }
508 } else {
509 opref = nullptr;
510 }
511 garglist[gargnum]._opaqueref = opref;
512 gargnum++;
513 cx++;
514 break;
515 case 'C':
516 if (*cx == 'u')
517 garglist[gargnum]._uch = (unsigned char)(thisval);
518 else if (*cx == 's')
519 garglist[gargnum]._sch = (signed char)(thisval);
520 else if (*cx == 'n')
521 garglist[gargnum]._ch = (char)(thisval);
522 else
523 error("Illegal format string.");
524 gargnum++;
525 cx++;
526 break;
527 case 'S':
528 garglist[gargnum]._charstr = DecodeVMString(thisval);
529 gargnum++;
530 break;
531 #ifdef GLK_MODULE_UNICODE
532 case 'U':
533 garglist[gargnum]._unicharstr = DecodeVMUstring(thisval);
534 gargnum++;
535 break;
536 #endif /* GLK_MODULE_UNICODE */
537 default:
538 error("Illegal format string.");
539 break;
540 }
541 }
542 } else {
543 /* We got a null reference, so we have to skip the format element. */
544 if (typeclass == '[') {
545 int numsubwanted, refdepth;
546 numsubwanted = 0;
547 while (*cx >= '0' && *cx <= '9') {
548 numsubwanted = 10 * numsubwanted + (*cx - '0');
549 cx++;
550 }
551 refdepth = 1;
552 while (refdepth > 0) {
553 if (*cx == '[')
554 refdepth++;
555 else if (*cx == ']')
556 refdepth--;
557 cx++;
558 }
559 } else if (typeclass == 'S' || typeclass == 'U') {
560 /* leave it */
561 } else {
562 cx++;
563 if (isarray)
564 ix++;
565 }
566 }
567 }
568
569 if (depth > 0) {
570 if (*cx != ']')
571 error("Illegal format string.");
572 cx++;
573 } else {
574 if (*cx != ':' && *cx != '\0')
575 error("Illegal format string.");
576 }
577
578 *proto = cx;
579 *argnumptr = gargnum;
580 }
581
unparse_glk_args(dispatch_splot_t * splot,const char ** proto,int depth,int * argnumptr,uint subaddress,int subpassout)582 void Glulx::unparse_glk_args(dispatch_splot_t *splot, const char **proto, int depth,
583 int *argnumptr, uint subaddress, int subpassout) {
584 const char *cx;
585 int ix, argx;
586 int gargnum, numwanted;
587 void *opref;
588 gluniversal_t *garglist;
589 uint *varglist;
590
591 garglist = splot->garglist;
592 varglist = splot->varglist;
593 gargnum = *argnumptr;
594 cx = *proto;
595
596 numwanted = 0;
597 while (*cx >= '0' && *cx <= '9') {
598 numwanted = 10 * numwanted + (*cx - '0');
599 cx++;
600 }
601
602 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
603 char typeclass;
604 int skipval;
605 int isref, passin, passout, nullok, isarray, isretained, isreturn;
606 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
607 &isretained, &isreturn);
608
609 typeclass = *cx;
610 cx++;
611
612 skipval = false;
613 if (isref) {
614 if (!isreturn && varglist[ix] == 0) {
615 if (!nullok)
616 error("Zero passed invalidly to Glk function.");
617 garglist[gargnum]._ptrflag = false;
618 gargnum++;
619 skipval = true;
620 } else {
621 garglist[gargnum]._ptrflag = true;
622 gargnum++;
623 }
624 }
625 if (!skipval) {
626 uint thisval = 0;
627
628 if (typeclass == '[') {
629
630 unparse_glk_args(splot, &cx, depth + 1, &gargnum, varglist[ix], passout);
631
632 } else if (isarray) {
633 /* definitely isref */
634
635 switch (typeclass) {
636 case 'C':
637 ReleaseCArray((char *)garglist[gargnum]._array, varglist[ix], varglist[ix + 1], passout);
638 gargnum++;
639 ix++;
640 gargnum++;
641 cx++;
642 break;
643 case 'I':
644 ReleaseIArray((uint *)garglist[gargnum]._array, varglist[ix], varglist[ix + 1], passout);
645 gargnum++;
646 ix++;
647 gargnum++;
648 cx++;
649 break;
650 case 'Q':
651 ReleasePtrArray((void **)garglist[gargnum]._array, varglist[ix], varglist[ix + 1], (*cx - 'a'), passout);
652 gargnum++;
653 ix++;
654 gargnum++;
655 cx++;
656 break;
657 default:
658 error("Illegal format string.");
659 break;
660 }
661 } else {
662 /* a plain value or a reference to one. */
663
664 if (isreturn || (depth > 0 && subpassout) || (isref && passout)) {
665 skipval = false;
666 } else {
667 skipval = true;
668 }
669
670 switch (typeclass) {
671 case 'I':
672 if (!skipval) {
673 if (*cx == 'u')
674 thisval = (uint)garglist[gargnum]._uint;
675 else if (*cx == 's')
676 thisval = (uint)garglist[gargnum]._sint;
677 else
678 error("Illegal format string.");
679 }
680 gargnum++;
681 cx++;
682 break;
683 case 'Q':
684 if (!skipval) {
685 opref = garglist[gargnum]._opaqueref;
686 if (opref) {
687 gidispatch_rock_t objrock = gidispatch_get_objrock(opref, *cx - 'a');
688 assert(objrock.ptr);
689 thisval = ((classref_t *)objrock.ptr)->id;
690 } else {
691 thisval = 0;
692 }
693 }
694 gargnum++;
695 cx++;
696 break;
697 case 'C':
698 if (!skipval) {
699 if (*cx == 'u')
700 thisval = (uint)garglist[gargnum]._uch;
701 else if (*cx == 's')
702 thisval = (uint)garglist[gargnum]._sch;
703 else if (*cx == 'n')
704 thisval = (uint)garglist[gargnum]._ch;
705 else
706 error("Illegal format string.");
707 }
708 gargnum++;
709 cx++;
710 break;
711 case 'S':
712 if (garglist[gargnum]._charstr)
713 ReleaseVMString(garglist[gargnum]._charstr);
714 gargnum++;
715 break;
716 #ifdef GLK_MODULE_UNICODE
717 case 'U':
718 if (garglist[gargnum]._unicharstr)
719 ReleaseVMUstring(garglist[gargnum]._unicharstr);
720 gargnum++;
721 break;
722 #endif /* GLK_MODULE_UNICODE */
723 default:
724 error("Illegal format string.");
725 break;
726 }
727
728 if (isreturn) {
729 *(splot->retval) = thisval;
730 } else if (depth > 0) {
731 /* Definitely not isref or isarray. */
732 if (subpassout)
733 WriteStructField(subaddress, ix, thisval);
734 } else if (isref) {
735 if (passout)
736 WriteMemory(varglist[ix], thisval);
737 }
738 }
739 } else {
740 /* We got a null reference, so we have to skip the format element. */
741 if (typeclass == '[') {
742 int numsubwanted, refdepth;
743 numsubwanted = 0;
744 while (*cx >= '0' && *cx <= '9') {
745 numsubwanted = 10 * numsubwanted + (*cx - '0');
746 cx++;
747 }
748 refdepth = 1;
749 while (refdepth > 0) {
750 if (*cx == '[')
751 refdepth++;
752 else if (*cx == ']')
753 refdepth--;
754 cx++;
755 }
756 } else if (typeclass == 'S' || typeclass == 'U') {
757 /* leave it */
758 } else {
759 cx++;
760 if (isarray)
761 ix++;
762 }
763 }
764 }
765
766 if (depth > 0) {
767 if (*cx != ']')
768 error("Illegal format string.");
769 cx++;
770 } else {
771 if (*cx != ':' && *cx != '\0')
772 error("Illegal format string.");
773 }
774
775 *proto = cx;
776 *argnumptr = gargnum;
777 }
778
find_stream_by_id(uint objid)779 strid_t Glulx::find_stream_by_id(uint objid) {
780 if (!objid)
781 return nullptr;
782
783 // Recall that class 1 ("b") is streams
784 return (strid_t)classes_get(gidisp_Class_Stream, objid);
785 }
786
find_id_for_window(winid_t win)787 uint Glulx::find_id_for_window(winid_t win) {
788 gidispatch_rock_t objrock;
789
790 if (!win)
791 return 0;
792
793 objrock = gidispatch_get_objrock(win, gidisp_Class_Window);
794 if (!objrock.ptr)
795 return 0;
796 return ((classref_t *)objrock.ptr)->id;
797 }
798
find_id_for_stream(strid_t str)799 uint Glulx::find_id_for_stream(strid_t str) {
800 gidispatch_rock_t objrock;
801
802 if (!str)
803 return 0;
804
805 objrock = gidispatch_get_objrock(str, gidisp_Class_Stream);
806 if (!objrock.ptr)
807 return 0;
808 return ((classref_t *)objrock.ptr)->id;
809 }
810
find_id_for_fileref(frefid_t fref)811 uint Glulx::find_id_for_fileref(frefid_t fref) {
812 gidispatch_rock_t objrock;
813
814 if (!fref)
815 return 0;
816
817 objrock = gidispatch_get_objrock(fref, gidisp_Class_Fileref);
818 if (!objrock.ptr)
819 return 0;
820 return ((classref_t *)objrock.ptr)->id;
821 }
822
find_id_for_schannel(schanid_t schan)823 uint Glulx::find_id_for_schannel(schanid_t schan) {
824 gidispatch_rock_t objrock;
825
826 if (!schan)
827 return 0;
828
829 objrock = gidispatch_get_objrock(schan, gidisp_Class_Schannel);
830 if (!objrock.ptr)
831 return 0;
832 return ((classref_t *)objrock.ptr)->id;
833 }
834
new_classtable(uint firstid)835 classtable_t *Glulx::new_classtable(uint firstid) {
836 int ix;
837 classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t));
838 if (!ctab)
839 return nullptr;
840
841 for (ix = 0; ix < CLASSHASH_SIZE; ix++)
842 ctab->bucket[ix] = nullptr;
843
844 ctab->lastid = firstid;
845
846 return ctab;
847 }
848
classes_get(int classid,uint objid)849 void *Glulx::classes_get(int classid, uint objid) {
850 classtable_t *ctab;
851 classref_t *cref;
852 if (classid < 0 || classid >= num_classes)
853 return nullptr;
854 ctab = classes[classid];
855 cref = ctab->bucket[objid % CLASSHASH_SIZE];
856 for (; cref; cref = cref->next) {
857 if (cref->id == objid)
858 return cref->obj;
859 }
860 return nullptr;
861 }
862
classes_put(int classid,void * obj,uint origid)863 classref_t *Glulx::classes_put(int classid, void *obj, uint origid) {
864 int bucknum;
865 classtable_t *ctab;
866 classref_t *cref;
867 if (classid < 0 || classid >= num_classes)
868 return nullptr;
869 ctab = classes[classid];
870 cref = (classref_t *)glulx_malloc(sizeof(classref_t));
871 if (!cref)
872 return nullptr;
873 cref->obj = obj;
874 if (!origid) {
875 cref->id = ctab->lastid;
876 ctab->lastid++;
877 } else {
878 cref->id = origid;
879 if (ctab->lastid <= origid)
880 ctab->lastid = origid + 1;
881 }
882 bucknum = cref->id % CLASSHASH_SIZE;
883 cref->bucknum = bucknum;
884 cref->next = ctab->bucket[bucknum];
885 ctab->bucket[bucknum] = cref;
886 return cref;
887 }
888
classes_remove(int classid,void * obj)889 void Glulx::classes_remove(int classid, void *obj) {
890 classtable_t *ctab;
891 classref_t *cref;
892 classref_t **crefp;
893 gidispatch_rock_t objrock;
894 if (classid < 0 || classid >= num_classes)
895 return;
896 ctab = classes[classid];
897 objrock = gidispatch_get_objrock(obj, classid);
898 cref = (classref_t *)objrock.ptr;
899 if (!cref)
900 return;
901 crefp = &(ctab->bucket[cref->bucknum]);
902 for (; *crefp; crefp = &((*crefp)->next)) {
903 if ((*crefp) == cref) {
904 *crefp = cref->next;
905 if (!cref->obj) {
906 nonfatal_warning("attempt to free nullptr object!");
907 }
908 cref->obj = nullptr;
909 cref->id = 0;
910 cref->next = nullptr;
911 glulx_free(cref);
912 return;
913 }
914 }
915 return;
916 }
917
glulxe_classtable_register(void * obj,uint objclass)918 gidispatch_rock_t Glulx::glulxe_classtable_register(void *obj, uint objclass) {
919 classref_t *cref;
920 gidispatch_rock_t objrock;
921 cref = classes_put(objclass, obj, 0);
922 objrock.ptr = cref;
923 return objrock;
924 }
925
glulxe_classtable_unregister(void * obj,uint objclass,gidispatch_rock_t objrock)926 void Glulx::glulxe_classtable_unregister(void *obj, uint objclass,
927 gidispatch_rock_t objrock) {
928 classes_remove(objclass, obj);
929 }
930
glulxe_classtable_register_existing(void * obj,uint objclass,uint dispid)931 gidispatch_rock_t Glulx::glulxe_classtable_register_existing(void *obj, uint objclass, uint dispid) {
932 classref_t *cref;
933 gidispatch_rock_t objrock;
934 cref = classes_put(objclass, obj, dispid);
935 objrock.ptr = cref;
936 return objrock;
937 }
938
grab_temp_c_array(uint addr,uint len,int passin)939 char *Glulx::grab_temp_c_array(uint addr, uint len, int passin) {
940 arrayref_t *arref = nullptr;
941 char *arr = nullptr;
942 uint ix, addr2;
943
944 if (len) {
945 arr = (char *)glulx_malloc(len * sizeof(char));
946 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
947 if (!arr || !arref)
948 error("Unable to allocate space for array argument to Glk call.");
949
950 arref->array = arr;
951 arref->addr = addr;
952 arref->elemsize = 1;
953 arref->retained = false;
954 arref->len = len;
955 arref->next = arrays;
956 arrays = arref;
957
958 if (passin) {
959 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 1) {
960 arr[ix] = Mem1(addr2);
961 }
962 }
963 }
964
965 return arr;
966 }
967
release_temp_c_array(char * arr,uint addr,uint len,int passout)968 void Glulx::release_temp_c_array(char *arr, uint addr, uint len, int passout) {
969 arrayref_t *arref = nullptr;
970 arrayref_t **aptr;
971 uint ix, val, addr2;
972
973 if (arr) {
974 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
975 if ((*aptr)->array == arr)
976 break;
977 }
978 arref = *aptr;
979 if (!arref)
980 error("Unable to re-find array argument in Glk call.");
981 if (arref->addr != addr || arref->len != len)
982 error("Mismatched array argument in Glk call.");
983
984 if (arref->retained) {
985 return;
986 }
987
988 *aptr = arref->next;
989 arref->next = nullptr;
990
991 if (passout) {
992 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 1) {
993 val = arr[ix];
994 MemW1(addr2, val);
995 }
996 }
997 glulx_free(arr);
998 glulx_free(arref);
999 }
1000 }
1001
grab_temp_i_array(uint addr,uint len,int passin)1002 uint *Glulx::grab_temp_i_array(uint addr, uint len, int passin) {
1003 arrayref_t *arref = nullptr;
1004 uint *arr = nullptr;
1005 uint ix, addr2;
1006
1007 if (len) {
1008 arr = (uint *)glulx_malloc(len * sizeof(uint));
1009 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
1010 if (!arr || !arref)
1011 error("Unable to allocate space for array argument to Glk call.");
1012
1013 arref->array = arr;
1014 arref->addr = addr;
1015 arref->elemsize = 4;
1016 arref->retained = false;
1017 arref->len = len;
1018 arref->next = arrays;
1019 arrays = arref;
1020
1021 if (passin) {
1022 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1023 arr[ix] = Mem4(addr2);
1024 }
1025 }
1026 }
1027
1028 return arr;
1029 }
1030
release_temp_i_array(uint * arr,uint addr,uint len,int passout)1031 void Glulx::release_temp_i_array(uint *arr, uint addr, uint len, int passout) {
1032 arrayref_t *arref = nullptr;
1033 arrayref_t **aptr;
1034 uint ix, val, addr2;
1035
1036 if (arr) {
1037 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1038 if ((*aptr)->array == arr)
1039 break;
1040 }
1041 arref = *aptr;
1042 if (!arref)
1043 error("Unable to re-find array argument in Glk call.");
1044 if (arref->addr != addr || arref->len != len)
1045 error("Mismatched array argument in Glk call.");
1046
1047 if (arref->retained) {
1048 return;
1049 }
1050
1051 *aptr = arref->next;
1052 arref->next = nullptr;
1053
1054 if (passout) {
1055 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1056 val = arr[ix];
1057 MemW4(addr2, val);
1058 }
1059 }
1060 glulx_free(arr);
1061 glulx_free(arref);
1062 }
1063 }
1064
grab_temp_ptr_array(uint addr,uint len,int objclass,int passin)1065 void **Glulx::grab_temp_ptr_array(uint addr, uint len, int objclass, int passin) {
1066 arrayref_t *arref = nullptr;
1067 void **arr = nullptr;
1068 uint ix, addr2;
1069
1070 if (len) {
1071 arr = (void **)glulx_malloc(len * sizeof(void *));
1072 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
1073 if (!arr || !arref)
1074 error("Unable to allocate space for array argument to Glk call.");
1075
1076 arref->array = arr;
1077 arref->addr = addr;
1078 arref->elemsize = sizeof(void *);
1079 arref->retained = false;
1080 arref->len = len;
1081 arref->next = arrays;
1082 arrays = arref;
1083
1084 if (passin) {
1085 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1086 uint thisval = Mem4(addr2);
1087 if (thisval)
1088 arr[ix] = classes_get(objclass, thisval);
1089 else
1090 arr[ix] = nullptr;
1091 }
1092 }
1093 }
1094
1095 return arr;
1096 }
1097
release_temp_ptr_array(void ** arr,uint addr,uint len,int objclass,int passout)1098 void Glulx::release_temp_ptr_array(void **arr, uint addr, uint len, int objclass, int passout) {
1099 arrayref_t *arref = nullptr;
1100 arrayref_t **aptr;
1101 uint ix, val, addr2;
1102
1103 if (arr) {
1104 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1105 if ((*aptr)->array == arr)
1106 break;
1107 }
1108 arref = *aptr;
1109 if (!arref)
1110 error("Unable to re-find array argument in Glk call.");
1111 if (arref->addr != addr || arref->len != len)
1112 error("Mismatched array argument in Glk call.");
1113
1114 if (arref->retained) {
1115 return;
1116 }
1117
1118 *aptr = arref->next;
1119 arref->next = nullptr;
1120
1121 if (passout) {
1122 for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
1123 void *opref = arr[ix];
1124 if (opref) {
1125 gidispatch_rock_t objrock =
1126 gidispatch_get_objrock(opref, objclass);
1127 val = ((classref_t *)objrock.ptr)->id;
1128 } else {
1129 val = 0;
1130 }
1131 MemW4(addr2, val);
1132 }
1133 }
1134 glulx_free(arr);
1135 glulx_free(arref);
1136 }
1137 }
1138
glulxe_retained_register(void * array,uint len,const char * typecode)1139 gidispatch_rock_t Glulx::glulxe_retained_register(void *array, uint len, const char *typecode) {
1140 gidispatch_rock_t rock;
1141 arrayref_t *arref = nullptr;
1142 arrayref_t **aptr;
1143 uint elemsize = 0;
1144
1145 if (typecode[4] == 'C')
1146 elemsize = 1;
1147 else if (typecode[4] == 'I')
1148 elemsize = 4;
1149
1150 if (!elemsize || array == nullptr) {
1151 rock.ptr = nullptr;
1152 return rock;
1153 }
1154
1155 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1156 if ((*aptr)->array == array)
1157 break;
1158 }
1159 arref = *aptr;
1160 if (!arref)
1161 error("Unable to re-find array argument in Glk call.");
1162 if (arref->elemsize != elemsize || arref->len != len)
1163 error("Mismatched array argument in Glk call.");
1164
1165 arref->retained = true;
1166
1167 rock.ptr = arref;
1168 return rock;
1169 }
1170
glulxe_retained_unregister(void * array,uint len,const char * typecode,gidispatch_rock_t objrock)1171 void Glulx::glulxe_retained_unregister(void *array, uint len, const char *typecode, gidispatch_rock_t objrock) {
1172 arrayref_t *arref = nullptr;
1173 arrayref_t **aptr;
1174 uint ix, addr2, val;
1175 uint elemsize = 0;
1176
1177 // TODO: See if original GLULXE has code I'm overlooking to cleanly close everything before freeing memmap
1178 if (!memmap)
1179 return;
1180
1181 if (typecode[4] == 'C')
1182 elemsize = 1;
1183 else if (typecode[4] == 'I')
1184 elemsize = 4;
1185
1186 if (!elemsize || array == nullptr) {
1187 return;
1188 }
1189
1190 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1191 if ((*aptr)->array == array)
1192 break;
1193 }
1194 arref = *aptr;
1195 if (!arref)
1196 error("Unable to re-find array argument in Glk call.");
1197 if (arref != objrock.ptr)
1198 error("Mismatched array reference in Glk call.");
1199 if (!arref->retained)
1200 error("Unretained array reference in Glk call.");
1201 if (arref->elemsize != elemsize || arref->len != len)
1202 error("Mismatched array argument in Glk call.");
1203
1204 *aptr = arref->next;
1205 arref->next = nullptr;
1206
1207 if (elemsize == 1) {
1208 for (ix = 0, addr2 = arref->addr; ix < arref->len; ix++, addr2 += 1) {
1209 val = ((char *)array)[ix];
1210 MemW1(addr2, val);
1211 }
1212 } else if (elemsize == 4) {
1213 for (ix = 0, addr2 = arref->addr; ix < arref->len; ix++, addr2 += 4) {
1214 val = ((uint *)array)[ix];
1215 MemW4(addr2, val);
1216 }
1217 }
1218
1219 glulx_free(array);
1220 glulx_free(arref);
1221 }
1222
glulxe_array_locate(void * array,uint len,char * typecode,gidispatch_rock_t objrock,int * elemsizeref)1223 long Glulx::glulxe_array_locate(void *array, uint len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref) {
1224 arrayref_t *arref = nullptr;
1225 arrayref_t **aptr;
1226 uint elemsize = 0;
1227
1228 if (typecode[4] == 'C')
1229 elemsize = 1;
1230 else if (typecode[4] == 'I')
1231 elemsize = 4;
1232
1233 if (!elemsize || array == nullptr) {
1234 *elemsizeref = 0; /* No need to save the array separately */
1235 return (unsigned char *)array - memmap;
1236 }
1237
1238 for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
1239 if ((*aptr)->array == array)
1240 break;
1241 }
1242 arref = *aptr;
1243 if (!arref)
1244 error("Unable to re-find array argument in array_locate.");
1245 if (arref != objrock.ptr)
1246 error("Mismatched array reference in array_locate.");
1247 if (!arref->retained)
1248 error("Unretained array reference in array_locate.");
1249 if (arref->elemsize != elemsize || arref->len != len)
1250 error("Mismatched array argument in array_locate.");
1251
1252 *elemsizeref = arref->elemsize;
1253 return arref->addr;
1254 }
1255
glulxe_array_restore(long bufkey,uint len,char * typecode,void ** arrayref)1256 gidispatch_rock_t Glulx::glulxe_array_restore(long bufkey, uint len, char *typecode, void **arrayref) {
1257 gidispatch_rock_t rock;
1258 int elemsize = 0;
1259
1260 if (typecode[4] == 'C')
1261 elemsize = 1;
1262 else if (typecode[4] == 'I')
1263 elemsize = 4;
1264
1265 if (!elemsize) {
1266 unsigned char *buf = memmap + bufkey;
1267 *arrayref = buf;
1268 rock.ptr = nullptr;
1269 return rock;
1270 }
1271
1272 if (elemsize == 1) {
1273 char *cbuf = grab_temp_c_array(bufkey, len, false);
1274 rock = glulxe_retained_register(cbuf, len, typecode);
1275 *arrayref = cbuf;
1276 } else {
1277 uint *ubuf = grab_temp_i_array(bufkey, len, false);
1278 rock = glulxe_retained_register(ubuf, len, typecode);
1279 *arrayref = ubuf;
1280 }
1281 return rock;
1282 }
1283
set_library_select_hook(void (* func)(uint))1284 void Glulx::set_library_select_hook(void (*func)(uint)) {
1285 library_select_hook = func;
1286 }
1287
get_game_id()1288 char *Glulx::get_game_id() {
1289 /* This buffer gets rewritten on every call, but that's okay -- the caller
1290 is supposed to copy out the result. */
1291 static char buf[2 * 64 + 2];
1292 int ix, jx;
1293
1294 if (!memmap)
1295 return nullptr;
1296
1297 for (ix = 0, jx = 0; ix < 64; ix++) {
1298 char ch = memmap[ix];
1299 int val = ((ch >> 4) & 0x0F);
1300 buf[jx++] = ((val < 10) ? (val + '0') : (val + 'A' - 10));
1301 val = (ch & 0x0F);
1302 buf[jx++] = ((val < 10) ? (val + '0') : (val + 'A' - 10));
1303 }
1304 buf[jx++] = '\0';
1305
1306 return buf;
1307 }
1308
ReadMemory(uint addr)1309 uint Glulx::ReadMemory(uint addr) {
1310 if (addr == 0xffffffff) {
1311 stackptr -= 4;
1312 return Stk4(stackptr);
1313 } else {
1314 return Mem4(addr);
1315 }
1316 }
1317
WriteMemory(uint addr,uint val)1318 void Glulx::WriteMemory(uint addr, uint val) {
1319 if (addr == 0xffffffff) {
1320 StkW4(stackptr, (val));
1321 stackptr += 4;
1322 } else {
1323 MemW4(addr, val);
1324 }
1325 }
1326
CaptureCArray(uint addr,uint len,int passin)1327 char *Glulx::CaptureCArray(uint addr, uint len, int passin) {
1328 return grab_temp_c_array(addr, len, passin);
1329 }
1330
ReleaseCArray(char * ptr,uint addr,uint len,int passout)1331 void Glulx::ReleaseCArray(char *ptr, uint addr, uint len, int passout) {
1332 release_temp_c_array(ptr, addr, len, passout);
1333 }
1334
CaptureIArray(uint addr,uint len,int passin)1335 uint *Glulx::CaptureIArray(uint addr, uint len, int passin) {
1336 return grab_temp_i_array(addr, len, passin);
1337 }
1338
ReleaseIArray(uint * ptr,uint addr,uint len,int passout)1339 void Glulx::ReleaseIArray(uint *ptr, uint addr, uint len, int passout) {
1340 release_temp_i_array(ptr, addr, len, passout);
1341 }
1342
CapturePtrArray(uint addr,uint len,int objclass,int passin)1343 void **Glulx::CapturePtrArray(uint addr, uint len, int objclass, int passin) {
1344 return grab_temp_ptr_array(addr, len, objclass, passin);
1345 }
1346
ReleasePtrArray(void ** ptr,uint addr,uint len,int objclass,int passout)1347 void Glulx::ReleasePtrArray(void **ptr, uint addr, uint len, int objclass, int passout) {
1348 return release_temp_ptr_array(ptr, addr, len, objclass, passout);
1349 }
1350
ReadStructField(uint addr,uint fieldnum)1351 uint Glulx::ReadStructField(uint addr, uint fieldnum) {
1352 if (addr == 0xffffffff) {
1353 stackptr -= 4;
1354 return Stk4(stackptr);
1355 } else {
1356 return Mem4(addr + (fieldnum * 4));
1357 }
1358 }
1359
WriteStructField(uint addr,uint fieldnum,uint val)1360 void Glulx::WriteStructField(uint addr, uint fieldnum, uint val) {
1361 if (addr == 0xffffffff) {
1362 StkW4(stackptr, val);
1363 stackptr += 4;
1364 } else {
1365 MemW4(addr + (fieldnum * 4), val);
1366 }
1367 }
1368
DecodeVMString(uint addr)1369 char *Glulx::DecodeVMString(uint addr) {
1370 return make_temp_string(addr);
1371 }
1372
ReleaseVMString(char * ptr)1373 void Glulx::ReleaseVMString(char *ptr) {
1374 free_temp_string(ptr);
1375 }
1376
DecodeVMUstring(uint addr)1377 uint32 *Glulx::DecodeVMUstring(uint addr) {
1378 return make_temp_ustring(addr);
1379 }
1380
ReleaseVMUstring(uint32 * ptr)1381 void Glulx::ReleaseVMUstring(uint32 *ptr) {
1382 free_temp_ustring(ptr);
1383 }
1384
1385 } // End of namespace Glulx
1386 } // End of namespace Glk
1387