pax_global_header00006660000000000000000000000064130414530350014510gustar00rootroot0000000000000052 comment=d44292db33e5a40a00c1b4cb602e491f4d0b07f3 glulxe/000077500000000000000000000000001304145303500123545ustar00rootroot00000000000000glulxe/.gitignore000066400000000000000000000000131304145303500143360ustar00rootroot00000000000000*.o glulxe glulxe/LICENSE000066400000000000000000000020711304145303500133610ustar00rootroot00000000000000The MIT License Copyright (c) 1999-2016, Andrew Plotkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. glulxe/Makefile000066400000000000000000000033261304145303500140200ustar00rootroot00000000000000# Unix Makefile for Glulxe. # To use this, you must set three variables. GLKINCLUDEDIR must be the # directory containing glk.h, glkstart.h, and the Make.library file. # GLKLIBDIR must be the directory containing the library.a file. # And GLKMAKEFILE must be the name of the Make.library file. Two # sets of values appear below; uncomment one of them and change the # directories appropriately. GLKINCLUDEDIR = ../cheapglk GLKLIBDIR = ../cheapglk GLKMAKEFILE = Make.cheapglk #GLKINCLUDEDIR = ../glkterm #GLKLIBDIR = ../glkterm #GLKMAKEFILE = Make.glkterm #GLKINCLUDEDIR = ../xglk #GLKLIBDIR = ../xglk #GLKMAKEFILE = Make.xglk #GLKINCLUDEDIR = ../remglk #GLKLIBDIR = ../remglk #GLKMAKEFILE = Make.remglk #GLKINCLUDEDIR = ../gtkglk/src #GLKLIBDIR = ../gtkglk #GLKMAKEFILE = ../Make.gtkglk # Pick a C compiler. #CC = cc CC = gcc OPTIONS = -g -Wall -Wmissing-prototypes -Wstrict-prototypes -Wno-unused -DOS_UNIX # Locate the libxml2 library. You only need these lines if you are using # the VM_DEBUGGER option. If so, uncomment these and set appropriately. #XMLLIB = -L/usr/local/lib -lxml2 #XMLLIBINCLUDEDIR = -I/usr/local/include/libxml2 include $(GLKINCLUDEDIR)/$(GLKMAKEFILE) CFLAGS = $(OPTIONS) -I$(GLKINCLUDEDIR) $(XMLLIBINCLUDEDIR) LIBS = -L$(GLKLIBDIR) $(GLKLIB) $(LINKLIBS) -lm $(XMLLIB) OBJS = main.o files.o vm.o exec.o funcs.o operand.o string.o glkop.o \ heap.o serial.o search.o accel.o float.o gestalt.o osdepend.o \ profile.o debugger.o all: glulxe glulxe: $(OBJS) unixstrt.o $(CC) $(OPTIONS) -o glulxe $(OBJS) unixstrt.o $(LIBS) glulxdump: glulxdump.o $(CC) -o glulxdump glulxdump.o $(OBJS) unixstrt.o: glulxe.h exec.o operand.o: opcodes.h gestalt.o: gestalt.h clean: rm -f *~ *.o glulxe glulxdump profile-raw glulxe/README.md000066400000000000000000000135661304145303500136460ustar00rootroot00000000000000# Glulxe: the Glulx VM interpreter - Version 0.5.4 - Designed by Andrew Plotkin - [Glulx home page][glulx] [glulx]: http://eblong.com/zarf/glulx/index.html [glk]: http://eblong.com/zarf/glk/index.html ## Compiling Since this is a Glk program, it must be built with a Glk library. See the [Glk home page][glk]. The Unix Makefile that comes with this package is designed to link any of the Unix libraries (CheapGlk, GlkTerm, RemGlk, etc.) You'll have to go into the Makefile and set three variables to find the library. There are instructions at the top of the Makefile. Then just type make glulxe That should suffice. When the program is built, type ./glulxe filename.ulx where "filename.ulx" is a Glulx game file to execute. To build this program with a Mac or Windows interface, or any other interface, you'll need the appropriate Glk library. This program supports floating-point operations, which are implemented using the standard C99 math functions. The Makefile uses "-lm" to link these in. If your platform does not support these functions, you can comment out the "#define FLOAT_SUPPORT" line in glulxe.h. If you define the VM_DEBUGGER symbol (uncomment the "#define VM_DEBUGGER" line in glulxe.h), you must include the libxml2 library. See the XMLLIB definition in the Makefile. ## Version 0.5.4 (Jan 23, 2017): - Added an internal debugger. Compile with "#define VM_DEBUGGER" (and a debug-supporting Glk library) to use it. - Expanded the TOLERATE_SUPERGLUS_BUG behavior to tolerate more Superglus game files. 0.5.3 (Oct 25, 2016): - Turn on SERIALIZE_CACHE_RAM in the default build. This speeds up save and save-undo operations. - Other tweaks to speed up launch, restart, restore, etc. - Fixed a bug where accelerated functions were not being autosaved. (Only relevant for iOS, currently.) - When profiling, restore/restore-undo operations will now fail (and the game will continue) instead of causing a fatal error. - Added a build option to tolerate the Superglus bug where (very old) game files would try to write to memory address zero. (Not on by default.) - Switched from my old ad-hoc license to the MIT license. 0.5.2 (Mar 27, 2014): - Added acceleration functions 8 through 13, which work correctly when NUM_ATTR_BYTES is changed. 0.5.1 (Mar 10, 2013): - Fixed a bug in glkop.c that prevented get_buffer_stream() from working right. - Updated profile-analyze.py to understand the upcoming, updated I6 debug file format. (See http://inform7.com/mantis/view.php?id=1073) The old format is still supported. 0.5.0 (Oct 18, 2012): - Turned on memory-range checking in the default build. (Should have done this years ago.) - Fixed a bug where @setmemsize could crash an open char memory stream or char line input request. - Updated glkop.c to handle arrays of Glk objects. (This is needed to support glk_schannel_play_multi().) - Added hooks for the library to execute at startup and select time. - Added a hook which allows the library to get and pass on a game ID string. - Clean up all memory allocation when the VM exits. 0.4.7 (Oct 10, 2011): - Abstracted powf() to an osdepend wrapper. (Needed for Windows.) - Fixed a @ceil bug, for some C math libraries. - Improved the profiling system in several ways. - Fixed a bug in glkop.c dispatching, to do with optional array arguments. 0.4.6 (Aug 17, 2010): - Added floating-point math feature. - Updated winstart.c. (Thanks David Kinder.) - Fixed @random even more, on Windows. - @verify works right on game files with extended memory. - @getiosys works right when the two store operands are different variable types. (E.g., one local and one global.) 0.4.5 (Nov 23, 2009): - VERIFY_MEMORY_ACCESS now detects writes to the ROM section of memory. - Fixed off-by-eight bug in @astorebit and @aloadbit with negative bit numbers. - Fixed an obscure bug with division and modulo of $80000000. (Thanks Evin Robertson.) - Fixed an extremely obscure problem with changing I/O mode in the middle of printing a number. - Glk array/string operations are now checked for memory overflows (though not for ROM writing). This generates a warning at present; in the future, it will be a fatal error. - Better fix for the @random bug. 0.4.4 (Mar 11, 2009): - Added profiling code, which is turned off by default. To compile it in, define VM_PROFILING in Makefile or in glulxe.h. - Added function-accleration feature. - Fixed bug where @random 0 was returning only positive numbers. 0.4.3 (Jan 23, 2008): - Verify the presence of Unicode calls in the Glk library at runtime. (Thanks Simon Baldwin.) - Added a compile-time option to check for invalid memory accesses. (This is slower, but safer. Define VERIFY_MEMORY_ACCESS in Makefile or in glulxe.h. Thanks Evin Robertson.) - Fixed a memory leak of undo states. (Thanks Matthew Wightman.) - Fixed a linked-list handling error for Glk unicode arrays. (Thanks David Kinder.) 0.4.2 (Feb 15, 2007): - Fixed a bug that preventing compiling with old (pre-Unicode) Glk libraries. 0.4.1 (Feb 11, 2007): - Added array copy and heap allocation functionality. (Glulx spec 3.1.0.) 0.4.0 (Aug 13, 2006): - Added Unicode functionality. (Glulx spec 3.0.0.) 0.3.5 (Aug 24, 2000): - Fixed El-Stupido bug in the modulo opcode. 0.3.4 (Jul 11, 2000): - Finally supports string arguments to Glk calls. 0.3.3 (Mar 29, 2000): - Added setiosys, getiosys opcodes. - Fixed bug in binarysearch. 0.3.2 (Feb 21, 2000): - Added search, jumpabs, callf, and gestalt opcodes. 0.3.1 (Aug 23, 1999): - Startup code now handles Blorb files correctly. 0.3.0 (Aug 17, 1999): - Added support for compressed strings. 0.2.2 (Jun 15, 1999): - Another pre-release version. 0.2.0 (May 30, 1999): - A pre-release version. ## Permissions The source code in this package is copyright 1999-2016 by Andrew Plotkin. It is distributed under the MIT license; see the "[LICENSE][]" file. [LICENSE]: ./LICENSE glulxe/accel.c000066400000000000000000000417611304145303500136000ustar00rootroot00000000000000/* accel.c: Glulxe code for accelerated functions Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" /* Git passes along function arguments in reverse order. To make our lives more interesting. */ #ifdef ARGS_REVERSED #define ARG(argv, argc, ix) (argv[(argc-1)-ix]) #else #define ARG(argv, argc, ix) (argv[ix]) #endif /* Any function can be called with any number of arguments. This macro lets us snarf a given argument, or zero if it wasn't supplied. */ #define ARG_IF_GIVEN(argv, argc, ix) ((argc > ix) ? (ARG(argv, argc, ix)) : 0) static void accel_error(char *msg); static glui32 func_1_z__region(glui32 argc, glui32 *argv); static glui32 func_2_cp__tab(glui32 argc, glui32 *argv); static glui32 func_3_ra__pr(glui32 argc, glui32 *argv); static glui32 func_4_rl__pr(glui32 argc, glui32 *argv); static glui32 func_5_oc__cl(glui32 argc, glui32 *argv); static glui32 func_6_rv__pr(glui32 argc, glui32 *argv); static glui32 func_7_op__pr(glui32 argc, glui32 *argv); static glui32 func_8_cp__tab(glui32 argc, glui32 *argv); static glui32 func_9_ra__pr(glui32 argc, glui32 *argv); static glui32 func_10_rl__pr(glui32 argc, glui32 *argv); static glui32 func_11_oc__cl(glui32 argc, glui32 *argv); static glui32 func_12_rv__pr(glui32 argc, glui32 *argv); static glui32 func_13_op__pr(glui32 argc, glui32 *argv); static int obj_in_class(glui32 obj); static glui32 get_prop(glui32 obj, glui32 id); static glui32 get_prop_new(glui32 obj, glui32 id); /* Parameters, set by @accelparam. */ static glui32 classes_table = 0; /* class object array */ static glui32 indiv_prop_start = 0; /* first individual prop ID */ static glui32 class_metaclass = 0; /* "Class" class object */ static glui32 object_metaclass = 0; /* "Object" class object */ static glui32 routine_metaclass = 0; /* "Routine" class object */ static glui32 string_metaclass = 0; /* "String" class object */ static glui32 self = 0; /* address of global "self" */ static glui32 num_attr_bytes = 0; /* number of attributes / 8 */ static glui32 cpv__start = 0; /* array of common prop defaults */ typedef struct accelentry_struct { glui32 addr; glui32 index; acceleration_func func; struct accelentry_struct *next; } accelentry_t; #define ACCEL_HASH_SIZE (511) static accelentry_t **accelentries = NULL; void init_accel() { accelentries = NULL; } acceleration_func accel_find_func(glui32 index) { switch (index) { case 0: return NULL; /* 0 always means no acceleration */ case 1: return func_1_z__region; case 2: return func_2_cp__tab; case 3: return func_3_ra__pr; case 4: return func_4_rl__pr; case 5: return func_5_oc__cl; case 6: return func_6_rv__pr; case 7: return func_7_op__pr; case 8: return func_8_cp__tab; case 9: return func_9_ra__pr; case 10: return func_10_rl__pr; case 11: return func_11_oc__cl; case 12: return func_12_rv__pr; case 13: return func_13_op__pr; } return NULL; } acceleration_func accel_get_func(glui32 addr) { int bucknum; accelentry_t *ptr; if (!accelentries) return NULL; bucknum = (addr % ACCEL_HASH_SIZE); for (ptr = accelentries[bucknum]; ptr; ptr = ptr->next) { if (ptr->addr == addr) return ptr->func; } return NULL; } /* Iterate the entire acceleration table, calling the callback for each (non-NULL) entry. This is used only for autosave. */ void accel_iterate_funcs(void (*func)(glui32 index, glui32 addr)) { int bucknum; accelentry_t *ptr; if (!accelentries) return; for (bucknum=0; bucknumnext) { if (ptr->func) { func(ptr->index, ptr->addr); } } } } void accel_set_func(glui32 index, glui32 addr) { int bucknum; accelentry_t *ptr; int functype; acceleration_func new_func = NULL; /* Check the Glulx type identifier byte. */ functype = Mem1(addr); if (functype != 0xC0 && functype != 0xC1) { fatal_error_i("Attempt to accelerate non-function.", addr); } if (!accelentries) { accelentries = (accelentry_t **)glulx_malloc(ACCEL_HASH_SIZE * sizeof(accelentry_t *)); if (!accelentries) fatal_error("Cannot malloc acceleration table."); for (bucknum=0; bucknumnext) { if (ptr->addr == addr) break; } if (!ptr) { if (!new_func) { return; /* no need for a new entry */ } ptr = (accelentry_t *)glulx_malloc(sizeof(accelentry_t)); if (!ptr) fatal_error("Cannot malloc acceleration entry."); ptr->addr = addr; ptr->index = 0; ptr->func = NULL; ptr->next = accelentries[bucknum]; accelentries[bucknum] = ptr; } ptr->index = index; ptr->func = new_func; } void accel_set_param(glui32 index, glui32 val) { switch (index) { case 0: classes_table = val; break; case 1: indiv_prop_start = val; break; case 2: class_metaclass = val; break; case 3: object_metaclass = val; break; case 4: routine_metaclass = val; break; case 5: string_metaclass = val; break; case 6: self = val; break; case 7: num_attr_bytes = val; break; case 8: cpv__start = val; break; } } /* This is used only for autosave. */ glui32 accel_get_param_count() { return 9; } /* This is used only for autosave. */ glui32 accel_get_param(glui32 index) { switch (index) { case 0: return classes_table; case 1: return indiv_prop_start; case 2: return class_metaclass; case 3: return object_metaclass; case 4: return routine_metaclass; case 5: return string_metaclass; case 6: return self; case 7: return num_attr_bytes; case 8: return cpv__start; default: return 0; } } static void accel_error(char *msg) { glk_put_char('\n'); glk_put_string(msg); glk_put_char('\n'); } static int obj_in_class(glui32 obj) { /* This checks whether obj is contained in Class, not whether it is a member of Class. */ return (Mem4(obj + 13 + num_attr_bytes) == class_metaclass); } /* Look up a property entry. */ static glui32 get_prop(glui32 obj, glui32 id) { glui32 cla = 0; glui32 prop; glui32 call_argv[2]; if (id & 0xFFFF0000) { cla = Mem4(classes_table+((id & 0xFFFF) * 4)); ARG(call_argv, 2, 0) = obj; ARG(call_argv, 2, 1) = cla; if (func_5_oc__cl(2, call_argv) == 0) return 0; id >>= 16; obj = cla; } ARG(call_argv, 2, 0) = obj; ARG(call_argv, 2, 1) = id; prop = func_2_cp__tab(2, call_argv); if (prop == 0) return 0; if (obj_in_class(obj) && (cla == 0)) { if ((id < indiv_prop_start) || (id >= indiv_prop_start+8)) return 0; } if (Mem4(self) != obj) { if (Mem1(prop + 9) & 1) return 0; } return prop; } /* Look up a property entry. This is part of the newer set of accel functions (8 through 13), which support increasing NUM_ATTR_BYTES. It is identical to get_prop() except that it calls the new versions of func_5 and func_2. */ static glui32 get_prop_new(glui32 obj, glui32 id) { glui32 cla = 0; glui32 prop; glui32 call_argv[2]; if (id & 0xFFFF0000) { cla = Mem4(classes_table+((id & 0xFFFF) * 4)); ARG(call_argv, 2, 0) = obj; ARG(call_argv, 2, 1) = cla; if (func_11_oc__cl(2, call_argv) == 0) return 0; id >>= 16; obj = cla; } ARG(call_argv, 2, 0) = obj; ARG(call_argv, 2, 1) = id; prop = func_8_cp__tab(2, call_argv); if (prop == 0) return 0; if (obj_in_class(obj) && (cla == 0)) { if ((id < indiv_prop_start) || (id >= indiv_prop_start+8)) return 0; } if (Mem4(self) != obj) { if (Mem1(prop + 9) & 1) return 0; } return prop; } static glui32 func_1_z__region(glui32 argc, glui32 *argv) { glui32 addr; glui32 tb; if (argc < 1) return 0; addr = ARG(argv, argc, 0); if (addr < 36) return 0; if (addr >= endmem) return 0; tb = Mem1(addr); if (tb >= 0xE0) { return 3; } if (tb >= 0xC0) { return 2; } if (tb >= 0x70 && tb <= 0x7F && addr >= ramstart) { return 1; } return 0; } /* The old set of accel functions (2 through 7) are deprecated; they behave badly if the Inform 6 NUM_ATTR_BYTES option (parameter 7) is changed from its default value (7). They will not be removed, but new games should use functions 8 through 13 instead. */ static glui32 func_2_cp__tab(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 otab, max; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); if (func_1_z__region(1, &obj) != 1) { accel_error("[** Programming error: tried to find the \".\" of (something) **]"); return 0; } otab = Mem4(obj + 16); if (!otab) return 0; max = Mem4(otab); otab += 4; /* @binarysearch id 2 otab 10 max 0 0 res; */ return binary_search(id, 2, otab, 10, max, 0, 0); } static glui32 func_3_ra__pr(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 prop; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); prop = get_prop(obj, id); if (prop == 0) return 0; return Mem4(prop + 4); } static glui32 func_4_rl__pr(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 prop; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); prop = get_prop(obj, id); if (prop == 0) return 0; return 4 * Mem2(prop + 2); } static glui32 func_5_oc__cl(glui32 argc, glui32 *argv) { glui32 obj; glui32 cla; glui32 zr, prop, inlist, inlistlen, jx; obj = ARG_IF_GIVEN(argv, argc, 0); cla = ARG_IF_GIVEN(argv, argc, 1); zr = func_1_z__region(1, &obj); if (zr == 3) return (cla == string_metaclass) ? 1 : 0; if (zr == 2) return (cla == routine_metaclass) ? 1 : 0; if (zr != 1) return 0; if (cla == class_metaclass) { if (obj_in_class(obj)) return 1; if (obj == class_metaclass) return 1; if (obj == string_metaclass) return 1; if (obj == routine_metaclass) return 1; if (obj == object_metaclass) return 1; return 0; } if (cla == object_metaclass) { if (obj_in_class(obj)) return 0; if (obj == class_metaclass) return 0; if (obj == string_metaclass) return 0; if (obj == routine_metaclass) return 0; if (obj == object_metaclass) return 0; return 1; } if ((cla == string_metaclass) || (cla == routine_metaclass)) return 0; if (!obj_in_class(cla)) { accel_error("[** Programming error: tried to apply 'ofclass' with non-class **]"); return 0; } prop = get_prop(obj, 2); if (prop == 0) return 0; inlist = Mem4(prop + 4); if (inlist == 0) return 0; inlistlen = Mem2(prop + 2); for (jx = 0; jx < inlistlen; jx++) { if (Mem4(inlist + (4 * jx)) == cla) return 1; } return 0; } static glui32 func_6_rv__pr(glui32 argc, glui32 *argv) { glui32 id; glui32 addr; id = ARG_IF_GIVEN(argv, argc, 1); addr = func_3_ra__pr(argc, argv); if (addr == 0) { if ((id > 0) && (id < indiv_prop_start)) return Mem4(cpv__start + (4 * id)); accel_error("[** Programming error: tried to read (something) **]"); return 0; } return Mem4(addr); } static glui32 func_7_op__pr(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 zr; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); zr = func_1_z__region(1, &obj); if (zr == 3) { /* print is INDIV_PROP_START+6 */ if (id == indiv_prop_start+6) return 1; /* print_to_array is INDIV_PROP_START+7 */ if (id == indiv_prop_start+7) return 1; return 0; } if (zr == 2) { /* call is INDIV_PROP_START+5 */ return ((id == indiv_prop_start+5) ? 1 : 0); } if (zr != 1) return 0; if ((id >= indiv_prop_start) && (id < indiv_prop_start+8)) { if (obj_in_class(obj)) return 1; } return ((func_3_ra__pr(argc, argv)) ? 1 : 0); } /* Here are the newer functions, which support changing NUM_ATTR_BYTES. These call get_prop_new() instead of get_prop(). */ static glui32 func_8_cp__tab(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 otab, max; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); if (func_1_z__region(1, &obj) != 1) { accel_error("[** Programming error: tried to find the \".\" of (something) **]"); return 0; } otab = Mem4(obj + 4*(3+(int)(num_attr_bytes/4))); if (!otab) return 0; max = Mem4(otab); otab += 4; /* @binarysearch id 2 otab 10 max 0 0 res; */ return binary_search(id, 2, otab, 10, max, 0, 0); } static glui32 func_9_ra__pr(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 prop; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); prop = get_prop_new(obj, id); if (prop == 0) return 0; return Mem4(prop + 4); } static glui32 func_10_rl__pr(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 prop; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); prop = get_prop_new(obj, id); if (prop == 0) return 0; return 4 * Mem2(prop + 2); } static glui32 func_11_oc__cl(glui32 argc, glui32 *argv) { glui32 obj; glui32 cla; glui32 zr, prop, inlist, inlistlen, jx; obj = ARG_IF_GIVEN(argv, argc, 0); cla = ARG_IF_GIVEN(argv, argc, 1); zr = func_1_z__region(1, &obj); if (zr == 3) return (cla == string_metaclass) ? 1 : 0; if (zr == 2) return (cla == routine_metaclass) ? 1 : 0; if (zr != 1) return 0; if (cla == class_metaclass) { if (obj_in_class(obj)) return 1; if (obj == class_metaclass) return 1; if (obj == string_metaclass) return 1; if (obj == routine_metaclass) return 1; if (obj == object_metaclass) return 1; return 0; } if (cla == object_metaclass) { if (obj_in_class(obj)) return 0; if (obj == class_metaclass) return 0; if (obj == string_metaclass) return 0; if (obj == routine_metaclass) return 0; if (obj == object_metaclass) return 0; return 1; } if ((cla == string_metaclass) || (cla == routine_metaclass)) return 0; if (!obj_in_class(cla)) { accel_error("[** Programming error: tried to apply 'ofclass' with non-class **]"); return 0; } prop = get_prop_new(obj, 2); if (prop == 0) return 0; inlist = Mem4(prop + 4); if (inlist == 0) return 0; inlistlen = Mem2(prop + 2); for (jx = 0; jx < inlistlen; jx++) { if (Mem4(inlist + (4 * jx)) == cla) return 1; } return 0; } static glui32 func_12_rv__pr(glui32 argc, glui32 *argv) { glui32 id; glui32 addr; id = ARG_IF_GIVEN(argv, argc, 1); addr = func_9_ra__pr(argc, argv); if (addr == 0) { if ((id > 0) && (id < indiv_prop_start)) return Mem4(cpv__start + (4 * id)); accel_error("[** Programming error: tried to read (something) **]"); return 0; } return Mem4(addr); } static glui32 func_13_op__pr(glui32 argc, glui32 *argv) { glui32 obj; glui32 id; glui32 zr; obj = ARG_IF_GIVEN(argv, argc, 0); id = ARG_IF_GIVEN(argv, argc, 1); zr = func_1_z__region(1, &obj); if (zr == 3) { /* print is INDIV_PROP_START+6 */ if (id == indiv_prop_start+6) return 1; /* print_to_array is INDIV_PROP_START+7 */ if (id == indiv_prop_start+7) return 1; return 0; } if (zr == 2) { /* call is INDIV_PROP_START+5 */ return ((id == indiv_prop_start+5) ? 1 : 0); } if (zr != 1) return 0; if ((id >= indiv_prop_start) && (id < indiv_prop_start+8)) { if (obj_in_class(obj)) return 1; } return ((func_9_ra__pr(argc, argv)) ? 1 : 0); } glulxe/debugger.c000066400000000000000000001563231304145303500143160ustar00rootroot00000000000000/* debugger.c: Glulxe debugger functions. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ /* This module is a low-level debugger for Inform 6 (and 7) games. This code is only compiled in if the "#define VM_DEBUGGER" line in glulxe.h is uncommented. You will then have to compile with a Glk library that supports the debug feature. (See gi_debug.h as distributed with CheapGlk.) You will also need to link the libxml2 library. When debugging is compiled in, glulxe looks for a "Dbug" chunk in the Blorb file. This should contain the "gameinfo.dbg" file generated by the I6 compiler. (Must be the new XML-based debug format.) It also offers a bunch of new command-line options: --gameinfo filename: Read the "gameinfo.dbg" from an external file. (If you're not using Blorb or have not pulled the debug info into it.) --cpu: Display time and VM CPU cycles used for each input. --starttrap: Enter debug mode as soon as the game starts up. (Before any code has executed.) --quittrap: Enter debug mode when the game quits. --crashtrap: Enter debug mode if any fatal error occurs (including Glk library fatal errors). You do not need "gameinfo.dbg" data to use the debugger. However, that file contains the I6-level names of all functions and variables. So without it, you'll see a lot of "???" in the debug output. The debug model assumes that you have a "debug console" available, which can accept debug commands and display responses. (The implementation of this depends on the Glk library.) You can enter commands whenever the game is awaiting input, or if it pauses because of the --starttrap flag, the --crashtrap flag, a breakpoint, or a @debugtrap opcode. The debugger is currently pretty minimal. You can display the stack (including local variables), display global variables, and set breakpoints (only at the beginning of a function). Type "help" into the debug console for the command list. (If you've compiled with CheapGlk, then the "debug console" is just standard output. Enter a line beginning with "/" to send a debug command -- "/help", etc.) (This "/" convention is *only* used in CheapGlk. Other libraries will have debug interfaces that don't suck.) */ #include "glk.h" #include "glulxe.h" #if VM_DEBUGGER #include #include #include "gi_debug.h" #include /* Data structures to store the debug info in memory. */ typedef enum grouptype_enum { grp_None = 0, grp_Constant = 1, grp_Attribute = 2, grp_Property = 3, grp_Routine = 4, grp_Global = 5, grp_Object = 6, grp_Array = 7, } grouptype; /* Used for constants, globals, locals, objects -- the meaning of the value field varies. */ typedef struct infoconstant_struct { const xmlChar *identifier; int32_t value; } infoconstant; typedef struct inforoutine_struct { const xmlChar *identifier; int32_t address; int32_t length; /* Address of the next higher function. May be beyond length if there are gaps. */ int32_t nextaddress; /* The locals is a block of infoconstants where the value is frame-offset. We adopt Inform's assumption that locals are always 4 bytes long. */ int32_t numlocals; infoconstant *locals; } inforoutine; typedef struct infoobject_struct { const xmlChar *identifier; int32_t address; /* Address of the next higher function. May be beyond length if there are gaps. */ int32_t nextaddress; } infoobject; typedef struct infoarray_struct { const xmlChar *identifier; int32_t address; int32_t bytelength; int32_t bytesize; /* per element */ int32_t count; int lengthfield; /* true if the zeroth element is the length */ /* Address of the next higher function. May be beyond length if there are gaps. */ int32_t nextaddress; } infoarray; typedef struct breakpoint_struct { inforoutine *func; int32_t address; struct breakpoint_struct *next; /* linked list of breakpoints */ } breakpoint; typedef struct debuginfofile_struct { strid_t str; int32_t strread; int32_t strreadmax; int failed; grouptype curgrouptype; int tempcounter; infoconstant *tempconstant; inforoutine *temproutine; infoarray *temparray; infoobject *tempobject; int tempnumlocals; int templocalssize; infoconstant *templocals; const xmlChar *storyfileprefix; xmlHashTablePtr constants; xmlHashTablePtr attributes; xmlHashTablePtr properties; xmlHashTablePtr globals; xmlHashTablePtr objects; int numobjects; infoobject **objectlist; /* array, ordered by address */ xmlHashTablePtr arrays; int numarrays; infoarray **arraylist; /* array, ordered by address */ xmlHashTablePtr routines; int numroutines; inforoutine **routinelist; /* array, ordered by address */ } debuginfofile; /* This global holds the loaded debug info, if we have any. */ static debuginfofile *debuginfo = NULL; /* Lists of breakpoints. */ static breakpoint *funcbreakpoints = NULL; /* linked list */ /* Internal functions used while loading the debug info. */ static int xmlreadstreamfunc(void *rock, char *buffer, int len); static int xmlreadchunkfunc(void *rock, char *buffer, int len); static int xmlclosefunc(void *rock); static void xmlhandlenode(xmlTextReaderPtr reader, debuginfofile *context); static int finalize_debuginfo(debuginfofile *context); static debuginfofile *create_debuginfofile() { debuginfofile *context = (debuginfofile *)malloc(sizeof(debuginfofile)); context->str = NULL; context->failed = 0; context->tempconstant = NULL; context->temparray = NULL; context->tempobject = NULL; context->temproutine = NULL; context->tempnumlocals = 0; context->templocals = NULL; context->templocalssize = 0; context->curgrouptype = grp_None; context->constants = xmlHashCreate(16); context->attributes = xmlHashCreate(16); context->properties = xmlHashCreate(16); context->globals = xmlHashCreate(16); context->objects = xmlHashCreate(16); context->numobjects = 0; context->objectlist = NULL; context->arrays = xmlHashCreate(16); context->numarrays = 0; context->arraylist = NULL; context->routines = xmlHashCreate(16); context->numroutines = 0; context->routinelist = NULL; context->storyfileprefix = NULL; return context; } static void free_debuginfofile(debuginfofile *context) { if (!context) return; context->str = NULL; context->tempconstant = NULL; context->temparray = NULL; context->tempobject = NULL; context->temproutine = NULL; /* We don't bother to free the member structures, because this only happens once at startup and then only on error conditions. */ if (context->constants) { xmlHashFree(context->constants, NULL); context->constants = NULL; } if (context->attributes) { xmlHashFree(context->attributes, NULL); context->attributes = NULL; } if (context->properties) { xmlHashFree(context->properties, NULL); context->properties = NULL; } if (context->globals) { xmlHashFree(context->globals, NULL); context->globals = NULL; } if (context->objects) { xmlHashFree(context->objects, NULL); context->objects = NULL; } if (context->objectlist) { free(context->objectlist); context->objectlist = NULL; } if (context->arrays) { xmlHashFree(context->arrays, NULL); context->arrays = NULL; } if (context->arraylist) { free(context->arraylist); context->arraylist = NULL; } if (context->routines) { xmlHashFree(context->routines, NULL); context->routines = NULL; } if (context->routinelist) { free(context->routinelist); context->routinelist = NULL; } free(context); } static infoconstant *create_infoconstant() { infoconstant *cons = (infoconstant *)malloc(sizeof(infoconstant)); cons->identifier = NULL; cons->value = 0; return cons; } static infoobject *create_infoobject() { infoobject *object = (infoobject *)malloc(sizeof(infoobject)); object->identifier = NULL; object->address = 0; object->nextaddress = 0; return object; } static infoarray *create_infoarray() { infoarray *arr = (infoarray *)malloc(sizeof(infoarray)); arr->identifier = NULL; arr->address = 0; arr->bytelength = 0; arr->bytesize = 1; arr->count = 0; arr->lengthfield = 0; arr->nextaddress = 0; return arr; } static inforoutine *create_inforoutine() { inforoutine *func = (inforoutine *)malloc(sizeof(inforoutine)); func->identifier = NULL; func->address = 0; func->length = 0; func->nextaddress = 0; func->numlocals = 0; func->locals = NULL; return func; } static breakpoint *create_breakpoint(glui32 addr) { breakpoint *bp = (breakpoint *)malloc(sizeof(breakpoint)); bp->func = NULL; /* useful? */ bp->address = addr; bp->next = NULL; return bp; } static void add_object_to_table(void *obj, void *rock, xmlChar *name) { debuginfofile *context = rock; infoobject *object = obj; if (context->tempcounter >= context->numobjects) { printf("### object overflow!\n"); /*###*/ return; } context->objectlist[context->tempcounter++] = object; } static int sort_objects_table(const void *obj1, const void *obj2) { infoobject **object1 = (infoobject **)obj1; infoobject **object2 = (infoobject **)obj2; return ((*object1)->address - (*object2)->address); } static int find_object_in_table(const void *keyptr, const void *obj) { /* Binary-search callback. We rely on address and nextaddress so that there are no gaps. */ glui32 addr = *(glui32 *)(keyptr); infoobject **object = (infoobject **)obj; if (addr < (*object)->address) return -1; if (addr >= (*object)->nextaddress) return 1; return 0; } static void add_array_to_table(void *obj, void *rock, xmlChar *name) { debuginfofile *context = rock; infoarray *array = obj; if (context->tempcounter >= context->numarrays) { printf("### array overflow!\n"); /*###*/ return; } context->arraylist[context->tempcounter++] = array; } static int sort_arrays_table(const void *obj1, const void *obj2) { infoarray **array1 = (infoarray **)obj1; infoarray **array2 = (infoarray **)obj2; return ((*array1)->address - (*array2)->address); } static int find_array_in_table(const void *keyptr, const void *obj) { /* Binary-search callback. We rely on address and nextaddress so that there are no gaps. */ glui32 addr = *(glui32 *)(keyptr); infoarray **array = (infoarray **)obj; if (addr < (*array)->address) return -1; if (addr >= (*array)->nextaddress) return 1; return 0; } static void add_routine_to_table(void *obj, void *rock, xmlChar *name) { debuginfofile *context = rock; inforoutine *routine = obj; if (context->tempcounter >= context->numroutines) { printf("### array overflow!\n"); /*###*/ return; } context->routinelist[context->tempcounter++] = routine; } static int sort_routines_table(const void *obj1, const void *obj2) { inforoutine **routine1 = (inforoutine **)obj1; inforoutine **routine2 = (inforoutine **)obj2; return ((*routine1)->address - (*routine2)->address); } static int find_routine_in_table(const void *keyptr, const void *obj) { /* Binary-search callback. We rely on address and nextaddress so that there are no gaps. */ glui32 addr = *(glui32 *)(keyptr); inforoutine **routine = (inforoutine **)obj; if (addr < (*routine)->address) return -1; if (addr >= (*routine)->nextaddress) return 1; return 0; } /* Top-level function for loading debug info from a Glk stream. The debug data must take up the entire file; this will read until EOF. If successful, fills out the debuginfo global and returns true. If not, reports an error and returns false. (The stream will not be closed.) */ int debugger_load_info_stream(strid_t stream) { debuginfofile *context = create_debuginfofile(); context->str = stream; context->strread = 0; /* not used */ context->strreadmax = 0; /* not used */ xmlTextReaderPtr reader; reader = xmlReaderForIO(xmlreadstreamfunc, xmlclosefunc, context, NULL, NULL, XML_PARSE_RECOVER|XML_PARSE_NOENT|XML_PARSE_NONET|XML_PARSE_NOCDATA|XML_PARSE_COMPACT); if (!reader) { printf("Error: Unable to create XML reader.\n"); /*###*/ free_debuginfofile(context); return 0; } while (1) { int res = xmlTextReaderRead(reader); if (res < 0) { context->failed = 1; break; /* error */ } if (res == 0) { break; /* EOF */ } xmlhandlenode(reader, context); } xmlFreeTextReader(reader); context->str = NULL; /* the reader didn't close it, but we're done with it. */ if (context->failed) { printf("Error: Unable to load debug info.\n"); /*###*/ free_debuginfofile(context); return 0; } /* Now that all the data is loaded in, we go through and create some indexes that will be handy. */ return finalize_debuginfo(context); } /* Top-level function for loading debug info from a segment of a Glk stream. This starts at position pos in the file and reads len bytes. If successful, fills out the debuginfo global and returns true. If not, reports an error and returns false. (The stream will not be closed.) */ int debugger_load_info_chunk(strid_t stream, glui32 pos, glui32 len) { debuginfofile *context = create_debuginfofile(); context->str = stream; context->strread = 0; context->strreadmax = len; glk_stream_set_position(stream, pos, seekmode_Start); xmlTextReaderPtr reader; reader = xmlReaderForIO(xmlreadchunkfunc, xmlclosefunc, context, NULL, NULL, XML_PARSE_RECOVER|XML_PARSE_NOENT|XML_PARSE_NONET|XML_PARSE_NOCDATA|XML_PARSE_COMPACT); if (!reader) { printf("Error: Unable to create XML reader.\n"); /*###*/ free_debuginfofile(context); return 0; } while (1) { int res = xmlTextReaderRead(reader); if (res < 0) { context->failed = 1; break; /* error */ } if (res == 0) { break; /* EOF */ } xmlhandlenode(reader, context); } xmlFreeTextReader(reader); context->str = NULL; /* the reader didn't close it, but we're done with it. */ if (context->failed) { printf("Error: Unable to load debug info.\n"); /*###*/ free_debuginfofile(context); return 0; } /* Now that all the data is loaded in, we go through and create some indexes that will be handy. */ return finalize_debuginfo(context); } /* xmlReader callback to read from a stream (until EOF). */ static int xmlreadstreamfunc(void *rock, char *buffer, int len) { debuginfofile *context = rock; int res = glk_get_buffer_stream(context->str, buffer, len); if (res < 0) return -1; return res; } /* xmlReader callback to read from a stream (until a given position). */ static int xmlreadchunkfunc(void *rock, char *buffer, int len) { debuginfofile *context = rock; if (context->strread >= context->strreadmax) return 0; if (len > context->strreadmax - context->strread) len = context->strreadmax - context->strread; int res = glk_get_buffer_stream(context->str, buffer, len); if (res < 0) return -1; context->strread += res; return res; } /* xmlReader callback to stop reading a stream. We don't actually close the stream here, just discard the reference. */ static int xmlclosefunc(void *rock) { debuginfofile *context = rock; context->str = NULL; return 0; } /* xmlReader callback: an XML node has arrived. (Might be the beginning of a tag, the end of a tag, or a block of text.) All the work of parsing the debug format happens here, which is why this function is big and ugly. */ static void xmlhandlenode(xmlTextReaderPtr reader, debuginfofile *context) { int depth = xmlTextReaderDepth(reader); int nodetype = xmlTextReaderNodeType(reader); if (depth == 0) { if (nodetype == XML_ELEMENT_NODE) { const xmlChar *name = xmlTextReaderConstName(reader); if (xmlStrcmp(name, BAD_CAST "inform-story-file")) { printf("Error: This is not an Inform debug info file.\n"); /*###*/ context->failed = 1; } } else if (nodetype == XML_ELEMENT_DECL) { /* End of document */ context->curgrouptype = grp_None; context->tempconstant = NULL; context->temparray = NULL; context->tempobject = NULL; context->temproutine = NULL; } } else if (depth == 1) { if (nodetype == XML_ELEMENT_NODE) { const xmlChar *name = xmlTextReaderConstName(reader); if (!xmlStrcmp(name, BAD_CAST "constant")) { context->curgrouptype = grp_Constant; context->tempconstant = create_infoconstant(); } else if (!xmlStrcmp(name, BAD_CAST "attribute")) { context->curgrouptype = grp_Attribute; context->tempconstant = create_infoconstant(); } else if (!xmlStrcmp(name, BAD_CAST "property")) { context->curgrouptype = grp_Property; context->tempconstant = create_infoconstant(); } else if (!xmlStrcmp(name, BAD_CAST "routine")) { context->curgrouptype = grp_Routine; context->temproutine = create_inforoutine(); context->tempnumlocals = 0; } else if (!xmlStrcmp(name, BAD_CAST "global-variable")) { context->curgrouptype = grp_Global; context->tempconstant = create_infoconstant(); } else if (!xmlStrcmp(name, BAD_CAST "object")) { context->curgrouptype = grp_Object; context->tempobject = create_infoobject(); } else if (!xmlStrcmp(name, BAD_CAST "array")) { context->curgrouptype = grp_Array; context->temparray = create_infoarray(); } else if (!xmlStrcmp(name, BAD_CAST "story-file-prefix")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod && nod->children && nod->children->type == XML_TEXT_NODE) { context->storyfileprefix = xmlStrdup(nod->children->content); } } else { context->curgrouptype = grp_None; } } else if (nodetype == XML_ELEMENT_DECL) { /* End of group */ switch (context->curgrouptype) { case grp_Constant: if (context->tempconstant) { infoconstant *dat = context->tempconstant; context->tempconstant = NULL; xmlHashAddEntry(context->constants, dat->identifier, dat); } break; case grp_Attribute: if (context->tempconstant) { infoconstant *dat = context->tempconstant; context->tempconstant = NULL; xmlHashAddEntry(context->attributes, dat->identifier, dat); } break; case grp_Property: if (context->tempconstant) { infoconstant *dat = context->tempconstant; context->tempconstant = NULL; xmlHashAddEntry(context->properties, dat->identifier, dat); } break; case grp_Global: if (context->tempconstant) { infoconstant *dat = context->tempconstant; context->tempconstant = NULL; xmlHashAddEntry(context->globals, dat->identifier, dat); } break; case grp_Object: if (context->tempobject) { infoobject *dat = context->tempobject; context->tempobject = NULL; xmlHashAddEntry(context->objects, dat->identifier, dat); } break; case grp_Array: if (context->temparray) { infoarray *dat = context->temparray; context->temparray = NULL; dat->count = dat->bytelength / dat->bytesize; xmlHashAddEntry(context->arrays, dat->identifier, dat); } break; case grp_Routine: if (context->temproutine) { inforoutine *dat = context->temproutine; context->temproutine = NULL; /* Copy the list of locals into the inforoutine structure. This loop totally assumes that locals are found in order in the debug file! */ if (context->tempnumlocals && context->templocals) { int ix; dat->numlocals = context->tempnumlocals; dat->locals = (infoconstant *)malloc(context->tempnumlocals * sizeof(infoconstant)); for (ix=0; ixtempnumlocals; ix++) { dat->locals[ix].identifier = context->templocals[ix].identifier; dat->locals[ix].value = context->templocals[ix].value; context->templocals[ix].identifier = NULL; context->templocals[ix].value = 0; } } context->tempnumlocals = 0; /* An address of 0 is impossible. Inform uses this to indicate a routine that was eliminated because it was never called. */ if (dat->address != 0) { xmlHashAddEntry(context->routines, dat->identifier, dat); } } break; default: break; } context->curgrouptype = grp_None; } } else { if (nodetype == XML_ELEMENT_NODE) { const xmlChar *name = xmlTextReaderConstName(reader); /* These fields are always simple text nodes. */ if (!xmlStrcmp(name, BAD_CAST "identifier")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod && nod->children && nod->children->type == XML_TEXT_NODE) { xmlChar *text = nod->children->content; if (context->curgrouptype == grp_Constant || context->curgrouptype == grp_Attribute || context->curgrouptype == grp_Property || context->curgrouptype == grp_Global) { if (context->tempconstant) { if (depth == 2) context->tempconstant->identifier = xmlStrdup(text); } } else if (context->curgrouptype == grp_Object) { if (context->tempobject) { if (depth == 2) context->tempobject->identifier = xmlStrdup(text); } } else if (context->curgrouptype == grp_Array) { if (context->temparray) { if (depth == 2) context->temparray->identifier = xmlStrdup(text); } } else if (context->curgrouptype == grp_Routine) { if (context->temproutine) { if (depth == 2) context->temproutine->identifier = xmlStrdup(text); } } } } else if (!xmlStrcmp(name, BAD_CAST "value")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod && nod->children && nod->children->type == XML_TEXT_NODE) { if (context->curgrouptype == grp_Constant || context->curgrouptype == grp_Attribute || context->curgrouptype == grp_Property) { if (context->tempconstant) { if (depth == 2) context->tempconstant->value = strtol((char *)nod->children->content, NULL, 10); } } else if (context->curgrouptype == grp_Object) { if (context->tempobject) { if (depth == 2) context->tempobject->address = strtol((char *)nod->children->content, NULL, 10); } } else if (context->curgrouptype == grp_Array) { if (context->temparray) { if (depth == 2) context->temparray->address = strtol((char *)nod->children->content, NULL, 10); } } else if (context->curgrouptype == grp_Routine) { if (context->temproutine) { if (depth == 2) context->temproutine->address = strtol((char *)nod->children->content, NULL, 10); } } } } else if (!xmlStrcmp(name, BAD_CAST "address")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod && nod->children && nod->children->type == XML_TEXT_NODE) { if (context->curgrouptype == grp_Global) { if (context->tempconstant) { if (depth == 2) context->tempconstant->value = strtol((char *)nod->children->content, NULL, 10); } } } } else if (!xmlStrcmp(name, BAD_CAST "byte-count")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod && nod->children && nod->children->type == XML_TEXT_NODE) { if (context->curgrouptype == grp_Routine) { if (context->temproutine) { if (depth == 2) context->temproutine->length = strtol((char *)nod->children->content, NULL, 10); } } else if (context->curgrouptype == grp_Array) { if (context->temparray) { if (depth == 2) context->temparray->bytelength = strtol((char *)nod->children->content, NULL, 10); } } } } else if (!xmlStrcmp(name, BAD_CAST "bytes-per-element")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod && nod->children && nod->children->type == XML_TEXT_NODE) { if (context->curgrouptype == grp_Array) { if (context->temparray) { if (depth == 2) context->temparray->bytesize = strtol((char *)nod->children->content, NULL, 10); } } } } else if (!xmlStrcmp(name, BAD_CAST "zeroth-element-holds-length")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod && nod->children && nod->children->type == XML_TEXT_NODE) { if (context->curgrouptype == grp_Array) { if (context->temparray) { if (depth == 2) if (!xmlStrcmp(nod->children->content, BAD_CAST "true")) context->temparray->lengthfield = 1; } } } } else if (!xmlStrcmp(name, BAD_CAST "local-variable")) { xmlNodePtr nod = xmlTextReaderExpand(reader); if (nod) { if (!context->templocals) { context->templocalssize = 8; context->templocals = (infoconstant *)malloc(context->templocalssize * sizeof(infoconstant)); } if (context->tempnumlocals >= context->templocalssize) { context->templocalssize = 2*context->tempnumlocals + 4; context->templocals = (infoconstant *)realloc(context->templocals, context->templocalssize * sizeof(infoconstant)); } infoconstant *templocal = &(context->templocals[context->tempnumlocals]); context->tempnumlocals += 1; for (nod = nod->children; nod; nod=nod->next) { if (nod->type == XML_ELEMENT_NODE) { if (!xmlStrcmp(nod->name, BAD_CAST "identifier") && nod->children && nod->children->type == XML_TEXT_NODE) { templocal->identifier = xmlStrdup(nod->children->content); } if (!xmlStrcmp(nod->name, BAD_CAST "frame-offset") && nod->children && nod->children->type == XML_TEXT_NODE) { templocal->value = strtol((char *)nod->children->content, NULL, 10); } } } } } } } } /* This is called after the XML data is parsed. We wrap up what we've found and store it in the debuginfo global. Returns 1 on success. */ static int finalize_debuginfo(debuginfofile *context) { int ix; context->numarrays = xmlHashSize(context->arrays); context->arraylist = (infoarray **)malloc(context->numarrays * sizeof(infoarray *)); context->tempcounter = 0; xmlHashScan(context->arrays, add_array_to_table, context); if (context->tempcounter != context->numarrays) printf("### array underflow!\n"); /*###*/ qsort(context->arraylist, context->numarrays, sizeof(infoarray *), sort_arrays_table); for (ix=0; ixnumarrays; ix++) { if (ix+1 < context->numarrays) { context->arraylist[ix]->nextaddress = context->arraylist[ix+1]->address; } else { context->arraylist[ix]->nextaddress = context->arraylist[ix]->address + context->arraylist[ix]->bytelength; } } context->numobjects = xmlHashSize(context->objects); context->objectlist = (infoobject **)malloc(context->numobjects * sizeof(infoobject *)); context->tempcounter = 0; xmlHashScan(context->objects, add_object_to_table, context); if (context->tempcounter != context->numobjects) printf("### object underflow!\n"); /*###*/ qsort(context->objectlist, context->numobjects, sizeof(infoobject *), sort_objects_table); for (ix=0; ixnumobjects; ix++) { if (ix+1 < context->numobjects) { context->objectlist[ix]->nextaddress = context->objectlist[ix+1]->address; } else { context->objectlist[ix]->nextaddress = context->objectlist[ix]->address + 1; } } context->numroutines = xmlHashSize(context->routines); context->routinelist = (inforoutine **)malloc(context->numroutines * sizeof(inforoutine *)); context->tempcounter = 0; xmlHashScan(context->routines, add_routine_to_table, context); if (context->tempcounter != context->numroutines) printf("### array underflow!\n"); /*###*/ qsort(context->routinelist, context->numroutines, sizeof(inforoutine *), sort_routines_table); for (ix=0; ixnumroutines; ix++) { if (ix+1 < context->numroutines) { context->routinelist[ix]->nextaddress = context->routinelist[ix+1]->address; } else { context->routinelist[ix]->nextaddress = context->routinelist[ix]->address + context->routinelist[ix]->length; } } if (context->templocals) { free(context->templocals); context->templocals = NULL; } context->templocalssize = 0; context->tempnumlocals = 0; /* Install into global. */ debuginfo = context; return 1; } /* Compare main memory to the story-file-prefix we found. If it doesn't match, display a warning. */ void debugger_check_story_file() { if (!debuginfo || !debuginfo->storyfileprefix) return; /* Check that this looks like an Inform 6 game file. See the Glulx Inform Technical Reference. */ if (Mem4(0x24) != 0x496E666F) { /* 'Info' */ gidebug_output("Warning: This game file does not look like it was generated by Inform."); } /* Decode the storyfileprefix, which is in base64. */ const unsigned char *cx; int pos = 0; int count = 0; uint32_t word = 0; int fail = FALSE; for (cx = debuginfo->storyfileprefix; *cx && *cx != '='; cx++) { unsigned int sixbit = 0; if (*cx >= 'A' && *cx <= 'Z') sixbit = (*cx) - 'A'; else if (*cx >= 'a' && *cx <= 'z') sixbit = ((*cx) - 'a') + 26; else if (*cx >= '0' && *cx <= '9') sixbit = ((*cx) - '0') + 52; else if (*cx == '+') sixbit = 62; else if (*cx == '/') sixbit = 63; else sixbit = 0; switch (count) { case 0: word = (sixbit << 18); break; case 1: word |= (sixbit << 12); break; case 2: word |= (sixbit << 6); break; case 3: word |= (sixbit); break; } count++; if (count == 4) { unsigned char byte; byte = (word >> 16) & 0xFF; if (byte != Mem1(pos)) fail = TRUE; byte = (word >> 8) & 0xFF; if (byte != Mem1(pos+1)) fail = TRUE; byte = (word) & 0xFF; if (byte != Mem1(pos+2)) fail = TRUE; pos += 3; count = 0; } } if (fail) gidebug_output("Warning: debug info does not match this game file."); } /* The rest of this file is the debugger itself. */ static char *linebuf = NULL; static int linebufsize = 0; /* Expand the output line buffer to a given length, if necessary. If you want to write an N-character string, call ensure_line_buf(N). Then use snprintf() just to be safe. */ static void ensure_line_buf(int len) { len += 1; /* for the closing null */ if (linebuf && len <= linebufsize) return; linebufsize = linebufsize*2+16; if (linebufsize < len) linebufsize = len+16; if (!linebuf) linebuf = malloc(linebufsize * sizeof(char)); else linebuf = realloc(linebuf, linebufsize * sizeof(char)); } static int track_cpu = FALSE; static int start_trap = FALSE; static int crash_trap = FALSE; static int quit_trap = FALSE; static int debugger_invoked = FALSE; /* true if cycle handler called */ unsigned long debugger_opcount = 0; /* incremented in exec.c */ static struct timeval debugger_timer; /* Set the track-CPU flag. (In fact we always track the VM CPU usage. This flag determines whether we report it to the debug console.) */ void debugger_track_cpu(int flag) { track_cpu = flag; } /* Set the block-on-startup flag. */ void debugger_set_start_trap(int flag) { start_trap = flag; } /* Set the block-on-quit flag. */ void debugger_set_quit_trap(int flag) { quit_trap = flag; } /* Set the block-on-crash flag. */ void debugger_set_crash_trap(int flag) { crash_trap = flag; } /* Has the library activated debugger features? If not, we assume that the debug console is entirely hidden from the user. */ int debugger_ever_invoked() { return debugger_invoked; } /* Returns 1 if this is a (hex or decimal) numeric constant; 0 if not; -1 if it looks like a constant but is malformed. On 1, the number itself is returned in res. */ static int parse_numeric_constant(char *arg, glui32 *res) { if (arg[0] == '$') { char *cx; glui32 val = 0; for (cx=arg+1; *cx; cx++) { if (*cx >= '0' && *cx <= '9') { val = 16 * val + (*cx - '0'); continue; } if (*cx >= 'A' && *cx <= 'F') { val = 16 * val + (*cx - 'A' + 10); continue; } if (*cx >= 'a' && *cx <= 'f') { val = 16 * val + (*cx - 'a' + 10); continue; } snprintf(linebuf, linebufsize, "Invalid hex number"); gidebug_output(linebuf); return -1; } *res = val; return 1; } if (arg[0] >= '0' && arg[0] <= '9') { char *cx; glui32 val = 0; for (cx=arg; *cx; cx++) { if (*cx >= '0' && *cx <= '9') { val = 10 * val + (*cx - '0'); continue; } snprintf(linebuf, linebufsize, "Invalid number"); gidebug_output(linebuf); return -1; } *res = val; return 1; } return 0; } static infoarray *find_array_for_address(glui32 addr) { if (!debuginfo) return NULL; infoarray **res = bsearch(&addr, debuginfo->arraylist, debuginfo->numarrays, sizeof(infoarray *), find_array_in_table); if (!res) return NULL; infoarray *arr = *res; if (addr < arr->address || addr >= arr->address + arr->bytelength) return NULL; return arr; } static infoobject *find_object_for_address(glui32 addr) { if (!debuginfo) return NULL; infoobject **res = bsearch(&addr, debuginfo->objectlist, debuginfo->numobjects, sizeof(infoobject *), find_object_in_table); if (!res) return NULL; infoobject *arr = *res; if (addr != arr->address) return NULL; return arr; } static inforoutine *find_routine_for_address(glui32 addr) { if (!debuginfo) return NULL; inforoutine **res = bsearch(&addr, debuginfo->routinelist, debuginfo->numroutines, sizeof(inforoutine *), find_routine_in_table); if (!res) return NULL; inforoutine *func = *res; if (addr < func->address || addr >= func->address + func->length) return NULL; return func; } static void render_value_linebuf(glui32 val) { int tmplen; /* Special case for single-digit numbers: display the decimal digit and stop. */ if (val < 10) { tmplen = strlen(linebuf); ensure_line_buf(tmplen+4); snprintf(linebuf+tmplen, linebufsize-tmplen, "%d", val); return; } /* Always display the signed decimal and unsigned hex. */ tmplen = strlen(linebuf); ensure_line_buf(tmplen+40); if (val <= 0x7FFFFFFF) snprintf(linebuf+tmplen, linebufsize-tmplen, "%d ($%X)", val, val); else snprintf(linebuf+tmplen, linebufsize-tmplen, "%d ($%X)", -(int)(0x100000000-val), val); /* If the address of a function, display it. (But not addresses in the middle of a function.) */ inforoutine *func = find_routine_for_address(val); if (func) { if (val == func->address) { tmplen = strlen(linebuf); ensure_line_buf(tmplen+40); snprintf(linebuf+tmplen, linebufsize-tmplen, ", %s()", func->identifier); } } /* If the address of an array, display it. */ infoarray *arr = find_array_for_address(val); if (arr) { if (val == arr->address) { tmplen = strlen(linebuf); ensure_line_buf(tmplen+40); snprintf(linebuf+tmplen, linebufsize-tmplen, ", %s[%d]", arr->identifier, arr->count); } } /* If the address of an object, display it. */ infoobject *object = find_object_for_address(val); if (object) { if (val == object->address) { tmplen = strlen(linebuf); ensure_line_buf(tmplen+40); snprintf(linebuf+tmplen, linebufsize-tmplen, ", %s", object->identifier); } } } static void debugcmd_backtrace(int wholestack) { if (stack) { ensure_line_buf(256); glui32 curpc = pc; glui32 curframeptr = frameptr; glui32 curstackptr = stackptr; glui32 curvalstackbase = valstackbase; glui32 curlocalsbase = localsbase; glui32 locptr; int locnum; while (1) { inforoutine *routine = find_routine_for_address(curpc); if (!routine) snprintf(linebuf, linebufsize, "- %s() (pc=$%.2X)", "???", curpc); else snprintf(linebuf, linebufsize, "- %s() (pc=$%.2X)", routine->identifier, curpc); gidebug_output(linebuf); strcpy(linebuf, " "); /* Again, this loop assumes that all locals are 4 bytes. */ for (locptr = curlocalsbase, locnum = 0; locptr < curvalstackbase; locptr += 4, locnum++) { int tmplen = strlen(linebuf); ensure_line_buf(tmplen+32); if (!routine || !routine->locals || locnum >= routine->numlocals) snprintf(linebuf+tmplen, linebufsize-tmplen, "%sloc#%d=", (locnum?"; ":""), locnum); else snprintf(linebuf+tmplen, linebufsize-tmplen, "%s%s=", (locnum?"; ":""), routine->locals[locnum].identifier); glui32 val = Stk4(locptr); render_value_linebuf(val); } if (!locnum) { strcat(linebuf, "(no locals)"); } gidebug_output(linebuf); if (!wholestack) break; curstackptr = curframeptr; if (curstackptr < 16) break; curstackptr -= 16; glui32 newframeptr = Stk4(curstackptr+12); glui32 newpc = Stk4(curstackptr+8); curframeptr = newframeptr; curpc = newpc; curvalstackbase = curframeptr + Stk4(curframeptr); curlocalsbase = curframeptr + Stk4(curframeptr+4); } } } static void debugcmd_set_breakpoint(char *arg) { while (*arg == ' ') arg++; if (*arg == '\0') { gidebug_output("What function do you want to set a breakpoint at?"); return; } int found = FALSE; glui32 addr = 0; if (!found) { int res = parse_numeric_constant(arg, &addr); if (res < 0) return; if (res > 0) { found = TRUE; /* If possible, check whether this looks like a function address. But if it doesn't, just print a warning. */ if (debuginfo) { inforoutine *routine = find_routine_for_address(addr); if (!routine || routine->address != addr) { ensure_line_buf(128); strcpy(linebuf, "This does not look like a function address: "); render_value_linebuf(addr); gidebug_output(linebuf); } } } } if (!found) { if (!debuginfo) { gidebug_output("No debug info; cannot look up functions by name"); return; } inforoutine *routine = xmlHashLookup(debuginfo->routines, BAD_CAST arg); if (!routine) { ensure_line_buf(128); snprintf(linebuf, linebufsize, "Not a function name: %s", arg); gidebug_output(linebuf); return; } found = TRUE; addr = routine->address; } if (!found) return; breakpoint *bp; for (bp = funcbreakpoints; bp; bp=bp->next) { if (bp->address == addr) { ensure_line_buf(128); strcpy(linebuf, "Breakpoint is already set for function: "); render_value_linebuf(addr); gidebug_output(linebuf); return; } } bp = create_breakpoint(addr); bp->next = funcbreakpoints; funcbreakpoints = bp; ensure_line_buf(128); strcpy(linebuf, "Breakpoint set for function: "); render_value_linebuf(addr); gidebug_output(linebuf); } static void debugcmd_clear_breakpoint(char *arg) { while (*arg == ' ') arg++; if (*arg == '\0') { gidebug_output("What function do you want to clear a breakpoint at?"); return; } int found = FALSE; glui32 addr = 0; if (!found) { int res = parse_numeric_constant(arg, &addr); if (res < 0) return; if (res > 0) { found = TRUE; /* If possible, check whether this looks like a function address. But if it doesn't, just print a warning. */ if (debuginfo) { inforoutine *routine = find_routine_for_address(addr); if (!routine || routine->address != addr) { ensure_line_buf(128); strcpy(linebuf, "This does not look like a function address: "); render_value_linebuf(addr); gidebug_output(linebuf); } } } } if (!found) { if (!debuginfo) { gidebug_output("No debug info; cannot look up functions by name"); return; } inforoutine *routine = xmlHashLookup(debuginfo->routines, BAD_CAST arg); if (!routine) { ensure_line_buf(128); snprintf(linebuf, linebufsize, "Not a function name: %s", arg); gidebug_output(linebuf); return; } found = TRUE; addr = routine->address; } if (!found) return; breakpoint **bpp; for (bpp = &funcbreakpoints; *bpp; bpp=&((*bpp)->next)) { if ((*bpp)->address == addr) { breakpoint *bp = *bpp; *bpp = bp->next; free(bp); ensure_line_buf(128); strcpy(linebuf, "Cleared breakpoint for function: "); render_value_linebuf(addr); gidebug_output(linebuf); return; } } ensure_line_buf(128); strcpy(linebuf, "No breakpoint found for function: "); render_value_linebuf(addr); gidebug_output(linebuf); } static void debugcmd_print(char *arg) { ensure_line_buf(128); /* for a start */ while (*arg == ' ') arg++; if (*arg == '\0') { gidebug_output("What do you want to print?"); return; } /* For plain numbers, and $HEX numbers, we print the value directly. */ { glui32 val = 0; int res = parse_numeric_constant(arg, &val); if (res < 0) return; if (res > 0) { strcpy(linebuf, ""); render_value_linebuf(val); gidebug_output(linebuf); return; } } /* Symbol recognition should be case-insensitive */ /* Is it a local variable name? */ if (debuginfo) { glui32 curpc = pc; glui32 curlocalsbase = localsbase; /* Should have a way to trawl up and down the stack. */ inforoutine *routine = find_routine_for_address(curpc); if (routine && routine->locals) { int ix; for (ix=0; ixnumlocals; ix++) { if (!xmlStrcmp(routine->locals[ix].identifier, BAD_CAST arg)) { glui32 locptr = curlocalsbase + 4*ix; glui32 val = Stk4(locptr); snprintf(linebuf, linebufsize, "local %s = ", routine->locals[ix].identifier); render_value_linebuf(val); gidebug_output(linebuf); return; } } } } /* Is it a constant name? */ if (debuginfo) { infoconstant *cons = xmlHashLookup(debuginfo->constants, BAD_CAST arg); if (cons) { snprintf(linebuf, linebufsize, "%d ($%X): constant", cons->value, cons->value); gidebug_output(linebuf); return; } } /* Is it an attribute name? */ if (debuginfo) { infoconstant *cons = xmlHashLookup(debuginfo->attributes, BAD_CAST arg); if (cons) { snprintf(linebuf, linebufsize, "%d ($%X): attribute", cons->value, cons->value); gidebug_output(linebuf); return; } } /* Is it a property name? */ if (debuginfo) { infoconstant *cons = xmlHashLookup(debuginfo->properties, BAD_CAST arg); if (cons) { snprintf(linebuf, linebufsize, "%d ($%X): property", cons->value, cons->value); gidebug_output(linebuf); return; } } /* Is it an object name? */ if (debuginfo) { infoobject *cons = xmlHashLookup(debuginfo->objects, BAD_CAST arg); if (cons) { snprintf(linebuf, linebufsize, "%d ($%X): object", cons->address, cons->address); gidebug_output(linebuf); return; } } /* Is it an array name? */ if (debuginfo) { infoarray *arr = xmlHashLookup(debuginfo->arrays, BAD_CAST arg); if (arr) { snprintf(linebuf, linebufsize, "%d ($%X): array[%d] of %d-byte values", arr->address, arr->address, arr->count, arr->bytesize); gidebug_output(linebuf); return; } } /* Is it a global name? */ if (debuginfo) { infoconstant *cons = xmlHashLookup(debuginfo->globals, BAD_CAST arg); if (cons) { glui32 val = Mem4(cons->value); snprintf(linebuf, linebufsize, "global %s = ", cons->identifier); render_value_linebuf(val); gidebug_output(linebuf); return; } } /* Is it a routine name? */ if (debuginfo) { inforoutine *routine = xmlHashLookup(debuginfo->routines, BAD_CAST arg); if (routine) { snprintf(linebuf, linebufsize, "%d ($%X): routine", routine->address, routine->address); gidebug_output(linebuf); return; } } gidebug_output("Symbol not found"); } static void debugcmd_help(char *arg) { gidebug_output("Glulxe built-in debugger. Commands:"); gidebug_output("- print : Print a symbol or number."); gidebug_output("- bt: Display the current stack backtrace, with local variables."); gidebug_output("- break : Set a breakpoint. (Must be a function name or the address of a function. Breakpoints currently only work at the start of a function.)"); gidebug_output("- clear : Clear a breakpoint."); gidebug_output("- cont: Continue execution. (From a breakpoint or other trap.)"); gidebug_output("- help/?: This list."); } /* Debug console callback: This is invoked whenever the user enters a debug command. Returns 0 for most commands, 1 if the command ends a debugger pause. */ int debugger_cmd_handler(char *cmd) { /* Trim spaces from start */ while (*cmd == ' ') cmd++; /* Trim spaces from end */ int len = strlen(cmd); while (len > 0 && cmd[len-1] == ' ') { cmd[len-1] = '\0'; len--; } if (*cmd == '\0') return 0; /* empty command */ char *cx; for (cx=cmd; *cx && *cx != ' '; cx++) { } len = (cx - cmd); if (len == 2 && !strncmp(cmd, "bt", len)) { debugcmd_backtrace(1); return 0; } if (len == 5 && !strncmp(cmd, "print", len)) { debugcmd_print(cx); return 0; } if (len == 5 && !strncmp(cmd, "break", len)) { debugcmd_set_breakpoint(cx); return 0; } if (len == 5 && !strncmp(cmd, "clear", len)) { debugcmd_clear_breakpoint(cx); return 0; } if (len == 4 && !strncmp(cmd, "cont", len)) { gidebug_output("Continuing..."); return 1; } if (len == 4 && !strncmp(cmd, "help", len)) { debugcmd_help(cx); return 0; } if (len == 1 && !strncmp(cmd, "?", len)) { debugcmd_help(cx); return 0; } ensure_line_buf(strlen(cmd) + 64); snprintf(linebuf, linebufsize, "Unknown debug command: %s", cmd); gidebug_output(linebuf); return 0; } /* Debug console callback: This is invoked when the game starts, when it ends, and when each input cycle begins and ends. We take this opportunity to report CPU usage, if the track_cpu flag is set. */ void debugger_cycle_handler(int cycle) { struct timeval now; double diff; debugger_invoked = TRUE; if (track_cpu) { switch (cycle) { case gidebug_cycle_Start: debugger_opcount = 0; gettimeofday(&debugger_timer, NULL); break; case gidebug_cycle_InputWait: case gidebug_cycle_DebugPause: gettimeofday(&now, NULL); diff = (now.tv_sec - debugger_timer.tv_sec) * 1000.0 + (now.tv_usec - debugger_timer.tv_usec) / 1000.0; ensure_line_buf(64); snprintf(linebuf, linebufsize, "VM: %ld cycles in %.3f ms", debugger_opcount, diff); gidebug_output(linebuf); break; case gidebug_cycle_InputAccept: case gidebug_cycle_DebugUnpause: debugger_opcount = 0; gettimeofday(&debugger_timer, NULL); break; } } } /* Do any start-time setup. This is called after VM memory is loaded, but before execution begins. */ void debugger_setup_start_state(void) { if (start_trap) { /* Block and debug right now. This doesn't install a trap, it just calls gidebug_pause() directly. */ gidebug_output("Break at start:"); debugcmd_backtrace(0); gidebug_pause(); } } /* Check whether we've set a breakpoint at this function address. If so, block and debug. */ void debugger_check_func_breakpoint(glui32 addr) { int pause = FALSE; breakpoint *bp; for (bp = funcbreakpoints; bp; bp=bp->next) { if (bp->address == addr) { pause = TRUE; break; } } if (pause) { gidebug_output("Breakpoint:"); debugcmd_backtrace(0); gidebug_pause(); } } /* Invoke the library's debug-block mechanism. */ void debugger_block_and_debug(char *msg) { gidebug_output(msg); gidebug_pause(); } /* If the quit-trap preference is set, enter debug mode. */ void debugger_handle_quit() { if (quit_trap) { gidebug_output("Quit trap, pausing..."); debugcmd_backtrace(0); gidebug_pause(); } } /* Report a fatal error to the debug console, along with the current stack trace. Depending on preferences, we may then enter full-on debug mode. */ void debugger_handle_crash(char *msg) { char *prefix = "Glulxe fatal error: "; ensure_line_buf(strlen(prefix) + strlen(msg)); strcpy(linebuf, prefix); strcat(linebuf, msg); gidebug_output(linebuf); debugcmd_backtrace(1); if (crash_trap) { gidebug_pause(); } } #endif /* VM_DEBUGGER */ glulxe/exec.c000066400000000000000000000772671304145303500134670ustar00rootroot00000000000000/* exec.c: Glulxe code for program execution. The main interpreter loop. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" #include "opcodes.h" #ifdef FLOAT_SUPPORT #include #endif /* FLOAT_SUPPORT */ /* execute_loop(): The main interpreter loop. This repeats until the program is done. */ void execute_loop() { int done_executing = FALSE; int ix; glui32 opcode; operandlist_t *oplist; oparg_t inst[MAX_OPERANDS]; glui32 value, addr, val0, val1; glsi32 vals0, vals1; glui32 *arglist; glui32 arglistfix[3]; #ifdef FLOAT_SUPPORT gfloat32 valf, valf1, valf2; #endif /* FLOAT_SUPPORT */ while (!done_executing) { profile_tick(); debugger_tick(); /* Do OS-specific processing, if appropriate. */ glk_tick(); /* Stash the current opcode's address, in case the interpreter needs to serialize the VM state out-of-band. */ prevpc = pc; /* Fetch the opcode number. */ opcode = Mem1(pc); pc++; if (opcode & 0x80) { /* More than one-byte opcode. */ if (opcode & 0x40) { /* Four-byte opcode */ opcode &= 0x3F; opcode = (opcode << 8) | Mem1(pc); pc++; opcode = (opcode << 8) | Mem1(pc); pc++; opcode = (opcode << 8) | Mem1(pc); pc++; } else { /* Two-byte opcode */ opcode &= 0x7F; opcode = (opcode << 8) | Mem1(pc); pc++; } } /* Now we have an opcode number. */ /* Fetch the structure that describes how the operands for this opcode are arranged. This is a pointer to an immutable, static object. */ if (opcode < 0x80) oplist = fast_operandlist[opcode]; else oplist = lookup_operandlist(opcode); if (!oplist) fatal_error_i("Encountered unknown opcode.", opcode); /* Based on the oplist structure, load the actual operand values into inst. This moves the PC up to the end of the instruction. */ parse_operands(inst, oplist); /* Perform the opcode. This switch statement is split in two, based on some paranoid suspicions about the ability of compilers to optimize large-range switches. Ignore that. */ if (opcode < 0x80) { switch (opcode) { case op_nop: break; case op_add: value = inst[0].value + inst[1].value; store_operand(inst[2].desttype, inst[2].value, value); break; case op_sub: value = inst[0].value - inst[1].value; store_operand(inst[2].desttype, inst[2].value, value); break; case op_mul: value = inst[0].value * inst[1].value; store_operand(inst[2].desttype, inst[2].value, value); break; case op_div: vals0 = inst[0].value; vals1 = inst[1].value; if (vals1 == 0) fatal_error("Division by zero."); /* Since C doesn't guarantee the results of division of negative numbers, we carefully convert everything to positive values first. They have to be unsigned values, too, otherwise the 0x80000000 case goes wonky. */ if (vals0 < 0) { val0 = (-vals0); if (vals1 < 0) { val1 = (-vals1); value = val0 / val1; } else { val1 = vals1; value = -(val0 / val1); } } else { val0 = vals0; if (vals1 < 0) { val1 = (-vals1); value = -(val0 / val1); } else { val1 = vals1; value = val0 / val1; } } store_operand(inst[2].desttype, inst[2].value, value); break; case op_mod: vals0 = inst[0].value; vals1 = inst[1].value; if (vals1 == 0) fatal_error("Division by zero doing remainder."); if (vals1 < 0) { val1 = -vals1; } else { val1 = vals1; } if (vals0 < 0) { val0 = (-vals0); value = -(val0 % val1); } else { val0 = vals0; value = val0 % val1; } store_operand(inst[2].desttype, inst[2].value, value); break; case op_neg: vals0 = inst[0].value; value = (-vals0); store_operand(inst[1].desttype, inst[1].value, value); break; case op_bitand: value = (inst[0].value & inst[1].value); store_operand(inst[2].desttype, inst[2].value, value); break; case op_bitor: value = (inst[0].value | inst[1].value); store_operand(inst[2].desttype, inst[2].value, value); break; case op_bitxor: value = (inst[0].value ^ inst[1].value); store_operand(inst[2].desttype, inst[2].value, value); break; case op_bitnot: value = ~(inst[0].value); store_operand(inst[1].desttype, inst[1].value, value); break; case op_shiftl: vals0 = inst[1].value; if (vals0 < 0 || vals0 >= 32) value = 0; else value = ((glui32)(inst[0].value) << (glui32)vals0); store_operand(inst[2].desttype, inst[2].value, value); break; case op_ushiftr: vals0 = inst[1].value; if (vals0 < 0 || vals0 >= 32) value = 0; else value = ((glui32)(inst[0].value) >> (glui32)vals0); store_operand(inst[2].desttype, inst[2].value, value); break; case op_sshiftr: vals0 = inst[1].value; if (vals0 < 0 || vals0 >= 32) { if (inst[0].value & 0x80000000) value = 0xFFFFFFFF; else value = 0; } else { /* This is somewhat foolhardy -- C doesn't guarantee that right-shifting a signed value replicates the sign bit. We'll assume it for now. */ value = ((glsi32)(inst[0].value) >> (glsi32)vals0); } store_operand(inst[2].desttype, inst[2].value, value); break; case op_jump: value = inst[0].value; /* fall through to PerformJump label. */ PerformJump: /* goto label for successful jumping... ironic, no? */ if (value == 0 || value == 1) { /* Return from function. This is exactly what happens in return_op, but it's only a few lines of code, so I won't bother with a "goto". */ leave_function(); if (stackptr == 0) { done_executing = TRUE; break; } pop_callstub(value); /* zero or one */ } else { /* Branch to a new PC value. */ pc = (pc + value - 2); } break; case op_jz: if (inst[0].value == 0) { value = inst[1].value; goto PerformJump; } break; case op_jnz: if (inst[0].value != 0) { value = inst[1].value; goto PerformJump; } break; case op_jeq: if (inst[0].value == inst[1].value) { value = inst[2].value; goto PerformJump; } break; case op_jne: if (inst[0].value != inst[1].value) { value = inst[2].value; goto PerformJump; } break; case op_jlt: vals0 = inst[0].value; vals1 = inst[1].value; if (vals0 < vals1) { value = inst[2].value; goto PerformJump; } break; case op_jgt: vals0 = inst[0].value; vals1 = inst[1].value; if (vals0 > vals1) { value = inst[2].value; goto PerformJump; } break; case op_jle: vals0 = inst[0].value; vals1 = inst[1].value; if (vals0 <= vals1) { value = inst[2].value; goto PerformJump; } break; case op_jge: vals0 = inst[0].value; vals1 = inst[1].value; if (vals0 >= vals1) { value = inst[2].value; goto PerformJump; } break; case op_jltu: val0 = inst[0].value; val1 = inst[1].value; if (val0 < val1) { value = inst[2].value; goto PerformJump; } break; case op_jgtu: val0 = inst[0].value; val1 = inst[1].value; if (val0 > val1) { value = inst[2].value; goto PerformJump; } break; case op_jleu: val0 = inst[0].value; val1 = inst[1].value; if (val0 <= val1) { value = inst[2].value; goto PerformJump; } break; case op_jgeu: val0 = inst[0].value; val1 = inst[1].value; if (val0 >= val1) { value = inst[2].value; goto PerformJump; } break; case op_call: value = inst[1].value; arglist = pop_arguments(value, 0); push_callstub(inst[2].desttype, inst[2].value); enter_function(inst[0].value, value, arglist); break; case op_return: leave_function(); if (stackptr == 0) { done_executing = TRUE; break; } pop_callstub(inst[0].value); break; case op_tailcall: value = inst[1].value; arglist = pop_arguments(value, 0); leave_function(); enter_function(inst[0].value, value, arglist); break; case op_catch: push_callstub(inst[0].desttype, inst[0].value); value = inst[1].value; val0 = stackptr; store_operand(inst[0].desttype, inst[0].value, val0); goto PerformJump; break; case op_throw: profile_fail("throw"); value = inst[0].value; stackptr = inst[1].value; pop_callstub(value); break; case op_copy: value = inst[0].value; #ifdef TOLERATE_SUPERGLUS_BUG if (inst[1].desttype == 1 && inst[1].value == 0) inst[1].desttype = 0; #endif /* TOLERATE_SUPERGLUS_BUG */ store_operand(inst[1].desttype, inst[1].value, value); break; case op_copys: value = inst[0].value; store_operand_s(inst[1].desttype, inst[1].value, value); break; case op_copyb: value = inst[0].value; store_operand_b(inst[1].desttype, inst[1].value, value); break; case op_sexs: val0 = inst[0].value; if (val0 & 0x8000) val0 |= 0xFFFF0000; else val0 &= 0x0000FFFF; store_operand(inst[1].desttype, inst[1].value, val0); break; case op_sexb: val0 = inst[0].value; if (val0 & 0x80) val0 |= 0xFFFFFF00; else val0 &= 0x000000FF; store_operand(inst[1].desttype, inst[1].value, val0); break; case op_aload: value = inst[0].value; value += 4 * inst[1].value; val0 = Mem4(value); store_operand(inst[2].desttype, inst[2].value, val0); break; case op_aloads: value = inst[0].value; value += 2 * inst[1].value; val0 = Mem2(value); store_operand(inst[2].desttype, inst[2].value, val0); break; case op_aloadb: value = inst[0].value; value += inst[1].value; val0 = Mem1(value); store_operand(inst[2].desttype, inst[2].value, val0); break; case op_aloadbit: value = inst[0].value; vals0 = inst[1].value; val1 = (vals0 & 7); if (vals0 >= 0) value += (vals0 >> 3); else value -= (1 + ((-1 - vals0) >> 3)); if (Mem1(value) & (1 << val1)) val0 = 1; else val0 = 0; store_operand(inst[2].desttype, inst[2].value, val0); break; case op_astore: value = inst[0].value; value += 4 * inst[1].value; val0 = inst[2].value; MemW4(value, val0); break; case op_astores: value = inst[0].value; value += 2 * inst[1].value; val0 = inst[2].value; MemW2(value, val0); break; case op_astoreb: value = inst[0].value; value += inst[1].value; val0 = inst[2].value; MemW1(value, val0); break; case op_astorebit: value = inst[0].value; vals0 = inst[1].value; val1 = (vals0 & 7); if (vals0 >= 0) value += (vals0 >> 3); else value -= (1 + ((-1 - vals0) >> 3)); val0 = Mem1(value); if (inst[2].value) val0 |= (1 << val1); else val0 &= ~((glui32)(1 << val1)); MemW1(value, val0); break; case op_stkcount: value = (stackptr - valstackbase) / 4; store_operand(inst[0].desttype, inst[0].value, value); break; case op_stkpeek: vals0 = inst[0].value * 4; if (vals0 < 0 || vals0 >= (stackptr - valstackbase)) fatal_error("Stkpeek outside current stack range."); value = Stk4(stackptr - (vals0+4)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_stkswap: if (stackptr < valstackbase+8) { fatal_error("Stack underflow in stkswap."); } val0 = Stk4(stackptr-4); val1 = Stk4(stackptr-8); StkW4(stackptr-4, val1); StkW4(stackptr-8, val0); break; case op_stkcopy: vals0 = inst[0].value; if (vals0 < 0) fatal_error("Negative operand in stkcopy."); if (vals0 == 0) break; if (stackptr < valstackbase+vals0*4) fatal_error("Stack underflow in stkcopy."); if (stackptr + vals0*4 > stacksize) fatal_error("Stack overflow in stkcopy."); addr = stackptr - vals0*4; for (ix=0; ix 0) { vals1 = vals1 % vals0; vals1 = (vals0) - vals1; } else { vals1 = (-vals1) % vals0; } if (vals1 == 0) break; addr = stackptr - vals0*4; for (ix=0; ix= 1) value = glulx_random() % (glui32)(vals0); else value = -(glulx_random() % (glui32)(-vals0)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_setrandom: glulx_setrandom(inst[0].value); break; case op_verify: value = perform_verify(); store_operand(inst[0].desttype, inst[0].value, value); break; case op_restart: profile_fail("restart"); vm_restart(); break; case op_protect: val0 = inst[0].value; val1 = val0 + inst[1].value; if (val0 == val1) { val0 = 0; val1 = 0; } protectstart = val0; protectend = val1; break; case op_save: push_callstub(inst[1].desttype, inst[1].value); value = perform_save(find_stream_by_id(inst[0].value)); pop_callstub(value); break; case op_restore: value = perform_restore(find_stream_by_id(inst[0].value), FALSE); if (value == 0) { /* We've succeeded, and the stack now contains the callstub saved during saveundo. Ignore this opcode's operand. */ value = -1; pop_callstub(value); } else { /* We've failed, so we must store the failure in this opcode's operand. */ store_operand(inst[1].desttype, inst[1].value, value); } break; case op_saveundo: push_callstub(inst[0].desttype, inst[0].value); value = perform_saveundo(); pop_callstub(value); break; case op_restoreundo: value = perform_restoreundo(); if (value == 0) { /* We've succeeded, and the stack now contains the callstub saved during saveundo. Ignore this opcode's operand. */ value = -1; pop_callstub(value); } else { /* We've failed, so we must store the failure in this opcode's operand. */ store_operand(inst[0].desttype, inst[0].value, value); } break; case op_quit: done_executing = TRUE; break; case op_linearsearch: value = linear_search(inst[0].value, inst[1].value, inst[2].value, inst[3].value, inst[4].value, inst[5].value, inst[6].value); store_operand(inst[7].desttype, inst[7].value, value); break; case op_binarysearch: value = binary_search(inst[0].value, inst[1].value, inst[2].value, inst[3].value, inst[4].value, inst[5].value, inst[6].value); store_operand(inst[7].desttype, inst[7].value, value); break; case op_linkedsearch: value = linked_search(inst[0].value, inst[1].value, inst[2].value, inst[3].value, inst[4].value, inst[5].value); store_operand(inst[6].desttype, inst[6].value, value); break; case op_mzero: { glui32 lx; glui32 count = inst[0].value; addr = inst[1].value; for (lx=0; lx 2147483647.0)) vals0 = 0x7FFFFFFF; else vals0 = (glsi32)(truncf(valf)); } else { if (isnan(valf) || isinf(valf) || (valf < -2147483647.0)) vals0 = 0x80000000; else vals0 = (glsi32)(truncf(valf)); } store_operand(inst[1].desttype, inst[1].value, vals0); break; case op_ftonumn: valf = decode_float(inst[0].value); if (!signbit(valf)) { if (isnan(valf) || isinf(valf) || (valf > 2147483647.0)) vals0 = 0x7FFFFFFF; else vals0 = (glsi32)(roundf(valf)); } else { if (isnan(valf) || isinf(valf) || (valf < -2147483647.0)) vals0 = 0x80000000; else vals0 = (glsi32)(roundf(valf)); } store_operand(inst[1].desttype, inst[1].value, vals0); break; case op_fadd: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); value = encode_float(valf1 + valf2); store_operand(inst[2].desttype, inst[2].value, value); break; case op_fsub: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); value = encode_float(valf1 - valf2); store_operand(inst[2].desttype, inst[2].value, value); break; case op_fmul: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); value = encode_float(valf1 * valf2); store_operand(inst[2].desttype, inst[2].value, value); break; case op_fdiv: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); value = encode_float(valf1 / valf2); store_operand(inst[2].desttype, inst[2].value, value); break; case op_fmod: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); valf = fmodf(valf1, valf2); val0 = encode_float(valf); val1 = encode_float((valf1-valf) / valf2); if (val1 == 0x0 || val1 == 0x80000000) { /* When the quotient is zero, the sign has been lost in the shuffle. We'll set that by hand, based on the original arguments. */ val1 = (inst[0].value ^ inst[1].value) & 0x80000000; } store_operand(inst[2].desttype, inst[2].value, val0); store_operand(inst[3].desttype, inst[3].value, val1); break; case op_floor: valf = decode_float(inst[0].value); value = encode_float(floorf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_ceil: valf = decode_float(inst[0].value); value = encode_float(ceilf(valf)); if (value == 0x0 || value == 0x80000000) { /* When the result is zero, the sign may have been lost in the shuffle. (This is a bug in some C libraries.) We'll set the sign by hand, based on the original argument. */ value = inst[0].value & 0x80000000; } store_operand(inst[1].desttype, inst[1].value, value); break; case op_sqrt: valf = decode_float(inst[0].value); value = encode_float(sqrtf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_log: valf = decode_float(inst[0].value); value = encode_float(logf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_exp: valf = decode_float(inst[0].value); value = encode_float(expf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_pow: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); value = encode_float(glulx_powf(valf1, valf2)); store_operand(inst[2].desttype, inst[2].value, value); break; case op_sin: valf = decode_float(inst[0].value); value = encode_float(sinf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_cos: valf = decode_float(inst[0].value); value = encode_float(cosf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_tan: valf = decode_float(inst[0].value); value = encode_float(tanf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_asin: valf = decode_float(inst[0].value); value = encode_float(asinf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_acos: valf = decode_float(inst[0].value); value = encode_float(acosf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_atan: valf = decode_float(inst[0].value); value = encode_float(atanf(valf)); store_operand(inst[1].desttype, inst[1].value, value); break; case op_atan2: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); value = encode_float(atan2f(valf1, valf2)); store_operand(inst[2].desttype, inst[2].value, value); break; case op_jisinf: /* Infinity is well-defined, so we don't bother to convert to float. */ val0 = inst[0].value; if (val0 == 0x7F800000 || val0 == 0xFF800000) { value = inst[1].value; goto PerformJump; } break; case op_jisnan: /* NaN is well-defined, so we don't bother to convert to float. */ val0 = inst[0].value; if ((val0 & 0x7F800000) == 0x7F800000 && (val0 & 0x007FFFFF) != 0) { value = inst[1].value; goto PerformJump; } break; case op_jfeq: if ((inst[2].value & 0x7F800000) == 0x7F800000 && (inst[2].value & 0x007FFFFF) != 0) { /* The delta is NaN, which can never match. */ val0 = 0; } else if ((inst[0].value == 0x7F800000 || inst[0].value == 0xFF800000) && (inst[1].value == 0x7F800000 || inst[1].value == 0xFF800000)) { /* Both are infinite. Opposite infinities are never equal, even if the difference is infinite, so this is easy. */ val0 = (inst[0].value == inst[1].value); } else { valf1 = decode_float(inst[1].value) - decode_float(inst[0].value); valf2 = fabs(decode_float(inst[2].value)); val0 = (valf1 <= valf2 && valf1 >= -valf2); } if (val0) { value = inst[3].value; goto PerformJump; } break; case op_jfne: if ((inst[2].value & 0x7F800000) == 0x7F800000 && (inst[2].value & 0x007FFFFF) != 0) { /* The delta is NaN, which can never match. */ val0 = 0; } else if ((inst[0].value == 0x7F800000 || inst[0].value == 0xFF800000) && (inst[1].value == 0x7F800000 || inst[1].value == 0xFF800000)) { /* Both are infinite. Opposite infinities are never equal, even if the difference is infinite, so this is easy. */ val0 = (inst[0].value == inst[1].value); } else { valf1 = decode_float(inst[1].value) - decode_float(inst[0].value); valf2 = fabs(decode_float(inst[2].value)); val0 = (valf1 <= valf2 && valf1 >= -valf2); } if (!val0) { value = inst[3].value; goto PerformJump; } break; case op_jflt: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); if (valf1 < valf2) { value = inst[2].value; goto PerformJump; } break; case op_jfgt: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); if (valf1 > valf2) { value = inst[2].value; goto PerformJump; } break; case op_jfle: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); if (valf1 <= valf2) { value = inst[2].value; goto PerformJump; } break; case op_jfge: valf1 = decode_float(inst[0].value); valf2 = decode_float(inst[1].value); if (valf1 >= valf2) { value = inst[2].value; goto PerformJump; } break; #endif /* FLOAT_SUPPORT */ #ifdef GLULX_EXTEND_OPCODES GLULX_EXTEND_OPCODES #endif /* GLULX_EXTEND_OPCODES */ default: fatal_error_i("Executed unknown opcode.", opcode); } } } /* done executing */ #if VM_DEBUGGER debugger_handle_quit(); #endif /* VM_DEBUGGER */ } glulxe/files.c000066400000000000000000000045171304145303500136310ustar00rootroot00000000000000/* files.c: Glulxe file-handling code. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "gi_blorb.h" #include "glulxe.h" /* is_gamefile_valid(): Check guess what. */ int is_gamefile_valid() { unsigned char buf[8]; int res; glui32 version; glk_stream_set_position(gamefile, gamefile_start, seekmode_Start); res = glk_get_buffer_stream(gamefile, (char *)buf, 8); if (res != 8) { fatal_error("This is too short to be a valid Glulx file."); return FALSE; } if (buf[0] != 'G' || buf[1] != 'l' || buf[2] != 'u' || buf[3] != 'l') { fatal_error("This is not a valid Glulx file."); return FALSE; } /* We support version 2.0 through 3.1.*. */ version = Read4(buf+4); if (version < 0x20000) { fatal_error("This Glulx file is too old a version to execute."); return FALSE; } if (version >= 0x30200) { fatal_error("This Glulx file is too new a version to execute."); return FALSE; } return TRUE; } /* locate_gamefile: Given that gamefile contains a Glk stream, which may be a Glulx file or a Blorb archive containing one, locate the beginning and end of the Glulx data. */ int locate_gamefile(int isblorb) { if (!isblorb) { /* The simple case. A bare Glulx file was opened, so we don't use Blorb at all. */ gamefile_start = 0; glk_stream_set_position(gamefile, 0, seekmode_End); gamefile_len = glk_stream_get_position(gamefile); return TRUE; } else { /* A Blorb file. We now have to open it and find the Glulx chunk. */ giblorb_err_t err; giblorb_result_t blorbres; giblorb_map_t *map; err = giblorb_set_resource_map(gamefile); if (err) { init_err = "This Blorb file seems to be invalid."; return FALSE; } map = giblorb_get_resource_map(); err = giblorb_load_resource(map, giblorb_method_FilePos, &blorbres, giblorb_ID_Exec, 0); if (err) { init_err = "This Blorb file does not contain an executable Glulx chunk."; return FALSE; } if (blorbres.chunktype != giblorb_make_id('G', 'L', 'U', 'L')) { init_err = "This Blorb file contains an executable chunk, but it is not a Glulx file."; return FALSE; } gamefile_start = blorbres.data.startpos; gamefile_len = blorbres.length; return TRUE; } } glulxe/float.c000066400000000000000000000065551304145303500136400ustar00rootroot00000000000000/* float.c: Glulxe code for floating-point operations Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" #ifdef FLOAT_SUPPORT #include /* This entire file is compiled out if the FLOAT_SUPPORT option is off. (Because we probably can't define a gfloat32 in that case.) */ #ifndef FLOAT_NOT_NATIVE int init_float() { /* Check and make sure the native float format is really IEEE-754 single-precision. */ if (sizeof(gfloat32) != 4) { fatal_error("gfloat32 is not 32 bits."); return FALSE; } if (encode_float((gfloat32)(-1)) != 0xBF800000) { fatal_error("The gfloat32 format of -1 did not match."); return FALSE; } return TRUE; } /* Encode and decode floats by reinterpret-casting. */ glui32 encode_float(gfloat32 val) { glui32 res; *(gfloat32 *)(&res) = val; return res; } gfloat32 decode_float(glui32 val) { gfloat32 res; *(glui32 *)(&res) = val; return res; } #else /* FLOAT_NOT_NATIVE */ int init_float() { return TRUE; } /* Encode and decode floats by a lot of annoying bit manipulation. The following functions are adapted from code in Python (Objects/floatobject.c). */ glui32 encode_float(gfloat32 val) { gfloat32 absval; glui32 sign; int expo; gfloat32 mant; glui32 fbits; if (signbit(val)) { sign = 0x80000000; absval = -val; } else { sign = 0x0; absval = val; } if (isinf(val)) { return sign | 0x7f800000; /* infinity */ } if (isnan(val)) { return sign | 0x7fc00000; } mant = frexpf(absval, &expo); /* Normalize mantissa to be in the range [1.0, 2.0) */ if (0.5 <= mant && mant < 1.0) { mant *= 2.0; expo--; } else if (mant == 0.0) { expo = 0; } else { return sign | 0x7f800000; /* infinity */ } if (expo >= 128) { return sign | 0x7f800000; /* infinity */ } else if (expo < -126) { /* Denormalized (very small) number */ mant = ldexpf(mant, 126 + expo); expo = 0; } else if (!(expo == 0 && mant == 0.0)) { expo += 127; mant -= 1.0; /* Get rid of leading 1 */ } mant *= 8388608.0; /* 2^23 */ fbits = (glui32)(mant + 0.5); /* round mant to nearest int */ if (fbits >> 23) { /* The carry propagated out of a string of 23 1 bits. */ fbits = 0; expo++; if (expo >= 255) { return sign | 0x7f800000; /* infinity */ } } return (sign) | ((glui32)(expo << 23)) | (fbits); } gfloat32 decode_float(glui32 val) { int sign; int expo; glui32 mant; gfloat32 res; /* First byte */ sign = ((val & 0x80000000) != 0); expo = (val >> 23) & 0xFF; mant = val & 0x7FFFFF; if (expo == 255) { if (mant == 0) { /* Infinity */ return (sign ? (-INFINITY) : (INFINITY)); } else { /* Not a number */ return (sign ? (-NAN) : (NAN)); } } res = (gfloat32)mant / 8388608.0; if (expo == 0) { expo = -126; } else { res += 1.0; expo -= 127; } res = ldexpf(res, expo); return (sign ? (-res) : (res)); } #endif /* FLOAT_NOT_NATIVE */ #endif /* FLOAT_SUPPORT */ glulxe/funcs.c000066400000000000000000000202071304145303500136370ustar00rootroot00000000000000/* funcs.c: Glulxe function-handling functions. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" /* enter_function(): This writes a new call frame onto the stack, at stackptr. It leaves frameptr pointing to the frame (ie, the original stackptr value.) argc and argv are an array of arguments. Note that if argc is zero, argv may be NULL. */ void enter_function(glui32 funcaddr, glui32 argc, glui32 *argv) { int ix, jx; acceleration_func accelfunc; int locallen; int functype; glui32 modeaddr, opaddr, val; int loctype, locnum; glui32 addr = funcaddr; accelfunc = accel_get_func(addr); if (accelfunc) { profile_in(addr, stackptr, TRUE); val = accelfunc(argc, argv); profile_out(stackptr); pop_callstub(val); return; } profile_in(addr, stackptr, FALSE); /* Check the Glulx type identifier byte. */ functype = Mem1(addr); if (functype != 0xC0 && functype != 0xC1) { if (functype >= 0xC0 && functype <= 0xDF) fatal_error_i("Call to unknown type of function.", addr); else fatal_error_i("Call to non-function.", addr); } addr++; /* Bump the frameptr to the top. */ frameptr = stackptr; /* Go through the function's locals-format list, copying it to the call frame. At the same time, we work out how much space the locals will actually take up. (Including padding.) */ ix = 0; locallen = 0; while (1) { /* Grab two bytes from the locals-format list. These are unsigned (0..255 range). */ loctype = Mem1(addr); addr++; locnum = Mem1(addr); addr++; /* Copy them into the call frame. */ StkW1(frameptr+8+2*ix, loctype); StkW1(frameptr+8+2*ix+1, locnum); ix++; /* If the type is zero, we're done, except possibly for two more zero bytes in the call frame (to ensure 4-byte alignment.) */ if (loctype == 0) { /* Make sure ix is even. */ if (ix & 1) { StkW1(frameptr+8+2*ix, 0); StkW1(frameptr+8+2*ix+1, 0); ix++; } break; } /* Pad to 4-byte or 2-byte alignment if these locals are 4 or 2 bytes long. */ if (loctype == 4) { while (locallen & 3) locallen++; } else if (loctype == 2) { while (locallen & 1) locallen++; } else if (loctype == 1) { /* no padding */ } else { fatal_error("Illegal local type in locals-format list."); } /* Add the length of the locals themselves. */ locallen += (loctype * locnum); } /* Pad the locals to 4-byte alignment. */ while (locallen & 3) locallen++; /* We now know how long the locals-frame and locals segments are. */ localsbase = frameptr+8+2*ix; valstackbase = localsbase+locallen; /* Test for stack overflow. */ /* This really isn't good enough; if the format list overflowed the stack, we've already written outside the stack array. */ if (valstackbase >= stacksize) fatal_error("Stack overflow in function call."); /* Fill in the beginning of the stack frame. */ StkW4(frameptr+4, 8+2*ix); StkW4(frameptr, 8+2*ix+locallen); /* Set the stackptr and PC. */ stackptr = valstackbase; pc = addr; /* Zero out all the locals. */ for (jx=0; jx= stacksize) fatal_error("Stack overflow in function arguments."); for (ix=0; ix stacksize) fatal_error("Stack overflow in callstub."); StkW4(stackptr+0, desttype); StkW4(stackptr+4, destaddr); StkW4(stackptr+8, pc); StkW4(stackptr+12, frameptr); stackptr += 16; } /* pop_callstub(): Remove the magic four values from the stack, and use them. The returnvalue, whatever it is, is put at the result destination; the PC and frameptr registers are set. */ void pop_callstub(glui32 returnvalue) { glui32 desttype, destaddr; glui32 newpc, newframeptr; if (stackptr < 16) fatal_error("Stack underflow in callstub."); stackptr -= 16; newframeptr = Stk4(stackptr+12); newpc = Stk4(stackptr+8); destaddr = Stk4(stackptr+4); desttype = Stk4(stackptr+0); pc = newpc; frameptr = newframeptr; /* Recompute valstackbase and localsbase */ valstackbase = frameptr + Stk4(frameptr); localsbase = frameptr + Stk4(frameptr+4); switch (desttype) { case 0x11: fatal_error("String-terminator call stub at end of function call."); break; case 0x10: /* This call stub was pushed during a string-decoding operation! We have to restart it. (Note that the return value is discarded.) */ stream_string(pc, 0xE1, destaddr); break; case 0x12: /* This call stub was pushed during a number-printing operation. Restart that. (Return value discarded.) */ stream_num(pc, TRUE, destaddr); break; case 0x13: /* This call stub was pushed during a C-string printing operation. We have to restart it. (Note that the return value is discarded.) */ stream_string(pc, 0xE0, destaddr); break; case 0x14: /* This call stub was pushed during a Unicode printing operation. We have to restart it. (Note that the return value is discarded.) */ stream_string(pc, 0xE2, destaddr); break; default: /* We're back in the original frame, so we can store the returnvalue. (If we tried to do this before resetting frameptr, a result destination on the stack would go astray.) */ store_operand(desttype, destaddr, returnvalue); break; } } /* pop_callstub_string(): Remove the magic four values, but interpret them as a string restart state. Returns zero if it's a termination stub, or returns the restart address. The bitnum is extra. */ glui32 pop_callstub_string(int *bitnum) { glui32 desttype, destaddr, newpc; if (stackptr < 16) fatal_error("Stack underflow in callstub."); stackptr -= 16; newpc = Stk4(stackptr+8); destaddr = Stk4(stackptr+4); desttype = Stk4(stackptr+0); pc = newpc; if (desttype == 0x11) { return 0; } if (desttype == 0x10) { *bitnum = destaddr; return pc; } fatal_error("Function-terminator call stub at end of string."); return 0; } glulxe/gestalt.c000066400000000000000000000036341304145303500141710ustar00rootroot00000000000000/* gestalt.c: Glulxe code for gestalt selectors Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" #include "gestalt.h" glui32 do_gestalt(glui32 val, glui32 val2) { switch (val) { case gestulx_GlulxVersion: return 0x00030102; /* Glulx spec version 3.1.2 */ case gestulx_TerpVersion: return 0x00000504; /* Glulxe version 0.5.4 */ case gestulx_ResizeMem: #ifdef FIXED_MEMSIZE return 0; /* The setmemsize opcodes are compiled out. */ #else /* FIXED_MEMSIZE */ return 1; /* We can handle setmemsize. */ #endif /* FIXED_MEMSIZE */ case gestulx_Undo: return 1; /* We can handle saveundo and restoreundo. */ case gestulx_IOSystem: switch (val2) { case 0: return 1; /* The "null" system always works. */ case 1: return 1; /* The "filter" system always works. */ case 2: return 1; /* A Glk library is hooked up. */ default: return 0; } case gestulx_Unicode: return 1; /* We can handle Unicode. */ case gestulx_MemCopy: return 1; /* We can do mcopy/mzero. */ case gestulx_MAlloc: #ifdef FIXED_MEMSIZE return 0; /* The malloc opcodes are compiled out. */ #else /* FIXED_MEMSIZE */ return 1; /* We can handle malloc/mfree. */ #endif /* FIXED_MEMSIZE */ case gestulx_MAllocHeap: return heap_get_start(); case gestulx_Acceleration: return 1; /* We can do accelfunc/accelparam. */ case gestulx_AccelFunc: if (accel_find_func(val2)) return 1; /* We know this accelerated function. */ return 0; case gestulx_Float: #ifdef FLOAT_SUPPORT return 1; /* We can do floating-point operations. */ #else /* FLOAT_SUPPORT */ return 0; /* The floating-point opcodes are not compiled in. */ #endif /* FLOAT_SUPPORT */ #ifdef GLULX_EXTEND_GESTALT GLULX_EXTEND_GESTALT #endif /* GLULX_EXTEND_GESTALT */ default: return 0; } } glulxe/gestalt.h000066400000000000000000000011001304145303500141600ustar00rootroot00000000000000/* gestalt.h: The list of gestalt selectors for Glulxe. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #ifndef _GESTALT_H #define _GESTALT_H #define gestulx_GlulxVersion (0) #define gestulx_TerpVersion (1) #define gestulx_ResizeMem (2) #define gestulx_Undo (3) #define gestulx_IOSystem (4) #define gestulx_Unicode (5) #define gestulx_MemCopy (6) #define gestulx_MAlloc (7) #define gestulx_MAllocHeap (8) #define gestulx_Acceleration (9) #define gestulx_AccelFunc (10) #define gestulx_Float (11) #endif /* _GESTALT_H */ glulxe/glkop.c000066400000000000000000001176441304145303500136510ustar00rootroot00000000000000/* glkop.c: Glulxe code for Glk API dispatching. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ /* This code is actually very general; it could work for almost any 32-bit VM which remotely resembles Glulxe or the Z-machine in design. To be precise, we make the following assumptions: - An argument list is an array of 32-bit values, which can represent either integers or addresses. - We can read or write to a 32-bit integer in VM memory using the macros ReadMemory(addr) and WriteMemory(addr), where addr is an address taken from the argument list. - A character array is a sequence of bytes somewhere in VM memory. The array can be turned into a C char array by the macro CaptureCArray(addr, len), and released by ReleaseCArray(). The passin, passout hints may be used to avoid unnecessary copying. - An integer array is a sequence of integers somewhere in VM memory. The array can be turned into a C integer array by the macro CaptureIArray(addr, len), and released by ReleaseIArray(). These macros are responsible for fixing byte-order and alignment (if the C ABI does not match the VM's). The passin, passout hints may be used to avoid unnecessary copying. - A Glk object array is a sequence of integers in VM memory. It is turned into a C pointer array (remember that C pointers may be more than 4 bytes!) The pointer array is allocated by CapturePtrArray(addr, len, objclass) and released by ReleasePtrArray(). Again, the macros handle the conversion. - A Glk structure (such as event_t) is a set of integers somewhere in VM memory, which can be read and written with the macros ReadStructField(addr, fieldnum) and WriteStructField(addr, fieldnum). The fieldnum is an integer (from 0 to 3, for event_t.) - A VM string can be turned into a C-style string with the macro ptr = DecodeVMString(addr). After the string is used, this code calls ReleaseVMString(ptr), which should free any memory that DecodeVMString allocates. - A VM Unicode string can be turned into a zero-terminated array of 32-bit integers, in the same way, with DecodeVMUstring and ReleaseVMUstring. To work this code over for a new VM, just diddle the macros. */ #define ReadMemory(addr) \ (((addr) == 0xffffffff) \ ? (stackptr -= 4, Stk4(stackptr)) \ : (Mem4(addr))) #define WriteMemory(addr, val) \ (((addr) == 0xffffffff) \ ? (StkW4(stackptr, (val)), stackptr += 4) \ : (MemW4((addr), (val)))) #define CaptureCArray(addr, len, passin) \ (grab_temp_c_array(addr, len, passin)) #define ReleaseCArray(ptr, addr, len, passout) \ (release_temp_c_array(ptr, addr, len, passout)) #define CaptureIArray(addr, len, passin) \ (grab_temp_i_array(addr, len, passin)) #define ReleaseIArray(ptr, addr, len, passout) \ (release_temp_i_array(ptr, addr, len, passout)) #define CapturePtrArray(addr, len, objclass, passin) \ (grab_temp_ptr_array(addr, len, objclass, passin)) #define ReleasePtrArray(ptr, addr, len, objclass, passout) \ (release_temp_ptr_array(ptr, addr, len, objclass, passout)) #define ReadStructField(addr, fieldnum) \ (((addr) == 0xffffffff) \ ? (stackptr -= 4, Stk4(stackptr)) \ : (Mem4((addr)+(fieldnum)*4))) #define WriteStructField(addr, fieldnum, val) \ (((addr) == 0xffffffff) \ ? (StkW4(stackptr, (val)), stackptr += 4) \ : (MemW4((addr)+(fieldnum)*4, (val)))) #define DecodeVMString(addr) \ (make_temp_string(addr)) #define ReleaseVMString(ptr) \ (free_temp_string(ptr)) #define DecodeVMUstring(addr) \ (make_temp_ustring(addr)) #define ReleaseVMUstring(ptr) \ (free_temp_ustring(ptr)) #include "glk.h" #include "glulxe.h" #include "gi_dispa.h" typedef struct dispatch_splot_struct { int numwanted; int maxargs; gluniversal_t *garglist; glui32 *varglist; int numvargs; glui32 *retval; } dispatch_splot_t; /* We maintain a linked list of arrays being used for Glk calls. It is only used for integer (glui32) arrays -- char arrays are handled in place. It's not worth bothering with a hash table, since most arrays appear here only momentarily. */ typedef struct arrayref_struct arrayref_t; struct arrayref_struct { void *array; glui32 addr; glui32 elemsize; glui32 len; /* elements */ int retained; arrayref_t *next; }; static arrayref_t *arrays = NULL; /* We maintain a hash table for each opaque Glk class. classref_t are the nodes of the table, and classtable_t are the tables themselves. */ typedef struct classref_struct classref_t; struct classref_struct { void *obj; glui32 id; int bucknum; classref_t *next; }; #define CLASSHASH_SIZE (31) typedef struct classtable_struct { glui32 lastid; classref_t *bucket[CLASSHASH_SIZE]; } classtable_t; /* The list of hash tables, for the classes. */ static int num_classes = 0; classtable_t **classes = NULL; static classtable_t *new_classtable(glui32 firstid); static void *classes_get(int classid, glui32 objid); static classref_t *classes_put(int classid, void *obj, glui32 origid); static void classes_remove(int classid, void *obj); static gidispatch_rock_t glulxe_classtable_register(void *obj, glui32 objclass); static void glulxe_classtable_unregister(void *obj, glui32 objclass, gidispatch_rock_t objrock); static gidispatch_rock_t glulxe_retained_register(void *array, glui32 len, char *typecode); static void glulxe_retained_unregister(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock); static long glulxe_array_locate(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref); static gidispatch_rock_t glulxe_array_restore(long bufkey, glui32 len, char *typecode, void **arrayref); /* This is only needed for autorestore. */ extern gidispatch_rock_t glulxe_classtable_register_existing(void *obj, glui32 objclass, glui32 dispid); /* The library_select_hook is called every time the VM blocks for input. The app might take this opportunity to autosave, for example. */ static void (*library_select_hook)(glui32) = NULL; static char *grab_temp_c_array(glui32 addr, glui32 len, int passin); static void release_temp_c_array(char *arr, glui32 addr, glui32 len, int passout); static glui32 *grab_temp_i_array(glui32 addr, glui32 len, int passin); static void release_temp_i_array(glui32 *arr, glui32 addr, glui32 len, int passout); static void **grab_temp_ptr_array(glui32 addr, glui32 len, int objclass, int passin); static void release_temp_ptr_array(void **arr, glui32 addr, glui32 len, int objclass, int passout); static void prepare_glk_args(char *proto, dispatch_splot_t *splot); static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, int *argnumptr, glui32 subaddress, int subpassin); static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth, int *argnumptr, glui32 subaddress, int subpassout); static char *get_game_id(void); /* init_dispatch(): Set up the class hash tables and other startup-time stuff. */ int init_dispatch() { int ix; /* What with one thing and another, this *could* be called more than once. We only need to allocate the tables once. */ if (classes) return TRUE; /* Set up the game-ID hook. (This is ifdeffed because not all Glk libraries have this call.) */ #ifdef GI_DISPA_GAME_ID_AVAILABLE gidispatch_set_game_id_hook(&get_game_id); #endif /* GI_DISPA_GAME_ID_AVAILABLE */ /* Allocate the class hash tables. */ num_classes = gidispatch_count_classes(); classes = (classtable_t **)glulx_malloc(num_classes * sizeof(classtable_t *)); if (!classes) return FALSE; for (ix=0; ix&+:#!" chars. */ static char *read_prefix(char *cx, int *isref, int *isarray, int *passin, int *passout, int *nullok, int *isretained, int *isreturn) { *isref = FALSE; *passin = FALSE; *passout = FALSE; *nullok = TRUE; *isarray = FALSE; *isretained = FALSE; *isreturn = FALSE; while (1) { if (*cx == '<') { *isref = TRUE; *passout = TRUE; } else if (*cx == '>') { *isref = TRUE; *passin = TRUE; } else if (*cx == '&') { *isref = TRUE; *passout = TRUE; *passin = TRUE; } else if (*cx == '+') { *nullok = FALSE; } else if (*cx == ':') { *isref = TRUE; *passout = TRUE; *nullok = FALSE; *isreturn = TRUE; } else if (*cx == '#') { *isarray = TRUE; } else if (*cx == '!') { *isretained = TRUE; } else { break; } cx++; } return cx; } /* prepare_glk_args(): This reads through the prototype string, and pulls Floo objects off the stack. It also works out the maximal number of gluniversal_t objects which could be used by the Glk call in question. It then allocates space for them. */ static void prepare_glk_args(char *proto, dispatch_splot_t *splot) { static gluniversal_t *garglist = NULL; static int garglist_size = 0; int ix; int numwanted, numvargswanted, maxargs; char *cx; cx = proto; numwanted = 0; while (*cx >= '0' && *cx <= '9') { numwanted = 10 * numwanted + (*cx - '0'); cx++; } splot->numwanted = numwanted; maxargs = 0; numvargswanted = 0; for (ix = 0; ix < numwanted; ix++) { int isref, passin, passout, nullok, isarray, isretained, isreturn; cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok, &isretained, &isreturn); if (isref) { maxargs += 2; } else { maxargs += 1; } if (!isreturn) { if (isarray) { numvargswanted += 2; } else { numvargswanted += 1; } } if (*cx == 'I' || *cx == 'C') { cx += 2; } else if (*cx == 'Q') { cx += 2; } else if (*cx == 'S' || *cx == 'U') { cx += 1; } else if (*cx == '[') { int refdepth, nwx; cx++; nwx = 0; while (*cx >= '0' && *cx <= '9') { nwx = 10 * nwx + (*cx - '0'); cx++; } maxargs += nwx; /* This is *only* correct because all structs contain plain values. */ refdepth = 1; while (refdepth > 0) { if (*cx == '[') refdepth++; else if (*cx == ']') refdepth--; cx++; } } else { fatal_error("Illegal format string."); } } if (*cx != ':' && *cx != '\0') fatal_error("Illegal format string."); splot->maxargs = maxargs; if (splot->numvargs != numvargswanted) fatal_error("Wrong number of arguments to Glk function."); if (garglist && garglist_size < maxargs) { glulx_free(garglist); garglist = NULL; garglist_size = 0; } if (!garglist) { garglist_size = maxargs + 16; garglist = (gluniversal_t *)glulx_malloc(garglist_size * sizeof(gluniversal_t)); } if (!garglist) fatal_error("Unable to allocate storage for Glk arguments."); splot->garglist = garglist; } /* parse_glk_args(): This long and unpleasant function translates a set of Floo objects into a gluniversal_t array. It's recursive, too, to deal with structures. */ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, int *argnumptr, glui32 subaddress, int subpassin) { char *cx; int ix, argx; int gargnum, numwanted; void *opref; gluniversal_t *garglist; glui32 *varglist; garglist = splot->garglist; varglist = splot->varglist; gargnum = *argnumptr; cx = *proto; numwanted = 0; while (*cx >= '0' && *cx <= '9') { numwanted = 10 * numwanted + (*cx - '0'); cx++; } for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) { char typeclass; int skipval; int isref, passin, passout, nullok, isarray, isretained, isreturn; cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok, &isretained, &isreturn); typeclass = *cx; cx++; skipval = FALSE; if (isref) { if (!isreturn && varglist[ix] == 0) { if (!nullok) fatal_error("Zero passed invalidly to Glk function."); garglist[gargnum].ptrflag = FALSE; gargnum++; skipval = TRUE; } else { garglist[gargnum].ptrflag = TRUE; gargnum++; } } if (!skipval) { glui32 thisval; if (typeclass == '[') { parse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passin); } else if (isarray) { /* definitely isref */ switch (typeclass) { case 'C': /* This test checks for a giant array length, which is deprecated. It displays a warning and cuts it down to something reasonable. Future releases of this interpreter may remove this test and go on to verify_array_addresses(), which treats this case as a fatal error. */ if (varglist[ix+1] > endmem || varglist[ix]+varglist[ix+1] > endmem) { nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix+1]); varglist[ix+1] = endmem - varglist[ix]; } verify_array_addresses(varglist[ix], varglist[ix+1], 1); garglist[gargnum].array = CaptureCArray(varglist[ix], varglist[ix+1], passin); gargnum++; ix++; garglist[gargnum].uint = varglist[ix]; gargnum++; cx++; break; case 'I': /* See comment above. */ if (varglist[ix+1] > endmem/4 || varglist[ix+1] > (endmem-varglist[ix])/4) { nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix+1]); varglist[ix+1] = (endmem - varglist[ix]) / 4; } verify_array_addresses(varglist[ix], varglist[ix+1], 4); garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin); gargnum++; ix++; garglist[gargnum].uint = varglist[ix]; gargnum++; cx++; break; case 'Q': /* This case was added after the giant arrays were deprecated, so we don't bother to allow for that case. We just verify the length. */ verify_array_addresses(varglist[ix], varglist[ix+1], 4); garglist[gargnum].array = CapturePtrArray(varglist[ix], varglist[ix+1], (*cx-'a'), passin); gargnum++; ix++; garglist[gargnum].uint = varglist[ix]; gargnum++; cx++; break; default: fatal_error("Illegal format string."); break; } } else { /* a plain value or a reference to one. */ if (isreturn) { thisval = 0; } else if (depth > 0) { /* Definitely not isref or isarray. */ if (subpassin) thisval = ReadStructField(subaddress, ix); else thisval = 0; } else if (isref) { if (passin) thisval = ReadMemory(varglist[ix]); else thisval = 0; } else { thisval = varglist[ix]; } switch (typeclass) { case 'I': if (*cx == 'u') garglist[gargnum].uint = (glui32)(thisval); else if (*cx == 's') garglist[gargnum].sint = (glsi32)(thisval); else fatal_error("Illegal format string."); gargnum++; cx++; break; case 'Q': if (thisval) { opref = classes_get(*cx-'a', thisval); if (!opref) { fatal_error("Reference to nonexistent Glk object."); } } else { opref = NULL; } garglist[gargnum].opaqueref = opref; gargnum++; cx++; break; case 'C': if (*cx == 'u') garglist[gargnum].uch = (unsigned char)(thisval); else if (*cx == 's') garglist[gargnum].sch = (signed char)(thisval); else if (*cx == 'n') garglist[gargnum].ch = (char)(thisval); else fatal_error("Illegal format string."); gargnum++; cx++; break; case 'S': garglist[gargnum].charstr = DecodeVMString(thisval); gargnum++; break; #ifdef GLK_MODULE_UNICODE case 'U': garglist[gargnum].unicharstr = DecodeVMUstring(thisval); gargnum++; break; #endif /* GLK_MODULE_UNICODE */ default: fatal_error("Illegal format string."); break; } } } else { /* We got a null reference, so we have to skip the format element. */ if (typeclass == '[') { int numsubwanted, refdepth; numsubwanted = 0; while (*cx >= '0' && *cx <= '9') { numsubwanted = 10 * numsubwanted + (*cx - '0'); cx++; } refdepth = 1; while (refdepth > 0) { if (*cx == '[') refdepth++; else if (*cx == ']') refdepth--; cx++; } } else if (typeclass == 'S' || typeclass == 'U') { /* leave it */ } else { cx++; if (isarray) ix++; } } } if (depth > 0) { if (*cx != ']') fatal_error("Illegal format string."); cx++; } else { if (*cx != ':' && *cx != '\0') fatal_error("Illegal format string."); } *proto = cx; *argnumptr = gargnum; } /* unparse_glk_args(): This is about the reverse of parse_glk_args(). */ static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth, int *argnumptr, glui32 subaddress, int subpassout) { char *cx; int ix, argx; int gargnum, numwanted; void *opref; gluniversal_t *garglist; glui32 *varglist; garglist = splot->garglist; varglist = splot->varglist; gargnum = *argnumptr; cx = *proto; numwanted = 0; while (*cx >= '0' && *cx <= '9') { numwanted = 10 * numwanted + (*cx - '0'); cx++; } for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) { char typeclass; int skipval; int isref, passin, passout, nullok, isarray, isretained, isreturn; cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok, &isretained, &isreturn); typeclass = *cx; cx++; skipval = FALSE; if (isref) { if (!isreturn && varglist[ix] == 0) { if (!nullok) fatal_error("Zero passed invalidly to Glk function."); garglist[gargnum].ptrflag = FALSE; gargnum++; skipval = TRUE; } else { garglist[gargnum].ptrflag = TRUE; gargnum++; } } if (!skipval) { glui32 thisval = 0; if (typeclass == '[') { unparse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passout); } else if (isarray) { /* definitely isref */ switch (typeclass) { case 'C': ReleaseCArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], passout); gargnum++; ix++; gargnum++; cx++; break; case 'I': ReleaseIArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], passout); gargnum++; ix++; gargnum++; cx++; break; case 'Q': ReleasePtrArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], (*cx-'a'), passout); gargnum++; ix++; gargnum++; cx++; break; default: fatal_error("Illegal format string."); break; } } else { /* a plain value or a reference to one. */ if (isreturn || (depth > 0 && subpassout) || (isref && passout)) { skipval = FALSE; } else { skipval = TRUE; } switch (typeclass) { case 'I': if (!skipval) { if (*cx == 'u') thisval = (glui32)garglist[gargnum].uint; else if (*cx == 's') thisval = (glui32)garglist[gargnum].sint; else fatal_error("Illegal format string."); } gargnum++; cx++; break; case 'Q': if (!skipval) { opref = garglist[gargnum].opaqueref; if (opref) { gidispatch_rock_t objrock = gidispatch_get_objrock(opref, *cx-'a'); thisval = ((classref_t *)objrock.ptr)->id; } else { thisval = 0; } } gargnum++; cx++; break; case 'C': if (!skipval) { if (*cx == 'u') thisval = (glui32)garglist[gargnum].uch; else if (*cx == 's') thisval = (glui32)garglist[gargnum].sch; else if (*cx == 'n') thisval = (glui32)garglist[gargnum].ch; else fatal_error("Illegal format string."); } gargnum++; cx++; break; case 'S': if (garglist[gargnum].charstr) ReleaseVMString(garglist[gargnum].charstr); gargnum++; break; #ifdef GLK_MODULE_UNICODE case 'U': if (garglist[gargnum].unicharstr) ReleaseVMUstring(garglist[gargnum].unicharstr); gargnum++; break; #endif /* GLK_MODULE_UNICODE */ default: fatal_error("Illegal format string."); break; } if (isreturn) { *(splot->retval) = thisval; } else if (depth > 0) { /* Definitely not isref or isarray. */ if (subpassout) WriteStructField(subaddress, ix, thisval); } else if (isref) { if (passout) WriteMemory(varglist[ix], thisval); } } } else { /* We got a null reference, so we have to skip the format element. */ if (typeclass == '[') { int numsubwanted, refdepth; numsubwanted = 0; while (*cx >= '0' && *cx <= '9') { numsubwanted = 10 * numsubwanted + (*cx - '0'); cx++; } refdepth = 1; while (refdepth > 0) { if (*cx == '[') refdepth++; else if (*cx == ']') refdepth--; cx++; } } else if (typeclass == 'S' || typeclass == 'U') { /* leave it */ } else { cx++; if (isarray) ix++; } } } if (depth > 0) { if (*cx != ']') fatal_error("Illegal format string."); cx++; } else { if (*cx != ':' && *cx != '\0') fatal_error("Illegal format string."); } *proto = cx; *argnumptr = gargnum; } /* find_stream_by_id(): This is used by some interpreter code which has to, well, find a Glk stream given its ID. */ strid_t find_stream_by_id(glui32 objid) { if (!objid) return NULL; /* Recall that class 1 ("b") is streams. */ return classes_get(gidisp_Class_Stream, objid); } /* find_id_for_window(): Return the ID of a given Glk window. */ glui32 find_id_for_window(winid_t win) { gidispatch_rock_t objrock; if (!win) return 0; objrock = gidispatch_get_objrock(win, gidisp_Class_Window); if (!objrock.ptr) return 0; return ((classref_t *)objrock.ptr)->id; } /* find_id_for_stream(): Return the ID of a given Glk stream. */ glui32 find_id_for_stream(strid_t str) { gidispatch_rock_t objrock; if (!str) return 0; objrock = gidispatch_get_objrock(str, gidisp_Class_Stream); if (!objrock.ptr) return 0; return ((classref_t *)objrock.ptr)->id; } /* find_id_for_fileref(): Return the ID of a given Glk fileref. */ glui32 find_id_for_fileref(frefid_t fref) { gidispatch_rock_t objrock; if (!fref) return 0; objrock = gidispatch_get_objrock(fref, gidisp_Class_Fileref); if (!objrock.ptr) return 0; return ((classref_t *)objrock.ptr)->id; } /* find_id_for_schannel(): Return the ID of a given Glk schannel. */ glui32 find_id_for_schannel(schanid_t schan) { gidispatch_rock_t objrock; if (!schan) return 0; objrock = gidispatch_get_objrock(schan, gidisp_Class_Schannel); if (!objrock.ptr) return 0; return ((classref_t *)objrock.ptr)->id; } /* Build a hash table to hold a set of Glk objects. */ static classtable_t *new_classtable(glui32 firstid) { int ix; classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t)); if (!ctab) return NULL; for (ix=0; ixbucket[ix] = NULL; ctab->lastid = firstid; return ctab; } /* Find a Glk object in the appropriate hash table. */ static void *classes_get(int classid, glui32 objid) { classtable_t *ctab; classref_t *cref; if (classid < 0 || classid >= num_classes) return NULL; ctab = classes[classid]; cref = ctab->bucket[objid % CLASSHASH_SIZE]; for (; cref; cref = cref->next) { if (cref->id == objid) return cref->obj; } return NULL; } /* Put a Glk object in the appropriate hash table. If origid is zero, invent a new unique ID for it. */ static classref_t *classes_put(int classid, void *obj, glui32 origid) { int bucknum; classtable_t *ctab; classref_t *cref; if (classid < 0 || classid >= num_classes) return NULL; ctab = classes[classid]; cref = (classref_t *)glulx_malloc(sizeof(classref_t)); if (!cref) return NULL; cref->obj = obj; if (!origid) { cref->id = ctab->lastid; ctab->lastid++; } else { cref->id = origid; if (ctab->lastid <= origid) ctab->lastid = origid+1; } bucknum = cref->id % CLASSHASH_SIZE; cref->bucknum = bucknum; cref->next = ctab->bucket[bucknum]; ctab->bucket[bucknum] = cref; return cref; } /* Delete a Glk object from the appropriate hash table. */ static void classes_remove(int classid, void *obj) { classtable_t *ctab; classref_t *cref; classref_t **crefp; gidispatch_rock_t objrock; if (classid < 0 || classid >= num_classes) return; ctab = classes[classid]; objrock = gidispatch_get_objrock(obj, classid); cref = objrock.ptr; if (!cref) return; crefp = &(ctab->bucket[cref->bucknum]); for (; *crefp; crefp = &((*crefp)->next)) { if ((*crefp) == cref) { *crefp = cref->next; if (!cref->obj) { nonfatal_warning("attempt to free NULL object!"); } cref->obj = NULL; cref->id = 0; cref->next = NULL; glulx_free(cref); return; } } return; } /* The object registration/unregistration callbacks that the library calls to keep the hash tables up to date. */ static gidispatch_rock_t glulxe_classtable_register(void *obj, glui32 objclass) { classref_t *cref; gidispatch_rock_t objrock; cref = classes_put(objclass, obj, 0); objrock.ptr = cref; return objrock; } static void glulxe_classtable_unregister(void *obj, glui32 objclass, gidispatch_rock_t objrock) { classes_remove(objclass, obj); } gidispatch_rock_t glulxe_classtable_register_existing(void *obj, glui32 objclass, glui32 dispid) { classref_t *cref; gidispatch_rock_t objrock; cref = classes_put(objclass, obj, dispid); objrock.ptr = cref; return objrock; } static char *grab_temp_c_array(glui32 addr, glui32 len, int passin) { arrayref_t *arref = NULL; char *arr = NULL; glui32 ix, addr2; if (len) { arr = (char *)glulx_malloc(len * sizeof(char)); arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t)); if (!arr || !arref) fatal_error("Unable to allocate space for array argument to Glk call."); arref->array = arr; arref->addr = addr; arref->elemsize = 1; arref->retained = FALSE; arref->len = len; arref->next = arrays; arrays = arref; if (passin) { for (ix=0, addr2=addr; ixnext))) { if ((*aptr)->array == arr) break; } arref = *aptr; if (!arref) fatal_error("Unable to re-find array argument in Glk call."); if (arref->addr != addr || arref->len != len) fatal_error("Mismatched array argument in Glk call."); if (arref->retained) { return; } *aptr = arref->next; arref->next = NULL; if (passout) { for (ix=0, addr2=addr; ixarray = arr; arref->addr = addr; arref->elemsize = 4; arref->retained = FALSE; arref->len = len; arref->next = arrays; arrays = arref; if (passin) { for (ix=0, addr2=addr; ixnext))) { if ((*aptr)->array == arr) break; } arref = *aptr; if (!arref) fatal_error("Unable to re-find array argument in Glk call."); if (arref->addr != addr || arref->len != len) fatal_error("Mismatched array argument in Glk call."); if (arref->retained) { return; } *aptr = arref->next; arref->next = NULL; if (passout) { for (ix=0, addr2=addr; ixarray = arr; arref->addr = addr; arref->elemsize = sizeof(void *); arref->retained = FALSE; arref->len = len; arref->next = arrays; arrays = arref; if (passin) { for (ix=0, addr2=addr; ixnext))) { if ((*aptr)->array == arr) break; } arref = *aptr; if (!arref) fatal_error("Unable to re-find array argument in Glk call."); if (arref->addr != addr || arref->len != len) fatal_error("Mismatched array argument in Glk call."); if (arref->retained) { return; } *aptr = arref->next; arref->next = NULL; if (passout) { for (ix=0, addr2=addr; ixid; } else { val = 0; } MemW4(addr2, val); } } glulx_free(arr); glulx_free(arref); } } static gidispatch_rock_t glulxe_retained_register(void *array, glui32 len, char *typecode) { gidispatch_rock_t rock; arrayref_t *arref = NULL; arrayref_t **aptr; int elemsize = 0; if (typecode[4] == 'C') elemsize = 1; else if (typecode[4] == 'I') elemsize = 4; if (!elemsize || array == NULL) { rock.ptr = NULL; return rock; } for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { if ((*aptr)->array == array) break; } arref = *aptr; if (!arref) fatal_error("Unable to re-find array argument in Glk call."); if (arref->elemsize != elemsize || arref->len != len) fatal_error("Mismatched array argument in Glk call."); arref->retained = TRUE; rock.ptr = arref; return rock; } static void glulxe_retained_unregister(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock) { arrayref_t *arref = NULL; arrayref_t **aptr; glui32 ix, addr2, val; int elemsize = 0; if (typecode[4] == 'C') elemsize = 1; else if (typecode[4] == 'I') elemsize = 4; if (!elemsize || array == NULL) { return; } for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { if ((*aptr)->array == array) break; } arref = *aptr; if (!arref) fatal_error("Unable to re-find array argument in Glk call."); if (arref != objrock.ptr) fatal_error("Mismatched array reference in Glk call."); if (!arref->retained) fatal_error("Unretained array reference in Glk call."); if (arref->elemsize != elemsize || arref->len != len) fatal_error("Mismatched array argument in Glk call."); *aptr = arref->next; arref->next = NULL; if (elemsize == 1) { for (ix=0, addr2=arref->addr; ixlen; ix++, addr2+=1) { val = ((char *)array)[ix]; MemW1(addr2, val); } } else if (elemsize == 4) { for (ix=0, addr2=arref->addr; ixlen; ix++, addr2+=4) { val = ((glui32 *)array)[ix]; MemW4(addr2, val); } } glulx_free(array); glulx_free(arref); } static long glulxe_array_locate(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref) { arrayref_t *arref = NULL; arrayref_t **aptr; int elemsize = 0; if (typecode[4] == 'C') elemsize = 1; else if (typecode[4] == 'I') elemsize = 4; if (!elemsize || array == NULL) { *elemsizeref = 0; /* No need to save the array separately */ return (unsigned char *)array - memmap; } for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { if ((*aptr)->array == array) break; } arref = *aptr; if (!arref) fatal_error("Unable to re-find array argument in array_locate."); if (arref != objrock.ptr) fatal_error("Mismatched array reference in array_locate."); if (!arref->retained) fatal_error("Unretained array reference in array_locate."); if (arref->elemsize != elemsize || arref->len != len) fatal_error("Mismatched array argument in array_locate."); *elemsizeref = arref->elemsize; return arref->addr; } static gidispatch_rock_t glulxe_array_restore(long bufkey, glui32 len, char *typecode, void **arrayref) { gidispatch_rock_t rock; int elemsize = 0; if (typecode[4] == 'C') elemsize = 1; else if (typecode[4] == 'I') elemsize = 4; if (!elemsize) { unsigned char *buf = memmap + bufkey; *arrayref = buf; rock.ptr = NULL; return rock; } if (elemsize == 1) { char *cbuf = grab_temp_c_array(bufkey, len, FALSE); rock = glulxe_retained_register(cbuf, len, typecode); *arrayref = cbuf; } else { glui32 *ubuf = grab_temp_i_array(bufkey, len, FALSE); rock = glulxe_retained_register(ubuf, len, typecode); *arrayref = ubuf; } return rock; } void set_library_select_hook(void (*func)(glui32)) { library_select_hook = func; } /* Create a string identifying this game. We use the first 64 bytes of the memory map, encoded as hex, */ static char *get_game_id() { /* This buffer gets rewritten on every call, but that's okay -- the caller is supposed to copy out the result. */ static char buf[2*64+2]; int ix, jx; if (!memmap) return NULL; for (ix=0, jx=0; ix<64; ix++) { char ch = memmap[ix]; int val = ((ch >> 4) & 0x0F); buf[jx++] = ((val < 10) ? (val + '0') : (val + 'A' - 10)); val = (ch & 0x0F); buf[jx++] = ((val < 10) ? (val + '0') : (val + 'A' - 10)); } buf[jx++] = '\0'; return buf; } glulxe/glulxdump.c000066400000000000000000000646611304145303500145560ustar00rootroot00000000000000/* glulxdump.c: Glulx game file decompiler. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ /* This program is very much a cheap hack. It is *not* a generic, neutral decompiler for any Glulx file in existence. It assumes that the Glulx file was generated by Glulx-Inform. (This, of course, is how it is able to display objects; there *are* no objects in the Glulx spec, but this program understands the tables that Inform generates.) We are making the following Inform-specific assumptions: 1. ROM contains only the header, functions, and strings, plus some padding. (In that order.) 2. All the functions and strings are in ROM, not RAM. 3. When disassembling the contents of a function, a C0, C1, E0, or E1 byte signals the start of a new object. (This will be true unless we add opcodes that start with those bytes. Which may happen someday. In fact, an opcode such as add (10) could be represented as C0000010, which would also violate this assumption, but Inform does not do this.) 4. The first object begins at the start of RAM. 5. Objects contain seven bytes worth of attributes. (This is currently hardwired in Inform, but can be changed easily by changing a #define and recompiling.) 6. Dictionary words are nine characters long, and have three two-byte fields appended. 7. Probably other stuff I forgot. This whole situation could be improved by adding a "layout convention" field, at the start of ROM, which could contain compiler-specific information about how to decompile the file. Maybe someday. */ #include #include #include /* If your system doesn't have stdint.h, you'll have to edit the typedefs below to make unsigned and signed 32-bit integers. */ #include typedef uint32_t glui32; typedef int32_t glsi32; #include "opcodes.h" /* We define our own TRUE and FALSE and NULL, because ANSI is a strange world. */ #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef NULL #define NULL 0 #endif #define Read4(ptr) \ ( (glui32)(((unsigned char *)(ptr))[0] << 24) \ | (glui32)(((unsigned char *)(ptr))[1] << 16) \ | (glui32)(((unsigned char *)(ptr))[2] << 8) \ | (glui32)(((unsigned char *)(ptr))[3])) #define Read2(ptr) \ ( (glui32)(((unsigned char *)(ptr))[0] << 8) \ | (glui32)(((unsigned char *)(ptr))[1])) #define Read1(ptr) \ ((unsigned char)(((unsigned char *)(ptr))[0])) #define Mem1(adr) (Read1(memmap+(adr))) #define Mem2(adr) (Read2(memmap+(adr))) #define Mem4(adr) (Read4(memmap+(adr))) /* The following #defines are copied directly from header.h in the inform6 distribution. */ #define nop_gc 0 #define add_gc 1 #define sub_gc 2 #define mul_gc 3 #define div_gc 4 #define mod_gc 5 #define neg_gc 6 #define bitand_gc 7 #define bitor_gc 8 #define bitxor_gc 9 #define bitnot_gc 10 #define shiftl_gc 11 #define sshiftr_gc 12 #define ushiftr_gc 13 #define jump_gc 14 #define jz_gc 15 #define jnz_gc 16 #define jeq_gc 17 #define jne_gc 18 #define jlt_gc 19 #define jge_gc 20 #define jgt_gc 21 #define jle_gc 22 #define jltu_gc 23 #define jgeu_gc 24 #define jgtu_gc 25 #define jleu_gc 26 #define call_gc 27 #define return_gc 28 #define catch_gc 29 #define throw_gc 30 #define tailcall_gc 31 #define copy_gc 32 #define copys_gc 33 #define copyb_gc 34 #define sexs_gc 35 #define sexb_gc 36 #define aload_gc 37 #define aloads_gc 38 #define aloadb_gc 39 #define aloadbit_gc 40 #define astore_gc 41 #define astores_gc 42 #define astoreb_gc 43 #define astorebit_gc 44 #define stkcount_gc 45 #define stkpeek_gc 46 #define stkswap_gc 47 #define stkroll_gc 48 #define stkcopy_gc 49 #define streamchar_gc 50 #define streamnum_gc 51 #define streamstr_gc 52 #define gestalt_gc 53 #define debugtrap_gc 54 #define getmemsize_gc 55 #define setmemsize_gc 56 #define jumpabs_gc 57 #define random_gc 58 #define setrandom_gc 59 #define quit_gc 60 #define verify_gc 61 #define restart_gc 62 #define save_gc 63 #define restore_gc 64 #define saveundo_gc 65 #define restoreundo_gc 66 #define protect_gc 67 #define glk_gc 68 #define getstringtbl_gc 69 #define setstringtbl_gc 70 #define getiosys_gc 71 #define setiosys_gc 72 #define linearsearch_gc 73 #define binarysearch_gc 74 #define linkedsearch_gc 75 #define callf_gc 76 #define callfi_gc 77 #define callfii_gc 78 #define callfiii_gc 79 #define streamunichar_gc 80 #define mzero_gc 81 #define mcopy_gc 82 #define malloc_gc 83 #define mfree_gc 84 #define accelfunc_gc 85 #define accelparam_gc 86 #define numtof_gc 87 #define ftonumz_gc 88 #define ftonumn_gc 89 #define ceil_gc 90 #define floor_gc 91 #define fadd_gc 92 #define fsub_gc 93 #define fmul_gc 94 #define fdiv_gc 95 #define fmod_gc 96 #define sqrt_gc 97 #define exp_gc 98 #define log_gc 99 #define pow_gc 100 #define sin_gc 101 #define cos_gc 102 #define tan_gc 103 #define asin_gc 104 #define acos_gc 105 #define atan_gc 106 #define atan2_gc 107 #define jfeq_gc 108 #define jfne_gc 109 #define jflt_gc 110 #define jfle_gc 111 #define jfgt_gc 112 #define jfge_gc 113 #define jisnan_gc 114 #define jisinf_gc 115 typedef struct opcode_struct { char *name; /* Lower case standard name */ long code; /* Opcode number */ int flags; /* Flags (see below) */ int op_rules; /* Any unusual operand rule applying (see below) */ int no; /* Number of operands */ } opcode_t; /* The following #defines, and the opcodes_table, are copied directly from asm.c in the inform6 distribution. */ #define St 1 /* Store (last operand is store-mode) */ #define Br 2 /* Branch (last operand is a label) */ #define Rf 4 /* "Return flag": execution never continues after this opcode (e.g., is a return or unconditional jump) */ #define St2 8 /* Store2 (second-to-last operand is store (Glulx)) */ #define GOP_Unicode 1 /* uses_unicode_features */ #define GOP_MemHeap 2 /* uses_memheap_features */ #define GOP_Acceleration 4 /* uses_acceleration_features */ #define GOP_Float 8 /* uses_float_features */ #define uchar char static opcode_t opcodes_table[] = { { (uchar *) "nop", 0x00, 0, 0, 0 }, { (uchar *) "add", 0x10, St, 0, 3 }, { (uchar *) "sub", 0x11, St, 0, 3 }, { (uchar *) "mul", 0x12, St, 0, 3 }, { (uchar *) "div", 0x13, St, 0, 3 }, { (uchar *) "mod", 0x14, St, 0, 3 }, { (uchar *) "neg", 0x15, St, 0, 2 }, { (uchar *) "bitand", 0x18, St, 0, 3 }, { (uchar *) "bitor", 0x19, St, 0, 3 }, { (uchar *) "bitxor", 0x1A, St, 0, 3 }, { (uchar *) "bitnot", 0x1B, St, 0, 2 }, { (uchar *) "shiftl", 0x1C, St, 0, 3 }, { (uchar *) "sshiftr", 0x1D, St, 0, 3 }, { (uchar *) "ushiftr", 0x1E, St, 0, 3 }, { (uchar *) "jump", 0x20, Br|Rf, 0, 1 }, { (uchar *) "jz", 0x22, Br, 0, 2 }, { (uchar *) "jnz", 0x23, Br, 0, 2 }, { (uchar *) "jeq", 0x24, Br, 0, 3 }, { (uchar *) "jne", 0x25, Br, 0, 3 }, { (uchar *) "jlt", 0x26, Br, 0, 3 }, { (uchar *) "jge", 0x27, Br, 0, 3 }, { (uchar *) "jgt", 0x28, Br, 0, 3 }, { (uchar *) "jle", 0x29, Br, 0, 3 }, { (uchar *) "jltu", 0x2A, Br, 0, 3 }, { (uchar *) "jgeu", 0x2B, Br, 0, 3 }, { (uchar *) "jgtu", 0x2C, Br, 0, 3 }, { (uchar *) "jleu", 0x2D, Br, 0, 3 }, { (uchar *) "call", 0x30, St, 0, 3 }, { (uchar *) "return", 0x31, Rf, 0, 1 }, { (uchar *) "catch", 0x32, Br|St, 0, 2 }, { (uchar *) "throw", 0x33, Rf, 0, 2 }, { (uchar *) "tailcall", 0x34, Rf, 0, 2 }, { (uchar *) "copy", 0x40, St, 0, 2 }, { (uchar *) "copys", 0x41, St, 0, 2 }, { (uchar *) "copyb", 0x42, St, 0, 2 }, { (uchar *) "sexs", 0x44, St, 0, 2 }, { (uchar *) "sexb", 0x45, St, 0, 2 }, { (uchar *) "aload", 0x48, St, 0, 3 }, { (uchar *) "aloads", 0x49, St, 0, 3 }, { (uchar *) "aloadb", 0x4A, St, 0, 3 }, { (uchar *) "aloadbit", 0x4B, St, 0, 3 }, { (uchar *) "astore", 0x4C, 0, 0, 3 }, { (uchar *) "astores", 0x4D, 0, 0, 3 }, { (uchar *) "astoreb", 0x4E, 0, 0, 3 }, { (uchar *) "astorebit", 0x4F, 0, 0, 3 }, { (uchar *) "stkcount", 0x50, St, 0, 1 }, { (uchar *) "stkpeek", 0x51, St, 0, 2 }, { (uchar *) "stkswap", 0x52, 0, 0, 0 }, { (uchar *) "stkroll", 0x53, 0, 0, 2 }, { (uchar *) "stkcopy", 0x54, 0, 0, 1 }, { (uchar *) "streamchar", 0x70, 0, 0, 1 }, { (uchar *) "streamnum", 0x71, 0, 0, 1 }, { (uchar *) "streamstr", 0x72, 0, 0, 1 }, { (uchar *) "gestalt", 0x0100, St, 0, 3 }, { (uchar *) "debugtrap", 0x0101, 0, 0, 1 }, { (uchar *) "getmemsize", 0x0102, St, 0, 1 }, { (uchar *) "setmemsize", 0x0103, St, 0, 2 }, { (uchar *) "jumpabs", 0x0104, Rf, 0, 1 }, { (uchar *) "random", 0x0110, St, 0, 2 }, { (uchar *) "setrandom", 0x0111, 0, 0, 1 }, { (uchar *) "quit", 0x0120, Rf, 0, 0 }, { (uchar *) "verify", 0x0121, St, 0, 1 }, { (uchar *) "restart", 0x0122, 0, 0, 0 }, { (uchar *) "save", 0x0123, St, 0, 2 }, { (uchar *) "restore", 0x0124, St, 0, 2 }, { (uchar *) "saveundo", 0x0125, St, 0, 1 }, { (uchar *) "restoreundo", 0x0126, St, 0, 1 }, { (uchar *) "protect", 0x0127, 0, 0, 2 }, { (uchar *) "glk", 0x0130, St, 0, 3 }, { (uchar *) "getstringtbl", 0x0140, St, 0, 1 }, { (uchar *) "setstringtbl", 0x0141, 0, 0, 1 }, { (uchar *) "getiosys", 0x0148, St|St2, 0, 2 }, { (uchar *) "setiosys", 0x0149, 0, 0, 2 }, { (uchar *) "linearsearch", 0x0150, St, 0, 8 }, { (uchar *) "binarysearch", 0x0151, St, 0, 8 }, { (uchar *) "linkedsearch", 0x0152, St, 0, 7 }, { (uchar *) "callf", 0x0160, St, 0, 2 }, { (uchar *) "callfi", 0x0161, St, 0, 3 }, { (uchar *) "callfii", 0x0162, St, 0, 4 }, { (uchar *) "callfiii", 0x0163, St, 0, 5 }, { (uchar *) "streamunichar", 0x73, 0, GOP_Unicode, 1 }, { (uchar *) "mzero", 0x170, 0, GOP_MemHeap, 2 }, { (uchar *) "mcopy", 0x171, 0, GOP_MemHeap, 3 }, { (uchar *) "malloc", 0x178, St, GOP_MemHeap, 2 }, { (uchar *) "mfree", 0x179, 0, GOP_MemHeap, 1 }, { (uchar *) "accelfunc", 0x180, 0, GOP_Acceleration, 2 }, { (uchar *) "accelparam", 0x181, 0, GOP_Acceleration, 2 }, { (uchar *) "numtof", 0x190, St, GOP_Float, 2 }, { (uchar *) "ftonumz", 0x191, St, GOP_Float, 2 }, { (uchar *) "ftonumn", 0x192, St, GOP_Float, 2 }, { (uchar *) "ceil", 0x198, St, GOP_Float, 2 }, { (uchar *) "floor", 0x199, St, GOP_Float, 2 }, { (uchar *) "fadd", 0x1A0, St, GOP_Float, 3 }, { (uchar *) "fsub", 0x1A1, St, GOP_Float, 3 }, { (uchar *) "fmul", 0x1A2, St, GOP_Float, 3 }, { (uchar *) "fdiv", 0x1A3, St, GOP_Float, 3 }, { (uchar *) "fmod", 0x1A4, St|St2, GOP_Float, 4 }, { (uchar *) "sqrt", 0x1A8, St, GOP_Float, 2 }, { (uchar *) "exp", 0x1A9, St, GOP_Float, 2 }, { (uchar *) "log", 0x1AA, St, GOP_Float, 2 }, { (uchar *) "pow", 0x1AB, St, GOP_Float, 3 }, { (uchar *) "sin", 0x1B0, St, GOP_Float, 2 }, { (uchar *) "cos", 0x1B1, St, GOP_Float, 2 }, { (uchar *) "tan", 0x1B2, St, GOP_Float, 2 }, { (uchar *) "asin", 0x1B3, St, GOP_Float, 2 }, { (uchar *) "acos", 0x1B4, St, GOP_Float, 2 }, { (uchar *) "atan", 0x1B5, St, GOP_Float, 2 }, { (uchar *) "atan2", 0x1B6, St, GOP_Float, 3 }, { (uchar *) "jfeq", 0x1C0, Br, GOP_Float, 4 }, { (uchar *) "jfne", 0x1C1, Br, GOP_Float, 4 }, { (uchar *) "jflt", 0x1C2, Br, GOP_Float, 3 }, { (uchar *) "jfle", 0x1C3, Br, GOP_Float, 3 }, { (uchar *) "jfgt", 0x1C4, Br, GOP_Float, 3 }, { (uchar *) "jfge", 0x1C5, Br, GOP_Float, 3 }, { (uchar *) "jisnan", 0x1C8, Br, GOP_Float, 2 }, { (uchar *) "jisinf", 0x1C9, Br, GOP_Float, 2 }, }; static int read_header(FILE *fl); static int findopcode(int opnum); void print_string(glui32 pos); void print_proptable(glui32 pos); void dump_ram(void); void dump_objs(void); void dump_action_table(void); void dump_dict_table(void); void dump_grammar_table(void); int dumpfuncs = FALSE; int dumpstrings = FALSE; int dumpobjs = FALSE; int dumpheader = FALSE; int dumpactiontbl = FALSE; int dumpdicttbl = FALSE; int dumpgrammartbl = FALSE; glui32 posactiontbl = 0; glui32 posdicttbl = 0; glui32 posgrammartbl = 0; unsigned char *memmap = NULL; glui32 version; glui32 ramstart; glui32 endgamefile; glui32 endmem; glui32 stacksize; glui32 startfuncaddr; glui32 stringtable; glui32 checksum; int main(int argc, char *argv[]) { FILE *fl; int ix; glui32 val; char *filename = NULL; if (argc < 2) { printf("Usage: glulxdump file\n"); exit(1); } for (ix=1; ix= argc || (val = strtol(argv[ix], NULL, 16)) == 0) { printf("-a must be followed by address.\n"); exit(1); } posactiontbl = val; dumpactiontbl = TRUE; } else if (!strcmp(argv[ix], "-d")) { ix++; if (ix >= argc || (val = strtol(argv[ix], NULL, 16)) == 0) { printf("-d must be followed by address.\n"); exit(1); } posdicttbl = val; dumpdicttbl = TRUE; } else if (!strcmp(argv[ix], "-g")) { ix++; if (ix >= argc || (val = strtol(argv[ix], NULL, 16)) == 0) { printf("-g must be followed by address.\n"); exit(1); } posgrammartbl = val; dumpgrammartbl = TRUE; } else filename = argv[ix]; } if (!filename) { printf("No file given\n"); exit(1); } if (!dumpfuncs && !dumpstrings && !dumpobjs && !dumpheader && !dumpactiontbl && !dumpdicttbl && !dumpgrammartbl) { dumpfuncs = TRUE; dumpstrings = TRUE; dumpobjs = TRUE; dumpheader = TRUE; } fl = fopen(filename, "r"); if (!fl) { perror("unable to open file"); exit(1); } ix = read_header(fl); if (!ix) { fclose(fl); exit(1); } memmap = (unsigned char *)malloc(endgamefile); rewind(fl); ix = fread(memmap, 1, endgamefile, fl); if (ix != endgamefile) { printf("File too short.\n"); fclose(fl); exit(1); } fclose(fl); fl = NULL; if (dumpheader) { printf("Version: %08lx\n", (long)version); printf("RAMSTART: %08lx\n", (long)ramstart); printf("ENDGAME: %08lx\n", (long)endgamefile); printf("ENDMEM: %08lx\n", (long)endmem); printf("STACKSIZE: %08lx\n", (long)stacksize); printf("StartFunc: %08lx\n", (long)startfuncaddr); printf("StringTbl: %08lx\n", (long)stringtable); printf("CheckSum: %08lx\n", (long)checksum); } if (dumpstrings || dumpfuncs) dump_ram(); if (dumpobjs) dump_objs(); if (dumpactiontbl) dump_action_table(); if (dumpdicttbl) dump_dict_table(); if (dumpgrammartbl) dump_grammar_table(); exit(0); } static int read_header(FILE *fl) { unsigned char buf[4 * 9]; int res; /* Read in all the size constants from the game file header. */ res = fread(buf, 1, 4 * 9, fl); if (res != 4 * 9) { printf("This file is too short.\n"); return FALSE; } if (buf[0] != 'G' || buf[1] != 'l' || buf[2] != 'u' || buf[3] != 'l') { printf("This does not appear to be a Glulx file.\n"); return FALSE; } version = Read4(buf+4); ramstart = Read4(buf+8); endgamefile = Read4(buf+12); endmem = Read4(buf+16); stacksize = Read4(buf+20); startfuncaddr = Read4(buf+24); stringtable = Read4(buf+28); checksum = Read4(buf+32); return TRUE; } void dump_ram() { glui32 pos, startpos; unsigned char ch; int jx; pos = 4 * (9+5); while (pos < ramstart) { startpos = pos; ch = Mem1(pos); if (ch == 0) { /* skip padding */ pos++; } else if (ch == 0xE0) { if (dumpstrings) { printf("String (%08lx): ", (long)startpos); pos++; while (1) { ch = Mem1(pos); pos++; if (ch == '\0') break; putchar(ch); } printf("\n"); } else { while (1) { ch = Mem1(pos); pos++; if (ch == '\0') break; } } } else if (ch == 0xC0 || ch == 0xC1) { int loctype, locnum; int opcode; opcode_t *opco; glui32 exstart; int opmodes[16]; printf("Function (%08lx), ", (long)startpos); if (ch == 0xC0) printf("stack-called:"); else printf("locals-called:"); pos++; while (1) { loctype = Mem1(pos); pos++; locnum = Mem1(pos); pos++; if (loctype == 0) break; printf(" %d %d-byte local%s,", locnum, loctype, ((locnum == 1) ? "" : "s")); } printf("\n"); ch = Mem1(pos); pos++; exstart = pos; while (ch != 0xC0 && ch != 0xC1 && ch != 0xE0) { /* wrong, but the hell with it */ /* Get the opcode */ if ((ch & 0x80) == 0) { opcode = ch; } else if ((ch & 0x40) == 0) { opcode = (ch & 0x7F); ch = Mem1(pos); pos++; opcode = (opcode << 8) | ch; } else { opcode = (ch & 0x3F); ch = Mem1(pos); pos++; opcode = (opcode << 8) | ch; ch = Mem1(pos); pos++; opcode = (opcode << 8) | ch; ch = Mem1(pos); pos++; opcode = (opcode << 8) | ch; } opco = &opcodes_table[findopcode(opcode)]; printf(" %08lx: %12s ", (long)(pos-exstart), opco->name); for (jx=0; jxno; jx+=2) { ch = Mem1(pos); pos++; opmodes[jx+0] = (ch & 0x0F); opmodes[jx+1] = ((ch >> 4) & 0x0F); } for (jx=0; jxno; jx++) { int val = 0; printf(" "); switch (opmodes[jx]) { case 0: printf("zero"); break; case 8: printf("stackptr"); break; case 1: case 5: case 9: case 13: if ((opmodes[jx] & 0x0C) == 4) printf("*"); else if ((opmodes[jx] & 0x0C) == 8) printf("Fr:"); else if ((opmodes[jx] & 0x0C) == 12) printf("*R:"); ch = Mem1(pos); pos++; val = ch; if (val & 0x80) val |= 0xFFFFFF00; printf("%02x", val & 0xFF); break; case 2: case 6: case 10: case 14: if ((opmodes[jx] & 0x0C) == 4) printf("*"); else if ((opmodes[jx] & 0x0C) == 8) printf("Fr:"); else if ((opmodes[jx] & 0x0C) == 12) printf("*R:"); ch = Mem1(pos); pos++; val = ch; if (val & 0x80) val |= 0xFFFFFF00; ch = Mem1(pos); pos++; val = (val << 8) | ch; printf("%04x", val & 0xFFFF); break; case 3: case 7: case 11: case 15: if ((opmodes[jx] & 0x0C) == 4) printf("*"); else if ((opmodes[jx] & 0x0C) == 8) printf("Fr:"); else if ((opmodes[jx] & 0x0C) == 12) printf("*R:"); ch = Mem1(pos); pos++; val = ch; ch = Mem1(pos); pos++; val = (val << 8) | ch; ch = Mem1(pos); pos++; val = (val << 8) | ch; ch = Mem1(pos); pos++; val = (val << 8) | ch; printf("%08x", val); break; } if ((opco->flags & Br) && (jx == opco->no-1)) { if (val == 0) { printf(" (rfalse)"); } else if (val == 1) { printf(" (rtrue)"); } else { printf(" (%08lx)", (long)((pos-exstart)+val-2+1)); } } } printf("\n"); ch = Mem1(pos); pos++; } pos--; } else { printf("Unknown thing.\n"); pos++; } } } static int findopcode(int opnum) { switch (opnum) { case op_nop: return nop_gc; case op_add: return add_gc; case op_sub: return sub_gc; case op_mul: return mul_gc; case op_div: return div_gc; case op_mod: return mod_gc; case op_neg: return neg_gc; case op_bitand: return bitand_gc; case op_bitor: return bitor_gc; case op_bitxor: return bitxor_gc; case op_bitnot: return bitnot_gc; case op_shiftl: return shiftl_gc; case op_sshiftr: return sshiftr_gc; case op_ushiftr: return ushiftr_gc; case op_jump: return jump_gc; case op_jz: return jz_gc; case op_jnz: return jnz_gc; case op_jeq: return jeq_gc; case op_jne: return jne_gc; case op_jlt: return jlt_gc; case op_jge: return jge_gc; case op_jgt: return jgt_gc; case op_jle: return jle_gc; case op_call: return call_gc; case op_return: return return_gc; case op_catch: return catch_gc; case op_throw: return throw_gc; case op_copy: return copy_gc; case op_copys: return copys_gc; case op_copyb: return copyb_gc; case op_sexs: return sexs_gc; case op_sexb: return sexb_gc; case op_aload: return aload_gc; case op_aloads: return aloads_gc; case op_aloadb: return aloadb_gc; case op_aloadbit: return aloadbit_gc; case op_astore: return astore_gc; case op_astores: return astores_gc; case op_astoreb: return astoreb_gc; case op_astorebit: return astorebit_gc; case op_stkcount: return stkcount_gc; case op_stkpeek: return stkpeek_gc; case op_stkswap: return stkswap_gc; case op_stkroll: return stkroll_gc; case op_stkcopy: return stkcopy_gc; case op_streamchar: return streamchar_gc; case op_streamunichar: return streamunichar_gc; case op_streamnum: return streamnum_gc; case op_streamstr: return streamstr_gc; case op_gestalt: return gestalt_gc; case op_random: return random_gc; case op_setrandom: return setrandom_gc; case op_quit: return quit_gc; case op_verify: return verify_gc; case op_restart: return restart_gc; case op_save: return save_gc; case op_restore: return restore_gc; case op_saveundo: return saveundo_gc; case op_restoreundo: return restoreundo_gc; case op_protect: return protect_gc; case op_glk: return glk_gc; default: printf("Unknown opcode %02x\n", opnum); return nop_gc; } } void dump_objs() { glui32 startpos, pos, nextstartpos, proptablepos; unsigned char ch; int ix, jx; startpos = ramstart; while (startpos) { ch = Mem1(startpos); if (ch != 0x70) { printf("Non-object in object list (%08lx)\n", (long)startpos); return; } pos = startpos+1; printf("Object (%08lx):\n", (long)startpos); printf(" attrs:"); for (ix=0; ix<7; ix++) { ch = Mem1(pos); pos++; printf(" "); for (jx=0; jx<8; jx++) { printf("%c", (ch&1) ? '1' : '0'); ch >>= 1; } } printf("\n"); for (ix=0; ix<6; ix++) { static char *labellist[6] = { "next", "name", "props", "parent", "sibling", "child" }; long val; ch = Mem1(pos); pos++; val = ch; ch = Mem1(pos); pos++; val = (val << 8) | ch; ch = Mem1(pos); pos++; val = (val << 8) | ch; ch = Mem1(pos); pos++; val = (val << 8) | ch; printf(" %7s: ", labellist[ix]); printf("%08lx", val); switch (ix) { case 0: nextstartpos = val; break; case 1: printf(" "); print_string(val); break; case 2: proptablepos = val; break; } printf("\n"); } print_proptable(proptablepos); startpos = nextstartpos; printf("\n"); } } void print_string(glui32 pos) { unsigned char ch; ch = Mem1(pos); if (ch != 0xE0) { printf("", (long)pos); return; } pos++; while (1) { ch = Mem1(pos); pos++; if (ch == '\0') return; putchar(ch); } } void print_proptable(glui32 pos) { int ix, jx; int numprops; numprops = Mem4(pos); pos += 4; printf("%d properties:\n", numprops); for (ix=0; ix http://eblong.com/zarf/glulx/index.html */ #ifndef _GLULXE_H #define _GLULXE_H /* Import definitions for glui32, glsi32, and other Glk types. */ #include "glk.h" /* We define our own TRUE and FALSE and NULL, because ANSI is a strange world. */ #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef NULL #define NULL 0 #endif /* If glk.h defined GLK_ATTRIBUTE_NORETURN, great, we'll use it. (This is a function attribute for functions that never return, e.g glk_exit().) If we have an older glk.h, that definition is missing, so we define it as a blank stub. */ #ifndef GLK_ATTRIBUTE_NORETURN #define GLK_ATTRIBUTE_NORETURN #endif /* GLK_ATTRIBUTE_NORETURN */ /* If your system does not have , you'll have to remove this include line. Then edit the definition of glui16 to make sure it's really a 16-bit unsigned integer type, and glsi16 to make sure it's really a 16-bit signed integer type. If they're not, horrible things will happen. */ #include typedef uint16_t glui16; typedef int16_t glsi16; /* Comment this definition to turn off memory-address checking. With verification on, all reads and writes to main memory will be checked to ensure they're in range. This is slower, but prevents malformed game files from crashing the interpreter. */ #define VERIFY_MEMORY_ACCESS (1) /* Uncomment this definition to permit an exception for memory-address checking for @glk and @copy opcodes that try to write to memory address 0. This was a bug in old Superglus-built game files. */ /* #define TOLERATE_SUPERGLUS_BUG (1) */ /* Uncomment this definition to turn on Glulx VM profiling. In this mode, all function calls are timed, and the timing information is written to a data file called "profile-raw". (Build note: on Linux, glibc may require you to also define _BSD_SOURCE or _DEFAULT_SOURCE or both for the timeradd() macro.) */ /* #define VM_PROFILING (1) */ /* Uncomment this definition to turn on the Glulx debugger. You should only do this when debugging facilities are desired; it slows down the interpreter. If you do, you will need to build with libxml2; see the Makefile. */ /* #define VM_DEBUGGER (1) */ /* Comment this definition to turn off floating-point support. You might need to do this if you are building on a very limited platform with no math library. */ #define FLOAT_SUPPORT (1) /* Comment this definition to not cache the original state of RAM in (real) memory. This saves some memory, but slows down save/restore/undo operations, which will have to read the original state off disk every time. */ #define SERIALIZE_CACHE_RAM (1) /* Some macros to read and write integers to memory, always in big-endian format. */ #define Read4(ptr) \ ( (glui32)(((unsigned char *)(ptr))[0] << 24) \ | (glui32)(((unsigned char *)(ptr))[1] << 16) \ | (glui32)(((unsigned char *)(ptr))[2] << 8) \ | (glui32)(((unsigned char *)(ptr))[3])) #define Read2(ptr) \ ( (glui16)(((unsigned char *)(ptr))[0] << 8) \ | (glui16)(((unsigned char *)(ptr))[1])) #define Read1(ptr) \ ((unsigned char)(((unsigned char *)(ptr))[0])) #define Write4(ptr, vl) \ (((ptr)[0] = (unsigned char)(((glui32)(vl)) >> 24)), \ ((ptr)[1] = (unsigned char)(((glui32)(vl)) >> 16)), \ ((ptr)[2] = (unsigned char)(((glui32)(vl)) >> 8)), \ ((ptr)[3] = (unsigned char)(((glui32)(vl))))) #define Write2(ptr, vl) \ (((ptr)[0] = (unsigned char)(((glui32)(vl)) >> 8)), \ ((ptr)[1] = (unsigned char)(((glui32)(vl))))) #define Write1(ptr, vl) \ (((unsigned char *)(ptr))[0] = (vl)) #if VERIFY_MEMORY_ACCESS #define Verify(adr, ln) verify_address(adr, ln) #define VerifyW(adr, ln) verify_address_write(adr, ln) #else #define Verify(adr, ln) (0) #define VerifyW(adr, ln) (0) #endif /* VERIFY_MEMORY_ACCESS */ #define Mem1(adr) (Verify(adr, 1), Read1(memmap+(adr))) #define Mem2(adr) (Verify(adr, 2), Read2(memmap+(adr))) #define Mem4(adr) (Verify(adr, 4), Read4(memmap+(adr))) #define MemW1(adr, vl) (VerifyW(adr, 1), Write1(memmap+(adr), (vl))) #define MemW2(adr, vl) (VerifyW(adr, 2), Write2(memmap+(adr), (vl))) #define MemW4(adr, vl) (VerifyW(adr, 4), Write4(memmap+(adr), (vl))) /* Macros to access values on the stack. These *must* be used with proper alignment! (That is, Stk4 and StkW4 must take addresses which are multiples of four, etc.) If the alignment rules are not followed, the program will see performance degradation or even crashes, depending on the machine CPU. */ #define Stk1(adr) \ (*((unsigned char *)(stack+(adr)))) #define Stk2(adr) \ (*((glui16 *)(stack+(adr)))) #define Stk4(adr) \ (*((glui32 *)(stack+(adr)))) #define StkW1(adr, vl) \ (*((unsigned char *)(stack+(adr))) = (unsigned char)(vl)) #define StkW2(adr, vl) \ (*((glui16 *)(stack+(adr))) = (glui16)(vl)) #define StkW4(adr, vl) \ (*((glui32 *)(stack+(adr))) = (glui32)(vl)) /* Some useful structures. */ /* oparg_t: Represents one operand value to an instruction being executed. The code in exec.c assumes that no instruction has more than MAX_OPERANDS of these. */ typedef struct oparg_struct { glui32 desttype; glui32 value; } oparg_t; #define MAX_OPERANDS (8) /* operandlist_t: Represents the operand structure of an opcode. */ typedef struct operandlist_struct { int num_ops; /* Number of operands for this opcode */ int arg_size; /* Usually 4, but can be 1 or 2 */ int *formlist; /* Array of values, either modeform_Load or modeform_Store */ } operandlist_t; #define modeform_Load (1) #define modeform_Store (2) /* Some useful globals */ extern int vm_exited_cleanly; extern strid_t gamefile; extern glui32 gamefile_start, gamefile_len; extern char *init_err, *init_err2; extern unsigned char *memmap; extern unsigned char *stack; extern glui32 ramstart; extern glui32 endgamefile; extern glui32 origendmem; extern glui32 stacksize; extern glui32 startfuncaddr; extern glui32 checksum; extern glui32 stackptr; extern glui32 frameptr; extern glui32 pc; extern glui32 origstringtable; extern glui32 stringtable; extern glui32 valstackbase; extern glui32 localsbase; extern glui32 endmem; extern glui32 protectstart, protectend; extern glui32 prevpc; extern void (*stream_char_handler)(unsigned char ch); extern void (*stream_unichar_handler)(glui32 ch); /* main.c */ extern void set_library_start_hook(void (*)(void)); extern void set_library_autorestore_hook(void (*)(void)); extern void fatal_error_handler(char *str, char *arg, int useval, glsi32 val) GLK_ATTRIBUTE_NORETURN; extern void nonfatal_warning_handler(char *str, char *arg, int useval, glsi32 val); #define fatal_error(s) (fatal_error_handler((s), NULL, FALSE, 0)) #define fatal_error_2(s1, s2) (fatal_error_handler((s1), (s2), FALSE, 0)) #define fatal_error_i(s, v) (fatal_error_handler((s), NULL, TRUE, (v))) #define nonfatal_warning(s) (nonfatal_warning_handler((s), NULL, FALSE, 0)) #define nonfatal_warning_2(s1, s2) (nonfatal_warning_handler((s1), (s2), FALSE, 0)) #define nonfatal_warning_i(s, v) (nonfatal_warning_handler((s), NULL, TRUE, (v))) /* files.c */ extern int is_gamefile_valid(void); extern int locate_gamefile(int isblorb); /* vm.c */ extern void setup_vm(void); extern void finalize_vm(void); extern void vm_restart(void); extern glui32 change_memsize(glui32 newlen, int internal); extern glui32 *pop_arguments(glui32 count, glui32 addr); extern void verify_address(glui32 addr, glui32 count); extern void verify_address_write(glui32 addr, glui32 count); extern void verify_array_addresses(glui32 addr, glui32 count, glui32 size); /* exec.c */ extern void execute_loop(void); /* operand.c */ extern operandlist_t *fast_operandlist[0x80]; extern void init_operands(void); extern operandlist_t *lookup_operandlist(glui32 opcode); extern void parse_operands(oparg_t *opargs, operandlist_t *oplist); extern void store_operand(glui32 desttype, glui32 destaddr, glui32 storeval); extern void store_operand_s(glui32 desttype, glui32 destaddr, glui32 storeval); extern void store_operand_b(glui32 desttype, glui32 destaddr, glui32 storeval); /* funcs.c */ extern void enter_function(glui32 addr, glui32 argc, glui32 *argv); extern void leave_function(void); extern void push_callstub(glui32 desttype, glui32 destaddr); extern void pop_callstub(glui32 returnvalue); extern glui32 pop_callstub_string(int *bitnum); /* string.c */ extern void stream_num(glsi32 val, int inmiddle, int charnum); extern void stream_string(glui32 addr, int inmiddle, int bitnum); extern glui32 stream_get_table(void); extern void stream_set_table(glui32 addr); extern void stream_get_iosys(glui32 *mode, glui32 *rock); extern void stream_set_iosys(glui32 mode, glui32 rock); extern char *make_temp_string(glui32 addr); extern glui32 *make_temp_ustring(glui32 addr); extern void free_temp_string(char *str); extern void free_temp_ustring(glui32 *str); /* heap.c */ extern void heap_clear(void); extern int heap_is_active(void); extern glui32 heap_get_start(void); extern glui32 heap_alloc(glui32 len); extern void heap_free(glui32 addr); extern int heap_get_summary(glui32 *valcount, glui32 **summary); extern int heap_apply_summary(glui32 valcount, glui32 *summary); extern void heap_sanity_check(void); /* serial.c */ extern int max_undo_level; extern int init_serial(void); extern void final_serial(void); extern glui32 perform_save(strid_t str); extern glui32 perform_restore(strid_t str, int fromshell); extern glui32 perform_saveundo(void); extern glui32 perform_restoreundo(void); extern glui32 perform_verify(void); /* search.c */ extern glui32 linear_search(glui32 key, glui32 keysize, glui32 start, glui32 structsize, glui32 numstructs, glui32 keyoffset, glui32 options); extern glui32 binary_search(glui32 key, glui32 keysize, glui32 start, glui32 structsize, glui32 numstructs, glui32 keyoffset, glui32 options); extern glui32 linked_search(glui32 key, glui32 keysize, glui32 start, glui32 keyoffset, glui32 nextoffset, glui32 options); /* osdepend.c */ extern void *glulx_malloc(glui32 len); extern void *glulx_realloc(void *ptr, glui32 len); extern void glulx_free(void *ptr); extern void glulx_setrandom(glui32 seed); extern glui32 glulx_random(void); extern void glulx_sort(void *addr, int count, int size, int (*comparefunc)(void *p1, void *p2)); /* gestalt.c */ extern glui32 do_gestalt(glui32 val, glui32 val2); /* glkop.c */ extern void set_library_select_hook(void (*func)(glui32)); extern int init_dispatch(void); extern glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist); extern strid_t find_stream_by_id(glui32 objid); extern glui32 find_id_for_window(winid_t win); extern glui32 find_id_for_stream(strid_t str); extern glui32 find_id_for_fileref(frefid_t fref); extern glui32 find_id_for_schannel(schanid_t schan); /* profile.c */ extern void setup_profile(strid_t stream, char *filename); extern int init_profile(void); extern void profile_set_call_counts(int flag); #if VM_PROFILING extern glui32 profile_opcount; #define profile_tick() (profile_opcount++) extern int profile_profiling_active(void); extern void profile_in(glui32 addr, glui32 stackuse, int accel); extern void profile_out(glui32 stackuse); extern void profile_fail(char *reason); extern void profile_quit(void); #else /* VM_PROFILING */ #define profile_tick() (0) #define profile_profiling_active() (0) #define profile_in(addr, stackuse, accel) (0) #define profile_out(stackuse) (0) #define profile_fail(reason) (0) #define profile_quit() (0) #endif /* VM_PROFILING */ #if VM_DEBUGGER extern unsigned long debugger_opcount; #define debugger_tick() (debugger_opcount++) extern int debugger_load_info_stream(strid_t stream); extern int debugger_load_info_chunk(strid_t stream, glui32 pos, glui32 len); extern void debugger_track_cpu(int flag); extern void debugger_set_start_trap(int flag); extern void debugger_set_quit_trap(int flag); extern void debugger_set_crash_trap(int flag); extern void debugger_check_story_file(void); extern void debugger_setup_start_state(void); extern int debugger_ever_invoked(void); extern int debugger_cmd_handler(char *cmd); extern void debugger_cycle_handler(int cycle); extern void debugger_check_func_breakpoint(glui32 addr); extern void debugger_block_and_debug(char *msg); extern void debugger_handle_crash(char *msg); extern void debugger_handle_quit(void); #else /* VM_DEBUGGER */ #define debugger_tick() (0) #define debugger_check_story_file() (0) #define debugger_setup_start_state() (0) #define debugger_check_func_breakpoint(addr) (0) #define debugger_handle_crash(msg) (0) #endif /* VM_DEBUGGER */ /* accel.c */ typedef glui32 (*acceleration_func)(glui32 argc, glui32 *argv); extern void init_accel(void); extern acceleration_func accel_find_func(glui32 index); extern acceleration_func accel_get_func(glui32 addr); extern void accel_set_func(glui32 index, glui32 addr); extern void accel_set_param(glui32 index, glui32 val); extern glui32 accel_get_param_count(void); extern glui32 accel_get_param(glui32 index); extern void accel_iterate_funcs(void (*func)(glui32 index, glui32 addr)); #ifdef FLOAT_SUPPORT /* You may have to edit the definition of gfloat32 to make sure it's really a 32-bit floating-point type. */ typedef float gfloat32; /* Uncomment this definition if your gfloat32 type is not a standard IEEE-754 single-precision (32-bit) format. Normally, Glulxe assumes that it can reinterpret-cast IEEE-754 int values into gfloat32 values. If you uncomment this, Glulxe switches to lengthier (but safer) encoding and decoding functions. */ /* #define FLOAT_NOT_NATIVE (1) */ /* float.c */ extern int init_float(void); extern glui32 encode_float(gfloat32 val); extern gfloat32 decode_float(glui32 val); /* Uncomment this definition if your powf() function does not support all the corner cases specified by C99. If you uncomment this, osdepend.c will provide a safer implementation of glulx_powf(). */ /* #define FLOAT_COMPILE_SAFER_POWF (1) */ extern gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2); #endif /* FLOAT_SUPPORT */ #endif /* _GLULXE_H */ glulxe/heap.c000066400000000000000000000263201304145303500134400ustar00rootroot00000000000000/* heap.c: Glulxe code related to the dynamic allocation heap. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" typedef struct heapblock_struct { glui32 addr; glui32 len; int isfree; struct heapblock_struct *next; struct heapblock_struct *prev; } heapblock_t; static glui32 heap_start = 0; /* zero for inactive heap */ static int alloc_count = 0; /* The heap_head/heap_tail is a doubly-linked list of blocks, both free and allocated. It is kept in address order. It should be complete -- that is, the first block starts at heap_start, and each block ends at the beginning of the next block, until the last one, which ends at endmem. (Heap_start is never the same as end_mem; if there is no heap space, then the heap is inactive and heap_start is zero.) Adjacent free blocks may be merged at heap_alloc() time. ### To make alloc more efficient, we could keep a separate free-list. To make free more efficient, we could keep a hash table of allocations. */ static heapblock_t *heap_head = NULL; static heapblock_t *heap_tail = NULL; /* heap_clear(): Set the heap state to inactive, and free the block lists. This is called when the game starts or restarts. */ void heap_clear() { while (heap_head) { heapblock_t *blo = heap_head; heap_head = blo->next; blo->next = NULL; blo->prev = NULL; glulx_free(blo); } heap_tail = NULL; if (heap_start) { glui32 res = change_memsize(heap_start, TRUE); if (res) fatal_error_i("Unable to revert memory size when deactivating heap.", heap_start); } heap_start = 0; alloc_count = 0; /* heap_sanity_check(); */ } /* heap_is_active(): Returns whether the heap is active. */ int heap_is_active() { return (heap_start != 0); } /* heap_get_start(): Returns the start address of the heap, or 0 if the heap is not active. */ glui32 heap_get_start() { return heap_start; } /* heap_alloc(): Allocate a block. If necessary, activate the heap and/or extend memory. This may not be available at all; #define FIXED_MEMSIZE if you want the interpreter to unconditionally refuse. Returns the memory address of the block, or 0 if the operation failed. */ glui32 heap_alloc(glui32 len) { heapblock_t *blo, *newblo; #ifdef FIXED_MEMSIZE return 0; #else /* FIXED_MEMSIZE */ if (len <= 0) fatal_error("Heap allocation length must be positive."); blo = heap_head; while (blo) { if (blo->isfree && blo->len >= len) break; if (!blo->isfree) { blo = blo->next; continue; } if (!blo->next || !blo->next->isfree) { blo = blo->next; continue; } /* This is a free block, but the next block in the list is also free, so we "advance" by merging rather than by going to blo->next. */ newblo = blo->next; blo->len += newblo->len; if (newblo->next) { blo->next = newblo->next; newblo->next->prev = blo; } else { blo->next = NULL; heap_tail = blo; } newblo->next = NULL; newblo->prev = NULL; glulx_free(newblo); newblo = NULL; continue; } if (!blo) { /* No free area is visible on the list. Try extending memory. How much? Double the heap size, or by 256 bytes, or by the memory length requested -- whichever is greatest. */ glui32 res; glui32 extension; glui32 oldendmem = endmem; extension = 0; if (heap_start) extension = endmem - heap_start; if (extension < len) extension = len; if (extension < 256) extension = 256; /* And it must be rounded up to a multiple of 256. */ extension = (extension + 0xFF) & (~(glui32)0xFF); res = change_memsize(endmem+extension, TRUE); if (res) return 0; /* If we just started the heap, note that. */ if (heap_start == 0) heap_start = oldendmem; if (heap_tail && heap_tail->isfree) { /* Append the new space to the last block. */ blo = heap_tail; blo->len += extension; } else { /* Append the new space to the block list, as a new block. */ newblo = glulx_malloc(sizeof(heapblock_t)); if (!newblo) fatal_error("Unable to allocate record for heap block."); newblo->addr = oldendmem; newblo->len = extension; newblo->isfree = TRUE; newblo->next = NULL; newblo->prev = NULL; if (!heap_tail) { heap_head = newblo; heap_tail = newblo; } else { blo = heap_tail; heap_tail = newblo; blo->next = newblo; newblo->prev = blo; } blo = newblo; newblo = NULL; } /* and continue forwards, using this new block (blo). */ } /* Something strange happened. */ if (!blo || !blo->isfree || blo->len < len) return 0; /* We now have a free block of size len or longer. */ if (blo->len == len) { blo->isfree = FALSE; } else { newblo = glulx_malloc(sizeof(heapblock_t)); if (!newblo) fatal_error("Unable to allocate record for heap block."); newblo->isfree = TRUE; newblo->addr = blo->addr + len; newblo->len = blo->len - len; blo->len = len; blo->isfree = FALSE; newblo->next = blo->next; if (newblo->next) newblo->next->prev = newblo; newblo->prev = blo; blo->next = newblo; if (heap_tail == blo) heap_tail = newblo; } alloc_count++; /* heap_sanity_check(); */ return blo->addr; #endif /* FIXED_MEMSIZE */ } /* heap_free(): Free a heap block. If necessary, deactivate the heap. */ void heap_free(glui32 addr) { heapblock_t *blo; for (blo = heap_head; blo; blo = blo->next) { if (blo->addr == addr) break; }; if (!blo || blo->isfree) fatal_error_i("Attempt to free unallocated address from heap.", addr); blo->isfree = TRUE; alloc_count--; if (alloc_count <= 0) { heap_clear(); } /* heap_sanity_check(); */ } /* heap_get_summary(): Create an array of words, in the VM serialization format: heap_start alloc_count addr of first block len of first block ... (Note that these are glui32 values -- native byte ordering. Also, the blocks will be in address order, which is a stricter guarantee than the VM specifies; that'll help in heap_apply_summary().) If the heap is inactive, store NULL. Return 0 for success; otherwise, the operation failed. The array returned in summary must be freed with glulx_free() after the caller uses it. */ int heap_get_summary(glui32 *valcount, glui32 **summary) { glui32 *arr, len, pos; heapblock_t *blo; *valcount = 0; *summary = NULL; if (heap_start == 0) return 0; len = 2 + 2*alloc_count; arr = glulx_malloc(len * sizeof(glui32)); if (!arr) return 1; pos = 0; arr[pos++] = heap_start; arr[pos++] = alloc_count; for (blo = heap_head; blo; blo = blo->next) { if (blo->isfree) continue; arr[pos++] = blo->addr; arr[pos++] = blo->len; } if (pos != len) fatal_error("Wrong number of active blocks in heap"); *valcount = len; *summary = arr; return 0; } /* heap_apply_summary(): Given an array of words in the above format, set up the heap to contain it. As noted above, the caller must ensure that the blocks are in address order. When this is called, the heap must be inactive. Return 0 for success. Otherwise the operation failed (and, most likely, caused a fatal error). */ int heap_apply_summary(glui32 valcount, glui32 *summary) { glui32 lx, jx, lastend; if (heap_start) fatal_error("Heap active when heap_apply_summary called"); if (valcount == 0 || summary == NULL) return 0; if (valcount == 2 && summary[0] == 0 && summary[1] == 0) return 0; #ifdef FIXED_MEMSIZE return 1; #else /* FIXED_MEMSIZE */ lx = 0; heap_start = summary[lx++]; alloc_count = summary[lx++]; for (jx=lx; jx+2= summary[jx+2]) fatal_error("Heap block summary is out of order."); } lastend = heap_start; while (lx < valcount || lastend < endmem) { heapblock_t *blo; blo = glulx_malloc(sizeof(heapblock_t)); if (!blo) fatal_error("Unable to allocate record for heap block."); if (lx >= valcount) { blo->addr = lastend; blo->len = endmem - lastend; blo->isfree = TRUE; } else { if (lastend < summary[lx]) { blo->addr = lastend; blo->len = summary[lx] - lastend; blo->isfree = TRUE; } else { blo->addr = summary[lx++]; blo->len = summary[lx++]; blo->isfree = FALSE; } } blo->prev = NULL; blo->next = NULL; if (!heap_head) { heap_head = blo; heap_tail = blo; } else { heap_tail->next = blo; blo->prev = heap_tail; heap_tail = blo; } lastend = blo->addr + blo->len; } /* heap_sanity_check(); */ return 0; #endif /* FIXED_MEMSIZE */ } #if 0 #include static void heap_dump(void); /* heap_dump(): Print out the heap list (using printf). This exists for debugging, which is why it's ifdeffed out. */ static void heap_dump() { heapblock_t *blo; if (heap_start == 0) { printf("# Heap is inactive.\n"); return; } printf("# Heap active: %d outstanding blocks\n", alloc_count); printf("# Heap start: %ld\n", heap_start); for (blo = heap_head; blo; blo = blo->next) { printf("# %s at %ld..%ld, len %ld\n", (blo->isfree ? " free" : "*used"), blo->addr, blo->addr+blo->len, blo->len); } printf("# Heap end: %ld\n", endmem); } /* heap_sanity_check(): Check the validity of the heap. Throw a fatal error if anything is wrong. */ void heap_sanity_check() { heapblock_t *blo, *last; int livecount; heap_dump(); if (heap_start == 0) { if (heap_head || heap_tail) fatal_error("Heap sanity: nonempty list when heap is inactive."); if (alloc_count) fatal_error_i("Heap sanity: outstanding blocks when heap is inactive.", alloc_count); return; } #ifdef FIXED_MEMSIZE fatal_error("Heap sanity: heap is active, but interpreter is compiled with no allocation."); #endif /* FIXED_MEMSIZE */ /* When the heap is active there may, briefly, be no heapblocks on the list. */ last = NULL; livecount = 0; for (blo = heap_head; blo; last = blo, blo = blo->next) { glui32 lastend; if (blo->prev != last) fatal_error("Heap sanity: prev pointer mismatch."); if (!last) lastend = heap_start; else lastend = last->addr + last->len; if (lastend != blo->addr) fatal_error("Heap sanity: addr+len mismatch."); if (!blo->isfree) livecount++; } if (!last) { if (heap_start != endmem) fatal_error_i("Heap sanity: empty list, but endmem is not heap start.", heap_start); if (heap_tail) fatal_error("Heap sanity: empty list, but heap tail exists."); } else { if (last->addr + last->len != endmem) fatal_error_i("Heap sanity: last block does not end at endmem.", last->addr + last->len); if (last != heap_tail) fatal_error("Heap sanity: heap tail points wrong."); } if (livecount != alloc_count) fatal_error_i("Heap sanity: wrong number of live blocks.", livecount); } #endif /* 0 */ glulxe/iosstart.h000066400000000000000000000026061304145303500144010ustar00rootroot00000000000000 #include "glk.h" #include "iosglk_startup.h" /* This structure contains VM state which is not stored in a normal save file, but which is needed for an autorestore. (The reason it's not stored in a normal save file is that it's useless unless you serialize the entire Glk state along with the VM. Glulx normally doesn't do that, but for an iOS autosave, we do.) */ typedef struct library_state_data_struct { BOOL active; glui32 protectstart, protectend; glui32 iosys_mode, iosys_rock; glui32 stringtable; NSArray *accel_params; // array of NSNumber -- manually retained! NSArray *accel_funcs; // array of GlulxAccelEntry -- manually retained! glui32 gamefiletag; NSArray *id_map_list; // array of GlkObjIdEntry -- manually retained! } library_state_data; extern void iosglk_do_autosave(glui32 eventaddr); extern void iosglk_clear_autosave(void); extern void iosglk_set_can_restart_flag(int); extern int iosglk_can_restart_cleanly(void); extern void iosglk_shut_down_process(void) GLK_ATTRIBUTE_NORETURN; @interface GlkObjIdEntry : NSObject { glui32 objclass; glui32 tag; glui32 dispid; } - (id) initWithClass:(int)objclass tag:(NSNumber *)tag id:(glui32)dispid; - (glui32) objclass; - (glui32) tag; - (glui32) dispid; @end @interface GlulxAccelEntry : NSObject { glui32 index; glui32 addr; } - (id) initWithIndex:(glui32)index addr:(glui32)addr; - (glui32) index; - (glui32) addr; @end glulxe/iosstart.m000066400000000000000000000456331304145303500144150ustar00rootroot00000000000000/* iosstart.m: iOS-specific interface code for Glulx. (Objective C) Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #import "TerpGlkViewController.h" #import "TerpGlkDelegate.h" #import "GlkLibrary.h" #import "GlkAppWrapper.h" #import "GlkWindow.h" #import "GlkStream.h" #import "GlkFileRef.h" #include "glk.h" /* This comes with the IosGlk library. */ #include "glulxe.h" #include "iosstart.h" #include "iosglk_startup.h" /* This comes with the IosGlk library. */ static library_state_data library_state; /* used by the archive/unarchive hooks */ static void iosglk_game_start(void); static void iosglk_game_autorestore(void); static void iosglk_game_select(glui32 eventaddr); static void stash_library_state(void); static void recover_library_state(void); static void free_library_state(void); static void iosglk_library_archive(NSCoder *encoder); static void iosglk_library_unarchive(NSCoder *decoder); /* This is only needed for autorestore. */ extern gidispatch_rock_t glulxe_classtable_register_existing(void *obj, glui32 objclass, glui32 dispid); static NSString *documents_dir() { /* We use an old-fashioned way of locating the Documents directory. (The NSManager method for this is iOS 4.0 and later.) */ NSArray *dirlist = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); if (!dirlist || [dirlist count] == 0) { NSLog(@"unable to locate Documents directory."); return nil; } return [dirlist objectAtIndex:0]; } /* Backtrack through the current opcode (at prevpc), and figure out whether its input arguments are on the stack or not. This will be important when setting up the saved VM state for restarting its opcode. The opmodes argument must be an array int[3]. Returns YES on success. */ static int parse_partial_operand(int *opmodes) { glui32 addr = prevpc; /* Fetch the opcode number. */ glui32 opcode = Mem1(addr); addr++; if (opcode & 0x80) { /* More than one-byte opcode. */ if (opcode & 0x40) { /* Four-byte opcode */ opcode &= 0x3F; opcode = (opcode << 8) | Mem1(addr); addr++; opcode = (opcode << 8) | Mem1(addr); addr++; opcode = (opcode << 8) | Mem1(addr); addr++; } else { /* Two-byte opcode */ opcode &= 0x7F; opcode = (opcode << 8) | Mem1(addr); addr++; } } if (opcode != 0x130) { /* op_glk */ NSLog(@"iosglk_startup_code: parsed wrong opcode: %d", opcode); return NO; } /* @glk has operands LLS. */ opmodes[0] = Mem1(addr) & 0x0F; opmodes[1] = (Mem1(addr) >> 4) & 0x0F; opmodes[2] = Mem1(addr+1) & 0x0F; return YES; } /* We don't load in the game file here. Instead, we set a hook which glk_main() will call back to do that. Why? Because of the annoying restartability of the VM under iosglk; we may finish glk_main() and then have to call it again. */ void iosglk_startup_code() { set_library_start_hook(&iosglk_game_start); set_library_autorestore_hook(&iosglk_game_autorestore); set_library_select_hook(&iosglk_game_select); max_undo_level = 32; // allow 32 undo steps #ifdef IOSGLK_EXTEND_STARTUP_CODE IOSGLK_EXTEND_STARTUP_CODE #endif // IOSGLK_EXTEND_STARTUP_CODE } /* This is the library_start_hook, which will be called every time glk_main() begins. (VM thread) */ static void iosglk_game_start() { TerpGlkViewController *glkviewc = [TerpGlkViewController singleton]; NSString *pathname = glkviewc.terpDelegate.gamePath; NSLog(@"iosglk_startup_code: game path is %@", pathname); /* Retain this, because we're assigning it to a global. (It will look like a leak to XCode's leak-profiler.) */ gamefile = [[GlkStreamFile alloc] initWithMode:filemode_Read rock:1 unicode:NO textmode:NO dirname:@"." pathname:pathname]; /* Now we have to check to see if it's a Blorb file. */ int res; unsigned char buf[12]; glk_stream_set_position(gamefile, 0, seekmode_Start); res = glk_get_buffer_stream(gamefile, (char *)buf, 12); if (!res) { init_err = "The data in this stand-alone game is too short to read."; return; } if (buf[0] == 'G' && buf[1] == 'l' && buf[2] == 'u' && buf[3] == 'l') { locate_gamefile(FALSE); } else if (buf[0] == 'F' && buf[1] == 'O' && buf[2] == 'R' && buf[3] == 'M' && buf[8] == 'I' && buf[9] == 'F' && buf[10] == 'R' && buf[11] == 'S') { locate_gamefile(TRUE); } else { init_err = "This is neither a Glulx game file nor a Blorb file which contains one."; } } /* This is the library_autorestore_hook, which will be called from glk_main() between VM setup and the beginning of the execution loop. (VM thread) */ static void iosglk_game_autorestore() { GlkLibrary *library = [GlkLibrary singleton]; NSString *dirname = documents_dir(); if (!dirname) return; NSString *gamepath = [dirname stringByAppendingPathComponent:@"autosave.glksave"]; NSString *libpath = [dirname stringByAppendingPathComponent:@"autosave.plist"]; if (![library.filemanager fileExistsAtPath:gamepath]) return; if (![library.filemanager fileExistsAtPath:libpath]) return; bzero(&library_state, sizeof(library_state)); GlkLibrary *newlib = nil; [GlkLibrary setExtraUnarchiveHook:iosglk_library_unarchive]; @try { newlib = [NSKeyedUnarchiver unarchiveObjectWithFile:libpath]; } @catch (NSException *ex) { // leave newlib as nil NSLog(@"Unable to restore autosave library: %@", ex); } [GlkLibrary setExtraUnarchiveHook:nil]; if (!newlib || !library_state.active) { /* Without a Glk state, there's no point in even trying the VM state. */ NSLog(@"library autorestore failed!"); return; } int res; GlkStreamFile *savefile = [[[GlkStreamFile alloc] initWithMode:filemode_Read rock:1 unicode:NO textmode:NO dirname:dirname pathname:gamepath] autorelease]; res = perform_restore(savefile, TRUE); glk_stream_close(savefile, nil); savefile = nil; if (res) { NSLog(@"VM autorestore failed!"); return; } pop_callstub(0); /* Annoyingly, the updateFromLibrary we're about to do will close the currently-open gamefile. We'll recover it immediately, in recover_library_state(). */ gamefile = nil; [library updateFromLibrary:newlib]; recover_library_state(); NSLog(@"autorestore succeeded."); free_library_state(); } /* This is the library_select_hook, which will be called every time glk_select() is invoked. (VM thread) */ static void iosglk_game_select(glui32 eventaddr) { glui32 lasteventtype = [GlkAppWrapper singleton].lasteventtype; //NSLog(@"### game called select, last event was %d", lasteventtype); /* Do not autosave if we've just started up, or if the last event was a rearrange event. (We get rearranges in clusters, and they don't change anything interesting anyhow.) */ if (lasteventtype == -1 || lasteventtype == evtype_Arrange) return; iosglk_do_autosave(eventaddr); } void iosglk_do_autosave(glui32 eventaddr) { GlkLibrary *library = [GlkLibrary singleton]; //NSLog(@"### attempting autosave (pc = %x, eventaddr = %x, stack = %d before stub)", prevpc, eventaddr, stackptr); /* When the save file is autorestored, the VM will restart the @glk opcode. That means that the Glk argument (the event structure address) must be waiting on the stack. Possibly also the @glk opcode's operands -- these might or might not have come off the stack. */ int res; int opmodes[3]; res = parse_partial_operand(opmodes); if (!res) return; NSString *dirname = documents_dir(); if (!dirname) return; NSString *tmpgamepath = [dirname stringByAppendingPathComponent:@"autosave-tmp.glksave"]; GlkStreamFile *savefile = [[[GlkStreamFile alloc] initWithMode:filemode_Write rock:1 unicode:NO textmode:NO dirname:dirname pathname:tmpgamepath] autorelease]; /* Push all the necessary arguments for the @glk opcode. */ glui32 origstackptr = stackptr; int stackvals = 0; /* The event structure address: */ stackvals++; if (stackptr+4 > stacksize) fatal_error("Stack overflow in autosave callstub."); StkW4(stackptr, eventaddr); stackptr += 4; if (opmodes[1] == 8) { /* The number of Glk arguments (1): */ stackvals++; if (stackptr+4 > stacksize) fatal_error("Stack overflow in autosave callstub."); StkW4(stackptr, 1); stackptr += 4; } if (opmodes[0] == 8) { /* The Glk call selector (0x00C0): */ stackvals++; if (stackptr+4 > stacksize) fatal_error("Stack overflow in autosave callstub."); StkW4(stackptr, 0x00C0); /* glk_select */ stackptr += 4; } /* Push a temporary callstub which contains the *last* PC -- the address of the @glk(select) invocation. */ if (stackptr+16 > stacksize) fatal_error("Stack overflow in autosave callstub."); StkW4(stackptr+0, 0); StkW4(stackptr+4, 0); StkW4(stackptr+8, prevpc); StkW4(stackptr+12, frameptr); stackptr += 16; res = perform_save(savefile); stackptr -= 16; // discard the temporary callstub stackptr -= 4 * stackvals; // discard the temporary arguments if (origstackptr != stackptr) fatal_error("Stack pointer mismatch in autosave"); glk_stream_close(savefile, nil); savefile = nil; if (res) { NSLog(@"VM autosave failed!"); return; } bzero(&library_state, sizeof(library_state)); stash_library_state(); /* The iosglk_library_archive hook will write out the contents of library_state. */ NSString *tmplibpath = [dirname stringByAppendingPathComponent:@"autosave-tmp.plist"]; [GlkLibrary setExtraArchiveHook:iosglk_library_archive]; res = [NSKeyedArchiver archiveRootObject:library toFile:tmplibpath]; [GlkLibrary setExtraArchiveHook:nil]; free_library_state(); if (!res) { NSLog(@"library serialize failed!"); return; } NSString *finalgamepath = [dirname stringByAppendingPathComponent:@"autosave.glksave"]; NSString *finallibpath = [dirname stringByAppendingPathComponent:@"autosave.plist"]; /* This is not really atomic, but we're already past the serious failure modes. */ [library.filemanager removeItemAtPath:finallibpath error:nil]; [library.filemanager removeItemAtPath:finalgamepath error:nil]; res = [library.filemanager moveItemAtPath:tmpgamepath toPath:finalgamepath error:nil]; if (!res) { NSLog(@"could not move game autosave to final position!"); return; } res = [library.filemanager moveItemAtPath:tmplibpath toPath:finallibpath error:nil]; if (!res) { NSLog(@"could not move library autosave to final position"); return; } } /* Delete an autosaved game, if one exists. */ void iosglk_clear_autosave() { GlkLibrary *library = [GlkLibrary singleton]; NSString *dirname = documents_dir(); if (!dirname) return; NSString *finalgamepath = [dirname stringByAppendingPathComponent:@"autosave.glksave"]; NSString *finallibpath = [dirname stringByAppendingPathComponent:@"autosave.plist"]; [library.filemanager removeItemAtPath:finallibpath error:nil]; [library.filemanager removeItemAtPath:finalgamepath error:nil]; } /* Utility function used by stash_library_state. Assumes that library_state.accel_funcs is a valid NSMutableArray. */ static void stash_one_accel_func(glui32 index, glui32 addr) { NSMutableArray *arr = (NSMutableArray *)library_state.accel_funcs; GlulxAccelEntry *ent = [[[GlulxAccelEntry alloc] initWithIndex:index addr:addr] autorelease]; [arr addObject:ent]; } /* Copy extra chunks of the VM state into the (static) library_state object. This is information needed by autosave, but not included in the regular save process. */ static void stash_library_state() { library_state.active = YES; library_state.protectstart = protectstart; library_state.protectend = protectend; stream_get_iosys(&library_state.iosys_mode, &library_state.iosys_rock); library_state.stringtable = stream_get_table(); glui32 count = accel_get_param_count(); NSMutableArray *accel_params = [NSMutableArray arrayWithCapacity:count]; library_state.accel_params = [accel_params retain]; for (int ix=0; ixapp_creator = 'gUlx'; data->startup_model = macglk_model_ChooseOrBuiltIn; data->gamefile_types = gamefile_types; data->num_gamefile_types = 2; data->savefile_type = 'IFZS'; data->datafile_type = 'UlxD'; data->gamefile = &gamefile; data->when_selected = &startup_when_selected; data->when_builtin = &startup_when_builtin; return TRUE; } static Boolean startup_when_selected(FSSpec *file, OSType filetype) { if (filetype == 'UlxG') { return locate_gamefile(FALSE); } else if (filetype == 'IFRS') { return locate_gamefile(TRUE); } else { init_err = "This is neither a Glulx game file nor a Blorb file which contains one."; return FALSE; } } static Boolean startup_when_builtin() { unsigned char buf[12]; int res; glk_stream_set_position(gamefile, 0, seekmode_Start); res = glk_get_buffer_stream(gamefile, (char *)buf, 12); if (!res) { init_err = "The data in this stand-alone game is too short to read."; return FALSE; } if (buf[0] == 'G' && buf[1] == 'l' && buf[2] == 'u' && buf[3] == 'l') { return locate_gamefile(FALSE); } else if (buf[0] == 'F' && buf[1] == 'O' && buf[2] == 'R' && buf[3] == 'M' && buf[8] == 'I' && buf[9] == 'F' && buf[10] == 'R' && buf[11] == 'S') { return locate_gamefile(TRUE); } else { init_err = "This is neither a Glulx game file nor a Blorb file which contains one."; return FALSE; } } glulxe/main.c000066400000000000000000000111721304145303500134460ustar00rootroot00000000000000/* main.c: Glulxe top-level code. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" int vm_exited_cleanly = TRUE; strid_t gamefile = NULL; /* The stream containing the Glulx file. */ glui32 gamefile_start = 0; /* The position within the stream. (This will not be zero if the Glulx file is a chunk inside a Blorb archive.) */ glui32 gamefile_len = 0; /* The length within the stream. */ char *init_err = NULL; char *init_err2 = NULL; /* The library_start_hook is called at the beginning of glk_main. This is not normally necessary -- the library can do all its setup work before calling glk_main -- but iosglk has some weird cases which require it. */ static void (*library_start_hook)(void) = NULL; /* The library_autorestore_hook is called right after the VM's initial setup. This is an appropriate time to autorestore an initial game state, if the library has that capability. (Currently, only iosglk does.) */ static void (*library_autorestore_hook)(void) = NULL; static winid_t get_error_win(void); static void stream_hexnum(glsi32 val); /* glk_main(): The top-level routine. This does everything, and consequently is very simple. */ void glk_main() { vm_exited_cleanly = FALSE; if (library_start_hook) library_start_hook(); if (init_err) { fatal_error_2(init_err, init_err2); return; } if (!is_gamefile_valid()) { /* The fatal error has already been displayed. */ return; } glulx_setrandom(0); #ifdef FLOAT_SUPPORT if (!init_float()) { return; } #endif /* FLOAT_SUPPORT */ if (!init_dispatch()) { return; } if (!init_profile()) { return; } setup_vm(); if (library_autorestore_hook) library_autorestore_hook(); execute_loop(); finalize_vm(); gamefile = NULL; gamefile_start = 0; gamefile_len = 0; init_err = NULL; vm_exited_cleanly = TRUE; profile_quit(); glk_exit(); } void set_library_start_hook(void (*func)(void)) { library_start_hook = func; } void set_library_autorestore_hook(void (*func)(void)) { library_autorestore_hook = func; } /* get_error_win(): Return a window in which to display errors. The first time this is called, it creates a new window; after that it returns the window it first created. */ static winid_t get_error_win() { static winid_t errorwin = NULL; if (!errorwin) { winid_t rootwin = glk_window_get_root(); if (!rootwin) { errorwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); } else { errorwin = glk_window_open(rootwin, winmethod_Below | winmethod_Fixed, 3, wintype_TextBuffer, 0); } if (!errorwin) errorwin = rootwin; } return errorwin; } /* fatal_error_handler(): Display an error in the error window, and then exit. */ void fatal_error_handler(char *str, char *arg, int useval, glsi32 val) { /* If the debugger is compiled in, send the error message to the debug console. This may also block for debug commands, depending on preferences. */ debugger_handle_crash(str); winid_t win = get_error_win(); if (win) { glk_set_window(win); glk_put_string("Glulxe fatal error: "); glk_put_string(str); if (arg || useval) { glk_put_string(" ("); if (arg) glk_put_string(arg); if (arg && useval) glk_put_string(" "); if (useval) stream_hexnum(val); glk_put_string(")"); } glk_put_string("\n"); } glk_exit(); } /* nonfatal_warning_handler(): Display a warning in the error window, and then continue. */ void nonfatal_warning_handler(char *str, char *arg, int useval, glsi32 val) { winid_t win = get_error_win(); if (win) { strid_t oldstr = glk_stream_get_current(); glk_set_window(win); glk_put_string("Glulxe warning: "); glk_put_string(str); if (arg || useval) { glk_put_string(" ("); if (arg) glk_put_string(arg); if (arg && useval) glk_put_string(" "); if (useval) stream_hexnum(val); glk_put_string(")"); } glk_put_string("\n"); glk_stream_set_current(oldstr); } } /* stream_hexnum(): Write a signed integer to the current Glk output stream. */ static void stream_hexnum(glsi32 val) { char buf[16]; glui32 ival; int ix; if (val == 0) { glk_put_char('0'); return; } if (val < 0) { glk_put_char('-'); ival = -val; } else { ival = val; } ix = 0; while (ival != 0) { buf[ix] = (ival % 16) + '0'; if (buf[ix] > '9') buf[ix] += ('A' - ('9' + 1)); ix++; ival /= 16; } while (ix) { ix--; glk_put_char(buf[ix]); } } glulxe/opcodes.h000066400000000000000000000074711304145303500141720ustar00rootroot00000000000000/* opcodes.h: The big list of opcode values for Glulxe. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #ifndef _OPCODES_H #define _OPCODES_H #define op_nop (0x00) #define op_add (0x10) #define op_sub (0x11) #define op_mul (0x12) #define op_div (0x13) #define op_mod (0x14) #define op_neg (0x15) #define op_bitand (0x18) #define op_bitor (0x19) #define op_bitxor (0x1A) #define op_bitnot (0x1B) #define op_shiftl (0x1C) #define op_sshiftr (0x1D) #define op_ushiftr (0x1E) #define op_jump (0x20) #define op_jz (0x22) #define op_jnz (0x23) #define op_jeq (0x24) #define op_jne (0x25) #define op_jlt (0x26) #define op_jge (0x27) #define op_jgt (0x28) #define op_jle (0x29) #define op_jltu (0x2A) #define op_jgeu (0x2B) #define op_jgtu (0x2C) #define op_jleu (0x2D) #define op_call (0x30) #define op_return (0x31) #define op_catch (0x32) #define op_throw (0x33) #define op_tailcall (0x34) #define op_copy (0x40) #define op_copys (0x41) #define op_copyb (0x42) #define op_sexs (0x44) #define op_sexb (0x45) #define op_aload (0x48) #define op_aloads (0x49) #define op_aloadb (0x4A) #define op_aloadbit (0x4B) #define op_astore (0x4C) #define op_astores (0x4D) #define op_astoreb (0x4E) #define op_astorebit (0x4F) #define op_stkcount (0x50) #define op_stkpeek (0x51) #define op_stkswap (0x52) #define op_stkroll (0x53) #define op_stkcopy (0x54) #define op_streamchar (0x70) #define op_streamnum (0x71) #define op_streamstr (0x72) #define op_streamunichar (0x73) #define op_gestalt (0x100) #define op_debugtrap (0x101) #define op_getmemsize (0x102) #define op_setmemsize (0x103) #define op_jumpabs (0x104) #define op_random (0x110) #define op_setrandom (0x111) #define op_quit (0x120) #define op_verify (0x121) #define op_restart (0x122) #define op_save (0x123) #define op_restore (0x124) #define op_saveundo (0x125) #define op_restoreundo (0x126) #define op_protect (0x127) #define op_glk (0x130) #define op_getstringtbl (0x140) #define op_setstringtbl (0x141) #define op_getiosys (0x148) #define op_setiosys (0x149) #define op_linearsearch (0x150) #define op_binarysearch (0x151) #define op_linkedsearch (0x152) #define op_callf (0x160) #define op_callfi (0x161) #define op_callfii (0x162) #define op_callfiii (0x163) #define op_mzero (0x170) #define op_mcopy (0x171) #define op_malloc (0x178) #define op_mfree (0x179) #define op_accelfunc (0x180) #define op_accelparam (0x181) #define op_numtof (0x190) #define op_ftonumz (0x191) #define op_ftonumn (0x192) #define op_ceil (0x198) #define op_floor (0x199) #define op_fadd (0x1A0) #define op_fsub (0x1A1) #define op_fmul (0x1A2) #define op_fdiv (0x1A3) #define op_fmod (0x1A4) #define op_sqrt (0x1A8) #define op_exp (0x1A9) #define op_log (0x1AA) #define op_pow (0x1AB) #define op_sin (0x1B0) #define op_cos (0x1B1) #define op_tan (0x1B2) #define op_asin (0x1B3) #define op_acos (0x1B4) #define op_atan (0x1B5) #define op_atan2 (0x1B6) #define op_jfeq (0x1C0) #define op_jfne (0x1C1) #define op_jflt (0x1C2) #define op_jfle (0x1C3) #define op_jfgt (0x1C4) #define op_jfge (0x1C5) #define op_jisnan (0x1C8) #define op_jisinf (0x1C9) #endif /* _OPCODES_H */ glulxe/operand.c000066400000000000000000000363441304145303500141620ustar00rootroot00000000000000/* operand.c: Glulxe code for instruction operands, reading and writing. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" #include "opcodes.h" /* ### We could save a few cycles per operand by generating a function for each operandlist type. */ /* fast_operandlist[]: This is a handy array in which to look up operandlists quickly. It stores the operandlists for the first 128 opcodes, which are the ones used most frequently. */ operandlist_t *fast_operandlist[0x80]; /* The actual immutable structures which lookup_operandlist() returns. */ static operandlist_t list_none = { 0, 4, NULL }; static int array_S[1] = { modeform_Store }; static operandlist_t list_S = { 1, 4, array_S }; static int array_LS[2] = { modeform_Load, modeform_Store }; static operandlist_t list_LS = { 2, 4, array_LS }; static int array_LLS[3] = { modeform_Load, modeform_Load, modeform_Store }; static operandlist_t list_LLS = { 3, 4, array_LLS }; static int array_LLLS[4] = { modeform_Load, modeform_Load, modeform_Load, modeform_Store }; static operandlist_t list_LLLS = { 4, 4, array_LLLS }; static int array_LLLLS[5] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store }; static operandlist_t list_LLLLS = { 5, 4, array_LLLLS }; /* static int array_LLLLLS[6] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store }; static operandlist_t list_LLLLLS = { 6, 4, array_LLLLLS }; */ /* not currently used */ static int array_LLLLLLS[7] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store }; static operandlist_t list_LLLLLLS = { 7, 4, array_LLLLLLS }; static int array_LLLLLLLS[8] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store }; static operandlist_t list_LLLLLLLS = { 8, 4, array_LLLLLLLS }; static int array_L[1] = { modeform_Load }; static operandlist_t list_L = { 1, 4, array_L }; static int array_LL[2] = { modeform_Load, modeform_Load }; static operandlist_t list_LL = { 2, 4, array_LL }; static int array_LLL[3] = { modeform_Load, modeform_Load, modeform_Load }; static operandlist_t list_LLL = { 3, 4, array_LLL }; static operandlist_t list_2LS = { 2, 2, array_LS }; static operandlist_t list_1LS = { 2, 1, array_LS }; static int array_LLLL[4] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load }; static operandlist_t list_LLLL = { 4, 4, array_LLLL }; static int array_SL[2] = { modeform_Store, modeform_Load }; static operandlist_t list_SL = { 2, 4, array_SL }; static int array_SS[2] = { modeform_Store, modeform_Store }; static operandlist_t list_SS = { 2, 4, array_SS }; static int array_LLSS[4] = { modeform_Load, modeform_Load, modeform_Store, modeform_Store }; static operandlist_t list_LLSS = { 4, 4, array_LLSS }; /* init_operands(): Set up the fast-lookup array of operandlists. This is called just once, when the terp starts up. */ void init_operands() { int ix; for (ix=0; ix<0x80; ix++) fast_operandlist[ix] = lookup_operandlist(ix); } /* lookup_operandlist(): Return the operandlist for a given opcode. For opcodes in the range 00..7F, it's faster to use the array fast_operandlist[]. */ operandlist_t *lookup_operandlist(glui32 opcode) { switch (opcode) { case op_nop: return &list_none; case op_add: case op_sub: case op_mul: case op_div: case op_mod: case op_bitand: case op_bitor: case op_bitxor: case op_shiftl: case op_sshiftr: case op_ushiftr: return &list_LLS; case op_neg: case op_bitnot: return &list_LS; case op_jump: case op_jumpabs: return &list_L; case op_jz: case op_jnz: return &list_LL; case op_jeq: case op_jne: case op_jlt: case op_jge: case op_jgt: case op_jle: case op_jltu: case op_jgeu: case op_jgtu: case op_jleu: return &list_LLL; case op_call: return &list_LLS; case op_return: return &list_L; case op_catch: return &list_SL; case op_throw: return &list_LL; case op_tailcall: return &list_LL; case op_sexb: case op_sexs: return &list_LS; case op_copy: return &list_LS; case op_copys: return &list_2LS; case op_copyb: return &list_1LS; case op_aload: case op_aloads: case op_aloadb: case op_aloadbit: return &list_LLS; case op_astore: case op_astores: case op_astoreb: case op_astorebit: return &list_LLL; case op_stkcount: return &list_S; case op_stkpeek: return &list_LS; case op_stkswap: return &list_none; case op_stkroll: return &list_LL; case op_stkcopy: return &list_L; case op_streamchar: case op_streamunichar: case op_streamnum: case op_streamstr: return &list_L; case op_getstringtbl: return &list_S; case op_setstringtbl: return &list_L; case op_getiosys: return &list_SS; case op_setiosys: return &list_LL; case op_random: return &list_LS; case op_setrandom: return &list_L; case op_verify: return &list_S; case op_restart: return &list_none; case op_save: case op_restore: return &list_LS; case op_saveundo: case op_restoreundo: return &list_S; case op_protect: return &list_LL; case op_quit: return &list_none; case op_gestalt: return &list_LLS; case op_debugtrap: return &list_L; case op_getmemsize: return &list_S; case op_setmemsize: return &list_LS; case op_linearsearch: return &list_LLLLLLLS; case op_binarysearch: return &list_LLLLLLLS; case op_linkedsearch: return &list_LLLLLLS; case op_glk: return &list_LLS; case op_callf: return &list_LS; case op_callfi: return &list_LLS; case op_callfii: return &list_LLLS; case op_callfiii: return &list_LLLLS; case op_mzero: return &list_LL; case op_mcopy: return &list_LLL; case op_malloc: return &list_LS; case op_mfree: return &list_L; case op_accelfunc: case op_accelparam: return &list_LL; #ifdef FLOAT_SUPPORT case op_numtof: case op_ftonumz: case op_ftonumn: case op_ceil: case op_floor: case op_sqrt: case op_exp: case op_log: return &list_LS; case op_fadd: case op_fsub: case op_fmul: case op_fdiv: case op_pow: case op_atan2: return &list_LLS; case op_fmod: return &list_LLSS; case op_sin: case op_cos: case op_tan: case op_asin: case op_acos: case op_atan: return &list_LS; case op_jfeq: case op_jfne: return &list_LLLL; case op_jflt: case op_jfle: case op_jfgt: case op_jfge: return &list_LLL; case op_jisnan: case op_jisinf: return &list_LL; #endif /* FLOAT_SUPPORT */ #ifdef GLULX_EXTEND_OPERANDS GLULX_EXTEND_OPERANDS #endif /* GLULX_EXTEND_OPERANDS */ default: return NULL; } } /* parse_operands(): Read the list of operands of an instruction, and put the values in args. This assumes that the PC is at the beginning of the operand mode list (right after an opcode number.) Upon return, the PC will be at the beginning of the next instruction. This also assumes that args points at an allocated array of MAX_OPERANDS oparg_t structures. */ void parse_operands(oparg_t *args, operandlist_t *oplist) { int ix; oparg_t *curarg; int numops = oplist->num_ops; int argsize = oplist->arg_size; glui32 modeaddr = pc; int modeval; pc += (numops+1) / 2; for (ix=0, curarg=args; ixdesttype = 0; if ((ix & 1) == 0) { modeval = Mem1(modeaddr); mode = (modeval & 0x0F); } else { mode = ((modeval >> 4) & 0x0F); modeaddr++; } if (oplist->formlist[ix] == modeform_Load) { switch (mode) { case 8: /* pop off stack */ if (stackptr < valstackbase+4) { fatal_error("Stack underflow in operand."); } stackptr -= 4; value = Stk4(stackptr); break; case 0: /* constant zero */ value = 0; break; case 1: /* one-byte constant */ /* Sign-extend from 8 bits to 32 */ value = (glsi32)(signed char)(Mem1(pc)); pc++; break; case 2: /* two-byte constant */ /* Sign-extend the first byte from 8 bits to 32; the subsequent byte must not be sign-extended. */ value = (glsi32)(signed char)(Mem1(pc)); pc++; value = (value << 8) | (glui32)(Mem1(pc)); pc++; break; case 3: /* four-byte constant */ /* Bytes must not be sign-extended. */ value = Mem4(pc); pc += 4; break; case 15: /* main memory RAM, four-byte address */ addr = Mem4(pc); addr += ramstart; pc += 4; goto MainMemAddr; case 14: /* main memory RAM, two-byte address */ addr = (glui32)Mem2(pc); addr += ramstart; pc += 2; goto MainMemAddr; case 13: /* main memory RAM, one-byte address */ addr = (glui32)(Mem1(pc)); addr += ramstart; pc++; goto MainMemAddr; case 7: /* main memory, four-byte address */ addr = Mem4(pc); pc += 4; goto MainMemAddr; case 6: /* main memory, two-byte address */ addr = (glui32)Mem2(pc); pc += 2; goto MainMemAddr; case 5: /* main memory, one-byte address */ addr = (glui32)(Mem1(pc)); pc++; /* fall through */ MainMemAddr: /* cases 5, 6, 7, 13, 14, 15 all wind up here. */ if (argsize == 4) { value = Mem4(addr); } else if (argsize == 2) { value = Mem2(addr); } else { value = Mem1(addr); } break; case 11: /* locals, four-byte address */ addr = Mem4(pc); pc += 4; goto LocalsAddr; case 10: /* locals, two-byte address */ addr = (glui32)Mem2(pc); pc += 2; goto LocalsAddr; case 9: /* locals, one-byte address */ addr = (glui32)(Mem1(pc)); pc++; /* fall through */ LocalsAddr: /* cases 9, 10, 11 all wind up here. It's illegal for addr to not be four-byte aligned, but we don't check this explicitly. A "strict mode" interpreter probably should. It's also illegal for addr to be less than zero or greater than the size of the locals segment. */ addr += localsbase; if (argsize == 4) { value = Stk4(addr); } else if (argsize == 2) { value = Stk2(addr); } else { value = Stk1(addr); } break; default: value = 0; fatal_error("Unknown addressing mode in load operand."); } curarg->value = value; } else { /* modeform_Store */ switch (mode) { case 0: /* discard value */ curarg->desttype = 0; curarg->value = 0; break; case 8: /* push on stack */ curarg->desttype = 3; curarg->value = 0; break; case 15: /* main memory RAM, four-byte address */ addr = Mem4(pc); addr += ramstart; pc += 4; goto WrMainMemAddr; case 14: /* main memory RAM, two-byte address */ addr = (glui32)Mem2(pc); addr += ramstart; pc += 2; goto WrMainMemAddr; case 13: /* main memory RAM, one-byte address */ addr = (glui32)(Mem1(pc)); addr += ramstart; pc++; goto WrMainMemAddr; case 7: /* main memory, four-byte address */ addr = Mem4(pc); pc += 4; goto WrMainMemAddr; case 6: /* main memory, two-byte address */ addr = (glui32)Mem2(pc); pc += 2; goto WrMainMemAddr; case 5: /* main memory, one-byte address */ addr = (glui32)(Mem1(pc)); pc++; /* fall through */ WrMainMemAddr: /* cases 5, 6, 7 all wind up here. */ curarg->desttype = 1; curarg->value = addr; break; case 11: /* locals, four-byte address */ addr = Mem4(pc); pc += 4; goto WrLocalsAddr; case 10: /* locals, two-byte address */ addr = (glui32)Mem2(pc); pc += 2; goto WrLocalsAddr; case 9: /* locals, one-byte address */ addr = (glui32)(Mem1(pc)); pc++; /* fall through */ WrLocalsAddr: /* cases 9, 10, 11 all wind up here. It's illegal for addr to not be four-byte aligned, but we don't check this explicitly. A "strict mode" interpreter probably should. It's also illegal for addr to be less than zero or greater than the size of the locals segment. */ curarg->desttype = 2; /* We don't add localsbase here; the store address for desttype 2 is relative to the current locals segment, not an absolute stack position. */ curarg->value = addr; break; case 1: case 2: case 3: fatal_error("Constant addressing mode in store operand."); default: fatal_error("Unknown addressing mode in store operand."); } } } } /* store_operand(): Store a result value, according to the desttype and destaddress given. This is usually used to store the result of an opcode, but it's also used by any code that pulls a call-stub off the stack. */ void store_operand(glui32 desttype, glui32 destaddr, glui32 storeval) { switch (desttype) { case 0: /* do nothing; discard the value. */ return; case 1: /* main memory. */ MemW4(destaddr, storeval); return; case 2: /* locals. */ destaddr += localsbase; StkW4(destaddr, storeval); return; case 3: /* push on stack. */ if (stackptr+4 > stacksize) { fatal_error("Stack overflow in store operand."); } StkW4(stackptr, storeval); stackptr += 4; return; default: fatal_error("Unknown destination type in store operand."); } } void store_operand_s(glui32 desttype, glui32 destaddr, glui32 storeval) { storeval &= 0xFFFF; switch (desttype) { case 0: /* do nothing; discard the value. */ return; case 1: /* main memory. */ MemW2(destaddr, storeval); return; case 2: /* locals. */ destaddr += localsbase; StkW2(destaddr, storeval); return; case 3: /* push on stack. A four-byte value is actually pushed. */ if (stackptr+4 > stacksize) { fatal_error("Stack overflow in store operand."); } StkW4(stackptr, storeval); stackptr += 4; return; default: fatal_error("Unknown destination type in store operand."); } } void store_operand_b(glui32 desttype, glui32 destaddr, glui32 storeval) { storeval &= 0xFF; switch (desttype) { case 0: /* do nothing; discard the value. */ return; case 1: /* main memory. */ MemW1(destaddr, storeval); return; case 2: /* locals. */ destaddr += localsbase; StkW1(destaddr, storeval); return; case 3: /* push on stack. A four-byte value is actually pushed. */ if (stackptr+4 > stacksize) { fatal_error("Stack overflow in store operand."); } StkW4(stackptr, storeval); stackptr += 4; return; default: fatal_error("Unknown destination type in store operand."); } } glulxe/osdepend.c000066400000000000000000000115231304145303500143230ustar00rootroot00000000000000/* osdepend.c: Glulxe platform-dependent code. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" /* This file contains definitions for platform-dependent code. Since Glk takes care of I/O, this is a short list -- memory allocation and random numbers. The Makefile (or whatever) should define OS_UNIX, or some other symbol. Code contributions welcome. */ #ifdef OS_UNIX #include #include /* Allocate a chunk of memory. */ void *glulx_malloc(glui32 len) { return malloc(len); } /* Resize a chunk of memory. This must follow ANSI rules: if the size-change fails, this must return NULL, but the original chunk must remain unchanged. */ void *glulx_realloc(void *ptr, glui32 len) { return realloc(ptr, len); } /* Deallocate a chunk of memory. */ void glulx_free(void *ptr) { free(ptr); } /* Set the random-number seed; zero means use as random a source as possible. */ void glulx_setrandom(glui32 seed) { if (seed == 0) seed = time(NULL); srandom(seed); } /* Return a random number in the range 0 to 2^32-1. */ glui32 glulx_random() { return (random() << 16) ^ random(); } #endif /* OS_UNIX */ #ifdef OS_MAC /* The Glk library uses malloc/free liberally, so we might as well also. */ #include /* Allocate a chunk of memory. */ void *glulx_malloc(glui32 len) { return malloc(len); } /* Resize a chunk of memory. This must follow ANSI rules: if the size-change fails, this must return NULL, but the original chunk must remain unchanged. */ void *glulx_realloc(void *ptr, glui32 len) { return realloc(ptr, len); } /* Deallocate a chunk of memory. */ void glulx_free(void *ptr) { free(ptr); } #define COMPILE_RANDOM_CODE static glui32 lo_random(void); static void lo_seed_random(glui32 seed); /* Return a random number in the range 0 to 2^32-1. */ glui32 glulx_random() { return (lo_random() << 16) ^ lo_random(); } /* Set the random-number seed; zero means use as random a source as possible. */ void glulx_setrandom(glui32 seed) { if (seed == 0) seed = TickCount() ^ Random(); lo_seed_random(seed); } #endif /* OS_MAC */ #ifdef WIN32 #include #include /* Allocate a chunk of memory. */ void *glulx_malloc(glui32 len) { return malloc(len); } /* Resize a chunk of memory. This must follow ANSI rules: if the size-change fails, this must return NULL, but the original chunk must remain unchanged. */ void *glulx_realloc(void *ptr, glui32 len) { return realloc(ptr, len); } /* Deallocate a chunk of memory. */ void glulx_free(void *ptr) { free(ptr); } /* Set the random-number seed; zero means use as random a source as possible. */ void glulx_setrandom(glui32 seed) { if (seed == 0) seed = time(NULL); srand(seed); } /* Return a random number in the range 0 to 2^32-1. */ glui32 glulx_random() { return (rand() << 24) ^ (rand() << 12) ^ rand(); } #endif /* WIN32 */ #ifdef COMPILE_RANDOM_CODE /* Here is a pretty standard random-number generator and seed function. */ static glui32 lo_random(void); static void lo_seed_random(glui32 seed); static glui32 rand_table[55]; /* State for the RNG. */ static int rand_index1, rand_index2; static glui32 lo_random() { rand_index1 = (rand_index1 + 1) % 55; rand_index2 = (rand_index2 + 1) % 55; rand_table[rand_index1] = rand_table[rand_index1] - rand_table[rand_index2]; return rand_table[rand_index1]; } static void lo_seed_random(glui32 seed) { glui32 k = 1; int i, loop; rand_table[54] = seed; rand_index1 = 0; rand_index2 = 31; for (i = 0; i < 55; i++) { int ii = (21 * i) % 55; rand_table[ii] = k; k = seed - k; seed = rand_table[ii]; } for (loop = 0; loop < 4; loop++) { for (i = 0; i < 55; i++) rand_table[i] = rand_table[i] - rand_table[ (1 + i + 30) % 55]; } } #endif /* COMPILE_RANDOM_CODE */ #include /* I'm putting a wrapper for qsort() here, in case I ever have to worry about a platform without it. But I am not worrying at present. */ void glulx_sort(void *addr, int count, int size, int (*comparefunc)(void *p1, void *p2)) { qsort(addr, count, size, (int (*)(const void *, const void *))comparefunc); } #ifdef FLOAT_SUPPORT #include #ifdef FLOAT_COMPILE_SAFER_POWF /* This wrapper handles all special cases, even if the underlying powf() function doesn't. */ gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2) { if (val1 == 1.0f) return 1.0f; else if ((val2 == 0.0f) || (val2 == -0.0f)) return 1.0f; else if ((val1 == -1.0f) && isinf(val2)) return 1.0f; return powf(val1, val2); } #else /* FLOAT_COMPILE_SAFER_POWF */ /* This is the standard powf() function, unaltered. */ gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2) { return powf(val1, val2); } #endif /* FLOAT_COMPILE_SAFER_POWF */ #endif /* FLOAT_SUPPORT */ glulxe/profile-analyze.py000077500000000000000000001213521304145303500160360ustar00rootroot00000000000000#!/usr/bin/python from __future__ import print_function """ This script reads in the profile-raw file generated by Glulxe profiling, and lists the ten most costly functions. (In terms of how much total time was spent inside each function. If a function calls other functions, the time spent in them is not charged to the parent; that is, a function which does nothing but call other functions will be considered uncostly.) Optionally, this script can also read the debug output of the Inform 6 compiler (or the assembly output), and use that to figure out the names of all the functions that were profiled. Both the old and new (Inform 6.33) debug file format are supported. You can also generate profiling output in the same form as dumbfrotz's Z-machine profiling output. (If that happens to be what you want.) Use the --dumbfrotz argument. Using this script is currently a nuisance. The requirements: - You must compile Glulxe with profiling (the VM_PROFILING compile-time option). - (If you want function names) you should compile your Inform 6 source using the -k switch. This generates a "gameinfo.dbg" file. - Run Glulxe, using the "--profile profile-raw" option. (Optionally, also the "--profcalls" option.) Play some of the game, and quit. This generates a data file called "profile-raw". - Run this script, giving gameinfo.dbg and profile-raw as arguments. - You can provide "--glk dispatch_dump.xml" as an optional extra argument. This file gives the names of Glk functions; it is available from https://github.com/erkyrath/glk-dev/tree/master/dispatch_dump . To sum up, in command-line form: % inform -G -k game.inf % glulxe --profile profile-raw game.ulx % python profile-analyze.py profile-raw gameinfo.dbg --glk dispatch_dump.xml You can replace the debug output with the assembly output of the Inform compiler, which you get with the -a switch. Save the output and use it instead of the debug file: % inform -G -a game.inf > game.asm % glulxe --profile profile-raw game.ulx % python profile-analyze.py profile-raw game.asm --glk dispatch_dump.xml * The output: The output will be a list of ten functions, showing how much time and how many CPU cycles each one cost. Both the function's own cost and its cost including children (the functions it calls itself) are shown. (The top function is generally @glk_$c0 (or @glk_select). This just means that the game spent a lot of time waiting for user input.) You can change how many functions are listed with the --count option. You can change the sorting criterion with the --sort option: the options are self_time, self_ops, total_time, total_ops, and call_count. * The limitations: The profiling code is not smart about VM operations that rearrange the call stack. In fact, it's downright stupid. @restart, @restore, @restoreundo, or @throw will kill the interpreter. The old debug file format (Inform 6.32 and earlier) does not work correctly with game files larger than 16 megabytes. If you leave off the "--glk dispatch_dump.xml" argument, everything will still work, but @glk function entries will be listed by number rather than by name. If you included the "--profcalls" argument when running Glulxe, the function information will include the number of times it called, and was called by, every other function. This is rather slow, so it distorts the function timing information. Only use "--profcalls" when you are interested in specific call patterns (i.e., trying to identify where a particular function is being called from). * Interactive browsing: You can explore the profiling data in more detail by running the script interactively: % python -i profile-analyze.py profile-raw game.asm --glk dispatch_dump.xml After it runs, you'll be left at a Python prompt. You might want to list functions sorted in other ways: >>> list_by('total_time') # top 10 by total time including children >>> list_by('total_ops') # top 10 by total CPU cycles including children >>> list_by('self_ops', 20) # top 20 by CPU cycles excluding children You can also dig into the data directly. The environment will contain mappings called "functions" (mapping addresses to function objects), and "function_names" (names to function objects). >>> functions[0x3c] >>> function_names['Main__'] >>> function_names['Main__'].dump() Main__: at $00003c (line 0); called 1 times 0.000067 sec (1 ops) spent executing 6.273244 sec (117578 ops) including child calls >>> function_names['Main__'].show_calls() Main__: at $00003c (line 0); called 1 times: made 1 calls to other functions: 1 to (Again, the show_calls() information is only available if you use the "--profcalls" argument when running Glulxe.) A Function object has lots of attributes: addr=INT: The VM address of the function (in hex). hexaddr=STRING: The VM address of the function in hex (as a string). name=STRING: The function's name, or '' if the function is not known (veneer functions). linenum=INT: The line number of the function from the source code, or 0 if it is not derived from source (Main__, etc). call_count=INT: The number of times the function was called. accel_count=INT: The number of times the function was called with acceleration. total_time=FLOAT: The amount of time spent during all calls to the function (in seconds, as a floating-point value). total_ops=INT: The number of opcodes executed during all calls to the function. self_time=FLOAT: The amount of time spent during all calls to the function, excluding time spent in subcalls (functions called *by* the function). self_ops=INT: The number of opcodes executed during all calls to the function, excluding time spent in subcalls. max_depth=INT: The deepest this function has been nested on the stack, during any call. max_stack_use=INT: The greatest number of words on the stack, during any call. (This is measured when the function returns, so it may not capture the peak stack usage. If a function never returns, e.g. Main__(), then this value is approximate.) (The self_time is the "cost" used for the original listing.) Note that if a function does not make any function calls, total_time will be the same as self_time (and total_ops the same as self_ops). Some of the function entries refer to special interpreter operations. (These have high addresses, outside the range of normal game files.) Functions with addresses in the 0xE0000000 range are the interpreter's output opcodes: @streamchar, @streamunichar, @streamnum, @streamstr. Functions with addresses in the 0xF0000000 range are @glk opcode calls. The number in the lower bits specifies which Glk function was called. You will always see a large self_time for function 0xF00000C0; this represents all the time spent waiting for input in glk_select(). (Both the 0xE0000000 and 0xF0000000 entries represent time spent in the Glk library, but they get there by different code paths.) The function with the lowest address is the top-level Main__() function generated by the compiler. Its total_time is the running time of the entire program; its total_ops is the number of opcodes executed by the entire program; its max_depth is zero. * Debug file browsing: If you just want to browse an Inform debug file, and bypass all the profiling stuff, just do: # python -i profile-analyze.py -d gameinfo.dbg This will leave you at a Python prompt. The global "debugfile" contains the parsed debug information, which you can browse: >>> debugfile.arrays[0] )> """ import sys, os.path import optparse import io import xml.sax from struct import unpack from chunk import Chunk popt = optparse.OptionParser(usage='profile-analyze.py [options] profile-raw [ gameinfo.dbg | game.asm ]') popt.add_option('--glk', action='store', dest='dispatchfile', metavar='DISPATCH_DUMP', help='path to dispatch_dump.xml file (optional)') popt.add_option('--dumbfrotz', action='store_true', dest='dumbfrotz', help='use dumbfrotz-compatible output format') popt.add_option('-d', '--debug', action='store_true', dest='debugonly', help='read only the debug data, no profile data') popt.add_option('-c', '--count', action='store', dest='listcount', type=int, default=10, help='list the N slowest functions') popt.add_option('-s', '--sort', action='store', dest='listsort', type=str, default='self_time', help='sort criterion for functions (self_time, self_ops, total_time, total_ops, call_count)') (opts, args) = popt.parse_args() if (not args): print('Usage: profile-analyze.py [--dumbfrotz] [--glk dispatch_dump.xml] profile-raw [ gameinfo.dbg | game.asm ]') sys.exit(1) profile_raw = None game_file_data = None if not opts.debugonly: # Normal operation profile_raw = args[0] if (not os.path.exists(profile_raw)): print('File not readable:', profile_raw) sys.exit(1) if (len(args) >= 2): game_file_data = args[1] if (not os.path.exists(game_file_data)): print('File not readable:', game_file_data) sys.exit(1) else: # Debug-only operation game_file_data = args[0] if (not os.path.exists(game_file_data)): print('File not readable:', game_file_data) sys.exit(1) if (opts.dispatchfile): if (not os.path.exists(opts.dispatchfile)): print('File not readable:', opts.dispatchfile) sys.exit(1) special_functions = { 0xE0000001: 'streamchar', 0xE0000002: 'streamunichar', 0xE0000003: 'streamnum', 0xE0000004: 'streamstr', } glk_functions = {} functions = None callcounts = None sourcemap = None class Function: def __init__(self, addr, hexaddr, attrs): self.addr = addr self.hexaddr = hexaddr val = special_functions.get(addr) if (addr >= 0xF0000000): name = glk_functions.get(addr-0xF0000000) if (not name): name = hex(addr-0xF0000000)[2:] name = '$' + name.replace('L', '') self.name = '<@glk_' + name + '>' self.special = True elif (val is None): self.name = '' self.special = False else: self.name = '<@' + val + '>' self.special = True self.linenum = 0 self.call_count = int(attrs['call_count']) self.accel_count = 0 val = attrs.get('accel_count') if (val): self.accel_count = int(val) self.total_ops = int(attrs['total_ops']) self.total_time = float(attrs['total_time']) self.self_ops = int(attrs['self_ops']) self.self_time = float(attrs['self_time']) val = attrs.get('max_depth') if (val): self.max_depth = int(val) val = attrs.get('max_stack_use') if (val): self.max_stack_use = int(val) self.incalls = {} self.outcalls = {} def __repr__(self): return '' def dump(self): print('%s:' % (self.name,)) print(' %s' % (self.get_summary(),)) print(' %.6f sec (%d ops) spent executing' % (self.self_time, self.self_ops)) print(' %.6f sec (%d ops) including child calls' % (self.total_time, self.total_ops)) def dump_dumbfrotz_style(self): percent1 = ' ' percent2 = ' ' pc1 = int(100*(float(self.self_ops)/float(ops_executed))) pc2 = int(100*(float(self.total_ops)/float(ops_executed))) if (pc1 > 0): percent1 = "%3d%%" % pc1 if (pc2 > 0): percent2 = "%3d%%" % pc2 print('%-36s %s %-10lu %s %-10lu %-10lu %-4d' % (self.name, percent1, self.self_ops, percent2, self.total_ops, self.call_count, self.max_depth)) def get_summary(self): res = 'at $%06x' % (self.addr,) if (self.linenum): res += ' (line %d)' % (self.linenum,) res += '; called %d times' % (self.call_count,) if (self.accel_count): res += ' (%d accelerated)' % (self.accel_count,) return res def show_calls(self): if not callcounts: raise Exception('Profile data did not include call counts!') print('%s:' % (self.name,)) print(' %s:' % (self.get_summary(),)) ls = list(self.incalls.items()) ls.sort() # by addr for (addr, count) in ls: func = functions.get(addr, '') print(' %d from %s' % (count, func)) ls = list(self.outcalls.items()) ls.sort() # by addr val = sum([count for (addr, count) in ls]) print(' made %d calls to other functions:' % (val,)) for (addr, count) in ls: func = functions.get(addr, '') print(' %d to %s' % (count, func)) class DispatchDumpHandler(xml.sax.handler.ContentHandler): def startElement(self, name, attrs): if (name == 'function'): addr = int(attrs['id']) glk_functions[addr] = str(attrs['name']) class ProfileRawHandler(xml.sax.handler.ContentHandler): def startElement(self, name, attrs): global functions, callcounts if (name == 'profile'): functions = {} callcounts = {} if (name == 'function'): hexaddr = attrs.get('addr') addr = int(hexaddr, 16) func = Function(addr, hexaddr, attrs) functions[addr] = func if (name == 'calls'): hexaddr = attrs.get('fromaddr') fromaddr = int(hexaddr, 16) hexaddr = attrs.get('toaddr') toaddr = int(hexaddr, 16) count = int(attrs.get('count')) callcounts[(fromaddr, toaddr)] = count class SFrameHandler: def __init__(self, tag, parent=None, depth=None, children={}, active=None, handler=None): self.tag = tag self.parent = parent self.depth = depth self.children = children self.handler = handler if active is None: active = (handler is not None) self.active = active class SFrameFrame: def __init__(self, name, attrs, depth): self.name = name self.attrs = attrs self.depth = depth self.children = None self.handler = None self.accumchar = None self.accumobj = None def final(self): self.name = None self.attrs = None self.children = None self.handler = None self.accumchar = None self.accumobj = None class SimpleXMLFrame(xml.sax.handler.ContentHandler): def __init__(self): xml.sax.handler.ContentHandler.__init__(self) self.sstack = None self.taghandlers = {} self.init() def startDocument(self): self.sstack = [] def endDocument(self): assert len(self.sstack) == 0 def startElement(self, name, attrs): parframe = None if self.sstack: parframe = self.sstack[-1] frame = SFrameFrame(name, attrs, len(self.sstack)) self.sstack.append(frame) parhan = None if parframe and parframe.children: parhan = parframe.children.get(name) if parhan is not None: if type(parhan) is list: parhan = parhan[0] frame.handler = parhan if parhan in (int, str): frame.accumchar = [] elif parhan is (): taghan = self.taghandlers[name] frame.handler = taghan frame.children = taghan.children frame.accumobj = {} elif callable(parhan): frame.handler = parhan frame.accumchar = [] else: raise Exception('unknown element handler thingie: %s' % (parhan,)) else: taghan = self.taghandlers.get(name) if taghan: active = taghan.active if active: if taghan.parent is not None: if not (parframe and parframe.name == taghan.parent): active = False if taghan.depth is not None: if not (frame.depth == taghan.depth): active = False if active: frame.handler = taghan frame.children = taghan.children frame.accumobj = {} def characters(self, data): frame = self.sstack[-1] if frame.accumchar is not None: frame.accumchar.append(data) def endElement(self, name): frame = self.sstack.pop() assert frame.name == name res = None if frame.handler is None: pass elif frame.handler is str: res = ''.join(frame.accumchar) elif frame.handler is int: val = ''.join(frame.accumchar) res = int(val.strip()) elif isinstance(frame.handler, SFrameHandler): if frame.handler.handler: res = frame.handler.handler(frame.name, frame.attrs, frame.accumobj) else: res = frame.accumobj elif callable(frame.handler): val = ''.join(frame.accumchar) res = frame.handler(frame.name, frame.attrs, val) else: raise Exception('unknown element handler thingie: %s' % (frame.handler,)) frame.final() if self.sstack and res is not None: parframe = self.sstack[-1] if parframe.accumobj is not None: if type(parframe.children.get(name)) is list: subls = parframe.accumobj.get(name) if subls: subls.append(res) else: subls = [ res ] parframe.accumobj[name] = subls else: parframe.accumobj[name] = res def handle_tag(self, tag, parent=None, depth=None, children={}, active=None, handler=None): self.taghandlers[tag] = SFrameHandler(tag, parent, depth, children, active, handler) class NewDebugFile: def __init__(self): self.sourcefiles = {} self.constants = {} self.objects = [] self.globals = [] self.arrays = [] self.functions = [] class NewDebugSourceFile: def __init__(self, index, filename): self.index = index self.filename = filename def __repr__(self): return '' % (self.index, self.filename,) class NewDebugConstant: def __init__(self, ident, value=None, sourceloc=None): self.id = ident self.value = value if not sourceloc: sourceloc = 'compiler' self.sourceloc = sourceloc def __repr__(self): return '' % (self.id, self.value, self.sourceloc) class NewDebugObject: def __init__(self, ident, value=None, sourceloc=None, artificial=False): self.id = ident self.value = value if not sourceloc: sourceloc = 'compiler' self.sourceloc = sourceloc self.artificial = '*' if artificial else '' def __repr__(self): return '' % (self.id, self.artificial, self.value, self.sourceloc) class NewDebugGlobal: def __init__(self, ident, address, sourceloc=None): self.id = ident self.address = address if not sourceloc: sourceloc = 'compiler' self.sourceloc = sourceloc def __repr__(self): return '' % (self.id, self.address, self.sourceloc) class NewDebugArray: def __init__(self, ident, address, bytecount, bytesperel, sourceloc=None): self.id = ident self.address = address self.bytecount = bytecount self.bytesperel = bytesperel self.elcount = bytecount / bytesperel if not sourceloc: sourceloc = 'compiler' self.sourceloc = sourceloc def __repr__(self): return '' % (self.id, self.elcount, self.bytesperel, self.bytecount, self.address, self.sourceloc) class NewDebugFunction: def __init__(self, ident, address, args=(), sourceloc=None, artificial=False): self.id = ident self.address = address self.args = args if not sourceloc: sourceloc = 'veneer' self.sourceloc = sourceloc self.artificial = '*' if artificial else '' def __repr__(self): return '' % (self.id, self.artificial, self.address, self.sourceloc) class NewDebugSourceLoc: def __init__(self, line, fileref=None): self.line = line self.fileref = fileref def __repr__(self): if (isinstance(self.fileref, NewDebugSourceFile)): return '' % (self.fileref.filename, self.line) elif (self.fileref): return '' % (self.fileref, self.line) else: return '' % (self.line,) class NewDebugHandler(SimpleXMLFrame): def init(self): self._debugfile = NewDebugFile() self.handle_tag('source', parent='inform-story-file', children={'given-path':str}, handler=self.handle_source_file) self.handle_tag('constant', parent='inform-story-file', children={'identifier':str, 'value':int, 'source-code-location':()}, handler=self.handle_constant) self.handle_tag('object', parent='inform-story-file', children={'identifier':self.handle_ident_artificial, 'value':int, 'source-code-location':()}, handler=self.handle_object) self.handle_tag('global-variable', parent='inform-story-file', children={'identifier':str, 'address':int, 'source-code-location':()}, handler=self.handle_global_var) self.handle_tag('array', parent='inform-story-file', children={'identifier':str, 'value':int, 'byte-count':int, 'bytes-per-element':int, 'source-code-location':()}, handler=self.handle_array) self.handle_tag('routine', parent='inform-story-file', children={'identifier':self.handle_ident_artificial, 'address':int, 'source-code-location':(), 'local-variable':[()]}, handler=self.handle_function) self.handle_tag('source-code-location', active=False, children={'line':int, 'file-index':int}, handler=self.handle_source_code_loc) self.handle_tag('local-variable', children={'identifier':str}) def debugfile(self): return self._debugfile def handle_source_file(self, name, attrs, obj): srcfile = NewDebugSourceFile(int(attrs['index']), obj['given-path']) self._debugfile.sourcefiles[srcfile.index] = srcfile def handle_constant(self, name, attrs, obj): con = NewDebugConstant(obj['identifier'], obj.get('value'), obj.get('source-code-location')) self._debugfile.constants[con.id] = con def handle_object(self, name, attrs, obj): (ident, artificial) = obj['identifier'] con = NewDebugObject(ident, obj.get('value'), obj.get('source-code-location'), artificial=artificial) self._debugfile.objects.append(con) def handle_global_var(self, name, attrs, obj): glob = NewDebugGlobal(obj['identifier'], obj['address'], obj.get('source-code-location')) self._debugfile.globals.append(glob) def handle_array(self, name, attrs, obj): arr = NewDebugArray(obj['identifier'], obj['value'], obj['byte-count'], obj['bytes-per-element'], obj.get('source-code-location')) self._debugfile.arrays.append(arr) def handle_function(self, name, attrs, obj): (ident, artificial) = obj['identifier'] args = obj.get('local-variable') if not args: args = () else: args = tuple([ loc['identifier'] for loc in args ]) func = NewDebugFunction(ident, obj['address'], args, obj.get('source-code-location'), artificial=artificial) self._debugfile.functions.append(func) def handle_ident_artificial(self, name, attrs, obj): artificial = attrs.get('artificial') return (obj.strip(), artificial) def handle_source_code_loc(self, name, attrs, obj): fileref = obj.get('file-index') if fileref is not None: srcfile = self._debugfile.sourcefiles.get(fileref) if srcfile: fileref = srcfile return NewDebugSourceLoc(obj['line'], fileref) def parse_inform_assembly(fl): global sourcemap sourcemap = {} lasttup = None while True: ln = fl.readline() if (not ln): break ln = ln.strip() ls = ln.split() if (lasttup and not ls): (linenum, funcname, addr) = lasttup sourcemap[addr] = (linenum, funcname) lasttup = None try: if (len(ls) >= 4 and ls[2] == '[' and ls[1].startswith('+')): linenum = int(ls[0]) funcname = ls[3] addr = int(ls[1][1:], 16) lasttup = (linenum, funcname, addr) except ValueError: pass class InformFunc: def __init__(self, funcnum): self.funcnum = funcnum self.name = '' self.addr = 0 self.linenum = None self.endaddr = None self.endlinenum = None self.locals = None self.seqpts = None def __repr__(self): return '' class DebugFile: def __init__(self, fl): self.files = {} self.functions = {} self.function_names = {} self.classes = [] self.objects = {} self.arrays = {} self.globals = {} self.properties = {} self.attributes = {} self.actions = {} self.fake_actions = {} self.map = {} self.header = None dat = fl.read(2) val = unpack('>H', dat)[0] if (val != 0xDEBF): raise ValueError('not an Inform debug file') dat = fl.read(2) self.debugversion = unpack('>H', dat)[0] dat = fl.read(2) self.informversion = unpack('>H', dat)[0] rectable = { 1: self.read_file_rec, 2: self.read_class_rec, 3: self.read_object_rec, 4: self.read_global_rec, 5: self.read_attr_rec, 6: self.read_prop_rec, 7: self.read_fake_action_rec, 8: self.read_action_rec, 9: self.read_header_rec, 10: self.read_lineref_rec, 11: self.read_routine_rec, 12: self.read_array_rec, 13: self.read_map_rec, 14: self.read_routine_end_rec, } while True: dat = fl.read(1) rectype = unpack('>B', dat)[0] if (rectype == 0): break recfunc = rectable.get(rectype) if (not recfunc): raise ValueError('unknown debug record type: %d' % (rectype,)) recfunc(fl) for func in self.functions.values(): self.function_names[func.name] = func def read_file_rec(self, fl): dat = fl.read(1) filenum = unpack('>B', dat)[0] includename = self.read_string(fl) realname = self.read_string(fl) self.files[filenum] = ( includename, realname ) def read_class_rec(self, fl): name = self.read_string(fl) start = self.read_linenum(fl) end = self.read_linenum(fl) self.classes.append( (name, start, end) ) def read_object_rec(self, fl): dat = fl.read(2) num = unpack('>H', dat)[0] name = self.read_string(fl) start = self.read_linenum(fl) end = self.read_linenum(fl) self.objects[num] = (name, start, end) def read_global_rec(self, fl): dat = fl.read(1) num = unpack('>B', dat)[0] name = self.read_string(fl) self.globals[num] = name def read_array_rec(self, fl): dat = fl.read(2) num = unpack('>H', dat)[0] name = self.read_string(fl) self.arrays[num] = name def read_attr_rec(self, fl): dat = fl.read(2) num = unpack('>H', dat)[0] name = self.read_string(fl) self.attributes[num] = name def read_prop_rec(self, fl): dat = fl.read(2) num = unpack('>H', dat)[0] name = self.read_string(fl) self.properties[num] = name def read_action_rec(self, fl): dat = fl.read(2) num = unpack('>H', dat)[0] name = self.read_string(fl) self.actions[num] = name def read_fake_action_rec(self, fl): dat = fl.read(2) num = unpack('>H', dat)[0] name = self.read_string(fl) self.fake_actions[num] = name def read_routine_rec(self, fl): dat = fl.read(2) funcnum = unpack('>H', dat)[0] func = self.get_function(funcnum) func.linenum = self.read_linenum(fl) dat = fl.read(3) addr = unpack('>I', b'\0'+dat)[0] func.addr = int(addr) func.name = self.read_string(fl) locals = [] while True: val = self.read_string(fl) if (not val): break locals.append(val) func.locals = locals def read_lineref_rec(self, fl): dat = fl.read(2) funcnum = unpack('>H', dat)[0] func = self.get_function(funcnum) if (not func.seqpts): func.seqpts = [] dat = fl.read(2) count = unpack('>H', dat)[0] for ix in range(count): linenum = self.read_linenum(fl) dat = fl.read(2) addr = unpack('>H', dat)[0] func.seqpts.append( (linenum, addr) ) def read_routine_end_rec(self, fl): dat = fl.read(2) funcnum = unpack('>H', dat)[0] func = self.get_function(funcnum) func.endlinenum = self.read_linenum(fl) dat = fl.read(3) addr = unpack('>I', b'\0'+dat)[0] func.endaddr = int(addr) def read_header_rec(self, fl): dat = fl.read(64) self.header = dat def read_map_rec(self, fl): while True: name = self.read_string(fl) if (not name): break dat = fl.read(3) addr = unpack('>I', b'\0'+dat)[0] addr = int(addr) self.map[name] = addr def read_linenum(self, fl): dat = fl.read(4) (funcnum, linenum, charnum) = unpack('>BHB', dat) return (funcnum, linenum, charnum) def read_string(self, fl): val = b'' while True: dat = fl.read(1) if (dat == b'\0'): return val.decode() val += dat def get_function(self, funcnum): func = self.functions.get(funcnum) if (not func): func = InformFunc(funcnum) self.functions[funcnum] = func return func # A simple I/O wrapper class: read a subrange of a (readable binary) # file. We use this to treat a Blorb chunk as a readable file. class BinaryRangeIO(io.RawIOBase): def __init__(self, file, start, length): self.file = file self.start = start self.length = length self.offset = 0 self.file.seek(self.start) def readable(self): return True def close(self): self.file = None io.RawIOBase.close(self) def tell(self): return self.offset def readinto(self, dat): count = len(dat) if count > self.length - self.offset: count = self.length - self.offset newdat = self.file.read(count) newlen = len(newdat) dat[:newlen] = newdat else: newlen = self.file.readinto(dat) self.offset += newlen return newlen def typestring(dat): return "'" + dat.decode() + "'" class BlorbChunk: def __init__(self, formchunk, typ, start, len, formtype=None): self.formchunk = formchunk self.type = typ self.start = start self.len = len self.formtype = formtype def __repr__(self): return '' % (typestring(self.type), self.start, self.len) def data(self, max=None): self.formchunk.seek(self.start) toread = self.len if (max is not None): toread = min(self.len, max) return self.formchunk.read(toread) def describe(self): if (not self.formtype): return '%s (%d bytes, start %d)' % (typestring(self.type), self.len, self.start) else: return '%s/%s (%d+8 bytes, start %d)' % (typestring(self.type), typestring(self.formtype), self.len, self.start) def blorb_find_debug_chunk(filename): file = open(filename, 'rb') formchunk = Chunk(file) formchunk = formchunk if (formchunk.getname() != b'FORM'): raise Exception('This does not appear to be a Blorb file.') formtype = formchunk.read(4) if (formtype != b'IFRS'): raise Exception('This does not appear to be a Blorb file.') chunks = [] debugchunk = None formlen = formchunk.getsize() while formchunk.tell() < formlen: chunk = Chunk(formchunk) start = formchunk.tell() size = chunk.getsize() formtype = None if chunk.getname() == b'FORM': formtype = chunk.read(4) subchunk = BlorbChunk(formchunk, chunk.getname(), start, size, formtype) chunks.append(subchunk) chunk.skip() chunk.close() for chunk in chunks: if (chunk.type == b'Dbug'): debugchunk = chunk formchunk.close() file.close() file = None return debugchunk def list_by(key='self_time', limit=10): ls = functions.values() ls.sort(lambda x1, x2: cmp(getattr(x2, key), getattr(x1, key))) for func in ls[:limit]: func.dump() # Read in the various files if (opts.dispatchfile): # Fills out the glk_functions global xml.sax.parse(opts.dispatchfile, DispatchDumpHandler()) if (profile_raw): # Fills out the functions global xml.sax.parse(profile_raw, ProfileRawHandler()) need_function_address_offset = False if (game_file_data): # Fill out the sourcemap global, by one of various methods fl = open(game_file_data, 'rb') val = fl.read(2) fl.close() if (not val): pass elif (val == b'\xde\xbf'): # Old-style Inform debug info. need_function_address_offset = True fl = open(game_file_data, 'rb') debugfile = DebugFile(fl) fl.close() sourcemap = {} for func in debugfile.functions.values(): sourcemap[func.addr] = ( func.linenum[1], func.name ) elif (val == b' ops_executed): ops_executed = func.total_ops routine_calls = routine_calls + func.call_count print('Total opcodes: %lu' % ops_executed) print('Total routine calls: %lu' % routine_calls) print('Max. stack usage: %li' % max_stack_use) print('') print('%-35s %-10s %-10s %-10s %-4s' % ('Routine', 'Ops', 'Ops(+Subs)', 'Calls', 'Nest')) for func in ls: func.dump_dumbfrotz_style() else: print('Functions that consumed the most time (excluding children):') ls = list(functions.values()) sortfunc = lambda fn:-fn.self_time if (opts.listsort in ('self_time', 'self-time', 'selftime')): sortfunc = lambda fn:-fn.self_time elif (opts.listsort in ('self_ops', 'self-ops', 'selfops')): sortfunc = lambda fn:-fn.self_ops elif (opts.listsort in ('total_time', 'total-time', 'totaltime')): sortfunc = lambda fn:-fn.total_time elif (opts.listsort in ('total_ops', 'total-ops', 'totalops')): sortfunc = lambda fn:-fn.total_ops elif (opts.listsort in ('call_count', 'call-count', 'callcount')): sortfunc = lambda fn:-fn.call_count ls.sort(key=sortfunc) for func in ls[0 : opts.listcount]: func.dump() glulxe/profile.c000066400000000000000000000313731304145303500141670ustar00rootroot00000000000000/* profile.c: Glulxe profiling functions. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ /* If compiled in, these functions maintain a collection of profiling information as the Glulx program executes. The profiling code is not smart about VM operations that rearrange the call stack. In fact, it's downright stupid. @restart, @restore, @restoreundo, or @throw will kill the interpreter. On a normal VM exit (end of top-level routine or @quit), the profiler writes out a data file, using the filename you provided with the "--profile" option. Note that if the VM exits via glk_exit(), or is interrupted, the data file will be created (empty) but never filled in. The data file is an XML file of the form ... The calls lines are only included if you use the "--profcalls" option. These simply describe the number of times any function (identified by VM address) called any other function. There will be at most one such line for each (fromaddr, toaddr) pair. The function list includes every function which was called during the program's run. Each function tag includes the following attributes: addr=HEX: The VM address of the function (in hex). call_count=INT: The number of times the function was called. accel_count=INT: The number of times the function was called with acceleration. total_time=FLOAT: The amount of time spent during all calls to the function (in seconds, as a floating-point value). total_ops=INT: The number of opcodes executed during all calls to the function. self_time=FLOAT: The amount of time spent during all calls to the function, excluding time spent in subcalls (functions called *by* the function). self_ops=INT: The number of opcodes executed during all calls to the function, excluding time spent in subcalls. max_depth=INT: The deepest this function has been nested on the stack, during any call. max_stack_use=INT: The greatest number of words on the stack, during any call. (This is measured when the function returns, so it may not capture the peak stack usage. If a function never returns, e.g. Main__(), then this value is approximate.) Note that if a function does not make any function calls, total_time will be the same as self_time (and total_ops the same as self_ops). Some of the function entries refer to special interpreter operations. (These have high addresses, outside the range of normal game files.) Functions with addresses in the 0xE0000000 range are the interpreter's output opcodes: @streamchar, @streamunichar, @streamnum, @streamstr. Functions with addresses in the 0xF0000000 range are @glk opcode calls. The number in the lower bits specifies which Glk function was called. You will always see a large self_time for function 0xF00000C0; this represents all the time spent waiting for input in glk_select(). (Both the 0xE0000000 and 0xF0000000 entries represent time spent in the Glk library, but they get there by different code paths.) The function with the lowest address is the top-level Main__() function generated by the compiler. Its total_time is the running time of the entire program; its total_ops is the number of opcodes executed by the entire program; its max_depth is zero. */ #include "glk.h" #include "glulxe.h" #if VM_PROFILING #include #include #include /* Set if the --profile switch is used. */ static int profiling_active = FALSE; static char *profiling_filename = NULL; static strid_t profiling_stream = NULL; static int profiling_call_counts = FALSE; typedef struct function_struct { glui32 addr; glui32 call_count; glui32 accel_count; glui32 entry_depth; struct timeval entry_start_time; glui32 entry_start_op; glui32 max_depth; glui32 max_stack_use; struct timeval total_time; glui32 total_ops; struct timeval self_time; glui32 self_ops; struct callcount_struct *outcalls; struct function_struct *hash_next; } function_t; typedef struct callcount_struct { glui32 toaddr; glui32 count; struct callcount_struct *next; } callcount_t; typedef struct frame_struct { struct frame_struct *parent; function_t *func; glui32 depth; struct timeval entry_time; glui32 entry_op; struct timeval children_time; glui32 children_ops; } frame_t; #define FUNC_HASH_SIZE (511) static function_t **functions = NULL; static frame_t *current_frame = NULL; /* This counter is globally visible, because the profile_tick() macro increments it. */ glui32 profile_opcount = 0; /* This is called from the setup code -- glkunix_startup_code(), for the Unix version. If called, the interpreter will keep profiling information, and write it out at shutdown time. If this is not called, the interpreter will skip all the profiling code. (Although it won't be quite as fast as if the VM_PROFILING symbol were compiled out entirely.) The arguments are a little tricky, because I developed this on Unix, but I want it to remain accessible on all platforms. Pass a writable stream object as the first argument; at game-shutdown time, the terp will write the profiling data to this object and then close it. However, if it's not convenient to open a stream in the startup code, you can simply pass a filename as the second argument. This filename will be opened according to the usual Glk data file rules, which means it may wind up in a sandboxed data directory. The filename should not contain slashes or other pathname separators. If you pass NULL for both arguments, a file called "profile-raw" will be written. */ void setup_profile(strid_t stream, char *filename) { profiling_active = TRUE; if (stream) profiling_stream = stream; else if (filename) profiling_filename = filename; else profiling_filename = "profile-raw"; } int init_profile() { int bucknum; if (!profiling_active) return TRUE; functions = (function_t **)glulx_malloc(FUNC_HASH_SIZE * sizeof(function_t *)); if (!functions) return FALSE; for (bucknum=0; bucknumaddr != addr; func = func->hash_next) { } if (!func) { func = (function_t *)glulx_malloc(sizeof(function_t)); if (!func) fatal_error("Profiler: cannot malloc function."); memset(func, 0, sizeof(function_t)); func->hash_next = functions[bucknum]; functions[bucknum] = func; func->addr = addr; func->call_count = 0; func->accel_count = 0; timerclear(&func->entry_start_time); func->entry_start_op = 0; timerclear(&func->total_time); func->total_ops = 0; timerclear(&func->self_time); func->self_ops = 0; func->max_depth = 0; func->max_stack_use = 0; func->outcalls = NULL; } return func; } static char *timeprint(struct timeval *tv, char *buf) { sprintf(buf, "%ld.%.6ld", (long)tv->tv_sec, (long)tv->tv_usec); return buf; } void profile_in(glui32 addr, glui32 stackuse, int accel) { frame_t *fra; function_t *func; struct timeval now; if (!profiling_active) return; /* printf("### IN: %lx%s\n", addr, (accel?" accel":"")); */ if (profiling_call_counts && current_frame) { function_t *parfunc = current_frame->func; callcount_t **ccref; for (ccref = &parfunc->outcalls; *ccref; ccref = &((*ccref)->next)) { if ((*ccref)->toaddr == addr) break; } if (*ccref) { (*ccref)->count += 1; } else { *ccref = glulx_malloc(sizeof(callcount_t)); (*ccref)->toaddr = addr; (*ccref)->count = 1; (*ccref)->next = NULL; } } gettimeofday(&now, NULL); func = get_function(addr); func->call_count += 1; if (accel) func->accel_count += 1; if (!func->entry_depth) { func->entry_start_time = now; func->entry_start_op = profile_opcount; } func->entry_depth += 1; if (func->max_stack_use < stackuse) func->max_stack_use = stackuse; fra = (frame_t *)glulx_malloc(sizeof(frame_t)); if (!fra) fatal_error("Profiler: cannot malloc frame."); memset(fra, 0, sizeof(frame_t)); fra->parent = current_frame; current_frame = fra; if (fra->parent) fra->depth = fra->parent->depth + 1; fra->func = func; fra->entry_time = now; fra->entry_op = profile_opcount; timerclear(&fra->children_time); fra->children_ops = 0; } void profile_out(glui32 stackuse) { frame_t *fra; function_t *func; struct timeval now, runtime; glui32 runops; if (!profiling_active) return; /* printf("### OUT\n"); */ if (!current_frame) fatal_error("Profiler: stack underflow."); gettimeofday(&now, NULL); fra = current_frame; func = fra->func; timersub(&now, &fra->entry_time, &runtime); runops = profile_opcount - fra->entry_op; timeradd(&runtime, &func->self_time, &func->self_time); timersub(&func->self_time, &fra->children_time, &func->self_time); func->self_ops += runops; func->self_ops -= fra->children_ops; if (func->max_depth < fra->depth) func->max_depth = fra->depth; if (func->max_stack_use < stackuse) func->max_stack_use = stackuse; if (fra->parent) { timeradd(&runtime, &fra->parent->children_time, &fra->parent->children_time); fra->parent->children_ops += runops; } if (!func->entry_depth) fatal_error("Profiler: function entry underflow."); func->entry_depth -= 1; if (!func->entry_depth) { timersub(&now, &func->entry_start_time, &runtime); timerclear(&func->entry_start_time); runops = profile_opcount - func->entry_start_op; func->entry_start_op = 0; timeradd(&runtime, &func->total_time, &func->total_time); func->total_ops += runops; } current_frame = fra->parent; fra->parent = NULL; glulx_free(fra); } /* ### throw/catch */ /* ### restore/restore_undo/restart */ void profile_fail(char *reason) { if (!profiling_active) return; fatal_error_2("Profiler: unable to handle operation", reason); } void profile_quit() { int bucknum; function_t *func; char linebuf[512]; strid_t profstr; if (!profiling_active) return; while (current_frame) { profile_out(0); } if (profiling_stream) { profstr = profiling_stream; } else if (profiling_filename) { frefid_t profref = glk_fileref_create_by_name(fileusage_BinaryMode|fileusage_Data, profiling_filename, 0); if (!profref) fatal_error_2("Profiler: unable to create profile output fileref", profiling_filename); profstr = glk_stream_open_file(profref, filemode_Write, 0); } else { fatal_error("Profiler: no profile output handle!"); } glk_put_string_stream(profstr, "\n"); for (bucknum=0; bucknumhash_next) { /* ### sprintf(linebuf, "function %lx called %ld times, total ops %ld, total time %s, self ops %ld, self time %s\n", func->addr, func->call_count, func->total_ops, timeprint(&func->total_time, total_buf), func->self_ops, timeprint(&func->self_time, self_buf)); ### */ sprintf(linebuf, " \n", (unsigned long)func->addr, (long)func->call_count, (long)func->accel_count, (long)func->total_ops, timeprint(&func->total_time, total_buf), (long)func->self_ops, timeprint(&func->self_time, self_buf), (long)func->max_depth, (long)func->max_stack_use); glk_put_string_stream(profstr, linebuf); for (cc = func->outcalls; cc; cc = cc->next) { sprintf(linebuf, " \n", (unsigned long)func->addr, (unsigned long)cc->toaddr, (long)cc->count); glk_put_string_stream(profstr, linebuf); } } } glk_put_string_stream(profstr, "\n"); glk_stream_close(profstr, NULL); /* ### Ought to free the function structures, not just the hash array. */ glulx_free(functions); functions = NULL; } #else /* VM_PROFILING */ void setup_profile(strid_t stream, char *filename) { /* Profiling is not compiled in. Do nothing. */ } int init_profile() { /* Profiling is not compiled in. Do nothing. */ return TRUE; } #endif /* VM_PROFILING */ glulxe/search.c000066400000000000000000000157011304145303500137710ustar00rootroot00000000000000/* search.c: Glulxe code for built-in search opcodes Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" #define serop_KeyIndirect (0x01) #define serop_ZeroKeyTerminates (0x02) #define serop_ReturnIndex (0x04) /* ### KeyZeroBounded? variants? */ /* ### LowerBoundKey? */ /* In general, these search functions look through a bunch of structures in memory, searching for one whose key (a fixed-size sequence of bytes within the structure) matches a given key. The result can indicate a particular structure within the bunch, or it can be NULL ("not found".) Any or all of these options can be applied: KeyIndirect: If this is true, the key argument is taken to be the start of an array of bytes in memory (whose length is keysize). If it is false, the key argument contains the key itself. In this case, keysize *must* be 1, 2, or 4. The key is stored in the lower bytes of the key argument, big-endian. (The upper bytes are ignored.) ZeroKeyTerminates: If this is true, when the search reaches a struct whose key is all zeroes, the search terminates (and returns NULL). If the searched-for key happens to also be zeroes, the key-match (returning the struct) takes precedence over the zero-match (returning NULL.) ReturnIndex: If this is false, the return value is the memory address of the matching struct, or 0 to indicate NULL. If true, the return value is the array index of the matching struct, or -1 to indicate NULL. */ static void fetchkey(unsigned char *keybuf, glui32 key, glui32 keysize, glui32 options); /* linear_search(): An array of data structures is stored in memory, beginning at start, each structure being structsize bytes. Within each struct, there is a key value keysize bytes long, starting at position keyoffset (from the start of the structure.) Search through these in order. If one is found whose key matches, return it. If numstructs are searched with no result, return NULL. numstructs may be -1 (0xFFFFFFFF) to indicate no upper limit to the number of structures to search. The search will continue until a match is found, or (if ZeroKeyTerminates is set) a zero key. The KeyIndirect, ZeroKeyTerminates, and ReturnIndex options may be used. */ glui32 linear_search(glui32 key, glui32 keysize, glui32 start, glui32 structsize, glui32 numstructs, glui32 keyoffset, glui32 options) { unsigned char keybuf[4]; glui32 count; int ix; int retindex = ((options & serop_ReturnIndex) != 0); int zeroterm = ((options & serop_ZeroKeyTerminates) != 0); fetchkey(keybuf, key, keysize, options); for (count=0; count byte2) cmp = 1; } } else { for (ix=0; (!cmp) && ix byte2) cmp = 1; } } if (!cmp) { if (retindex) return val; else return addr; } if (cmp < 0) { bot = val+1; } else { top = val; } } if (retindex) return -1; else return 0; } /* linked_search(): The structures may be anywhere in memory, in any order. They are linked by a four-byte address field, which is found in each struct at position nextoffset. If this field contains zero, it indicates the end of the linked list. The KeyIndirect and ZeroKeyTerminates options may be used. */ glui32 linked_search(glui32 key, glui32 keysize, glui32 start, glui32 keyoffset, glui32 nextoffset, glui32 options) { unsigned char keybuf[4]; int ix; glui32 val; int zeroterm = ((options & serop_ZeroKeyTerminates) != 0); fetchkey(keybuf, key, keysize, options); while (start != 0) { int match = TRUE; if (keysize <= 4) { for (ix=0; match && ix http://eblong.com/zarf/glulx/index.html */ #include #include "glk.h" #include "glulxe.h" /* This structure allows us to write either to a Glk stream or to a dynamically-allocated memory chunk. */ typedef struct dest_struct { int ismem; /* If it's a Glk stream: */ strid_t str; /* If it's a block of memory: */ unsigned char *ptr; glui32 pos; glui32 size; } dest_t; #define IFFID(c1, c2, c3, c4) \ ( (((glui32)c1) << 24) \ | (((glui32)c2) << 16) \ | (((glui32)c3) << 8) \ | (((glui32)c4)) ) /* This can be adjusted before startup by platform-specific startup code -- that is, preference code. */ int max_undo_level = 8; static int undo_chain_size = 0; static int undo_chain_num = 0; static unsigned char **undo_chain = NULL; #ifdef SERIALIZE_CACHE_RAM /* This will contain a copy of RAM (ramstate to endmem) as it exists in the game file. */ static unsigned char *ramcache = NULL; #endif /* SERIALIZE_CACHE_RAM */ static glui32 write_memstate(dest_t *dest); static glui32 write_heapstate(dest_t *dest, int portable); static glui32 write_stackstate(dest_t *dest, int portable); static glui32 read_memstate(dest_t *dest, glui32 chunklen); static glui32 read_heapstate(dest_t *dest, glui32 chunklen, int portable, glui32 *sumlen, glui32 **summary); static glui32 read_stackstate(dest_t *dest, glui32 chunklen, int portable); static glui32 write_heapstate_sub(glui32 sumlen, glui32 *sumarray, dest_t *dest, int portable); static int sort_heap_summary(void *p1, void *p2); static int write_long(dest_t *dest, glui32 val); static int read_long(dest_t *dest, glui32 *val); static int write_byte(dest_t *dest, unsigned char val); static int read_byte(dest_t *dest, unsigned char *val); static int reposition_write(dest_t *dest, glui32 pos); /* init_serial(): Set up the undo chain and anything else that needs to be set up. */ int init_serial() { undo_chain_num = 0; undo_chain_size = max_undo_level; undo_chain = (unsigned char **)glulx_malloc(sizeof(unsigned char *) * undo_chain_size); if (!undo_chain) return FALSE; #ifdef SERIALIZE_CACHE_RAM { glui32 len = (endmem - ramstart); glui32 res; ramcache = (unsigned char *)glulx_malloc(sizeof(unsigned char *) * len); if (!ramcache) return FALSE; glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start); res = glk_get_buffer_stream(gamefile, (char *)ramcache, len); if (res != len) return FALSE; } #endif /* SERIALIZE_CACHE_RAM */ return TRUE; } /* final_serial(): Clean up memory when the VM shuts down. */ void final_serial() { if (undo_chain) { int ix; for (ix=0; ix= undo_chain_size) { glulx_free(undo_chain[undo_chain_num-1]); undo_chain[undo_chain_num-1] = NULL; } if (undo_chain_size > 1) memmove(undo_chain+1, undo_chain, (undo_chain_size-1) * sizeof(unsigned char *)); undo_chain[0] = dest.ptr; if (undo_chain_num < undo_chain_size) undo_chain_num += 1; dest.ptr = NULL; } else { /* It didn't work. */ if (dest.ptr) { glulx_free(dest.ptr); dest.ptr = NULL; } } return res; } /* perform_restoreundo(): Pull a state pointer from the undo chain. This returns 0 on success, 1 on failure. Note that if it succeeds, the frameptr, localsbase, and valstackbase registers are invalid; they must be rebuilt from the stack. */ glui32 perform_restoreundo() { dest_t dest; glui32 res, val; glui32 heapsumlen = 0; glui32 *heapsumarr = NULL; /* If profiling is enabled and active then fail. */ #if VM_PROFILING if (profile_profiling_active()) return 1; #endif /* VM_PROFILING */ if (undo_chain_size == 0 || undo_chain_num == 0) return 1; dest.ismem = TRUE; dest.size = 0; dest.pos = 0; dest.ptr = undo_chain[0]; dest.str = NULL; res = 0; if (res == 0) { res = read_long(&dest, &val); } if (res == 0) { res = read_memstate(&dest, val); } if (res == 0) { res = read_long(&dest, &val); } if (res == 0) { res = read_heapstate(&dest, val, FALSE, &heapsumlen, &heapsumarr); } if (res == 0) { res = read_long(&dest, &val); } if (res == 0) { res = read_stackstate(&dest, val, FALSE); } /* ### really, many of the failure modes of those calls ought to cause fatal errors. The stack or main memory may be damaged now. */ if (res == 0) { if (heapsumarr) res = heap_apply_summary(heapsumlen, heapsumarr); } if (res == 0) { /* It worked. */ if (undo_chain_size > 1) memmove(undo_chain, undo_chain+1, (undo_chain_size-1) * sizeof(unsigned char *)); undo_chain_num -= 1; glulx_free(dest.ptr); dest.ptr = NULL; } else { /* It didn't work. */ dest.ptr = NULL; } return res; } /* perform_save(): Write the state to the output stream. This returns 0 on success, 1 on failure. */ glui32 perform_save(strid_t str) { dest_t dest; int ix; glui32 res, lx, val; glui32 memstart, memlen, stackstart, stacklen, heapstart, heaplen; glui32 filestart=0, filelen; stream_get_iosys(&val, &lx); if (val != 2) { /* Not using the Glk I/O system, so bail. This function only knows how to write to a Glk stream. */ fatal_error("Streams are only available in Glk I/O system."); } if (str == 0) return 1; dest.ismem = FALSE; dest.size = 0; dest.pos = 0; dest.ptr = NULL; dest.str = str; res = 0; /* Quetzal header. */ if (res == 0) { res = write_long(&dest, IFFID('F', 'O', 'R', 'M')); } if (res == 0) { res = write_long(&dest, 0); /* space for file length */ filestart = dest.pos; } if (res == 0) { res = write_long(&dest, IFFID('I', 'F', 'Z', 'S')); /* ### ? */ } /* Header chunk. This is the first 128 bytes of memory. */ if (res == 0) { res = write_long(&dest, IFFID('I', 'F', 'h', 'd')); } if (res == 0) { res = write_long(&dest, 128); } for (ix=0; res==0 && ix<128; ix++) { res = write_byte(&dest, Mem1(ix)); } /* Always even, so no padding necessary. */ /* Memory chunk. */ if (res == 0) { res = write_long(&dest, IFFID('C', 'M', 'e', 'm')); } if (res == 0) { res = write_long(&dest, 0); /* space for chunk length */ } if (res == 0) { memstart = dest.pos; res = write_memstate(&dest); memlen = dest.pos - memstart; } if (res == 0 && (memlen & 1) != 0) { res = write_byte(&dest, 0); } /* Heap chunk. */ if (res == 0) { res = write_long(&dest, IFFID('M', 'A', 'l', 'l')); } if (res == 0) { res = write_long(&dest, 0); /* space for chunk length */ } if (res == 0) { heapstart = dest.pos; res = write_heapstate(&dest, TRUE); heaplen = dest.pos - heapstart; } /* Always even, so no padding necessary. */ /* Stack chunk. */ if (res == 0) { res = write_long(&dest, IFFID('S', 't', 'k', 's')); } if (res == 0) { res = write_long(&dest, 0); /* space for chunk length */ } if (res == 0) { stackstart = dest.pos; res = write_stackstate(&dest, TRUE); stacklen = dest.pos - stackstart; } if (res == 0 && (stacklen & 1) != 0) { res = write_byte(&dest, 0); } filelen = dest.pos - filestart; /* Okay, fill in all the lengths. */ if (res == 0) { res = reposition_write(&dest, memstart-4); } if (res == 0) { res = write_long(&dest, memlen); } if (res == 0) { res = reposition_write(&dest, heapstart-4); } if (res == 0) { res = write_long(&dest, heaplen); } if (res == 0) { res = reposition_write(&dest, stackstart-4); } if (res == 0) { res = write_long(&dest, stacklen); } if (res == 0) { res = reposition_write(&dest, filestart-4); } if (res == 0) { res = write_long(&dest, filelen); } /* All done. */ return res; } /* perform_restore(): Pull a state pointer from a stream. This returns 0 on success, 1 on failure. Note that if it succeeds, the frameptr, localsbase, and valstackbase registers are invalid; they must be rebuilt from the stack. If fromshell is true, the restore is being invoked by the library shell (an autorestore of some kind). This currently happens only in iosglk. */ glui32 perform_restore(strid_t str, int fromshell) { dest_t dest; int ix; glui32 lx, res, val; glui32 filestart, filelen; glui32 heapsumlen = 0; glui32 *heapsumarr = NULL; /* If profiling is enabled and active then fail. */ #if VM_PROFILING if (profile_profiling_active()) return 1; #endif /* VM_PROFILING */ stream_get_iosys(&val, &lx); if (val != 2 && !fromshell) { /* Not using the Glk I/O system, so bail. This function only knows how to read from a Glk stream. (But in the autorestore case, iosys hasn't been set yet, so ignore this test.) */ fatal_error("Streams are only available in Glk I/O system."); } if (str == 0) return 1; dest.ismem = FALSE; dest.size = 0; dest.pos = 0; dest.ptr = NULL; dest.str = str; res = 0; /* ### the format errors checked below should send error messages to the current stream. */ if (res == 0) { res = read_long(&dest, &val); } if (res == 0 && val != IFFID('F', 'O', 'R', 'M')) { /* ### bad header */ return 1; } if (res == 0) { res = read_long(&dest, &filelen); } filestart = dest.pos; if (res == 0) { res = read_long(&dest, &val); } if (res == 0 && val != IFFID('I', 'F', 'Z', 'S')) { /* ### ? */ /* ### bad header */ return 1; } while (res == 0 && dest.pos < filestart+filelen) { /* Read a chunk and deal with it. */ glui32 chunktype=0, chunkstart=0, chunklen=0; unsigned char dummy; if (res == 0) { res = read_long(&dest, &chunktype); } if (res == 0) { res = read_long(&dest, &chunklen); } chunkstart = dest.pos; if (chunktype == IFFID('I', 'F', 'h', 'd')) { for (ix=0; res==0 && ix<128; ix++) { res = read_byte(&dest, &dummy); if (res == 0 && Mem1(ix) != dummy) { /* ### non-matching header */ return 1; } } } else if (chunktype == IFFID('C', 'M', 'e', 'm')) { res = read_memstate(&dest, chunklen); } else if (chunktype == IFFID('M', 'A', 'l', 'l')) { res = read_heapstate(&dest, chunklen, TRUE, &heapsumlen, &heapsumarr); } else if (chunktype == IFFID('S', 't', 'k', 's')) { res = read_stackstate(&dest, chunklen, TRUE); } else { /* Unknown chunk type. Skip it. */ for (lx=0; res==0 && lxismem) { dest->pos = pos; } else { glk_stream_set_position(dest->str, pos, seekmode_Start); dest->pos = pos; } return 0; } static int write_buffer(dest_t *dest, unsigned char *ptr, glui32 len) { if (dest->ismem) { if (dest->pos+len > dest->size) { dest->size = dest->pos+len+1024; if (!dest->ptr) { dest->ptr = glulx_malloc(dest->size); } else { dest->ptr = glulx_realloc(dest->ptr, dest->size); } if (!dest->ptr) return 1; } memcpy(dest->ptr+dest->pos, ptr, len); } else { glk_put_buffer_stream(dest->str, (char *)ptr, len); } dest->pos += len; return 0; } static int read_buffer(dest_t *dest, unsigned char *ptr, glui32 len) { glui32 newlen; if (dest->ismem) { memcpy(ptr, dest->ptr+dest->pos, len); } else { newlen = glk_get_buffer_stream(dest->str, (char *)ptr, len); if (newlen != len) return 1; } dest->pos += len; return 0; } static int write_long(dest_t *dest, glui32 val) { unsigned char buf[4]; Write4(buf, val); return write_buffer(dest, buf, 4); } static int write_short(dest_t *dest, glui16 val) { unsigned char buf[2]; Write2(buf, val); return write_buffer(dest, buf, 2); } static int write_byte(dest_t *dest, unsigned char val) { return write_buffer(dest, &val, 1); } static int read_long(dest_t *dest, glui32 *val) { unsigned char buf[4]; int res = read_buffer(dest, buf, 4); if (res) return res; *val = Read4(buf); return 0; } static int read_short(dest_t *dest, glui16 *val) { unsigned char buf[2]; int res = read_buffer(dest, buf, 2); if (res) return res; *val = Read2(buf); return 0; } static int read_byte(dest_t *dest, unsigned char *val) { return read_buffer(dest, val, 1); } static glui32 write_memstate(dest_t *dest) { glui32 res, pos; int val; int runlen; unsigned char ch; #ifdef SERIALIZE_CACHE_RAM glui32 cachepos; #endif /* SERIALIZE_CACHE_RAM */ res = write_long(dest, endmem); if (res) return res; runlen = 0; #ifdef SERIALIZE_CACHE_RAM cachepos = 0; #else /* SERIALIZE_CACHE_RAM */ glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start); #endif /* SERIALIZE_CACHE_RAM */ for (pos=ramstart; pos= 0x100) val = 0x100; else val = runlen; res = write_byte(dest, 0); if (res) return res; res = write_byte(dest, (val-1)); if (res) return res; runlen -= val; } /* Write the byte we got. */ res = write_byte(dest, ch); if (res) return res; } } /* It's possible we've got a run left over, but we don't write it. */ return 0; } static glui32 read_memstate(dest_t *dest, glui32 chunklen) { glui32 chunkend = dest->pos + chunklen; glui32 newlen; glui32 res, pos; int val; int runlen; unsigned char ch, ch2; #ifdef SERIALIZE_CACHE_RAM glui32 cachepos; #endif /* SERIALIZE_CACHE_RAM */ heap_clear(); res = read_long(dest, &newlen); if (res) return res; res = change_memsize(newlen, FALSE); if (res) return res; runlen = 0; #ifdef SERIALIZE_CACHE_RAM cachepos = 0; #else /* SERIALIZE_CACHE_RAM */ glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start); #endif /* SERIALIZE_CACHE_RAM */ for (pos=ramstart; pospos >= chunkend) { /* we're into the final, unstored run. */ } else if (runlen) { runlen--; } else { res = read_byte(dest, &ch2); if (res) return res; if (ch2 == 0) { res = read_byte(dest, &ch2); if (res) return res; runlen = (glui32)ch2; } else { ch ^= ch2; } } if (pos >= protectstart && pos < protectend) continue; MemW1(pos, ch); } return 0; } static glui32 write_heapstate(dest_t *dest, int portable) { glui32 res; glui32 sumlen; glui32 *sumarray; res = heap_get_summary(&sumlen, &sumarray); if (res) return res; if (!sumarray) return 0; /* no heap */ res = write_heapstate_sub(sumlen, sumarray, dest, portable); glulx_free(sumarray); return res; } static glui32 write_heapstate_sub(glui32 sumlen, glui32 *sumarray, dest_t *dest, int portable) { glui32 res, lx; /* If we're storing for the purpose of undo, we don't need to do any byte-swapping, because the result will only be used by this session. */ if (!portable) { res = write_buffer(dest, (void *)sumarray, sumlen*sizeof(glui32)); if (res) return res; return 0; } for (lx=0; lx v2) return 1; return 0; } static glui32 read_heapstate(dest_t *dest, glui32 chunklen, int portable, glui32 *sumlen, glui32 **summary) { glui32 res, count, lx; glui32 *arr; *sumlen = 0; *summary = NULL; if (chunklen == 0) return 0; /* no heap */ if (!portable) { count = chunklen / sizeof(glui32); arr = glulx_malloc(chunklen); if (!arr) return 1; res = read_buffer(dest, (void *)arr, chunklen); if (res) return res; *sumlen = count; *summary = arr; return 0; } count = chunklen / 4; arr = glulx_malloc(count * sizeof(glui32)); if (!arr) return 1; for (lx=0; lx stacksize) return 1; stackptr = chunklen; frameptr = 0; valstackbase = 0; localsbase = 0; if (!portable) { res = read_buffer(dest, stack, stackptr); if (res) return res; return 0; } /* This isn't going to be pleasant; we're going to read the data in as a block, and then convert it in-place. */ res = read_buffer(dest, stack, stackptr); if (res) return res; frameend = stackptr; while (frameend != 0) { /* Read the beginning-of-frame pointer. Remember, right now, the whole frame is stored big-endian. So we have to read with the Read*() macros, and then write with the StkW*() macros. */ frm = Read4(stack+(frameend-4)); frm2 = frm; frlen = Read4(stack+frm2); StkW4(frm2, frlen); frm2 += 4; locpos = Read4(stack+frm2); StkW4(frm2, locpos); frm2 += 4; /* The locals-format list is in bytes, so we don't have to convert it. */ frm3 = frm2; frm2 = frm+locpos; numlocals = 0; while (1) { unsigned char loctype, loccount; loctype = Read1(stack+frm3); frm3 += 1; loccount = Read1(stack+frm3); frm3 += 1; if (loctype == 0 && loccount == 0) break; /* Skip up to 0, 1, or 3 bytes of padding, depending on loctype. */ while (frm2 & (loctype-1)) { StkW1(frm2, 0); frm2++; } /* Convert this set of locals. */ switch (loctype) { case 1: do { /* Don't need to convert bytes. */ frm2 += 1; loccount--; } while (loccount); break; case 2: do { glui16 loc = Read2(stack+frm2); StkW2(frm2, loc); frm2 += 2; loccount--; } while (loccount); break; case 4: do { glui32 loc = Read4(stack+frm2); StkW4(frm2, loc); frm2 += 4; loccount--; } while (loccount); break; } numlocals++; } if ((numlocals & 1) == 0) { StkW1(frm3, 0); frm3++; StkW1(frm3, 0); frm3++; } if (frm3 != frm+locpos) { return 1; } while (frm2 & 3) { StkW1(frm2, 0); frm2++; } if (frm2 != frm+frlen) { return 1; } /* Now, the values pushed on the stack after the call frame itself. This includes the stub. */ while (frm2 < frameend) { glui32 loc = Read4(stack+frm2); StkW4(frm2, loc); frm2 += 4; } frameend = frm; } return 0; } glui32 perform_verify() { glui32 len, checksum, newlen; unsigned char buf[4]; glui32 val, newsum, ix; len = gamefile_len; if (len < 256 || (len & 0xFF) != 0) return 1; glk_stream_set_position(gamefile, gamefile_start, seekmode_Start); newsum = 0; /* Read the header */ for (ix=0; ix<9; ix++) { newlen = glk_get_buffer_stream(gamefile, (char *)buf, 4); if (newlen != 4) return 1; val = Read4(buf); if (ix == 3) { if (len != val) return 1; } if (ix == 8) checksum = val; else newsum += val; } /* Read everything else */ for (; ix < len/4; ix++) { newlen = glk_get_buffer_stream(gamefile, (char *)buf, 4); if (newlen != 4) return 1; val = Read4(buf); newsum += val; } if (newsum != checksum) return 1; return 0; } glulxe/string.c000066400000000000000000000533611304145303500140360ustar00rootroot00000000000000/* string.c: Glulxe string and text functions. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" static glui32 iosys_mode; static glui32 iosys_rock; /* These constants are defined in the Glulx spec. */ #define iosys_None (0) #define iosys_Filter (1) #define iosys_Glk (2) #define CACHEBITS (4) #define CACHESIZE (1< 0xFF) val = '?'; glk_put_char(val); } /* stream_num(): Write a signed integer to the current output stream. */ void stream_num(glsi32 val, int inmiddle, int charnum) { int ix = 0; int res, jx; char buf[16]; glui32 ival; if (val == 0) { buf[ix] = '0'; ix++; } else { if (val < 0) ival = -val; else ival = val; while (ival != 0) { buf[ix] = (ival % 10) + '0'; ix++; ival /= 10; } if (val < 0) { buf[ix] = '-'; ix++; } } switch (iosys_mode) { case iosys_Glk: ix -= charnum; while (ix > 0) { ix--; glk_put_char(buf[ix]); } break; case iosys_Filter: if (!inmiddle) { push_callstub(0x11, 0); inmiddle = TRUE; } if (charnum < ix) { ival = buf[(ix-1)-charnum] & 0xFF; pc = val; push_callstub(0x12, charnum+1); enter_function(iosys_rock, 1, &ival); return; } break; default: break; } if (inmiddle) { res = pop_callstub_string(&jx); if (res) fatal_error("String-on-string call stub while printing number."); } } /* stream_string(): Write a Glulx string object to the current output stream. inmiddle is zero if we are beginning a new string, or nonzero if restarting one (E0/E1/E2, as appropriate for the string type). */ void stream_string(glui32 addr, int inmiddle, int bitnum) { int ch; int type; int alldone = FALSE; int substring = (inmiddle != 0); glui32 ival; if (!addr) fatal_error("Called stream_string with null address."); while (!alldone) { if (inmiddle == 0) { type = Mem1(addr); if (type == 0xE2) addr+=4; else addr++; bitnum = 0; } else { type = inmiddle; } if (type == 0xE1) { if (tablecache_valid) { int bits, numbits; int readahead; glui32 tmpaddr; cacheblock_t *cablist; int done = 0; /* bitnum is already set right */ bits = Mem1(addr); if (bitnum) bits >>= bitnum; numbits = (8 - bitnum); readahead = FALSE; if (tablecache.type != 0) { /* This is a bit of a cheat. If the top-level block is not a branch, then it must be a string-terminator -- otherwise the string would be an infinite repetition of that block. We check for this case and bail immediately. */ done = 1; } cablist = tablecache.u.branches; while (!done) { cacheblock_t *cab; if (numbits < CACHEBITS) { /* readahead is certainly false */ int newbyte = Mem1(addr+1); bits |= (newbyte << numbits); numbits += 8; readahead = TRUE; } cab = &(cablist[bits & CACHEMASK]); numbits -= cab->depth; bits >>= cab->depth; bitnum += cab->depth; if (bitnum >= 8) { addr += 1; bitnum -= 8; if (readahead) { readahead = FALSE; } else { int newbyte = Mem1(addr); bits |= (newbyte << numbits); numbits += 8; } } switch (cab->type) { case 0x00: /* non-leaf node */ cablist = cab->u.branches; break; case 0x01: /* string terminator */ done = 1; break; case 0x02: /* single character */ switch (iosys_mode) { case iosys_Glk: glk_put_char(cab->u.ch); break; case iosys_Filter: ival = cab->u.ch & 0xFF; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } cablist = tablecache.u.branches; break; case 0x04: /* single Unicode character */ switch (iosys_mode) { case iosys_Glk: glkio_unichar_han_ptr(cab->u.uch); break; case iosys_Filter: ival = cab->u.uch; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } cablist = tablecache.u.branches; break; case 0x03: /* C string */ switch (iosys_mode) { case iosys_Glk: for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++) glk_put_char(ch); cablist = tablecache.u.branches; break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE0; addr = cab->u.addr; done = 2; break; default: cablist = tablecache.u.branches; break; } break; case 0x05: /* C Unicode string */ switch (iosys_mode) { case iosys_Glk: for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4) glkio_unichar_han_ptr(ival); cablist = tablecache.u.branches; break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE2; addr = cab->u.addr; done = 2; break; default: cablist = tablecache.u.branches; break; } break; case 0x08: case 0x09: case 0x0A: case 0x0B: { glui32 oaddr; int otype; oaddr = cab->u.addr; if (cab->type >= 0x09) oaddr = Mem4(oaddr); if (cab->type == 0x0B) oaddr = Mem4(oaddr); otype = Mem1(oaddr); if (!substring) { push_callstub(0x11, 0); substring = TRUE; } if (otype >= 0xE0 && otype <= 0xFF) { pc = addr; push_callstub(0x10, bitnum); inmiddle = 0; addr = oaddr; done = 2; } else if (otype >= 0xC0 && otype <= 0xDF) { glui32 argc; glui32 *argv; if (cab->type == 0x0A || cab->type == 0x0B) { argc = Mem4(cab->u.addr+4); argv = pop_arguments(argc, cab->u.addr+8); } else { argc = 0; argv = NULL; } pc = addr; push_callstub(0x10, bitnum); enter_function(oaddr, argc, argv); return; } else { fatal_error("Unknown object while decoding string indirect reference."); } } break; default: fatal_error("Unknown entity in string decoding (cached)."); break; } } if (done > 1) { continue; /* restart the top-level loop */ } } else { /* tablecache not valid */ glui32 node; int byte; int nodetype; int done = 0; if (!stringtable) fatal_error("Attempted to print a compressed string with no table set."); /* bitnum is already set right */ byte = Mem1(addr); if (bitnum) byte >>= bitnum; node = Mem4(stringtable+8); while (!done) { nodetype = Mem1(node); node++; switch (nodetype) { case 0x00: /* non-leaf node */ if (byte & 1) node = Mem4(node+4); else node = Mem4(node+0); if (bitnum == 7) { bitnum = 0; addr++; byte = Mem1(addr); } else { bitnum++; byte >>= 1; } break; case 0x01: /* string terminator */ done = 1; break; case 0x02: /* single character */ ch = Mem1(node); switch (iosys_mode) { case iosys_Glk: glk_put_char(ch); break; case iosys_Filter: ival = ch & 0xFF; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } node = Mem4(stringtable+8); break; case 0x04: /* single Unicode character */ ival = Mem4(node); switch (iosys_mode) { case iosys_Glk: glkio_unichar_han_ptr(ival); break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } node = Mem4(stringtable+8); break; case 0x03: /* C string */ switch (iosys_mode) { case iosys_Glk: for (; (ch=Mem1(node)) != '\0'; node++) glk_put_char(ch); node = Mem4(stringtable+8); break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE0; addr = node; done = 2; break; default: node = Mem4(stringtable+8); break; } break; case 0x05: /* C Unicode string */ switch (iosys_mode) { case iosys_Glk: for (; (ival=Mem4(node)) != 0; node+=4) glkio_unichar_han_ptr(ival); node = Mem4(stringtable+8); break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE2; addr = node; done = 2; break; default: node = Mem4(stringtable+8); break; } break; case 0x08: case 0x09: case 0x0A: case 0x0B: { glui32 oaddr; int otype; oaddr = Mem4(node); if (nodetype == 0x09 || nodetype == 0x0B) oaddr = Mem4(oaddr); otype = Mem1(oaddr); if (!substring) { push_callstub(0x11, 0); substring = TRUE; } if (otype >= 0xE0 && otype <= 0xFF) { pc = addr; push_callstub(0x10, bitnum); inmiddle = 0; addr = oaddr; done = 2; } else if (otype >= 0xC0 && otype <= 0xDF) { glui32 argc; glui32 *argv; if (nodetype == 0x0A || nodetype == 0x0B) { argc = Mem4(node+4); argv = pop_arguments(argc, node+8); } else { argc = 0; argv = NULL; } pc = addr; push_callstub(0x10, bitnum); enter_function(oaddr, argc, argv); return; } else { fatal_error("Unknown object while decoding string indirect reference."); } } break; default: fatal_error("Unknown entity in string decoding."); break; } } if (done > 1) { continue; /* restart the top-level loop */ } } } else if (type == 0xE0) { switch (iosys_mode) { case iosys_Glk: while (1) { ch = Mem1(addr); addr++; if (ch == '\0') break; glk_put_char(ch); } break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } ch = Mem1(addr); addr++; if (ch != '\0') { ival = ch & 0xFF; pc = addr; push_callstub(0x13, 0); enter_function(iosys_rock, 1, &ival); return; } break; } } else if (type == 0xE2) { switch (iosys_mode) { case iosys_Glk: while (1) { ival = Mem4(addr); addr+=4; if (ival == 0) break; glkio_unichar_han_ptr(ival); } break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } ival = Mem4(addr); addr+=4; if (ival != 0) { pc = addr; push_callstub(0x14, 0); enter_function(iosys_rock, 1, &ival); return; } break; } } else if (type >= 0xE0 && type <= 0xFF) { fatal_error("Attempt to print unknown type of string."); } else { fatal_error("Attempt to print non-string."); } if (!substring) { /* Just get straight out. */ alldone = TRUE; } else { /* Pop a stub and see what's to be done. */ addr = pop_callstub_string(&bitnum); if (addr == 0) { alldone = TRUE; } else { inmiddle = 0xE1; } } } } /* stream_get_table(): Get the current table address. */ glui32 stream_get_table() { return stringtable; } /* stream_set_table(): Set the current table address, and rebuild decoding cache. */ void stream_set_table(glui32 addr) { if (stringtable == addr) return; /* Drop cache. */ if (tablecache_valid) { if (tablecache.type == 0) dropcache(tablecache.u.branches); tablecache.u.branches = NULL; tablecache_valid = FALSE; } stringtable = addr; if (stringtable) { /* Build cache. We can only do this if the table is entirely in ROM. */ glui32 tablelen = Mem4(stringtable); glui32 rootaddr = Mem4(stringtable+8); int cache_stringtable = (stringtable+tablelen <= ramstart); /* cache_stringtable = TRUE; ...for testing only */ /* cache_stringtable = FALSE; ...for testing only */ if (cache_stringtable) { buildcache(&tablecache, rootaddr, CACHEBITS, 0); /* dumpcache(&tablecache, 1, 0); */ tablecache_valid = TRUE; } } } static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth, int mask) { int ix, type; type = Mem1(nodeaddr); if (type == 0 && depth == CACHEBITS) { cacheblock_t *list, *cab; list = (cacheblock_t *)glulx_malloc(sizeof(cacheblock_t) * CACHESIZE); buildcache(list, nodeaddr, 0, 0); cab = &(cablist[mask]); cab->type = 0; cab->depth = CACHEBITS; cab->u.branches = list; return; } if (type == 0) { glui32 leftaddr = Mem4(nodeaddr+1); glui32 rightaddr = Mem4(nodeaddr+5); buildcache(cablist, leftaddr, depth+1, mask); buildcache(cablist, rightaddr, depth+1, (mask | (1 << depth))); return; } /* Leaf node. */ nodeaddr++; for (ix = mask; ix < CACHESIZE; ix += (1 << depth)) { cacheblock_t *cab = &(cablist[ix]); cab->type = type; cab->depth = depth; switch (type) { case 0x02: cab->u.ch = Mem1(nodeaddr); break; case 0x04: cab->u.uch = Mem4(nodeaddr); break; case 0x03: case 0x05: case 0x0A: case 0x0B: cab->u.addr = nodeaddr; break; case 0x08: case 0x09: cab->u.addr = Mem4(nodeaddr); break; } } } #if 0 #include static void dumpcache(cacheblock_t *cablist, int count, int indent) { int ix, jx; for (ix=0; ixtype) { case 0: printf("...\n"); dumpcache(cab->u.branches, CACHESIZE, indent+1); break; case 1: printf("\n"); break; case 2: printf("0x%02X", cab->u.ch); if (cab->u.ch < 32) printf(" ''\n"); else printf(" '%c'\n", cab->u.ch); break; default: printf("type %02X, address %06lX\n", cab->type, cab->u.addr); break; } } } #endif /* 0 */ static void dropcache(cacheblock_t *cablist) { int ix; for (ix=0; ixtype == 0) { dropcache(cab->u.branches); cab->u.branches = NULL; } } glulx_free(cablist); } /* This misbehaves if a Glk function has more than one S argument. */ #define STATIC_TEMP_BUFSIZE (127) static char temp_buf[STATIC_TEMP_BUFSIZE+1]; char *make_temp_string(glui32 addr) { int ix, len; glui32 addr2; char *res; if (Mem1(addr) != 0xE0) fatal_error("String argument to a Glk call must be unencoded."); addr++; for (addr2=addr; Mem1(addr2); addr2++) { }; len = (addr2 - addr); if (len < STATIC_TEMP_BUFSIZE) { res = temp_buf; } else { res = (char *)glulx_malloc(len+1); if (!res) fatal_error("Unable to allocate space for string argument to Glk call."); } for (ix=0, addr2=addr; ix http://eblong.com/zarf/glulx/index.html */ #include #include #include "glk.h" #include "gi_blorb.h" #include "glulxe.h" #include "glkstart.h" /* This comes with the Glk library. */ #if VM_DEBUGGER /* This header file may come with the Glk library. If it doesn't, comment out VM_DEBUGGER in glulxe.h -- you won't be able to use debugging. */ #include "gi_debug.h" #endif /* VM_DEBUGGER */ /* The only command-line argument is the filename. And the profiling switch, if that's compiled in. The only *two* command-line arguments are... You may wonder why there's no argument for a save file to autorestore at startup. That would be nice; unfortunately it can't work. A Glulx game expects to set up its Glk environment (@setiosys, open windows, etc) before handling a "restore" command. It can't pick up from a restored state without that environment in place. */ glkunix_argumentlist_t glkunix_arguments[] = { #if VM_PROFILING { "--profile", glkunix_arg_ValueFollows, "Generate profiling information to a file." }, { "--profcalls", glkunix_arg_NoValue, "Include what-called-what details in profiling. (Slow!)" }, #endif /* VM_PROFILING */ #if VM_DEBUGGER { "--gameinfo", glkunix_arg_ValueFollows, "Read debug information from a file." }, { "--cpu", glkunix_arg_NoValue, "Display CPU usage of each command (debug)." }, { "--starttrap", glkunix_arg_NoValue, "Enter debug mode at startup time (debug)." }, { "--quittrap", glkunix_arg_NoValue, "Enter debug mode at quit time (debug)." }, { "--crashtrap", glkunix_arg_NoValue, "Enter debug mode on any fatal error (debug)." }, #endif /* VM_DEBUGGER */ { "", glkunix_arg_ValueFollows, "filename: The game file to load." }, { NULL, glkunix_arg_End, NULL } }; int glkunix_startup_code(glkunix_startup_t *data) { /* It turns out to be more convenient if we return TRUE from here, even when an error occurs, and display an error in glk_main(). */ int ix; char *filename = NULL; char *gameinfofilename = NULL; int gameinfoloaded = FALSE; unsigned char buf[12]; int res; /* Parse out the arguments. They've already been checked for validity, and the library-specific ones stripped out. As usual for Unix, the zeroth argument is the executable name. */ for (ix=1; ixargc; ix++) { #if VM_PROFILING if (!strcmp(data->argv[ix], "--profile")) { ix++; if (ixargc) { strid_t profstr = glkunix_stream_open_pathname_gen(data->argv[ix], TRUE, FALSE, 1); if (!profstr) { init_err = "Unable to open profile output file."; init_err2 = data->argv[ix]; return TRUE; } setup_profile(profstr, NULL); } continue; } if (!strcmp(data->argv[ix], "--profcalls")) { profile_set_call_counts(TRUE); continue; } #endif /* VM_PROFILING */ #if VM_DEBUGGER if (!strcmp(data->argv[ix], "--gameinfo")) { ix++; if (ixargc) { gameinfofilename = data->argv[ix]; } continue; } if (!strcmp(data->argv[ix], "--cpu")) { debugger_track_cpu(TRUE); continue; } if (!strcmp(data->argv[ix], "--starttrap")) { debugger_set_start_trap(TRUE); continue; } if (!strcmp(data->argv[ix], "--quittrap")) { debugger_set_quit_trap(TRUE); continue; } if (!strcmp(data->argv[ix], "--crashtrap")) { debugger_set_crash_trap(TRUE); continue; } #endif /* VM_DEBUGGER */ if (filename) { init_err = "You must supply exactly one game file."; return TRUE; } filename = data->argv[ix]; } if (!filename) { init_err = "You must supply the name of a game file."; return TRUE; } gamefile = glkunix_stream_open_pathname(filename, FALSE, 1); if (!gamefile) { init_err = "The game file could not be opened."; init_err2 = filename; return TRUE; } #if VM_DEBUGGER if (gameinfofilename) { strid_t debugstr = glkunix_stream_open_pathname_gen(gameinfofilename, FALSE, FALSE, 1); if (!debugstr) { nonfatal_warning("Unable to open gameinfo file for debug data."); } else { int bres = debugger_load_info_stream(debugstr); glk_stream_close(debugstr, NULL); if (!bres) nonfatal_warning("Unable to parse game info."); else gameinfoloaded = TRUE; } } /* Report debugging available, whether a game info file is loaded or not. */ gidebug_debugging_available(debugger_cmd_handler, debugger_cycle_handler); #endif /* VM_DEBUGGER */ /* Now we have to check to see if it's a Blorb file. */ glk_stream_set_position(gamefile, 0, seekmode_Start); res = glk_get_buffer_stream(gamefile, (char *)buf, 12); if (!res) { init_err = "The data in this stand-alone game is too short to read."; return TRUE; } if (buf[0] == 'G' && buf[1] == 'l' && buf[2] == 'u' && buf[3] == 'l') { /* Load game directly from file. */ locate_gamefile(FALSE); return TRUE; } else if (buf[0] == 'F' && buf[1] == 'O' && buf[2] == 'R' && buf[3] == 'M' && buf[8] == 'I' && buf[9] == 'F' && buf[10] == 'R' && buf[11] == 'S') { /* Load game from a chunk in the Blorb file. */ locate_gamefile(TRUE); #if VM_DEBUGGER /* Load the debug info from the Blorb, if it wasn't loaded from a file. */ if (!gameinfoloaded) { glui32 giblorb_ID_Dbug = giblorb_make_id('D', 'b', 'u', 'g'); giblorb_err_t err; giblorb_result_t blorbres; err = giblorb_load_chunk_by_type(giblorb_get_resource_map(), giblorb_method_FilePos, &blorbres, giblorb_ID_Dbug, 0); if (!err) { int bres = debugger_load_info_chunk(gamefile, blorbres.data.startpos, blorbres.length); if (!bres) nonfatal_warning("Unable to parse game info."); else gameinfoloaded = TRUE; } } #endif /* VM_DEBUGGER */ return TRUE; } else { init_err = "This is neither a Glulx game file nor a Blorb file " "which contains one."; return TRUE; } } glulxe/vm.c000066400000000000000000000234641304145303500131530ustar00rootroot00000000000000/* vm.c: Glulxe code related to the VM overall. Also miscellaneous stuff. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" /* The memory blocks which contain VM main memory and the stack. */ unsigned char *memmap = NULL; unsigned char *stack = NULL; /* Various memory addresses which are useful. These are loaded in from the game file header. */ glui32 ramstart; glui32 endgamefile; glui32 origendmem; glui32 stacksize; glui32 startfuncaddr; glui32 origstringtable; glui32 checksum; /* The VM registers. */ glui32 stackptr; glui32 frameptr; glui32 pc; glui32 stringtable; glui32 valstackbase; glui32 localsbase; glui32 endmem; glui32 protectstart, protectend; /* This is not needed for VM operation, but it may be needed for autosave/autorestore. */ glui32 prevpc; void (*stream_char_handler)(unsigned char ch); void (*stream_unichar_handler)(glui32 ch); /* setup_vm(): Read in the game file and build the machine, allocating all the memory necessary. */ void setup_vm() { unsigned char buf[4 * 7]; int res; pc = 0; /* Clear this, so that error messages are cleaner. */ prevpc = 0; /* Read in all the size constants from the game file header. */ stream_char_handler = NULL; stream_unichar_handler = NULL; glk_stream_set_position(gamefile, gamefile_start+8, seekmode_Start); res = glk_get_buffer_stream(gamefile, (char *)buf, 4 * 7); if (res != 4 * 7) { fatal_error("The game file header is too short."); } ramstart = Read4(buf+0); endgamefile = Read4(buf+4); origendmem = Read4(buf+8); stacksize = Read4(buf+12); startfuncaddr = Read4(buf+16); origstringtable = Read4(buf+20); checksum = Read4(buf+24); /* Set the protection range to (0, 0), meaning "off". */ protectstart = 0; protectend = 0; /* Do a few sanity checks. */ if ((ramstart & 0xFF) || (endgamefile & 0xFF) || (origendmem & 0xFF) || (stacksize & 0xFF)) { nonfatal_warning("One of the segment boundaries in the header is not " "256-byte aligned."); } if (endgamefile != gamefile_len) { nonfatal_warning("The gamefile length does not match the header " "endgamefile length."); } if (ramstart < 0x100 || endgamefile < ramstart || origendmem < endgamefile) { fatal_error("The segment boundaries in the header are in an impossible " "order."); } if (stacksize < 0x100) { fatal_error("The stack size in the header is too small."); } /* Allocate main memory and the stack. This is where memory allocation errors are most likely to occur. */ endmem = origendmem; memmap = (unsigned char *)glulx_malloc(origendmem); if (!memmap) { fatal_error("Unable to allocate Glulx memory space."); } stack = (unsigned char *)glulx_malloc(stacksize); if (!stack) { glulx_free(memmap); memmap = NULL; fatal_error("Unable to allocate Glulx stack space."); } stringtable = 0; /* Initialize various other things in the terp. */ init_operands(); init_accel(); init_serial(); /* Set up the initial machine state. */ vm_restart(); /* If the debugger is compiled in, check that the debug data matches the game. (This only prints warnings for mismatch.) */ debugger_check_story_file(); /* Also, set up any start-time debugger state. This may do a block- and-debug, if the user has requested that. */ debugger_setup_start_state(); } /* finalize_vm(): Deallocate all the memory and shut down the machine. */ void finalize_vm() { stream_set_table(0); if (memmap) { glulx_free(memmap); memmap = NULL; } if (stack) { glulx_free(stack); stack = NULL; } final_serial(); } /* vm_restart(): Put the VM into a state where it's ready to begin executing the game. This is called both at startup time, and when the machine performs a "restart" opcode. */ void vm_restart() { glui32 lx; int res; int bufpos; char buf[0x100]; /* Deactivate the heap (if it was active). */ heap_clear(); /* Reset memory to the original size. */ lx = change_memsize(origendmem, FALSE); if (lx) fatal_error("Memory could not be reset to its original size."); /* Load in all of main memory. We do this in 256-byte chunks, because why rely on OS stream buffering? */ glk_stream_set_position(gamefile, gamefile_start, seekmode_Start); bufpos = 0x100; for (lx=0; lx= 0x100) { int count = glk_get_buffer_stream(gamefile, buf, 0x100); if (count != 0x100) { fatal_error("The game file ended unexpectedly."); } bufpos = 0; } res = buf[bufpos++]; if (lx >= protectstart && lx < protectend) continue; memmap[lx] = res; } for (lx=endgamefile; lx endmem) { for (lx=endmem; lx= count) { /* It fits. */ array = dynarray; } else { dynarray_size = count+8; dynarray = glulx_realloc(dynarray, sizeof(glui32) * dynarray_size); if (!dynarray) fatal_error("Unable to reallocate function arguments."); array = dynarray; } } } if (!addr) { if (stackptr < valstackbase+4*count) fatal_error("Stack underflow in arguments."); stackptr -= 4*count; for (ix=0; ix= endmem) fatal_error_i("Memory access out of range", addr); if (count > 1) { addr += (count-1); if (addr >= endmem) fatal_error_i("Memory access out of range", addr); } } /* verify_address_write(): Make sure that count bytes beginning with addr all fall within RAM. This is called at every memory write if VERIFY_MEMORY_ACCESS is defined in the header file. */ void verify_address_write(glui32 addr, glui32 count) { if (addr < ramstart) fatal_error_i("Memory write to read-only address", addr); if (addr >= endmem) fatal_error_i("Memory access out of range", addr); if (count > 1) { addr += (count-1); if (addr >= endmem) fatal_error_i("Memory access out of range", addr); } } /* verify_array_addresses(): Make sure that an array of count elements (size bytes each), starting at addr, does not fall outside the memory map. This goes to some trouble that verify_address() does not, because we need to be wary of lengths near -- or beyond -- 0x7FFFFFFF. */ void verify_array_addresses(glui32 addr, glui32 count, glui32 size) { glui32 bytecount; if (addr >= endmem) fatal_error_i("Memory access out of range", addr); if (count == 0) return; bytecount = count*size; /* If just multiplying by the element size overflows, we have trouble. */ if (bytecount < count) fatal_error_i("Memory access way too long", addr); /* If the byte length by itself is too long, or if its end overflows, we have trouble. */ if (bytecount > endmem || addr+bytecount < addr) fatal_error_i("Memory access much too long", addr); /* The simple length test. */ if (addr+bytecount > endmem) fatal_error_i("Memory access too long", addr); } glulxe/winstart.c000066400000000000000000000112541304145303500143760ustar00rootroot00000000000000/* winstart.c: Windows-specific code for the Glulxe interpreter. */ #include #include "glk.h" #include "gi_blorb.h" #include "WinGlk.h" #include "resource.h" int InitGlk(unsigned int iVersion); /* Entry point for all Glk applications */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { /* Attempt to initialise Glk */ if (InitGlk(0x00000700) == 0) exit(0); /* Call the Windows specific initialization routine */ if (winglk_startup_code(lpCmdLine) != 0) { /* Run the application */ glk_main(); /* There is no return from this routine */ glk_exit(); } return 0; } /* These are defined in glulxe.h */ extern int locate_gamefile(int isblorb); extern strid_t gamefile; extern char *init_err; #define IDS_GLULXE_TITLE 31000 #define IDS_GLULXE_OPEN 31001 #define IDS_GLULXE_FILTER 31002 int winglk_startup_code(const char* cmdline) { const char* pszFileName = 0; char* pszSeparator; char sExeName[_MAX_PATH]; char sFileName[_MAX_PATH]; char sWindowTitle[256]; char sBuffer[12]; int iBufferCount, iExtLoop; HINSTANCE hResources; frefid_t GameRef; winglk_set_gui(IDI_GLULX); winglk_app_set_name("Glulxe"); winglk_set_menu_name("&Glulxe"); winglk_show_game_dialog(); hResources = winglk_get_resource_handle(); LoadString(hResources,IDS_GLULXE_TITLE,sWindowTitle,256); winglk_window_set_title(sWindowTitle); winglk_set_about_text("Windows Glulxe 0.4.7.139"); /* Set up the help file */ if (GetModuleFileName(0,sExeName,_MAX_PATH) == 0) return 0; pszSeparator = strrchr(sExeName,'.'); if (pszSeparator != 0) { strcpy(pszSeparator,".chm"); winglk_set_help_file(sExeName); } /* First look for a Blorb file with the same name as the executable. */ if (GetModuleFileName(0,sExeName,_MAX_PATH) == 0) return 0; pszSeparator = strrchr(sExeName,'.'); if (pszSeparator != 0) { static char* Extensions[5] = { ".blb", ".blorb", ".glb", ".gblorb", ".ulx" }; for (iExtLoop = 0; iExtLoop < 5; iExtLoop++) { strcpy(pszSeparator,Extensions[iExtLoop]); if (GetFileAttributes(sExeName) != INVALID_FILE_ATTRIBUTES) { pszFileName = sExeName; break; } } } if (pszFileName == 0) { char sOpenTitle[256]; char sOpenFilter[256]; /* Check the command line for a file, or prompt the user. */ LoadString(hResources,IDS_GLULXE_OPEN,sOpenTitle,256); LoadString(hResources,IDS_GLULXE_FILTER,sOpenFilter,256); pszFileName = winglk_get_initial_filename(cmdline,sOpenTitle,sOpenFilter); } if (pszFileName == 0) return 0; /* Open the file as a stream */ strcpy(sFileName,pszFileName); GameRef = winglk_fileref_create_by_name( fileusage_BinaryMode|fileusage_Data,sFileName,0,0); if (GameRef == 0) return 0; gamefile = glk_stream_open_file(GameRef,filemode_Read,0); glk_fileref_destroy(GameRef); /* Examine the loaded file to see what type it is. */ glk_stream_set_position(gamefile,0,seekmode_Start); iBufferCount = glk_get_buffer_stream(gamefile,sBuffer,12); if (iBufferCount < 12) return 0; if (sBuffer[0] == 'G' && sBuffer[1] == 'l' && sBuffer[2] == 'u' && sBuffer[3] == 'l') { char* pszPeriod; if (locate_gamefile(0) == 0) return 0; /* Look for a Blorb resource file */ pszPeriod = strrchr(sFileName,'.'); if (pszPeriod) { static char* Extensions[2] = { ".blb", ".blorb" }; frefid_t BlorbRef = 0; for (iExtLoop = 0; iExtLoop < 2; iExtLoop++) { strcpy(pszPeriod,Extensions[iExtLoop]); /* Attempt to open the resource Blorb file */ BlorbRef = winglk_fileref_create_by_name( fileusage_BinaryMode|fileusage_Data,sFileName,0,0); if (glk_fileref_does_file_exist(BlorbRef)) { strid_t BlorbFile = glk_stream_open_file(BlorbRef,filemode_Read,0); giblorb_set_resource_map(BlorbFile); break; } } } } else if (sBuffer[0] == 'F' && sBuffer[1] == 'O' && sBuffer[2] == 'R' && sBuffer[3] == 'M' && sBuffer[8] == 'I' && sBuffer[9] == 'F' && sBuffer[10] == 'R' && sBuffer[11] == 'S') { if (locate_gamefile(1) == 0) { if (init_err != 0) MessageBox(0,init_err,"Glulxe",MB_OK|MB_ICONERROR); return 0; } } else { MessageBox(0,"This is not a Glulx game file.","Glulxe",MB_OK|MB_ICONERROR); return 0; } /* Set up the resource directory. */ pszSeparator = strrchr(sFileName,'\\'); if (pszSeparator != 0) { *pszSeparator = '\0'; winglk_set_resource_directory(sFileName); } /* Load configuration data */ strcpy(sFileName,pszFileName); winglk_load_config_file(sFileName); return 1; }