diff --git a/README.txt b/README.txt index f9b398b..4c1102b 100644 --- a/README.txt +++ b/README.txt @@ -192,6 +192,9 @@ also to Eliuk Blau for tracking down bugs in the memory management opcodes. * Version History +1.3.1 2012-11-09 Further fixes to glkop.c, following similar fixes added to + Glulxe 0.5.0. + 1.3.0 2011-12-16 Fixed a bug in glkop.c dispatching, to do with arrays of opaque objects, following a similar fix in Glulxe. Fixed a problem with the memory heap not being sorted diff --git a/glkop.c b/glkop.c index 72f8563..c492154 100644 --- a/glkop.c +++ b/glkop.c @@ -14,10 +14,10 @@ - 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 an actual array of bytes somewhere in terp - memory, whose actual address can be computed by the macro - AddressOfArray(addr). Again, addr is a VM address 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(). @@ -67,14 +67,14 @@ if ((addr) == 0xffffffff) \ { StkW4(gStackPointer, (val)); gStackPointer += 1;} \ else memWrite32((addr), (val)) -#define AddressOfArray(addr) \ - ((addr) < gRamStart ? (gRom + (addr)) : (gRam + (addr))) -#define AddressOfIArray(addr) \ - ((addr) < gRamStart ? (gRom + (addr)) : (gRam + (addr))) +#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_array(addr, len, passin)) + (grab_temp_i_array(addr, len, passin)) #define ReleaseIArray(ptr, addr, len, passout) \ - (release_temp_array(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) \ @@ -223,7 +223,7 @@ classtable_t **git_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); +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, @@ -235,8 +235,14 @@ static gidispatch_rock_t glulxe_retained_register(void *array, static void glulxe_retained_unregister(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock); -static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin); -static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout); +/* 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); @@ -246,12 +252,25 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth, int *argnumptr, glui32 subaddress, int subpassout); -/* init_dispatch(): - Set up the class hash tables and other startup-time stuff. +static char *get_game_id(void); + +/* git_init_dispatch(): + Set up the class hash tables and other startup-time stuff. */ int git_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 (git_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(); @@ -271,11 +290,11 @@ int git_init_dispatch() &glulxe_classtable_unregister); gidispatch_set_retained_registry(&glulxe_retained_register, &glulxe_retained_unregister); - + return TRUE; } -/* perform_glk(): +/* git_perform_glk(): Turn a list of Glulx arguments into a list of Glk arguments, dispatch the function call, and return the result. */ @@ -308,6 +327,13 @@ glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist) goto WrongArgNum; glk_put_char_stream(git_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; @@ -333,6 +359,7 @@ glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist) fatalError("Wrong number of arguments to Glk function."); break; + FullDispatcher: default: { /* Go through the full dispatcher prototype foo. */ char *proto, *cx; @@ -599,7 +626,7 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, if (varglist[ix+1] > gEndMem || varglist[ix]+varglist[ix+1] > gEndMem) varglist[ix+1] = gEndMem - varglist[ix]; - garglist[gargnum].array = (void*) AddressOfArray(varglist[ix]); + garglist[gargnum].array = (void*) CaptureCArray(varglist[ix], varglist[ix+1], passin); gargnum++; ix++; garglist[gargnum].uint = varglist[ix]; @@ -609,7 +636,7 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, case 'I': /* See comment above. */ if (varglist[ix+1] > gEndMem/4 || varglist[ix+1] > (gEndMem-varglist[ix])/4) - varglist[ix+1] = (gEndMem - varglist[ix]) / 4; + varglist[ix+1] = (gEndMem - varglist[ix]) / 4; garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin); gargnum++; @@ -619,8 +646,6 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, cx++; break; case 'Q': - /* This case was added after the giant arrays were deprecated, - so we don't bother to allow for that case. */ garglist[gargnum].array = CapturePtrArray(varglist[ix], varglist[ix+1], (*cx-'a'), passin); gargnum++; ix++; @@ -700,9 +725,9 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, #ifdef GLK_MODULE_UNICODE case 'U': garglist[gargnum].unicharstr = DecodeVMUstring(thisval); - gargnum++; + gargnum++; break; -#endif +#endif /* GLK_MODULE_UNICODE */ default: fatalError("Illegal format string."); break; @@ -840,71 +865,71 @@ static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth, 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 - fatalError("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 - fatalError("Illegal format string."); - } - gargnum++; - cx++; - break; - case 'S': - if (garglist[gargnum].charstr) - ReleaseVMString(garglist[gargnum].charstr); + 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 + fatalError("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 + fatalError("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 - default: - fatalError("Illegal format string."); - break; - } + gargnum++; + break; +#endif /* GLK_MODULE_UNICODE */ + default: + fatalError("Illegal format string."); + break; + } if (isreturn) { *(splot->retval) = thisval; @@ -912,15 +937,11 @@ static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth, else if (depth > 0) { /* Definitely not isref or isarray. */ if (subpassout) - { WriteStructField(subaddress, ix, thisval); - } } else if (isref) { if (passout) - { - WriteMemory(varglist[ix], thisval); - } + WriteMemory(varglist[ix], thisval); } } } @@ -967,7 +988,7 @@ static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth, *argnumptr = gargnum; } -/* find_stream_by_id(): +/* git_find_stream_by_id(): This is used by some interpreter code which has to, well, find a Glk stream given its ID. */ @@ -977,12 +998,25 @@ strid_t git_find_stream_by_id(glui32 objid) return NULL; /* Recall that class 1 ("b") is streams. */ - return classes_get(1, objid); + return classes_get(gidisp_Class_Stream, objid); } -/* find_id_for_stream(): - The converse of find_stream_by_id(). - This is only needed in this file, so it's static. +/* git_find_id_for_window(): + Return the ID of a given Glk window. +*/ +glui32 git_find_id_for_window(winid_t win) +{ + gidispatch_rock_t objrock; + + if (!win) + return 0; + + objrock = gidispatch_get_objrock(win, gidisp_Class_Window); + return ((classref_t *)objrock.ptr)->id; +} + +/* git_find_id_for_stream(): + Return the ID of a given Glk stream. */ glui32 git_find_id_for_stream(strid_t str) { @@ -991,7 +1025,35 @@ glui32 git_find_id_for_stream(strid_t str) if (!str) return 0; - objrock = gidispatch_get_objrock(str, 1); + objrock = gidispatch_get_objrock(str, gidisp_Class_Stream); + return ((classref_t *)objrock.ptr)->id; +} + +/* git_find_id_for_fileref(): + Return the ID of a given Glk fileref. +*/ +glui32 git_find_id_for_fileref(frefid_t fref) +{ + gidispatch_rock_t objrock; + + if (!fref) + return 0; + + objrock = gidispatch_get_objrock(fref, gidisp_Class_Fileref); + return ((classref_t *)objrock.ptr)->id; +} + +/* git_find_id_for_schannel(): + Return the ID of a given Glk schannel. +*/ +glui32 git_find_id_for_schannel(schanid_t schan) +{ + gidispatch_rock_t objrock; + + if (!schan) + return 0; + + objrock = gidispatch_get_objrock(schan, gidisp_Class_Schannel); return ((classref_t *)objrock.ptr)->id; } @@ -1027,8 +1089,9 @@ static void *classes_get(int classid, glui32 objid) return NULL; } -/* Put a Glk object in the appropriate hash table. */ -static classref_t *classes_put(int classid, void *obj) +/* 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; @@ -1040,8 +1103,15 @@ static classref_t *classes_put(int classid, void *obj) if (!cref) return NULL; cref->obj = obj; - cref->id = ctab->lastid; - ctab->lastid++; + 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]; @@ -1067,9 +1137,6 @@ static void classes_remove(int classid, void *obj) for (; *crefp; crefp = &((*crefp)->next)) { if ((*crefp) == cref) { *crefp = cref->next; - if (!cref->obj) { - fprintf(stderr, "attempt to free NULL object!\n"); - } cref->obj = NULL; cref->id = 0; cref->next = NULL; @@ -1088,7 +1155,7 @@ static gidispatch_rock_t glulxe_classtable_register(void *obj, { classref_t *cref; gidispatch_rock_t objrock; - cref = classes_put(objclass, obj); + cref = classes_put(objclass, obj, 0); objrock.ptr = cref; return objrock; } @@ -1099,7 +1166,72 @@ static void glulxe_classtable_unregister(void *obj, glui32 objclass, classes_remove(objclass, obj); } -static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin) +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) + fatalError("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) + fatalError("Unable to re-find array argument in Glk call."); + if (arref->addr != addr || arref->len != len) + fatalError("Mismatched array argument in Glk call."); + + if (arref->retained) { + return; + } + + *aptr = arref->next; + arref->next = NULL; + + if (passout) { + for (ix=0, addr2=addr; ixelemsize != 4 || arref->len != len) + if (arref->elemsize != elemsize || arref->len != len) fatalError("Mismatched array argument in Glk call."); arref->retained = TRUE; @@ -1270,15 +1407,20 @@ gidispatch_rock_t glulxe_retained_register(void *array, return rock; } -void glulxe_retained_unregister(void *array, glui32 len, +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] != 'I' || array == NULL) { - /* We only retain integer arrays. */ + if (typecode[4] == 'C') + elemsize = 1; + else if (typecode[4] == 'I') + elemsize = 4; + + if (!elemsize || array == NULL) { return; } @@ -1287,23 +1429,65 @@ void glulxe_retained_unregister(void *array, glui32 len, break; } arref = *aptr; - if (!arref) + if (!arref) { + if (objrock.num == 0) + return; fatalError("Unable to re-find array argument in Glk call."); + } if (arref != objrock.ptr) fatalError("Mismatched array reference in Glk call."); if (!arref->retained) fatalError("Unretained array reference in Glk call."); - if (arref->elemsize != 4 || arref->len != len) + if (arref->elemsize != elemsize || arref->len != len) fatalError("Mismatched array argument in Glk call."); *aptr = arref->next; arref->next = NULL; - for (ix=0, addr2=arref->addr; ixlen; ix++, addr2+=4) { - val = ((glui32 *)array)[ix]; - memWrite32(addr2, val); + if (elemsize == 1) { + for (ix=0, addr2=arref->addr; ixlen; ix++, addr2+=1) { + val = ((char *)array)[ix]; + memWrite8(addr2, val); + } + } + else if (elemsize == 4) { + for (ix=0, addr2=arref->addr; ixlen; ix++, addr2+=4) { + val = ((glui32 *)array)[ix]; + memWrite32(addr2, val); + } } + glulx_free(array); glulx_free(arref); } +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 (!gRom) + return NULL; + + for (ix=0, jx=0; ix<64; ix++) { + char ch = gRom[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; +} + diff --git a/help/glulx.htm b/help/glulx.htm index 651c154..2d934a8 100644 --- a/help/glulx.htm +++ b/help/glulx.htm @@ -10,7 +10,7 @@

Glulx

the Inform compiler could compile to, without the limitations of its original output format, Infocom's Z-Machine.

-Windows Git is based on Iain Merrick's Git 1.3.0, which is a complete implementation of version 3.1.2 +Windows Git is based on Iain Merrick's Git 1.3.1, which is a complete implementation of version 3.1.2 of the Glulx specification.

More information on Glulx is available from the Glulx web page: diff --git a/help/overview.htm b/help/overview.htm index bca3b50..13967ee 100644 --- a/help/overview.htm +++ b/help/overview.htm @@ -4,7 +4,7 @@ -

Windows Git 1.3.0
(Windows Glk 1.44)

+

Windows Git 1.3.1
(Windows Glk 1.44)

Windows Git is an interpreter for Glulx games. Windows Git consists of a core interpreter called Git, and an input/output library, called Windows Glk. Together these two components form the Windows Git package, which has been created diff --git a/help/revision.htm b/help/revision.htm index 2f02496..aaa98c5 100644 --- a/help/revision.htm +++ b/help/revision.htm @@ -6,6 +6,11 @@

Revision History

+Git 1.3.1 (Windows Glk 1.44) +
    +
  • Further fixes to glkop.c, following similar fixes added to Glulxe 0.5.0. +
+ Git 1.3.0 (Windows Glk 1.44)
  • Updated to Git 1.3.0, which fixes a bug related to Glk call dispatching, diff --git a/version.h b/version.h index 6eef8e5..9b192d8 100644 --- a/version.h +++ b/version.h @@ -1,4 +1,4 @@ // Automatically generated file -- do not edit! #define GIT_MAJOR 1 #define GIT_MINOR 3 -#define GIT_PATCH 0 +#define GIT_PATCH 1