diff options
author | Paul Gilbert | 2019-04-14 18:43:30 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-04-17 20:46:06 -0700 |
commit | 8e11a14939365c3a2994602b5db11a4fe6e8eaac (patch) | |
tree | a0bc1be59cf578317f3a0bd0d809d1b194388a2f | |
parent | e271cdc6534763f4472c3ff70c22292268300814 (diff) | |
download | scummvm-rg350-8e11a14939365c3a2994602b5db11a4fe6e8eaac.tar.gz scummvm-rg350-8e11a14939365c3a2994602b5db11a4fe6e8eaac.tar.bz2 scummvm-rg350-8e11a14939365c3a2994602b5db11a4fe6e8eaac.zip |
GLK: GLULXE: Set up method definitions, glkop methods
-rw-r--r-- | engines/glk/glk_api.cpp | 3 | ||||
-rw-r--r-- | engines/glk/glk_api.h | 12 | ||||
-rw-r--r-- | engines/glk/glk_dispa.cpp | 58 | ||||
-rw-r--r-- | engines/glk/glk_types.h | 2 | ||||
-rw-r--r-- | engines/glk/glulxe/glkop.cpp | 1353 | ||||
-rw-r--r-- | engines/glk/glulxe/glkop.h | 36 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe.cpp | 46 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe.h | 453 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe_types.h | 187 | ||||
-rw-r--r-- | engines/glk/sound.cpp | 2 | ||||
-rw-r--r-- | engines/glk/sound.h | 1 | ||||
-rw-r--r-- | engines/glk/streams.cpp | 6 | ||||
-rw-r--r-- | engines/glk/streams.h | 1 |
13 files changed, 2115 insertions, 45 deletions
diff --git a/engines/glk/glk_api.cpp b/engines/glk/glk_api.cpp index 61c7a55aea..e2f3117ef7 100644 --- a/engines/glk/glk_api.cpp +++ b/engines/glk/glk_api.cpp @@ -36,7 +36,8 @@ namespace Glk { GlkAPI::GlkAPI(OSystem *syst, const GlkGameDescription &gameDesc) : - GlkEngine(syst, gameDesc), _gliFirstEvent(false) { + GlkEngine(syst, gameDesc), _gliFirstEvent(false), gli_register_obj(nullptr), + gli_unregister_obj(nullptr), gli_register_arr(nullptr), gli_unregister_arr(nullptr) { // Set uppercase/lowercase tables int ix, res; for (ix = 0; ix < 256; ix++) { diff --git a/engines/glk/glk_api.h b/engines/glk/glk_api.h index 9e4a230cd9..e54cd3c042 100644 --- a/engines/glk/glk_api.h +++ b/engines/glk/glk_api.h @@ -52,6 +52,11 @@ private: bool _gliFirstEvent; unsigned char _charTolowerTable[256]; unsigned char _charToupperTable[256]; + + gidispatch_rock_t(*gli_register_obj)(void *obj, uint objclass); + void(*gli_unregister_obj)(void *obj, uint objclass, gidispatch_rock_t objrock); + gidispatch_rock_t(*gli_register_arr)(void *array, uint len, char *typecode); + void(*gli_unregister_arr)(void *array, uint len, char *typecode, gidispatch_rock_t objrock); public: /** * Constructor @@ -298,6 +303,12 @@ public: /* dispa methods */ + void gidispatch_set_object_registry(gidispatch_rock_t(*regi)(void *obj, uint objclass), + void(*unregi)(void *obj, uint objclass, gidispatch_rock_t objrock)); + + void gidispatch_set_retained_registry(gidispatch_rock_t(*regi)(void *array, uint len, char *typecode), + void(*unregi)(void *array, uint len, char *typecode, gidispatch_rock_t objrock)); + uint32 gidispatch_count_classes() const; const gidispatch_intconst_t *gidispatch_get_class(uint32 index) const; uint32 gidispatch_count_intconst() const; @@ -307,6 +318,7 @@ public: gidispatch_function_t *gidispatch_get_function_by_id(uint32 id) const; const char *gidispatch_prototype(uint32 funcnum) const; void gidispatch_call(uint32 funcnum, uint32 numargs, gluniversal_t *arglist); + gidispatch_rock_t gidispatch_get_objrock(void *obj, uint objclass); }; } // End of namespace Glk diff --git a/engines/glk/glk_dispa.cpp b/engines/glk/glk_dispa.cpp index 38b03e5b42..dfb638fa0a 100644 --- a/engines/glk/glk_dispa.cpp +++ b/engines/glk/glk_dispa.cpp @@ -335,6 +335,46 @@ static gidispatch_function_t function_table[] = { #endif /* GLK_MODULE_GARGLKTEXT */ }; +void GlkAPI::gidispatch_set_object_registry(gidispatch_rock_t(*regi)(void *obj, uint objclass), + void(*unregi)(void *obj, uint objclass, gidispatch_rock_t objrock)) { + Window *win; + Stream *str; + frefid_t fref; + + gli_register_obj = regi; + gli_unregister_obj = unregi; + + if (gli_register_obj) + { + /* It's now necessary to go through all existing objects, and register + them. */ + for (win = glk_window_iterate(NULL, NULL); + win; + win = glk_window_iterate(win, NULL)) + { + win->_dispRock = (*gli_register_obj)(win, gidisp_Class_Window); + } + for (str = glk_stream_iterate(NULL, NULL); + str; + str = glk_stream_iterate(str, NULL)) + { + str->_dispRock = (*gli_register_obj)(str, gidisp_Class_Stream); + } + for (fref = glk_fileref_iterate(NULL, NULL); + fref; + fref = glk_fileref_iterate(fref, NULL)) + { + fref->_dispRock = (*gli_register_obj)(fref, gidisp_Class_Fileref); + } + } +} + +void GlkAPI::gidispatch_set_retained_registry(gidispatch_rock_t(*regi)(void *array, uint len, char *typecode), + void(*unregi)(void *array, uint len, char *typecode, gidispatch_rock_t objrock)) { + gli_register_arr = regi; + gli_unregister_arr = unregi; +} + uint32 GlkAPI::gidispatch_count_classes() const { return NUMCLASSES; } @@ -1491,4 +1531,22 @@ void GlkAPI::gidispatch_call(uint32 funcnum, uint32 numargs, gluniversal_t *argl } } +gidispatch_rock_t GlkAPI::gidispatch_get_objrock(void *obj, uint objclass) { + switch (objclass) { + case gidisp_Class_Window: + return ((Window *)obj)->_dispRock; + case gidisp_Class_Stream: + return ((Stream *)obj)->_dispRock; + case gidisp_Class_Fileref: + return ((FileReference *)obj)->_dispRock; + case gidisp_Class_Schannel: + return ((SoundChannel *)obj)->_dispRock; + default: { + gidispatch_rock_t dummy; + dummy.num = 0; + return dummy; + } + } +} + } // End of namespace Glk diff --git a/engines/glk/glk_types.h b/engines/glk/glk_types.h index 3c4b4fb675..7095334344 100644 --- a/engines/glk/glk_types.h +++ b/engines/glk/glk_types.h @@ -230,7 +230,7 @@ union gluniversal_union { byte _uch; ///< Cu int8 _sch; ///< Cs char _ch; ///< Cn - char *_charstr; ///< S + const char *_charstr; ///< S uint32 *_unicharstr; ///< U void *_array; ///< all # arguments uint32 _ptrflag; ///< [ ... ] or *? diff --git a/engines/glk/glulxe/glkop.cpp b/engines/glk/glulxe/glkop.cpp index af650a3add..ee7f78bf42 100644 --- a/engines/glk/glulxe/glkop.cpp +++ b/engines/glk/glulxe/glkop.cpp @@ -20,11 +20,1362 @@ * */ -#include "glk/glulxe/glkop.h" +#include "engines/glk/glulxe/glulxe.h" namespace Glk { namespace Glulxe { +/* 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)) + +static gidispatch_rock_t classtable_register(void *obj, uint objclass) { + return g_vm->glulxe_classtable_register(obj, objclass); +} + +static void classtable_unregister(void *obj, uint objclass, gidispatch_rock_t objrock) { + g_vm->glulxe_classtable_unregister(obj, objclass, objrock); +} + +static gidispatch_rock_t retained_register(void *array, uint len, char *typecode) { + return g_vm->glulxe_retained_register(array, len, typecode); +} + +static void retained_unregister(void *array, uint len, char *typecode, gidispatch_rock_t objrock) { + g_vm->glulxe_retained_unregister(array, len, typecode, objrock); +} + +void Glulxe::glkopInit() { + library_select_hook = nullptr; + arrays = nullptr; + num_classes = 0; + classes = nullptr; +} + +/* init_dispatch(): + Set up the class hash tables and other startup-time stuff. +*/ +bool Glulxe::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<num_classes; ix++) { + classes[ix] = new_classtable((glulx_random() % (uint)(101)) + 1); + if (!classes[ix]) + return false; + } + + /* Set up the two callbacks. */ + gidispatch_set_object_registry(&classtable_register, &classtable_unregister); + gidispatch_set_retained_registry(&retained_register, &retained_unregister); + + /* If the library supports autorestore callbacks, set those up too. + (These are only used in iosglk, currently.) */ +#ifdef GIDISPATCH_AUTORESTORE_REGISTRY + gidispatch_set_autorestore_registry(&glulxe_array_locate, &glulxe_array_restore); +#endif /* GIDISPATCH_AUTORESTORE_REGISTRY */ + + return true; +} + +uint Glulxe::perform_glk(uint funcnum, uint numargs, uint *arglist) { + uint retval = 0; + + switch (funcnum) { + /* To speed life up, we implement commonly-used Glk functions + directly -- instead of bothering with the whole prototype + mess. */ + + case 0x0047: /* stream_set_current */ + if (numargs != 1) + goto WrongArgNum; + glk_stream_set_current(find_stream_by_id(arglist[0])); + break; + case 0x0048: /* stream_get_current */ + if (numargs != 0) + goto WrongArgNum; + retval = find_id_for_stream(glk_stream_get_current()); + break; + case 0x0080: /* put_char */ + if (numargs != 1) + goto WrongArgNum; + glk_put_char(arglist[0] & 0xFF); + break; + case 0x0081: /* put_char_stream */ + if (numargs != 2) + goto WrongArgNum; + glk_put_char_stream(find_stream_by_id(arglist[0]), arglist[1] & 0xFF); + break; + case 0x00C0: /* select */ + /* call a library hook on every glk_select() */ + if (library_select_hook) + library_select_hook(arglist[0]); + /* but then fall through to full dispatcher, because there's no real + need for speed here */ + goto FullDispatcher; + case 0x00A0: /* char_to_lower */ + if (numargs != 1) + goto WrongArgNum; + retval = glk_char_to_lower(arglist[0] & 0xFF); + break; + case 0x00A1: /* char_to_upper */ + if (numargs != 1) + goto WrongArgNum; + retval = glk_char_to_upper(arglist[0] & 0xFF); + break; + case 0x0128: /* put_char_uni */ + if (numargs != 1) + goto WrongArgNum; + glk_put_char_uni(arglist[0]); + break; + case 0x012B: /* put_char_stream_uni */ + if (numargs != 2) + goto WrongArgNum; + glk_put_char_stream_uni(find_stream_by_id(arglist[0]), arglist[1]); + break; + + WrongArgNum: + error("Wrong number of arguments to Glk function."); + break; + + FullDispatcher: + default: { + /* Go through the full dispatcher prototype foo. */ + const char *proto, *cx; + dispatch_splot_t splot; + int argnum, argnum2; + + /* Grab the string. */ + proto = gidispatch_prototype(funcnum); + if (!proto) + error("Unknown Glk function."); + + splot.varglist = arglist; + splot.numvargs = numargs; + splot.retval = &retval; + + /* The work goes in four phases. First, we figure out how many + arguments we want, and allocate space for the Glk argument + list. Then we go through the Glulxe arguments and load them + into the Glk list. Then we call. Then we go through the + arguments again, unloading the data back into Glulx memory. */ + + /* Phase 0. */ + prepare_glk_args(proto, &splot); + + /* Phase 1. */ + argnum = 0; + cx = proto; + parse_glk_args(&splot, &cx, 0, &argnum, 0, 0); + + /* Phase 2. */ + gidispatch_call(funcnum, argnum, splot.garglist); + + /* Phase 3. */ + argnum2 = 0; + cx = proto; + unparse_glk_args(&splot, &cx, 0, &argnum2, 0, 0); + if (argnum != argnum2) + error("Argument counts did not match."); + + break; + } + } + + return retval; +} + +const char *Glulxe::read_prefix(const 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; +} + +void Glulxe::prepare_glk_args(const char *proto, dispatch_splot_t *splot) { + static gluniversal_t *garglist = nullptr; + static int garglist_size = 0; + + int ix; + int numwanted, numvargswanted, maxargs; + const 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 { + error("Illegal format string."); + } + } + + if (*cx != ':' && *cx != '\0') + error("Illegal format string."); + + splot->maxargs = maxargs; + + if (splot->numvargs != numvargswanted) + error("Wrong number of arguments to Glk function."); + + if (garglist && garglist_size < maxargs) { + glulx_free(garglist); + garglist = nullptr; + garglist_size = 0; + } + if (!garglist) { + garglist_size = maxargs + 16; + garglist = (gluniversal_t *)glulx_malloc(garglist_size + * sizeof(gluniversal_t)); + } + if (!garglist) + error("Unable to allocate storage for Glk arguments."); + + splot->garglist = garglist; +} + +void Glulxe::parse_glk_args(dispatch_splot_t *splot, const char **proto, int depth, int *argnumptr, + uint subaddress, int subpassin) { + const char *cx; + int ix, argx; + int gargnum, numwanted; + void *opref; + gluniversal_t *garglist; + uint *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) + error("Zero passed invalidly to Glk function."); + garglist[gargnum]._ptrflag = false; + gargnum++; + skipval = true; + } + else { + garglist[gargnum]._ptrflag = true; + gargnum++; + } + } + if (!skipval) { + uint 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: + 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 = (uint)(thisval); + else if (*cx == 's') + garglist[gargnum]._sint = (int)(thisval); + else + error("Illegal format string."); + gargnum++; + cx++; + break; + case 'Q': + if (thisval) { + opref = classes_get(*cx-'a', thisval); + if (!opref) { + error("Reference to nonexistent Glk object."); + } + } + else { + opref = nullptr; + } + 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 + 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: + 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 != ']') + error("Illegal format string."); + cx++; + } + else { + if (*cx != ':' && *cx != '\0') + error("Illegal format string."); + } + + *proto = cx; + *argnumptr = gargnum; +} + +void Glulxe::unparse_glk_args(dispatch_splot_t *splot, const char **proto, int depth, + int *argnumptr, uint subaddress, int subpassout) { + const char *cx; + int ix, argx; + int gargnum, numwanted; + void *opref; + gluniversal_t *garglist; + uint *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) + error("Zero passed invalidly to Glk function."); + garglist[gargnum]._ptrflag = false; + gargnum++; + skipval = true; + } else { + garglist[gargnum]._ptrflag = true; + gargnum++; + } + } + if (!skipval) { + uint 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((char *)garglist[gargnum]._array, varglist[ix], varglist[ix+1], passout); + gargnum++; + ix++; + gargnum++; + cx++; + break; + case 'I': + ReleaseIArray((uint *)garglist[gargnum]._array, varglist[ix], varglist[ix+1], passout); + gargnum++; + ix++; + gargnum++; + cx++; + break; + case 'Q': + ReleasePtrArray((void **)garglist[gargnum]._array, varglist[ix], varglist[ix+1], (*cx-'a'), passout); + gargnum++; + ix++; + gargnum++; + cx++; + break; + default: + 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 = (uint)garglist[gargnum]._uint; + else if (*cx == 's') + thisval = (uint)garglist[gargnum]._sint; + else + 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 = (uint)garglist[gargnum]._uch; + else if (*cx == 's') + thisval = (uint)garglist[gargnum]._sch; + else if (*cx == 'n') + thisval = (uint)garglist[gargnum]._ch; + else + 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: + 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 != ']') + error("Illegal format string."); + cx++; + } + else { + if (*cx != ':' && *cx != '\0') + error("Illegal format string."); + } + + *proto = cx; + *argnumptr = gargnum; +} + +strid_t Glulxe::find_stream_by_id(uint objid) { + if (!objid) + return nullptr; + + // Recall that class 1 ("b") is streams + return (strid_t)classes_get(gidisp_Class_Stream, objid); +} + +uint Glulxe::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; +} + +uint Glulxe::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; +} + +uint Glulxe::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; +} + +uint Glulxe::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; +} + +classtable_t *Glulxe::new_classtable(uint firstid) { + int ix; + classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t)); + if (!ctab) + return nullptr; + + for (ix=0; ix<CLASSHASH_SIZE; ix++) + ctab->bucket[ix] = nullptr; + + ctab->lastid = firstid; + + return ctab; +} + +void *Glulxe::classes_get(int classid, uint objid) { + classtable_t *ctab; + classref_t *cref; + if (classid < 0 || classid >= num_classes) + return nullptr; + ctab = classes[classid]; + cref = ctab->bucket[objid % CLASSHASH_SIZE]; + for (; cref; cref = cref->next) { + if (cref->id == objid) + return cref->obj; + } + return nullptr; +} + +classref_t *Glulxe::classes_put(int classid, void *obj, uint origid) { + int bucknum; + classtable_t *ctab; + classref_t *cref; + if (classid < 0 || classid >= num_classes) + return nullptr; + ctab = classes[classid]; + cref = (classref_t *)glulx_malloc(sizeof(classref_t)); + if (!cref) + return nullptr; + 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; +} + +void Glulxe::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 = (classref_t *)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 nullptr object!"); + } + cref->obj = nullptr; + cref->id = 0; + cref->next = nullptr; + glulx_free(cref); + return; + } + } + return; +} + +gidispatch_rock_t Glulxe::glulxe_classtable_register(void *obj, uint objclass) { + classref_t *cref; + gidispatch_rock_t objrock; + cref = classes_put(objclass, obj, 0); + objrock.ptr = cref; + return objrock; +} + +void Glulxe::glulxe_classtable_unregister(void *obj, uint objclass, + gidispatch_rock_t objrock) { + classes_remove(objclass, obj); +} + +gidispatch_rock_t Glulxe::glulxe_classtable_register_existing(void *obj, uint objclass, uint dispid) { + classref_t *cref; + gidispatch_rock_t objrock; + cref = classes_put(objclass, obj, dispid); + objrock.ptr = cref; + return objrock; +} + +char *Glulxe::grab_temp_c_array(uint addr, uint len, int passin) { + arrayref_t *arref = nullptr; + char *arr = nullptr; + uint ix, addr2; + + if (len) { + arr = (char *)glulx_malloc(len * sizeof(char)); + arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t)); + if (!arr || !arref) + 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; ix<len; ix++, addr2+=1) { + arr[ix] = Mem1(addr2); + } + } + } + + return arr; +} + +void Glulxe::release_temp_c_array(char *arr, uint addr, uint len, int passout) { + arrayref_t *arref = nullptr; + arrayref_t **aptr; + uint ix, val, addr2; + + if (arr) { + for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { + if ((*aptr)->array == arr) + break; + } + arref = *aptr; + if (!arref) + error("Unable to re-find array argument in Glk call."); + if (arref->addr != addr || arref->len != len) + error("Mismatched array argument in Glk call."); + + if (arref->retained) { + return; + } + + *aptr = arref->next; + arref->next = nullptr; + + if (passout) { + for (ix=0, addr2=addr; ix<len; ix++, addr2+=1) { + val = arr[ix]; + MemW1(addr2, val); + } + } + glulx_free(arr); + glulx_free(arref); + } +} + +uint *Glulxe::grab_temp_i_array(uint addr, uint len, int passin) { + arrayref_t *arref = nullptr; + uint *arr = nullptr; + uint ix, addr2; + + if (len) { + arr = (uint *)glulx_malloc(len * sizeof(uint)); + arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t)); + if (!arr || !arref) + error("Unable to allocate space for array argument to Glk call."); + + arref->array = 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; ix<len; ix++, addr2+=4) { + arr[ix] = Mem4(addr2); + } + } + } + + return arr; +} + +void Glulxe::release_temp_i_array(uint *arr, uint addr, uint len, int passout) { + arrayref_t *arref = nullptr; + arrayref_t **aptr; + uint ix, val, addr2; + + if (arr) { + for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { + if ((*aptr)->array == arr) + break; + } + arref = *aptr; + if (!arref) + error("Unable to re-find array argument in Glk call."); + if (arref->addr != addr || arref->len != len) + error("Mismatched array argument in Glk call."); + + if (arref->retained) { + return; + } + + *aptr = arref->next; + arref->next = nullptr; + + if (passout) { + for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) { + val = arr[ix]; + MemW4(addr2, val); + } + } + glulx_free(arr); + glulx_free(arref); + } +} + +void **Glulxe::grab_temp_ptr_array(uint addr, uint len, int objclass, int passin) { + arrayref_t *arref = nullptr; + void **arr = nullptr; + uint ix, addr2; + + if (len) { + arr = (void **)glulx_malloc(len * sizeof(void *)); + arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t)); + if (!arr || !arref) + error("Unable to allocate space for array argument to Glk call."); + + arref->array = 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; ix<len; ix++, addr2+=4) { + uint thisval = Mem4(addr2); + if (thisval) + arr[ix] = classes_get(objclass, thisval); + else + arr[ix] = nullptr; + } + } + } + + return arr; +} + +void Glulxe::release_temp_ptr_array(void **arr, uint addr, uint len, int objclass, int passout) { + arrayref_t *arref = nullptr; + arrayref_t **aptr; + uint ix, val, addr2; + + if (arr) { + for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { + if ((*aptr)->array == arr) + break; + } + arref = *aptr; + if (!arref) + error("Unable to re-find array argument in Glk call."); + if (arref->addr != addr || arref->len != len) + error("Mismatched array argument in Glk call."); + + if (arref->retained) { + return; + } + + *aptr = arref->next; + arref->next = nullptr; + + if (passout) { + for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) { + void *opref = arr[ix]; + if (opref) { + gidispatch_rock_t objrock = + gidispatch_get_objrock(opref, objclass); + val = ((classref_t *)objrock.ptr)->id; + } + else { + val = 0; + } + MemW4(addr2, val); + } + } + glulx_free(arr); + glulx_free(arref); + } +} + +gidispatch_rock_t Glulxe::glulxe_retained_register(void *array, uint len, char *typecode) { + gidispatch_rock_t rock; + arrayref_t *arref = nullptr; + arrayref_t **aptr; + uint elemsize = 0; + + if (typecode[4] == 'C') + elemsize = 1; + else if (typecode[4] == 'I') + elemsize = 4; + + if (!elemsize || array == nullptr) { + rock.ptr = nullptr; + return rock; + } + + for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { + if ((*aptr)->array == array) + break; + } + arref = *aptr; + if (!arref) + error("Unable to re-find array argument in Glk call."); + if (arref->elemsize != elemsize || arref->len != len) + error("Mismatched array argument in Glk call."); + + arref->retained = true; + + rock.ptr = arref; + return rock; +} + +void Glulxe::glulxe_retained_unregister(void *array, uint len, char *typecode, gidispatch_rock_t objrock) { + arrayref_t *arref = nullptr; + arrayref_t **aptr; + uint ix, addr2, val; + uint elemsize = 0; + + if (typecode[4] == 'C') + elemsize = 1; + else if (typecode[4] == 'I') + elemsize = 4; + + if (!elemsize || array == nullptr) { + return; + } + + for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) { + if ((*aptr)->array == array) + break; + } + arref = *aptr; + if (!arref) + error("Unable to re-find array argument in Glk call."); + if (arref != objrock.ptr) + error("Mismatched array reference in Glk call."); + if (!arref->retained) + error("Unretained array reference in Glk call."); + if (arref->elemsize != elemsize || arref->len != len) + error("Mismatched array argument in Glk call."); + + *aptr = arref->next; + arref->next = nullptr; + + if (elemsize == 1) { + for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=1) { + val = ((char *)array)[ix]; + MemW1(addr2, val); + } + } + else if (elemsize == 4) { + for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=4) { + val = ((uint *)array)[ix]; + MemW4(addr2, val); + } + } + + glulx_free(array); + glulx_free(arref); +} + +long Glulxe::glulxe_array_locate(void *array, uint len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref) { + arrayref_t *arref = nullptr; + arrayref_t **aptr; + uint elemsize = 0; + + if (typecode[4] == 'C') + elemsize = 1; + else if (typecode[4] == 'I') + elemsize = 4; + + if (!elemsize || array == nullptr) { + *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) + error("Unable to re-find array argument in array_locate."); + if (arref != objrock.ptr) + error("Mismatched array reference in array_locate."); + if (!arref->retained) + error("Unretained array reference in array_locate."); + if (arref->elemsize != elemsize || arref->len != len) + error("Mismatched array argument in array_locate."); + + *elemsizeref = arref->elemsize; + return arref->addr; +} + +gidispatch_rock_t Glulxe::glulxe_array_restore(long bufkey, uint 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 = nullptr; + return rock; + } + + if (elemsize == 1) { + char *cbuf = grab_temp_c_array(bufkey, len, false); + rock = glulxe_retained_register(cbuf, len, typecode); + *arrayref = cbuf; + } + else { + uint *ubuf = grab_temp_i_array(bufkey, len, false); + rock = glulxe_retained_register(ubuf, len, typecode); + *arrayref = ubuf; + } + return rock; +} + +void Glulxe::set_library_select_hook(void (*func)(uint)) { + library_select_hook = func; +} + +char *Glulxe::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 nullptr; + + 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; +} } // End of namespace Glulxe } // End of namespace Glk diff --git a/engines/glk/glulxe/glkop.h b/engines/glk/glulxe/glkop.h deleted file mode 100644 index ed9d716764..0000000000 --- a/engines/glk/glulxe/glkop.h +++ /dev/null @@ -1,36 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef GLK_GLULXE_FLOAT -#define GLK_GLULXE_FLOAT - -#include "common/scummsys.h" -#include "glk/glk_api.h" - -namespace Glk { -namespace Glulxe { - - -} // End of namespace Glulxe -} // End of namespace Glk - -#endif diff --git a/engines/glk/glulxe/glulxe.cpp b/engines/glk/glulxe/glulxe.cpp index 40f4bdde57..bb1a65101c 100644 --- a/engines/glk/glulxe/glulxe.cpp +++ b/engines/glk/glulxe/glulxe.cpp @@ -27,8 +27,14 @@ namespace Glk { namespace Glulxe { +Glulxe *g_vm; + Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), - vm_exited_cleanly(false) { + vm_exited_cleanly(false), gamefile(nullptr), gamefile_start(0), gamefile_len(0), + memmap(nullptr), stack(nullptr), ramstart(0), endgamefile(0), origendmem(0), stacksize(0), + startfuncaddr(0), checksum(0), stackptr(0), frameptr(0), pc(0), prevpc(0), origstringtable(0), + stringtable(0), valstackbase(0), localsbase(0), endmem(0), protectstart(0), protectend(0) { + g_vm = this; } void Glulxe::runGame() { @@ -73,5 +79,43 @@ bool Glulxe::is_gamefile_valid() { return true; } +void Glulxe::fatal_error_handler(const char *str, const char *arg, bool useVal, int val) { + Common::String msg = "Glulxe fatal error: "; + + if (arg || useVal) { + msg += " ("; + + if (arg) + msg += Common::String::format("%s", arg); + if (arg && useVal) + msg += " "; + if (useVal) + msg += Common::String::format("%x", val); + + msg += ")"; + } + + error("%s", msg.c_str()); +} + +void Glulxe::nonfatal_warning_handler(const char *str, const char *arg, bool useVal, int val) { + Common::String msg = "Glulxe warning: "; + + if (arg || useVal) { + msg += " ("; + + if (arg) + msg += Common::String::format("%s", arg); + if (arg && useVal) + msg += " "; + if (useVal) + msg += Common::String::format("%x", val); + + msg += ")"; + } + + warning("%s", msg.c_str()); +} + } // End of namespace Glulxe } // End of namespace Glk diff --git a/engines/glk/glulxe/glulxe.h b/engines/glk/glulxe/glulxe.h index decdc298f7..b1f44535e8 100644 --- a/engines/glk/glulxe/glulxe.h +++ b/engines/glk/glulxe/glulxe.h @@ -25,6 +25,7 @@ #include "common/scummsys.h" #include "glk/glk_api.h" +#include "glk/glulxe/glulxe_types.h" namespace Glk { namespace Glulxe { @@ -35,11 +36,111 @@ namespace Glulxe { class Glulxe : public GlkAPI { public: bool vm_exited_cleanly; -private: + strid_t gamefile; + uint gamefile_start, gamefile_len; + char *init_err, *init_err2; + + unsigned char *memmap; + unsigned char *stack; + + uint ramstart; + uint endgamefile; + uint origendmem; + uint stacksize; + uint startfuncaddr; + uint checksum; + uint stackptr; + uint frameptr; + uint pc; + uint origstringtable; + uint stringtable; + uint valstackbase; + uint localsbase; + uint endmem; + uint protectstart, protectend; + uint prevpc; +protected: /** - * Validates the game file, and if it's invalid, displays an error dialog + * \defgroup glkop fields + * @{ */ - bool is_gamefile_valid(); + + /** + * The library_select_hook is called every time the VM blocks for input. + * The app might take this opportunity to autosave, for example. + */ + void (*library_select_hook)(uint); + + arrayref_t *arrays; + + /** + * The list of hash tables, for the classes. + */ + int num_classes; + classtable_t **classes; + + /**@}*/ +protected: + /** + * \defgroup glkop support methods + * @{ + */ + + /** + * Build a hash table to hold a set of Glk objects. + */ + classtable_t *new_classtable(uint firstid); + + /** + * Find a Glk object in the appropriate hash table. + */ + void *classes_get(int classid, uint objid); + + /** + * Put a Glk object in the appropriate hash table. If origid is zero, invent a new + * unique ID for it. + */ + classref_t *classes_put(int classid, void *obj, uint origid); + + /** + * Delete a Glk object from the appropriate hash table. + */ + void classes_remove(int classid, void *obj); + + long glulxe_array_locate(void *array, uint len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref); + gidispatch_rock_t glulxe_array_restore(long bufkey, uint len, char *typecode, void **arrayref); + + char *grab_temp_c_array(uint addr, uint len, int passin); + void release_temp_c_array(char *arr, uint addr, uint len, int passout); + uint *grab_temp_i_array(uint addr, uint len, int passin); + void release_temp_i_array(uint *arr, uint addr, uint len, int passout); + void **grab_temp_ptr_array(uint addr, uint len, int objclass, int passin); + void release_temp_ptr_array(void **arr, uint addr, uint len, int objclass, int passout); + + /** + * 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. + */ + void prepare_glk_args(const char *proto, dispatch_splot_t *splot); + + /** + * This long and unpleasant function translates a set of Floo objects into a gluniversal_t array. It's recursive, too, + * to deal with structures. + */ + void parse_glk_args(dispatch_splot_t *splot, const char **proto, int depth, int *argnumptr, uint subaddress, int subpassin); + + /** + * This is about the reverse of parse_glk_args(). + */ + void unparse_glk_args(dispatch_splot_t *splot, const char **proto, int depth, + int *argnumptr, uint subaddress, int subpassout); + + /** + * Create a string identifying this game. We use the first 64 bytes of the memory map, encoded as hex, + */ + char *get_game_id(); + + /**@}*/ public: /** * Constructor @@ -65,8 +166,354 @@ public: * Save the game to the passed stream */ virtual Common::Error saveGameData(strid_t file, const Common::String &desc) override; + + /** + * \defgroup Main access methods + * @{ + */ + void set_library_start_hook(void(*)(void)); + void set_library_autorestore_hook(void(*)(void)); + + /** + * Display an error in the error window, and then exit. + */ + void fatal_error_handler(const char *str, const char *arg, bool useVal, int val); + + /** + * Display a warning in the error window, and then continue. + */ + void nonfatal_warning_handler(const char *str, const char *arg, bool useVal, int val); +#define fatal_error(s) (fatal_error_handler((s), nullptr, 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), nullptr, true, (v))) +#define nonfatal_warning(s) (nonfatal_warning_handler((s), nullptr, 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), nullptr, true, (v))) + + /** + * \defgroup Files access methods + * @{ + */ + + /** + * Validates the game file, and if it's invalid, displays an error dialog + */ + bool is_gamefile_valid(); + + int locate_gamefile(int isblorb); + + /**@}*/ + + /** + * \defgroup Vm access methods + * @{ + */ + + void setup_vm(void); + void finalize_vm(void); + void vm_restart(void); + uint change_memsize(uint newlen, int internal); + uint *pop_arguments(uint count, uint addr); + void verify_address(uint addr, uint count); + void verify_address_write(uint addr, uint count); + void verify_array_addresses(uint addr, uint count, uint size); + + /**@}*/ + + /** + * \defgroup Exec access methods + * @{ + */ + void execute_loop(void); + + /**@}*/ + + /** + * \defgroup Operand access methods + * @{ + */ operandlist_t *fast_operandlist[0x80]; + void init_operands(void); + operandlist_t *lookup_operandlist(uint opcode); + void parse_operands(oparg_t *opargs, operandlist_t *oplist); + void store_operand(uint desttype, uint destaddr, uint storeval); + void store_operand_s(uint desttype, uint destaddr, uint storeval); + void store_operand_b(uint desttype, uint destaddr, uint storeval); + + /**@}*/ + + /** + * \defgroup Func access methods + * @{ + */ + + void enter_function(uint addr, uint argc, uint *argv); + void leave_function(void); + void push_callstub(uint desttype, uint destaddr); + void pop_callstub(uint returnvalue); + uint pop_callstub_string(int *bitnum); + + /**@}*/ + + /** + * \defgroup Strings access methods + * @{ + */ + + void stream_num(int val, int inmiddle, int charnum); + void stream_string(uint addr, int inmiddle, int bitnum); + uint stream_get_table(void); + void stream_set_table(uint addr); + void stream_get_iosys(uint *mode, uint *rock); + void stream_set_iosys(uint mode, uint rock); + char *make_temp_string(uint addr); + uint *make_temp_ustring(uint addr); + void free_temp_string(const char *str); + void free_temp_ustring(const uint *str); + + /**@}*/ + + /** + * \defgroup Heap access methods + * @{ + */ + void heap_clear(void); + int heap_is_active(void); + uint heap_get_start(void); + uint heap_alloc(uint len); + void heap_free(uint addr); + int heap_get_summary(uint *valcount, uint **summary); + int heap_apply_summary(uint valcount, uint *summary); + void heap_sanity_check(void); + + /**@}*/ + + /** + * \defgroup Serial access methods + * @{ + */ + + int max_undo_level; + int init_serial(void); + void final_serial(void); + uint perform_save(strid_t str); + uint perform_restore(strid_t str, int fromshell); + uint perform_saveundo(void); + uint perform_restoreundo(void); + uint perform_verify(void); + + /**@}*/ + + /** + * \defgroup Search access methods + * @{ + */ + + uint linear_search(uint key, uint keysize, + uint start, uint structsize, uint numstructs, + uint keyoffset, uint options); + uint binary_search(uint key, uint keysize, + uint start, uint structsize, uint numstructs, + uint keyoffset, uint options); + uint linked_search(uint key, uint keysize, + uint start, uint keyoffset, uint nextoffset, + uint options); + + /**@}*/ + + /** + * \defgroup Osdepend access methods + * @{ + */ + + void *glulx_malloc(uint len); + void *glulx_realloc(void *ptr, uint len); + void glulx_free(void *ptr); + void glulx_setrandom(uint seed); + uint glulx_random(void); + void glulx_sort(void *addr, int count, int size, + int(*comparefunc)(void *p1, void *p2)); + + /**@}*/ + + /** + * \defgroup Gestalt access methods + * @{ + */ + + uint do_gestalt(uint val, uint val2); + + /**@}*/ + + /** + * \defgroup Glkop access methods + * @{ + */ + + /** + * glkop section initialization + */ + void glkopInit(); + + void set_library_select_hook(void(*func)(uint)); + + bool init_dispatch(); + + /** + * The object registration/unregistration callbacks that the library calls + * to keep the hash tables up to date. + */ + gidispatch_rock_t glulxe_classtable_register(void *obj, uint objclass); + + gidispatch_rock_t glulxe_classtable_register_existing(void *obj, uint objclass, uint dispid); + + void glulxe_classtable_unregister(void *obj, uint objclass, gidispatch_rock_t objrock); + + gidispatch_rock_t glulxe_retained_register(void *array, uint len, char *typecode); + void glulxe_retained_unregister(void *array, uint len, char *typecode, gidispatch_rock_t objrock); + + /** + * Turn a list of Glulx arguments into a list of Glk arguments, dispatch the function call, and return the result. + */ + uint perform_glk(uint funcnum, uint numargs, uint *arglist); + + /** + * Read the prefixes of an argument string -- the "<>&+:#!" chars. + */ + const char *read_prefix(const char *cx, int *isref, int *isarray, int *passin, int *passout, + int *nullok, int *isretained, int *isreturn); + + /** + * This is used by some interpreter code which has to, well, find a Glk stream given its ID. + */ + strid_t find_stream_by_id(uint objid); + + /** + * Return the ID of a given Glk window. + */ + uint find_id_for_window(winid_t win); + + /** + * Return the ID of a given Glk stream. + */ + uint find_id_for_stream(strid_t str); + + /** + * Return the ID of a given Glk fileref. + */ + uint find_id_for_fileref(frefid_t fref); + + /** + * Return the ID of a given Glk schannel. + */ + uint find_id_for_schannel(schanid_t schan); + + /**@}*/ + + /** + * \defgroup Profile access methods + * @{ + */ + + void setup_profile(strid_t stream, char *filename); + int init_profile(void); + void profile_set_call_counts(int flag); +#if VM_PROFILING + uint profile_opcount; +#define profile_tick() (profile_opcount++) + int profile_profiling_active(void); + void profile_in(uint addr, uint stackuse, int accel); + void profile_out(uint stackuse); + void profile_fail(char *reason); + 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 + unsigned long debugger_opcount; +#define debugger_tick() (debugger_opcount++) + int debugger_load_info_stream(strid_t stream); + int debugger_load_info_chunk(strid_t stream, uint pos, uint len); + void debugger_track_cpu(int flag); + void debugger_set_start_trap(int flag); + void debugger_set_quit_trap(int flag); + void debugger_set_crash_trap(int flag); + void debugger_check_story_file(void); + void debugger_setup_start_state(void); + int debugger_ever_invoked(void); + int debugger_cmd_handler(char *cmd); + void debugger_cycle_handler(int cycle); + void debugger_check_func_breakpoint(uint addr); + void debugger_block_and_debug(char *msg); + void debugger_handle_crash(char *msg); + 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 */ + + /**@}*/ + + /** + * \defgroup Accel access methods + * @{ + */ + + typedef uint(*acceleration_func)(uint argc, uint *argv); + void init_accel(void); + acceleration_func accel_find_func(uint index); + acceleration_func accel_get_func(uint addr); + void accel_set_func(uint index, uint addr); + void accel_set_param(uint index, uint val); + uint accel_get_param_count(void); + uint accel_get_param(uint index); + void accel_iterate_funcs(void(*func)(uint index, uint addr)); + + /**@}*/ + + /** + * \defgroup Float access methods + * @{ + */ +#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 */ + int init_float(void); + uint encode_float(gfloat32 val); + gfloat32 decode_float(uint 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) */ + + gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2); + +#endif /* FLOAT_SUPPORT */ + /**@}*/ }; +extern Glulxe *g_vm; + } // End of namespace Glulxe } // End of namespace Glk diff --git a/engines/glk/glulxe/glulxe_types.h b/engines/glk/glulxe/glulxe_types.h new file mode 100644 index 0000000000..52a6363833 --- /dev/null +++ b/engines/glk/glulxe/glulxe_types.h @@ -0,0 +1,187 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GLK_GLULXE_TYPES +#define GLK_GLULXE_TYPES + +#include "common/scummsys.h" + +namespace Glk { +namespace Glulxe { + + + /* 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) READ_BE_UINT32(ptr) +#define Read2(ptr) READ_BE_UINT16(ptr) +#define Read1(ptr) ((byte)(((byte *)(ptr))[0])) +#define Write4(ptr, vl) WRITE_BE_UINT32(ptr, vl) +#define Write2(ptr, vl) WRITE_BE_UINT16(ptr, vl) +#define Write1(ptr, vl) (((byte *)(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) \ + (*((uint16 *)(stack+(adr)))) +#define Stk4(adr) \ + (*((uint32 *)(stack+(adr)))) + +#define StkW1(adr, vl) \ + (*((byte *)(stack+(adr))) = (byte)(vl)) +#define StkW2(adr, vl) \ + (*((uint16 *)(stack+(adr))) = (uint16)(vl)) +#define StkW4(adr, vl) \ + (*((uint32 *)(stack+(adr))) = (uint32)(vl)) + + +struct dispatch_splot_struct { + int numwanted; + int maxargs; + gluniversal_t *garglist; + uint *varglist; + int numvargs; + uint *retval; +}; +typedef dispatch_splot_struct dispatch_splot_t; + +/** + * We maintain a linked list of arrays being used for Glk calls. It is only used for integer + * (uint) arrays -- char arrays are handled in place. It's not worth bothering with a hash table, + * since most arrays appear here only momentarily. + */ +struct arrayref_struct { + void *array; + uint addr; + uint elemsize; + uint len; /* elements */ + int retained; + arrayref_struct *next; +}; +typedef arrayref_struct arrayref_t; + +/** + * 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. + */ +struct classref_struct { + void *obj; + uint id; + int bucknum; + classref_struct *next; +}; +typedef classref_struct classref_t; + +#define CLASSHASH_SIZE (31) +struct classtable_struct { + uint lastid; + classref_t *bucket[CLASSHASH_SIZE]; +}; +typedef classtable_struct classtable_t; + +/** + * Represents the operand structure of an opcode. + */ +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 */ +}; +typedef operandlist_struct operandlist_t; + +enum modeform { + modeform_Load = 1, + modeform_Store = 2 +}; + +/** + * 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. +*/ +struct oparg_struct { + uint desttype; + uint value; +}; +typedef oparg_struct oparg_t; + +#define MAX_OPERANDS (8) + +} // End of namespace Glulxe +} // End of namespace Glk + +#endif diff --git a/engines/glk/sound.cpp b/engines/glk/sound.cpp index 7a93b772f9..70c33c27da 100644 --- a/engines/glk/sound.cpp +++ b/engines/glk/sound.cpp @@ -75,6 +75,8 @@ void Sounds::poll() { SoundChannel::SoundChannel(Sounds *owner) : _owner(owner), _soundNum(0), _rock(0), _notify(0) { + _dispRock.num = 0; + _dispRock.ptr = nullptr; } SoundChannel::~SoundChannel() { diff --git a/engines/glk/sound.h b/engines/glk/sound.h index 337239b761..251c75a2fe 100644 --- a/engines/glk/sound.h +++ b/engines/glk/sound.h @@ -43,6 +43,7 @@ private: Audio::SoundHandle _handle; public: uint _rock; + gidispatch_rock_t _dispRock; public: /** * Constructor diff --git a/engines/glk/streams.cpp b/engines/glk/streams.cpp index 0fd98018b9..84904f9a70 100644 --- a/engines/glk/streams.cpp +++ b/engines/glk/streams.cpp @@ -34,8 +34,10 @@ namespace Glk { Stream::Stream(Streams *streams, bool readable, bool writable, uint rock, bool unicode) : - _streams(streams), _readable(readable), _writable(writable), _rock(0), _unicode(unicode), - _readCount(0), _writeCount(0), _prev(nullptr), _next(nullptr) { + _streams(streams), _readable(readable), _writable(writable), _rock(0), _unicode(unicode), + _readCount(0), _writeCount(0), _prev(nullptr), _next(nullptr) { + _dispRock.num = 0; + _dispRock.ptr = nullptr; } Stream::~Stream() { diff --git a/engines/glk/streams.h b/engines/glk/streams.h index a17ee28cbb..c254a89a28 100644 --- a/engines/glk/streams.h +++ b/engines/glk/streams.h @@ -136,6 +136,7 @@ public: Stream *_prev; Stream *_next; uint _rock; + gidispatch_rock_t _dispRock; bool _unicode; uint _readCount; uint _writeCount; |