From: Marijn van Vliet Date: Mon, 24 Sep 2012 09:38:10 +0000 (+0200) Subject: Merge branch 'gtk3' X-Git-Url: https://git.stderr.nl/gitweb?p=projects%2Fchimara%2Fchimara.git;a=commitdiff_plain;h=3c59ba5eef5cb4d39c06eb7f523b9c3b026bdc9b;hp=ed91d840318ed6ebfe3a5a77fa17114ddbf56640 Merge branch 'gtk3' --- diff --git a/COPYING b/COPYING index eb40986..976f4da 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 2010, Philip Chimento and Marijn van Vliet. +Copyright (C) 2012, Philip Chimento and Marijn van Vliet. All rights reserved. Chimara is free software copyrighted by Philip Chimento and Marijn van Vliet. @@ -9,19 +9,19 @@ are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -3. Neither of the names Philip Chimento or Marijn van Vliet, nor the name of any - other contributor may be used to endorse or promote products derived from +3. Neither of the names Philip Chimento or Marijn van Vliet, nor the name of any + other contributor may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ChangeLog b/ChangeLog index 17318ff..a366387 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1 +1 @@ -(to do before release) \ No newline at end of file +Please refer to `git log' for the change log. \ No newline at end of file diff --git a/Makefile.am b/Makefile.am index a43dbac..d0d290d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,11 +1,7 @@ ## Process this file with automake to produce Makefile.in ## Created by Anjuta -if TARGET_ILIAD -SUBDIRS = libchimara interpreters babel player po -else -SUBDIRS = libchimara interpreters babel player tests docs po -endif +SUBDIRS = libchimara interpreters player tests docs po chimaradocdir = $(datadir)/doc/chimara dist_chimaradoc_DATA = \ diff --git a/NEWS b/NEWS index 17318ff..0fde9c9 100644 --- a/NEWS +++ b/NEWS @@ -1 +1,3 @@ -(to do before release) \ No newline at end of file +2012-xx-xx: Release 0.99 +======================== +- First public release. \ No newline at end of file diff --git a/babel/MANIFEST b/babel/MANIFEST deleted file mode 100644 index 09048bd..0000000 --- a/babel/MANIFEST +++ /dev/null @@ -1,51 +0,0 @@ -adrift.c Treaty of Babel module for Adrift -advsys.c Treaty of Babel module for AdvSys -alan.c Treaty of Babel module for Alan -agt.c Treaty of Babel module for AGT -babel.h babel program header -md5.h L. Peter Deutsch's md5 header -modules.h babel module registry -treaty.h Treaty of Babel header -treaty_builder.h Macros to build treaty modules -babel.c Babel main program -babel_handler.h Babel handler header file -babel_handler.c The babel handler api -babel_ifiction_functions.c Babel program-specific ifiction operations -babel_multi_functions.c Babel program-specific multi operations -babel_story_functions.c Babel program-specific story operations -blorb.c babel handler blorb module -executable.c Treaty of Bable module for executables -glulx.c Treaty of Babel module for glulx -hugo.c Draft Treaty of Babel module for hugo -ifiction.h babel ifiction header -ifiction.c babel ifiction api -level9.c Treaty of Babel module for level9 -magscrolls.c Magnetic Scrolls treaty module -makefile Provisional makefile -md5.c L. Peter Deutsch's md5 implementation -misc.c babel memory allocator -register.c babel module registry -register_ifiction.c babel module registry for ifiction API -tads.h Prototypes for tads2.c and tads3.c -tads.c Common functions for TADS modules -tads2.c Treaty of Babel module for tads2 -tads3.c Treaty of Babel module for tads3 -zcode.c Treaty of Babel module for zcode -README documentation -MANIFEST this file -extras/babel-cache.pl Perl demo of babel interaction -extras/babel-infocom.pl Special bundler for the infocom corpus -extras/babel-list.c Babel API demo -extras/babel-marry.pl Perl simple blorb encapsulator -extras/babel-wed.pl Perl single file blorb encapsulator -extras/hotload.c Dynamic loader replacement for register.c -extras/hotload.h Header file for hotload.c -extras/ifiction-aggregate.c Utility to combine multiple ifiction files -extras/ifiction-xtract.c Ifiction API demo -extras/simple-marry.c Simplified C version of babel-marry -babel-get/babel-get.c The babel-get application -babel-get/get_dir.c Directory source -babel-get/get_ifiction.c ifiction source -babel-get/get_story.c story file source -babel-get/get_url.c URL source -babel-get/makefile Makefile for babel-get diff --git a/babel/Makefile.am b/babel/Makefile.am deleted file mode 100644 index 1e0d78d..0000000 --- a/babel/Makefile.am +++ /dev/null @@ -1,36 +0,0 @@ -noinst_LTLIBRARIES = libbabel.la libifiction.la libbabel_functions.la - -libbabel_la_SOURCES = babel_handler.c \ - register.c \ - misc.c \ - md5.c \ - zcode.c \ - magscrolls.c \ - blorb.c \ - glulx.c \ - hugo.c \ - agt.c \ - level9.c \ - executable.c \ - advsys.c \ - tads.c \ - tads2.c \ - tads3.c \ - adrift.c \ - alan.c \ - babel.h \ - babel_handler.h \ - md5.h \ - modules.h \ - tads.h \ - treaty_builder.h \ - treaty.h - -libifiction_la_SOURCES = ifiction.c ifiction.h \ - register_ifiction.c - -libbabel_functions_la_SOURCES = babel_story_functions.c \ - babel_ifiction_functions.c \ - babel_multi_functions.c - --include $(top_srcdir)/git.mk diff --git a/babel/README b/babel/README deleted file mode 100644 index b754cab..0000000 --- a/babel/README +++ /dev/null @@ -1,74 +0,0 @@ -Version 0.2b, Treaty of Babel Revision 7 -This is the source code for babel, the Treaty of Babel analysis tool. - -Most of this code is (c) 2006 by L. Ross Raszewski - -The following files are public domain: -zcode.c -glulx.c -executable.c -level9.c -magscrolls.c -agt.c -hugo.c -advsys.c -misc.c -alan.c -adrift.c -treaty.h -treaty_builder.h - -The following files are Copyright (C) 1999, 2000, 2002 Aladdin Enterprises: -md5.c -md5.h - -And are used in accordance with their licenses. - -All other files are (c) 2006 by L. Ross Raszewski and are released under -the Creative Commons Attribution2.5 License. - -To view a copy of this license, visit -http://creativecommons.org/licenses/by/2.5/ or send a letter to - -Creative Commons, -543 Howard Street, 5th Floor, -San Francisco, California, 94105, USA. - - -To build babel: - -1. compile all the source files in this directory -2. link them together -3. the end - -For folks who find makefiles more useful than generalizations, there is a -makefile provided for babel. The makefile is currently configured for -Borland's 32-bit C compiler. Comment out those lines and uncomment the block -which follows for gcc. - -To compile babel-get, first compile babel, then do the same thing in the -babel-get directory. - -To compile ifiction-aggregate, ifiction-xtract, babel-list, and simple-marry, -first compile babel, then compile the relevant C file in the extras/ directory -(These may rely on #include files from the babel directory, so, for example, -to compile ifiction-aggregate, "gcc -c -I.. ifiction-aggregate.c"), then link the -opbject file to the babel and ifiction libraries (babel.lib and ifiction.lib -under Windows, babel.a and ifiction.a most everywhere else. eg. -"gcc -o ifiction-aggregate ifiction-aggregate.o ../babel.a ../ifiction.a") - -Babel is intended to accept contributions in the form of treaty modules -as defined by the treaty of babel section 2.3.2. - -These modules should use the declarations made in treaty.h. -The file treaty_builder.h generates a generic framework which simplifies -the task of writing treaty modules. Its use is not required for treaty -compliance, but it should prove useful. - -Parts of babel are intended for use in other programs. When adapting -babel's source, the files babel.c, babel_story_functions.c and -babel_ifiction_functions.c will probably not prove useful. However, you -may wish to use babel_handler, which provides a framework for loading a -story file, selecting the proper treaty modules, and seamlessly handling -blorb-wrapped files. - diff --git a/babel/adrift.c b/babel/adrift.c deleted file mode 100644 index 3718d6a..0000000 --- a/babel/adrift.c +++ /dev/null @@ -1,89 +0,0 @@ -/* adrift.c Treaty of Babel module for Adrift files - * - * PROVISIONAL - Hold for someone else - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file - * may render it noncompliant with the Treaty of Babel - */ - -#define FORMAT adrift -#define HOME_PAGE "http://www.adrift.org.uk" -#define FORMAT_EXT ".taf" -#define NO_METADATA -#define NO_COVER - -#include "treaty_builder.h" - -#include -#include -#include - -/* VB RNG constants */ -#define VB_RAND1 0x43FD43FD -#define VB_RAND2 0x00C39EC3 -#define VB_RAND3 0x00FFFFFF -#define VB_INIT 0x00A09E86 -static int32 vbr_state; - -/* - Unobfuscates one byte from a taf file. This should be called on each byte - in order, as the ADRIFT obfuscation function is stately. - - The de-obfuscation algorithm works by xoring the byte with the next - byte in the sequence produced by the Visual Basic pseudorandom number - generator, which is simulated here. -*/ -static unsigned char taf_translate (unsigned char c) -{ - int32 r; - - vbr_state = (vbr_state*VB_RAND1+VB_RAND2) & VB_RAND3; - r=UCHAR_MAX * (unsigned) vbr_state; - r/=((unsigned) VB_RAND3)+1; - return r^c; -} - -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - int adv; - unsigned char buf[4]; - unsigned char *sf=(unsigned char *)story_file; - vbr_state=VB_INIT; - - if (extent <12) return INVALID_STORY_FILE_RV; - - buf[3]=0; - /* Burn the first 8 bytes of translation */ - for(adv=0;adv<8;adv++) taf_translate(0); - /* Bytes 8-11 contain the Adrift version number in the formay N.NN */ - buf[0]=taf_translate(sf[8]); - taf_translate(0); - buf[1]=taf_translate(sf[10]); - buf[2]=taf_translate(sf[11]); - adv=atoi((char *) buf); - ASSERT_OUTPUT_SIZE(12); - sprintf(output,"ADRIFT-%03d-",adv); - return INCOMPLETE_REPLY_RV; - -} - -/* The claim algorithm for ADRIFT is to unobfuscate the first - seven bytes, and check for the word "Version". - It seems fairly unlikely that the obfuscated form of that - word would occur in the wild -*/ -static int32 claim_story_file(void *story_file, int32 extent) -{ - unsigned char buf[8]; - int i; - unsigned char *sf=(unsigned char *)story_file; - buf[7]=0; - vbr_state=VB_INIT; - if (extent<12) return INVALID_STORY_FILE_RV; - for(i=0;i<7;i++) buf[i]=taf_translate(sf[i]); - if (strcmp((char *)buf,"Version")) return INVALID_STORY_FILE_RV; - return VALID_STORY_FILE_RV; - -} diff --git a/babel/advsys.c b/babel/advsys.c deleted file mode 100644 index e0e1cf8..0000000 --- a/babel/advsys.c +++ /dev/null @@ -1,49 +0,0 @@ -/* advsys.c Treaty of Babel module for AdvSys files - * 2006 By L. Ross Raszewski - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file - * may render it noncompliant with the Treaty of Babel - */ - -#define FORMAT advsys -#define HOME_PAGE "http://www.ifarchive.org/if-archive/programming/advsys/" -#define FORMAT_EXT ".dat" -#define NO_METADATA -#define NO_COVER - -#include "treaty_builder.h" -#include -#include - -/* IFIDs for AdvSys are formed by prepending ADVSYS- to the default - MD5 ifid -*/ -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - /* This line suppresses a warning from the borland compiler */ - if (story_file || extent) { } - ASSERT_OUTPUT_SIZE(8); - strcpy(output,"ADVSYS-"); - return INCOMPLETE_REPLY_RV; - -} - -/* The Advsys claim algorithm: bytes 2-8 of the file contain the - text "ADVSYS", unobfuscated in the following way: - 30 is added to each byte, then the bits are reversed -*/ -static int32 claim_story_file(void *story_file, int32 extent) -{ - char buf[7]; - int i; - if (extent >=8) - { - for(i=0;i<6;i++) - buf[i]=~(((char *)story_file)[i+2]+30); - buf[6]=0; - if (strcmp(buf,"ADVSYS")==0) return VALID_STORY_FILE_RV; - } - return INVALID_STORY_FILE_RV; -} diff --git a/babel/agt.c b/babel/agt.c deleted file mode 100644 index 66b6575..0000000 --- a/babel/agt.c +++ /dev/null @@ -1,59 +0,0 @@ -/* agt.c Treaty of Babel module for AGX-encapsulated AGT files - * 2006 By L. Ross Raszewski - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file - * may render it noncompliant with the Treaty of Babel - */ - -#define FORMAT agt -#define HOME_PAGE "http://www.ifarchive.org/indexes/if-archiveXprogrammingXagt" -#define FORMAT_EXT ".agx" -#define NO_METADATA -#define NO_COVER - -#include "treaty_builder.h" -#include -#include - - -static char AGX_MAGIC[4] = { 0x58, 0xC7, 0xC1, 0x51 }; - -/* Helper functions to unencode integers from AGT source */ -static int32 read_agt_short(unsigned char *sf) -{ - return sf[0] | (int32) sf[1]<<8; -} -static int32 read_agt_int(unsigned char *sf) -{ - return (read_agt_short(sf+2) << 16) | read_agt_short(sf); - -} - -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - int32 l, game_version, game_sig; - unsigned char *sf=(unsigned char *)story_file; - - /* Read the position of the game desciption block */ - l=read_agt_int(sf+32); - if (extent -#include - -static int32 read_alan_int(unsigned char *from) -{ - return ((unsigned long int) from[3])| ((unsigned long int)from[2] << 8) | - ((unsigned long int) from[1]<<16)| ((unsigned long int)from[0] << 24); -} -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - - if (story_file || extent) { } - ASSERT_OUTPUT_SIZE(6); - strcpy(output,"ALAN-"); - return INCOMPLETE_REPLY_RV; -} -/* - The claim algorithm for Alan files is: - * For Alan 3, check for the magic word - * load the file length in blocks - * check that the file length is correct - * For alan 2, each word between byte address 24 and 81 is a - word address within the file, so check that they're all within - the file - * Locate the checksum and verify that it is correct -*/ -static int32 claim_story_file(void *story_file, int32 extent) -{ - unsigned char *sf = (unsigned char *) story_file; - int32 bf, i, crc=0; - if (extent < 160) return INVALID_STORY_FILE_RV; - if (memcmp(sf,"ALAN",4)) - { /* Identify Alan 2.x */ - bf=read_alan_int(sf+4); - if (bf > extent/4) return INVALID_STORY_FILE_RV; - for (i=24;i<81;i+=4) - if (read_alan_int(sf+i) > extent/4) return INVALID_STORY_FILE_RV; - for (i=160;i<(bf*4);i++) - crc+=sf[i]; - if (crc!=read_alan_int(sf+152)) return INVALID_STORY_FILE_RV; - return VALID_STORY_FILE_RV; - } - else - { /* Identify Alan 3 */ - bf=read_alan_int(sf+12); - if (bf > (extent/4)) return INVALID_STORY_FILE_RV; - for (i=184;i<(bf*4);i++) - crc+=sf[i]; - if (crc!=read_alan_int(sf+176)) return INVALID_STORY_FILE_RV; - - } - return INVALID_STORY_FILE_RV; -} diff --git a/babel/babel b/babel/babel deleted file mode 100644 index 1808ec1..0000000 Binary files a/babel/babel and /dev/null differ diff --git a/babel/babel-makefile b/babel/babel-makefile deleted file mode 100644 index 1b31887..0000000 --- a/babel/babel-makefile +++ /dev/null @@ -1,75 +0,0 @@ -# provisional makefile for babel -# -# Note that to compile babel, it is necessary only to compile all the .c -# files in this distribution and link them. -# -# This makefile is provided purely as a convenience. -# -# The following targets are available: -# babel: make babel -# babel.lib: make babel handler library (for Borland) -# ifiction.lib: make babel ifiction library (for Borland) -# babel.a: make babel handler library (for gcc) -# ifiction.a: make babel ifiction library (for gcc) -# dist: make babel.zip, the babel source distribution -# -# Note that this is a GNU makefile, and may not work with other makes -# -# Comment/uncomment the following lines to make the program work - -#CC=bcc32 -#OBJ=.obj -#BABEL_LIB=babel.lib -#IFICTION_LIB=ifiction.lib -#BABEL_FLIB=babel_functions.lib -#OUTPUT_BABEL= - -CC=gcc -g -OBJ=.o -BABEL_LIB=babel.a -BABEL_FLIB=babel_functions.a -IFICTION_LIB=ifiction.a -OUTPUT_BABEL=-o babel - -treaty_objs = zcode${OBJ} magscrolls${OBJ} blorb${OBJ} glulx${OBJ} hugo${OBJ} agt${OBJ} level9${OBJ} executable${OBJ} advsys${OBJ} tads${OBJ} tads2${OBJ} tads3${OBJ} adrift${OBJ} alan${OBJ} -bh_objs = babel_handler${OBJ} register${OBJ} misc${OBJ} md5${OBJ} ${treaty_objs} -ifiction_objs = ifiction${OBJ} register_ifiction${OBJ} -babel_functions = babel_story_functions${OBJ} babel_ifiction_functions${OBJ} babel_multi_functions${OBJ} -babel_objs = babel${OBJ} $(BABEL_FLIB) $(IFICTION_LIB) $(BABEL_LIB) - -babel: ${babel_objs} - ${CC} ${OUTPUT_BABEL} ${babel_objs} - -%${OBJ} : %.c - ${CC} -c $^ - -register${OBJ}: modules.h - -babel.lib: ${foreach dep,${bh_objs},${dep}.bl} - -ifiction.lib: ${foreach dep,${ifiction_objs},${dep}.il} - -babel_functions.lib: ${foreach dep,${babel_functions},${dep}.fl} - -%.obj.bl: %.obj - tlib babel.lib +-$^ - echo made > $@ - -%.obj.il: %.obj - tlib ifiction.lib +-$^ - echo made > $@ -%.obj.fl: %.obj - tlib babel_functions.lib +-$^ - echo made > $@ - -babel.a: $(bh_objs) - ar -r babel.a $^ - -ifiction.a: $(ifiction_objs) - ar -r ifiction.a $^ - -babel_functions.a: $(babel_functions) - ar -r babel_functions.a $^ - -dist: - cut -c0-31 MANIFEST | zip babel.zip -@ diff --git a/babel/babel.a b/babel/babel.a deleted file mode 100644 index 142fb78..0000000 Binary files a/babel/babel.a and /dev/null differ diff --git a/babel/babel.c b/babel/babel.c deleted file mode 100644 index 5bb7213..0000000 --- a/babel/babel.c +++ /dev/null @@ -1,248 +0,0 @@ -/* babel.c The babel command line program - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends upon misc.c and babel.h - * - * This file exports one variable: char *rv, which points to the file name - * for an ifiction file. This is used only by babel_ifiction_verify - */ - -#include "babel.h" -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif -int chdir(const char *); -char *getcwd(char *, int); -#ifdef __cplusplus -} -#endif - -char *fn; - -/* checked malloc function */ -void *my_malloc(int, char *); - -/* babel performs several fundamental operations, which are specified - by command-line objects. Each of these functions corresponds to - a story function (defined in babel_story_functions.c) or an - ifiction function (defined in babel_ifiction_functions.c) or both. - These are the types of those functions. -*/ - -typedef void (*story_function)(void); -typedef void (*ifiction_function)(char *); -typedef void (*multi_function)(char **, char *, int); -/* This structure tells babel what to do with its command line arguments. - if either of story or ifiction are NULL, babel considers this command line - option inappropriate for that type of file. -*/ -struct function_handler { - char *function; /* the textual command line option */ - story_function story; /* handler for story files */ - ifiction_function ifiction; /* handler for ifiction files */ - char *desc; /* Textual description for help text */ - }; - -struct multi_handler { - char *function; - char *argnames; - multi_function handler; - int nargsm; - int nargsx; - char *desc; - }; -/* This is an array of function_handler objects which specify the legal - arguments. It is terminated by a function_handler with a NULL function - */ -static struct function_handler functions[] = { - { "-ifid", babel_story_ifid, babel_ifiction_ifid, "Deduce IFID"}, - { "-format", babel_story_format, NULL, "Deduce story format" }, - { "-ifiction", babel_story_ifiction, NULL, "Extract iFiction file" }, - { "-meta", babel_story_meta, NULL, "Print story metadata" }, - { "-identify", babel_story_identify, NULL, "Describe story file" }, - { "-cover", babel_story_cover, NULL, "Extract cover art" }, - { "-story", babel_story_story, NULL, "Extract story file (ie. from a blorb)" }, - { "-verify", NULL, babel_ifiction_verify, "Verify integrity of iFiction file" }, - { "-lint", NULL, babel_ifiction_lint, "Verify style of iFiction file" }, - { "-fish", babel_story_fish, babel_ifiction_fish, "Extract all iFiction and cover art"}, - { "-unblorb", babel_story_unblorb, NULL, "As -fish, but also extract story files"}, - { NULL, NULL, NULL } - }; -static struct multi_handler multifuncs[] = { - { "-blorb", " []", babel_multi_blorb, 2, 3, "Bundle story file and (sparse) iFiction into blorb" }, - { "-blorbs", " []", babel_multi_blorb1, 2, 3, "Bundle story file and (sparse) iFiction into sensibly-named blorb" }, - { "-complete", " ", babel_multi_complete, 2, 2, "Create complete iFiction file from sparse iFiction" }, - { NULL, NULL, NULL, 0, 0, NULL } -}; - -int main(int argc, char **argv) -{ - char *todir="."; - char cwd[512]; - int ok=1,i, l, ll; - FILE *f; - char *md=NULL; - /* Set the input filename. Note that if this is invalid, babel should - abort before anyone notices - */ - fn=argv[2]; - - if (argc < 3) ok=0; - /* Detect the presence of the "-to " argument. - */ - if (ok && argc >=5 && strcmp(argv[argc-2], "-to")==0) - { - todir=argv[argc-1]; - argc-=2; - } - if (ok) for(i=0;multifuncs[i].function;i++) - if (strcmp(argv[1],multifuncs[i].function)==0 && - argc>= multifuncs[i].nargsm+2 && - argc <= multifuncs[i].nargsx+2) - { - - multifuncs[i].handler(argv+2, todir, argc-2); - exit(0); - } - - if (argc!=3) ok=0; - - /* Find the apropriate function_handler */ - if (ok) { - for(i=0;functions[i].function && strcmp(functions[i].function,argv[1]);i++); - if (!functions[i].function) ok=0; - else if (strcmp(fn,"-")) { - f=fopen(argv[2],"r"); - if (!f) ok=0; - } - } - - /* Print usage error if anything has gone wrong */ - if (!ok) - { - printf("%s: Treaty of Babel Analysis Tool (%s, %s)\n" - "Usage:\n", argv[0],BABEL_VERSION, TREATY_COMPLIANCE); - for(i=0;functions[i].function;i++) - { - if (functions[i].story) - printf(" babel %s \n",functions[i].function); - if (functions[i].ifiction) - printf(" babel %s \n",functions[i].function); - printf(" %s\n",functions[i].desc); - } - for(i=0;multifuncs[i].function;i++) - { - printf("babel %s %s\n %s\n", - multifuncs[i].function, - multifuncs[i].argnames, - multifuncs[i].desc); - } - - printf ("\nFor functions which extract files, add \"-to \" to the command\n" - "to set the output directory.\n" - "The input file can be specified as \"-\" to read from standard input\n" - "(This may only work for .iFiction files)\n"); - return 1; - } - - /* For story files, we end up reading the file in twice. This - is unfortunate, but unavoidable, since we want to be all - cross-platformy, so the first time we read it in, we - do the read in text mode, and the second time, we do it in binary - mode, and there are platforms where this makes a difference. - */ - ll=0; - if (strcmp(fn,"-")) - { - fseek(f,0,SEEK_END); - l=ftell(f)+1; - fseek(f,0,SEEK_SET); - md=(char *)my_malloc(l,"Input file buffer"); - fread(md,1,l-1,f); - md[l-1]=0; - } - else - while(!feof(stdin)) - { - char *tt, mdb[1024]; - int ii; - ii=fread(mdb,1,1024,stdin); - tt=(char *)my_malloc(ll+ii,"file buffer"); - if (md) { memcpy(tt,md,ll); free(md); } - memcpy(tt+ll,mdb,ii); - md=tt; - ll+=ii; - if (ii<1024) break; - } - - - if (strstr(md,""); - if (pp) *(pp+10)=0; - getcwd(cwd,512); - chdir(todir); - l=0; - if (functions[i].ifiction) - functions[i].ifiction(md); - else - fprintf(stderr,"Error: option %s is not valid for iFiction files\n", - argv[1]); - chdir(cwd); - } - - if (strcmp(fn,"-")) - { - free(md); - fclose(f); - } - if (l) - { /* Appears to be a story */ - char *lt; - if (functions[i].story) - { - if (strcmp(fn,"-")) lt=babel_init(argv[2]); - else { lt=babel_init_raw(md,ll); - free(md); - } - - if (lt) - { - getcwd(cwd,512); - chdir(todir); - if (!babel_get_authoritative() && strcmp(argv[1],"-format")) - printf("Warning: Story format could not be positively identified. Guessing %s\n",lt); - functions[i].story(); - - chdir(cwd); - } - else if (strcmp(argv[1],"-ifid")==0) /* IFID is calculable for all files */ - { - babel_md5_ifid(cwd,512); - printf("IFID: %s\n",cwd); - } - else - fprintf(stderr,"Error: Did not recognize format of story file\n"); - babel_release(); - } - else - fprintf(stderr,"Error: option %s is not valid for story files\n", - argv[1]); - } - - return 0; -} diff --git a/babel/babel.h b/babel/babel.h deleted file mode 100644 index 4108e3d..0000000 --- a/babel/babel.h +++ /dev/null @@ -1,56 +0,0 @@ -/* babel.h declarations for babel - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends upon treaty.h, babel_ifiction_functions.c, - * babel_story_functions.c, and babel_handler.c - * - */ - -#define BABEL_VERSION "0.2b" - -#include "treaty.h" -#include "babel_handler.h" -#include "ifiction.h" -/* Functions from babel_story_functions.c - * - * Each of these assumes that the story file has been loaded by babel_handler - * - * Each function babel_story_XXXX coresponds to the command line option -XXXX - */ -void babel_story_ifid(void); -void babel_story_cover(void); -void babel_story_ifiction(void); -void babel_story_meta(void); -void babel_story_fish(void); -void babel_story_format(void); -void babel_story_identify(void); -void babel_story_story(void); -void babel_story_unblorb(void); -/* Functions from babel_ifiction_functions.c - * - * as with babel_story_XXXX, but for metadata, which is handed in as the - * C string parameter - */ -void babel_ifiction_ifid(char *); -void babel_ifiction_verify(char *); -void babel_ifiction_fish(char *); -void babel_ifiction_lint(char *); - -/* Functions from babel_multi_functions.c - * - */ -void babel_multi_blorb(char **, char * , int); -void babel_multi_blorb1(char **, char * , int); -void babel_multi_complete(char **, char *, int); - -/* uncomment this line on platforms which limit extensions to 3 characters */ -/* #define THREE_LETTER_EXTENSIONS */ diff --git a/babel/babel_functions.a b/babel/babel_functions.a deleted file mode 100644 index ea8a1f6..0000000 Binary files a/babel/babel_functions.a and /dev/null differ diff --git a/babel/babel_handler.c b/babel/babel_handler.c deleted file mode 100644 index f7afb78..0000000 --- a/babel/babel_handler.c +++ /dev/null @@ -1,366 +0,0 @@ -/* babel_handler.c dispatches Treaty of Babel queries to the treaty modules - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends upon register.c, misc.c, babel.h, and treaty.h - * and L. Peter Deutsch's md5.c - * usage: - * char *babel_init(char *filename) - * Initializes babel to use the specified file. MUST be called before - * babel_treaty. Returns the human-readable name of the format - * or NULL if the format is not known. Do not call babel_treaty unless - * babel_init returned a nonzero value. - * The returned string will be the name of a babel format, possibly - * prefixed by "blorbed " to indicate that babel will process this file - * as a blorb. - * int32 babel_treaty(int32 selector, void *output, void *output_extent) - * Dispatches the call to the treaty handler for the currently loaded - * file. - * When processing a blorb, all treaty calls will be deflected to the - * special blorb handler. For the case of GET_STORY_FILE_IFID_SEL, - * The treaty handler for the underlying format will be called if an - * IFID is not found in the blorb resources. - * void babel_release() - * Frees all resources allocated during babel_init. - * You should do this even if babel_init returned NULL. - * After this is called, do not call babel_treaty until after - * another successful call to babel_init. - * char *babel_get_format() - * Returns the same value as the last call to babel_init (ie, the format name) - * int32 babel_md5_ifid(char *buffer, int extent); - * Generates an MD5 IFID from the loaded story. Returns zero if something - * went seriously wrong. - * - * If you wish to use babel in multiple threads, you must use the contextualized - * versions of the above functions. - * Each function above has a companion function whose name ends in _ctx (eg. - * "babel_treaty_ctx") which takes one additional argument. This argument is - * the babel context. A new context is returned by void *ctx=get_babel_ctx(), - * and should be released when finished by calling release_babel_ctx(ctx); - */ - - -#include "treaty.h" -#include -#include -#include -#include -#include "md5.h" - -void *my_malloc(int, char *); - -struct babel_handler -{ - TREATY treaty_handler; - TREATY treaty_backup; - void *story_file; - int32 story_file_extent; - void *story_file_blorbed; - int32 story_file_blorbed_extent; - char blorb_mode; - char *format_name; - char auth; -}; - -static struct babel_handler default_ctx; - -extern TREATY treaty_registry[]; -extern TREATY container_registry[]; - -static char *deeper_babel_init(char *story_name, void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - int i; - char *ext; - - static char buffer[TREATY_MINIMUM_EXTENT]; - int best_candidate; - char buffert[TREATY_MINIMUM_EXTENT]; - - if (story_name) - { - ext=strrchr(story_name,'.'); - if (ext) for(i=0;ext[i];i++) ext[i]=tolower(ext[i]); - } - else ext=NULL; - best_candidate=-1; - if (ext) /* pass 1: try best candidates */ - for(i=0;container_registry[i];i++) - if (container_registry[i](GET_FILE_EXTENSIONS_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT) >=0 && - strstr(buffer,ext) && - container_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0)>=NO_REPLY_RV) - break; - if (!ext || !container_registry[i]) /* pass 2: try all candidates */ - { - - for(i=0;container_registry[i];i++) - {int l=container_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0); - - if (l==VALID_STORY_FILE_RV) - break; - else if (l==NO_REPLY_RV && best_candidate < 0) best_candidate=i; - } -} - if (!container_registry[i] && best_candidate >=0) { bh->auth=0; i=best_candidate; } - if (container_registry[i]) - { - char buffer2[TREATY_MINIMUM_EXTENT]; - - bh->treaty_handler=container_registry[i]; - container_registry[i](GET_FORMAT_NAME_SEL,NULL,0,buffert,TREATY_MINIMUM_EXTENT); - bh->blorb_mode=1; - - bh->story_file_blorbed_extent=container_registry[i](CONTAINER_GET_STORY_EXTENT_SEL,bh->story_file,bh->story_file_extent,NULL,0); - if (bh->story_file_blorbed_extent>0) bh->story_file_blorbed=my_malloc(bh->story_file_blorbed_extent, "contained story file"); - if (bh->story_file_blorbed_extent<=0 || - container_registry[i](CONTAINER_GET_STORY_FORMAT_SEL,bh->story_file,bh->story_file_extent,buffer2,TREATY_MINIMUM_EXTENT)<0 || - container_registry[i](CONTAINER_GET_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,bh->story_file_blorbed,bh->story_file_blorbed_extent)<=0 - ) - return NULL; - - for(i=0;treaty_registry[i];i++) - if (treaty_registry[i](GET_FORMAT_NAME_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT)>=0 && - strcmp(buffer,buffer2)==0 && - treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file_blorbed,bh->story_file_blorbed_extent,NULL,0)>=NO_REPLY_RV) - break; - if (!treaty_registry[i]) - return NULL; - bh->treaty_backup=treaty_registry[i]; - sprintf(buffer,"%sed %s",buffert,buffer2); - return buffer; - } - - bh->blorb_mode=0; - best_candidate=-1; - - if (ext) /* pass 1: try best candidates */ - for(i=0;treaty_registry[i];i++) - if (treaty_registry[i](GET_FILE_EXTENSIONS_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT) >=0 && - strstr(buffer,ext) && - treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0)>=NO_REPLY_RV) - break; - if (!ext || !treaty_registry[i]) /* pass 2: try all candidates */ - { - - for(i=0;treaty_registry[i];i++) - {int l; - l=treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0); - - if (l==VALID_STORY_FILE_RV) - break; - else if (l==NO_REPLY_RV && best_candidate < 0) best_candidate=i; - } - } - if (!treaty_registry[i]) - if (best_candidate>0) { i=best_candidate; bh->auth=0; } - else return NULL; - bh->treaty_handler=treaty_registry[i]; - - if (bh->treaty_handler(GET_FORMAT_NAME_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT)>=0) - return buffer; - return NULL; - - -} - -static char *deep_babel_init(char *story_name, void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - FILE *file; - - bh->treaty_handler=NULL; - bh->treaty_backup=NULL; - bh->story_file=NULL; - bh->story_file_extent=0; - bh->story_file_blorbed=NULL; - bh->story_file_blorbed_extent=0; - bh->format_name=NULL; - file=fopen(story_name, "rb"); - if (!file) return NULL; - fseek(file,0,SEEK_END); - bh->story_file_extent=ftell(file); - fseek(file,0,SEEK_SET); - bh->auth=1; - bh->story_file=my_malloc(bh->story_file_extent,"story file storage"); - fread(bh->story_file,1,bh->story_file_extent,file); - fclose(file); - - return deeper_babel_init(story_name, bhp); -} - -char *babel_init_ctx(char *sf, void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - char *b; - b=deep_babel_init(sf,bh); - if (b) bh->format_name=strdup(b); - return b; -} -char *babel_init(char *sf) -{ - return babel_init_ctx(sf, &default_ctx); -} - -char *babel_init_raw_ctx(void *sf, int32 extent, void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - char *b; - bh->treaty_handler=NULL; - bh->treaty_backup=NULL; - bh->story_file=NULL; - bh->story_file_extent=0; - bh->story_file_blorbed=NULL; - bh->story_file_blorbed_extent=0; - bh->format_name=NULL; - bh->story_file_extent=extent; - bh->auth=1; - bh->story_file=my_malloc(bh->story_file_extent,"story file storage"); - memcpy(bh->story_file,sf,extent); - - b=deeper_babel_init(NULL, bhp); - if (b) bh->format_name=strdup(b); - return b; -} -char *babel_init_raw(void *sf, int32 extent) -{ - return babel_init_raw_ctx(sf, extent, &default_ctx); -} - -void babel_release_ctx(void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - if (bh->story_file) free(bh->story_file); - bh->story_file=NULL; - if (bh->story_file_blorbed) free(bh->story_file_blorbed); - bh->story_file_blorbed=NULL; - if (bh->format_name) free(bh->format_name); - bh->format_name=NULL; -} -void babel_release() -{ - babel_release_ctx(&default_ctx); -} -int32 babel_md5_ifid_ctx(char *buffer, int32 extent, void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - md5_state_t md5; - int i; - unsigned char ob[16]; - if (extent <33 || bh->story_file==NULL) - return 0; - md5_init(&md5); - md5_append(&md5,bh->story_file,bh->story_file_extent); - md5_finish(&md5,ob); - for(i=0;i<16;i++) - sprintf(buffer+(2*i),"%02X",ob[i]); - buffer[32]=0; - return 1; - -} -int32 babel_md5_ifid(char *buffer, int32 extent) -{ - return babel_md5_ifid_ctx(buffer, extent, - &default_ctx); -} - -int32 babel_treaty_ctx(int32 sel, void *output, int32 output_extent,void *bhp) -{ - int32 rv; - struct babel_handler *bh=(struct babel_handler *) bhp; - if (!(sel & TREATY_SELECTOR_INPUT) && bh->blorb_mode) - rv=bh->treaty_backup(sel,bh->story_file_blorbed,bh->story_file_blorbed_extent,output, output_extent); - else - { - rv=bh->treaty_handler(sel,bh->story_file,bh->story_file_extent,output,output_extent); - if ((!rv|| rv==UNAVAILABLE_RV) && bh->blorb_mode) - rv=bh->treaty_backup(sel,bh->story_file_blorbed,bh->story_file_blorbed_extent,output, output_extent); - } - if (!rv && sel==GET_STORY_FILE_IFID_SEL) - return babel_md5_ifid_ctx(output,output_extent, bh); - if (rv==INCOMPLETE_REPLY_RV && sel==GET_STORY_FILE_IFID_SEL) - return babel_md5_ifid_ctx((void *)((char *) output+strlen((char *)output)), - output_extent-strlen((char *)output), - bh); - - return rv; -} -int32 babel_treaty(int32 sel, void *output, int32 output_extent) -{ - return babel_treaty_ctx(sel, output, output_extent, &default_ctx); -} -char *babel_get_format_ctx(void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - return bh->format_name; -} -char *babel_get_format() -{ - return babel_get_format_ctx(&default_ctx); -} -void *get_babel_ctx() -{ - return my_malloc(sizeof(struct babel_handler), "babel handler context"); -} -void release_babel_ctx(void *b) -{ - free(b); -} - -int32 babel_get_length_ctx(void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - return bh->story_file_extent; -} -int32 babel_get_length() -{ - return babel_get_length_ctx(&default_ctx); -} - -int32 babel_get_authoritative_ctx(void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - return bh->auth; -} -int32 babel_get_authoritative() -{ - return babel_get_authoritative_ctx(&default_ctx); -} -void *babel_get_file_ctx(void *bhp) -{ - struct babel_handler *bh=(struct babel_handler *) bhp; - return bh->story_file; -} -void *babel_get_file() -{ - return babel_get_file_ctx(&default_ctx); -} - -int32 babel_get_story_length_ctx(void *ctx) -{ - struct babel_handler *bh=(struct babel_handler *) ctx; - if (bh->blorb_mode) return bh->story_file_blorbed_extent; - return bh->story_file_extent; -} -int32 babel_get_story_length() -{ - - return babel_get_story_length_ctx(&default_ctx); -} -void *babel_get_story_file_ctx(void *ctx) -{ - struct babel_handler *bh=(struct babel_handler *) ctx; - if (bh->blorb_mode) return bh->story_file_blorbed; - return bh->story_file; -} -void *babel_get_story_file() -{ - return babel_get_story_file_ctx(&default_ctx); -} diff --git a/babel/babel_handler.h b/babel/babel_handler.h deleted file mode 100644 index a01194a..0000000 --- a/babel/babel_handler.h +++ /dev/null @@ -1,65 +0,0 @@ -/* babel_handler.h declarations for the babel handler API - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - */ - -#ifndef BABEL_HANDLER_H -#define BABEL_HANDLER_H - -#include "treaty.h" - -/* Functions from babel_handler.c */ -char *babel_init(char *filename); - /* initialize the babel handler */ -char *babel_init_raw(void *sf, int32 extent); - /* Initialize from loaded data */ -int32 babel_treaty(int32 selector, void *output, int32 output_extent); - /* Dispatch treaty calls */ -void babel_release(void); - /* Release babel_handler resources */ -char *babel_get_format(void); - /* return the format of the loaded file */ -int32 babel_md5_ifid(char *buffer, int32 extent); - /* IFID generator of last resort */ -int32 babel_get_length(void); - /* Fetch file length */ -int32 babel_get_story_length(void); - /* Fetch file length */ -int32 babel_get_authoritative(void); - /* Determine if babel handler has a good grasp on the format */ -void *babel_get_file(void); - /* Get loaded story file */ -void *babel_get_story_file(void); - /* Get loaded story file */ - -/* threadsafe versions of above */ -char *babel_init_ctx(char *filename, void *); - /* initialize the babel handler */ -int32 babel_treaty_ctx(int32 selector, void *output, int32 output_extent, void *); - /* Dispatch treaty calls */ -void babel_release_ctx(void *); - /* Release babel_handler resources */ -char *babel_get_format_ctx(void *); - /* return the format of the loaded file */ -int32 babel_md5_ifid_ctx(char *buffer, int extent, void *); - /* IFID generator of last resort */ -int32 babel_get_length_ctx(void *); -int32 babel_get_story_length_ctx(void *); -void *babel_get_file_ctx(void *bhp); -void *babel_get_story_ctx(void *bhp); -int32 babel_get_authoritative_ctx(void *bhp); -char *babel_init_raw_ctx(void *sf, int32 extent, void *bhp); -void *get_babel_ctx(void); -void release_babel_ctx(void *); - /* get and release babel contexts */ - -#endif diff --git a/babel/babel_ifiction_functions.c b/babel/babel_ifiction_functions.c deleted file mode 100644 index e59adf6..0000000 --- a/babel/babel_ifiction_functions.c +++ /dev/null @@ -1,168 +0,0 @@ -/* babel_ifiction_functions.c babel top-level operations for ifiction - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends upon babel.c (for rv), babel.h, misc.c and ifiction.c - */ - -#include "babel.h" -#include -#include -#include -#include - -#ifndef THREE_LETTER_EXTENSIONS -#define IFICTION_EXT ".iFiction" -#else -#define IFICTION_EXT ".ifi" -#endif - -void *my_malloc(int, char *); - -struct IFiction_Info -{ - char ifid[256]; - int wmode; -}; - -static void write_story_to_disk(struct XMLTag *xtg, void *ctx) -{ - char *b, *ep; - char *begin, *end; - char buffer[TREATY_MINIMUM_EXTENT]; - int32 l, j; - if (ctx) { } - - if (strcmp(xtg->tag,"story")==0) - { - begin=xtg->begin; - end=xtg->end; - l=end-begin+1; - b=(char *)my_malloc(l,"XML buffer"); - memcpy(b,begin,l-1); - b[l]=0; - j=ifiction_get_IFID(b,buffer,TREATY_MINIMUM_EXTENT); - if (!j) - { - fprintf(stderr,"No IFID found for this story\n"); - free(b); - return; - } - ep=strtok(buffer,","); - while(ep) - { - char buf2[256]; - FILE *f; - sprintf(buf2,"%s%s",ep,IFICTION_EXT); - f=fopen(buf2,"w"); - - if (!f || - fputs("\n" - "" - "\n" - " ", - f)==EOF || - fputs(b,f)==EOF || - fputs("/\n\n",f)==EOF - ) - { - fprintf(stderr,"Error writing to file %s\n",buf2); - } else - printf("Extracted %s\n",buf2); - if (f) fclose(f); - - ep=strtok(NULL,","); - } - - free(b); - } -} - -void babel_ifiction_ifid(char *md) -{ - char output[TREATY_MINIMUM_EXTENT]; - int i; - char *ep; - i=ifiction_get_IFID(md,output,TREATY_MINIMUM_EXTENT); - if (!i) - - { - fprintf(stderr,"Error: No IFIDs found in iFiction file\n"); - return; - } - ep=strtok(output,","); - while(ep) - { - printf("IFID: %s\n",ep); - ep=strtok(NULL,","); - } - -} - -static char isok; - -static void examine_tag(struct XMLTag *xtg, void *ctx) -{ - struct IFiction_Info *xti=(struct IFiction_Info *)ctx; - - if (strcmp("ifid",xtg->tag)==0 && strcmp(xti->ifid,"UNKNOWN")==0) - { - memcpy(xti->ifid,xtg->begin,xtg->end-xtg->begin); - xti->ifid[xtg->end-xtg->begin]=0; - } - -} -static void verify_eh(char *e, void *ctx) -{ - if (*((int *)ctx) < 0) return; - if (*((int *)ctx) || strncmp(e,"Warning",7)) - { isok=0; - fprintf(stderr, "%s\n",e); - } -} - - - -void babel_ifiction_fish(char *md) -{ - int i=-1; - ifiction_parse(md,write_story_to_disk,NULL,verify_eh,&i); -} - -void deep_ifiction_verify(char *md, int f) -{ - struct IFiction_Info ii; - int i=0; - ii.wmode=0; - isok=1; - strcpy(ii.ifid,"UNKNOWN"); - ifiction_parse(md,examine_tag,&ii,verify_eh,&i); - if (f&& isok) printf("Verified %s\n",ii.ifid); -} -void babel_ifiction_verify(char *md) -{ - deep_ifiction_verify(md,1); - -} - - -void babel_ifiction_lint(char *md) -{ - struct IFiction_Info ii; - int i=1; - ii.wmode=1; - isok=1; - strcpy(ii.ifid,"UNKNOWN"); - ifiction_parse(md,examine_tag,&ii,verify_eh,&i); - if (isok) printf("%s conforms to iFiction style guidelines\n",ii.ifid); -} - - diff --git a/babel/babel_multi_functions.c b/babel/babel_multi_functions.c deleted file mode 100644 index cd867c6..0000000 --- a/babel/babel_multi_functions.c +++ /dev/null @@ -1,312 +0,0 @@ -#include "babel.h" - -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif -int chdir(const char *); -char *getcwd(char *, int); -#ifdef __cplusplus -} -#endif - -void deep_ifiction_verify(char *md, int f); -void * my_malloc(int32, char *); -char *blorb_chunk_for_name(char *name); -#ifndef THREE_LETTER_EXTENSIONS -static char *ext_table[] = { "zcode", ".zblorb", - "glulx", ".gblorb", - NULL, NULL - }; -#else -static char *ext_table[] = { "zcode", ".zlb", - "glulx", ".glb", - NULL, NULL - }; - -#endif -char *blorb_ext_for_name(char *fmt) -{ - int i; - for(i=0;ext_table[i];i+=2) - if (strcmp(ext_table[i],fmt)==0) return ext_table[i+1]; -#ifndef THREE_LETTER_EXTENSIONS - return ".blorb"; -#else - return ".blb"; -#endif -} - -char *deep_complete_ifiction(char *fn, char *ifid, char *format) -{ - FILE *f; - int32 i; - char *md; - char *id, *idp; - char *idb; - f=fopen(fn,"r"); - if (!f) { fprintf(stderr,"Error: Can not open file %s\n",fn); - return NULL; - } - fseek(f,0,SEEK_END); - i=ftell(f); - fseek(f,0,SEEK_SET); - md=(char *) my_malloc(i+1,"Metadata buffer"); - fread(md,1,i,f); - md[i]=0; - id=strstr(md,""); - if (id) *(id+10)=0; - fclose(f); - id=strdup(ifid); - idp=strtok(id,","); - /* Find the identification chunk */ - { - char *bp, *ep; - bp=strstr(md,""); - if (!bp) - { - idb=(char *)my_malloc(TREATY_MINIMUM_EXTENT+128,"ident buffer"); - sprintf(idb,"%s\n", format); - } - else - { - int ii; - ep=strstr(bp,""); - idb=(char *)my_malloc(TREATY_MINIMUM_EXTENT+128+(ep-bp),"ident buffer"); - for(ii=16;bp+ii"); - if (bp) - if (memcmp(bp+8,format,strlen(format))) - fprintf(stderr,"Error: Format in sparse .iFiction does not match story\n"); - - } - - } - /* Insert the new ifids */ - while(idp) - { - char bfr[TREATY_MINIMUM_EXTENT]; - sprintf(bfr,"%s",idp); - if (!strstr(idb,bfr)) { strcat(idb,bfr); strcat(idb,"\n"); } - idp=strtok(NULL,","); - - } - free(id); - idp=(char *) my_malloc(strlen(md)+strlen(idb)+64, "Output metadata"); -/* printf("%d bytes for metadata\n",strlen(md)+strlen(idb)+64);*/ - id=strstr(md,""); - id[0]=0; - id+=7; - strcpy(idp,md); - strcat(idp,"\n \n"); - strcat(idp,idb); - free(idb); - strcat(idp," \n"); - strcat(idp,id); - free(md); - md=idp; - deep_ifiction_verify(md, 0); - return md; -} - -void write_int(int32 i, FILE *f) -{ - char bf[4]; - bf[0]=(((unsigned) i) >> 24) & 0xFF; - bf[1]=(((unsigned) i) >> 16) & 0xFF; - bf[2]=(((unsigned) i) >> 8) & 0xFF; - bf[3]=(((unsigned) i)) & 0xFF; - fwrite(bf,1,4,f); -} -static void _babel_multi_blorb(char *outfile, char **args, char *todir , int argc) -{ - int32 total, storyl, coverl, i; - char buffer[TREATY_MINIMUM_EXTENT+10]; - char b2[TREATY_MINIMUM_EXTENT]; - - char cwd[512]; - char *cover, *md, *cvrf, *ep; - - FILE *f, *c; - if (argc!=2 && argc !=3) - { - fprintf(stderr,"Invalid usage\n"); - return; - } - if (!babel_init(args[0])) - { - fprintf(stderr,"Error: Could not determine the format of file %s\n",args[0]); - return; - } - if (babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT)<=0 || - babel_treaty(GET_FORMAT_NAME_SEL,b2,TREATY_MINIMUM_EXTENT)<0 - ) - { - fprintf(stderr,"Error: Could not deduce an IFID for file %s\n",args[0]); - return; - } - if (babel_get_length() != babel_get_story_length()) - { - fprintf(stderr,"Warning: Story file will be extacted from container before blorbing\n"); - } -/* printf("Completing ifiction\n");*/ - md=deep_complete_ifiction(args[1],buffer,b2); -/* printf("Ifiction is %d bytes long\n",strlen(md));*/ - ep=strchr(buffer,','); - if (ep) *ep=0; - if (outfile) - strcpy(buffer,outfile); - strcat(buffer,blorb_ext_for_name(b2)); - getcwd(cwd,512); - chdir(todir); - f=fopen(buffer,"wb"); - chdir(cwd); - if (!f) - { - fprintf(stderr,"Error: Error writing to file %s\n",buffer); - return; - } - storyl=babel_get_story_length(); - total=storyl + (storyl%2) + 36; - if (md) total+=8+strlen(md)+strlen(md)%2; - if (argc==3) - { - c=fopen(args[2],"rb"); - if (c) - { - fseek(c,0,SEEK_END); - coverl=ftell(c); - if (coverl > 5){ - - cover=(char *) my_malloc(coverl+2,"Cover art buffer"); - fseek(c,0,SEEK_SET); - fread(cover,1,coverl,c); - if (memcmp(cover+1,"PNG",3)==0) cvrf="PNG "; - else cvrf="JPEG"; - total += 32+coverl + (coverl%2); - } - else argc=2; - fclose(c); - } - else argc=2; - } -/* printf("Writing header\n;");*/ - fwrite("FORM",1,4,f); - write_int(total,f); -/* printf("Writing index\n;");*/ - fwrite("IFRSRIdx",1,8,f); - write_int(argc==3 ? 28:16,f); - write_int(argc==3 ? 2:1,f); -/* printf("Writing story\n;");*/ - fwrite("Exec", 1,4,f); - write_int(0,f); - write_int(argc==3 ? 48:36,f); - if (argc==3) - { -/* printf("Writing image\n;"); */ - fwrite("Pict", 1,4,f); - write_int(1,f); - write_int(56+storyl+(storyl%2),f); - } -/* printf("Invoking chunk for name %s\n",b2); */ - fwrite(blorb_chunk_for_name(b2),1,4,f); - write_int(storyl,f); -/* printf("Writing story data\n"); */ - fwrite(babel_get_story_file(),1,storyl,f); - if (storyl%2) fwrite("\0",1,1,f); - if (argc==3) - { -/* printf("Writing cover data header %s\n",cvrf); */ - fwrite(cvrf,1,4,f); -/* printf("Writing cover data size %d\n",coverl); */ - write_int(coverl,f); -/* printf("Writing cover data\n"); */ - fwrite(cover,1,coverl,f); - if (coverl%2) fwrite("\0",1,1,f); -/* printf("Done with cover\n");*/ -/* free(cover);*/ -/* printf("Writing frontispiece\n;");*/ - fwrite("Fspc\0\0\0\004\0\0\0\001",1,12,f); - } - - if (md) { -/* printf("Writing metadata\n;");*/ - fwrite("IFmd",1,4,f); - write_int(strlen(md),f); - fwrite(md,1,strlen(md),f); - if (strlen(md)%2) - fwrite("\0",1,1,f); - free(md); - } - - fclose(f); - printf("Created %s\n",buffer); - -} -void babel_multi_complete(char **args, char *todir, int argc) -{ - char buffer[TREATY_MINIMUM_EXTENT+10]; - char b2[TREATY_MINIMUM_EXTENT]; - char cwd[512]; - char *ep, *md; - FILE *f; - if (argc!=2) - { - fprintf(stderr,"Invalid usage\n"); - return; - } - if (!babel_init(args[0])) - { - fprintf(stderr,"Error: Could not determine the format of file %s\n",args[0]); - return; - } - if (babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT)<=0 - || babel_treaty(GET_FORMAT_NAME_SEL,b2,TREATY_MINIMUM_EXTENT)<0) - { - fprintf(stderr,"Error: Could not deduce an IFID for file %s\n",args[0]); - return; - } - md=deep_complete_ifiction(args[1],buffer, b2); - if (!md) return; - ep=strchr(buffer,','); - if (ep) *ep=0; - strcat(buffer,".iFiction"); - getcwd(cwd,512); - chdir(todir); - f=fopen(buffer,"w"); - chdir(cwd); - if (!f || !fputs(md,f)) - { - fprintf(stderr,"Error: Error writing to file %s\n",buffer); - return; - } - fclose(f); - free(md); - printf("Created %s\n",buffer); -} -void babel_multi_blorb(char **args, char *todir , int argc) -{ - _babel_multi_blorb(NULL,args,todir,argc); -} -void babel_multi_blorb1(char **args, char *todir , int argc) -{ - char *buf; - char *bb; - buf=(char *)my_malloc(strlen(args[0])+1,"blorb name buffer"); - strcpy(buf,args[0]); - bb=strrchr(buf,'.'); - if (bb) *bb=0; - _babel_multi_blorb(buf,args,todir,argc); - free(buf); - - -} diff --git a/babel/babel_story_functions.c b/babel/babel_story_functions.c deleted file mode 100644 index b1cf0e9..0000000 --- a/babel/babel_story_functions.c +++ /dev/null @@ -1,411 +0,0 @@ -/* babel_story_functions.c babel top-level operations for story files - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends upon babel_handler.c, babel.h, and misc.c - */ - -#include "babel.h" -#include -#include -#include - -#ifndef THREE_LETTER_EXTENSIONS -#define IFICTION_EXT ".iFiction" -#else -#define IFICTION_EXT ".ifi" -#endif -void *my_malloc(int32, char *); - -void babel_story_ifid() -{ - char buffer[TREATY_MINIMUM_EXTENT]; - char *ep; - int i; - i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); - ep=strtok(buffer, ","); - while(ep) - { - printf("IFID: %s\n",ep); - ep=strtok(NULL,","); - } - if (!i) - fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); - -} - - -void babel_story_format() -{ - char *b; - b=babel_get_format(); - if (!b) b="unknown"; - if (!babel_get_authoritative()) - printf("Format: %s (non-authoritative)\n",b); - else printf("Format: %s\n",b); -} - -static void deep_babel_ifiction(char stopped) -{ - char buffer[TREATY_MINIMUM_EXTENT]; - char *md; - char *ep; - int32 i; - FILE *f; - - if (stopped!=2) - { - i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); - if (i==0 && !babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT)) - { - fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); - return; - } - - - ep=strtok(buffer, ","); - } - else ep="-"; - i=babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL,NULL,0); - if (i<=0) - { - if (stopped) printf("No iFiction record for %s\n",buffer); - return; - } - md=(char *)my_malloc(i,"Metadata buffer"); - if (babel_treaty(GET_STORY_FILE_METADATA_SEL,md,i)<0) - { - fprintf(stderr,"A serious error occurred while retrieving metadata.\n"); - free(md); - return; - } - while(ep) - { - char epb[TREATY_MINIMUM_EXTENT+9]; - if (stopped!=2) - { - strcpy(epb,ep); - strcat(epb, IFICTION_EXT); - - f=fopen(epb,"w"); - } - else f=stdout; - - if (!f || fputs(md,f)==EOF) - fprintf(stderr,"A serious error occurred writing to disk.\n"); - else if (stopped!=2) printf("Extracted %s\n",epb); - if (f) fclose(f); - if (stopped) break; - ep=strtok(NULL,","); - } - free(md); -} - -void babel_story_ifiction() -{ - deep_babel_ifiction(1); -} -static char *get_jpeg_dim(void *img, int32 extent) -{ - unsigned char *dp=(unsigned char *) img; - unsigned char *ep=dp+extent; - static char buffer[256]; - unsigned int t1, t2, w, h; - - - t1=*(dp++); - t2=*(dp++); - if (t1!=0xff || t2!=0xD8 ) - { - return "(invalid)"; - } - - while(1) - { - if (dp>ep) return "(invalid)"; - for(t1=*(dp++);t1!=0xff;t1=*(dp++)) if (dp>ep) return "(invalid)"; - do { t1=*(dp++); if (dp>ep) return "(invalid 4)";} while (t1 == 0xff); - - if ((t1 & 0xF0) == 0xC0 && !(t1==0xC4 || t1==0xC8 || t1==0xCC)) - { - dp+=3; - if (dp>ep) return "(invalid)"; - h=*(dp++) << 8; - if (dp>ep) return "(invalid)"; - h|=*(dp++); - if (dp>ep) return "(invalid)"; - w=*(dp++) << 8; - if (dp>ep) return "(invalid)"; - w|=*(dp); - sprintf(buffer, "(%dx%d)",w,h); - return buffer; - } - else if (t1==0xD8 || t1==0xD9) - break; - else - { int l; - - if (dp>ep) return "(invalid)"; - l=*(dp++) << 8; - if (dp>ep) return "(invalid)"; - l|= *(dp++); - l-=2; - dp+=l; - if (dp>ep) return "(invalid)"; - } - } - return "(invalid)"; -} - -static int32 read_int(unsigned char *mem) -{ - int32 i4 = mem[0], - i3 = mem[1], - i2 = mem[2], - i1 = mem[3]; - return i1 | (i2<<8) | (i3<<16) | (i4<<24); -} - - -static char *get_png_dim(void *img, int32 extent) -{ - unsigned char *dp=(unsigned char *)img; - static char buffer[256]; - int32 w, h; - if (extent<33 || - !(dp[0]==137 && dp[1]==80 && dp[2]==78 && dp[3]==71 && - dp[4]==13 && dp[5] == 10 && dp[6] == 26 && dp[7]==10)|| - !(dp[12]=='I' && dp[13]=='H' && dp[14]=='D' && dp[15]=='R')) - return "(invalid)"; - w=read_int(dp+16); - h=read_int(dp+20); - sprintf(buffer,"(%dx%d)",w,h); - return buffer; -} -static char *get_image_dim(void *img, int32 extent, int fmt) -{ - if (fmt==JPEG_COVER_FORMAT) return get_jpeg_dim(img,extent); - else if (fmt==PNG_COVER_FORMAT) return get_png_dim(img, extent); - return "(unknown)"; - -} -static void deep_babel_cover(char stopped) -{ - char buffer[TREATY_MINIMUM_EXTENT]; - void *md; - char *ep; - char *ext; - char *dim; - int32 i,j; - FILE *f; - i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); - if (i==0) - if (babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT)) - printf("IFID: %s\n",buffer); - else - { - fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); - return; - } - else - - ep=strtok(buffer, ","); - i=babel_treaty(GET_STORY_FILE_COVER_EXTENT_SEL,NULL,0); - j=babel_treaty(GET_STORY_FILE_COVER_FORMAT_SEL,NULL,0); - - if (i<=0 || j<=0) - { - if (stopped) printf("No cover art for %s\n",buffer); - return; - } - if (j==PNG_COVER_FORMAT) ext=".png"; - else if (j==JPEG_COVER_FORMAT) ext=".jpg"; - md=my_malloc(i,"Image buffer"); - if (babel_treaty(GET_STORY_FILE_COVER_SEL,md,i)<0) - { - fprintf(stderr,"A serious error occurred while retrieving cover art.\n"); - free(md); - return; - } - dim=get_image_dim(md,i,j); - while(ep) - { - char epb[TREATY_MINIMUM_EXTENT+9]; - strcpy(epb,ep); - strcat(epb, ext); - - f=fopen(epb,"wb"); - if (!f || fwrite(md,1,i,f)==EOF) - fprintf(stderr,"A serious error occurred writing to disk.\n"); - else printf("Extracted %s %s\n",epb, dim); - if (f) fclose(f); - if (stopped) break; - ep=strtok(NULL,","); - } - free(md); -} - -void babel_story_cover() -{ - deep_babel_cover(1); -} - -void babel_story_fish() -{ - deep_babel_ifiction(0); - deep_babel_cover(0); -} - -static char *get_biblio(void) -{ - int32 i; - char *md; - char *bib="No bibliographic data"; - char *bibb; char *bibe; - char *t; - static char buffer[TREATY_MINIMUM_EXTENT]; - - i=babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL,NULL,0); - if (i<=0) return bib; - - md=(char *) my_malloc(i,"Metadata buffer"); - if (babel_treaty(GET_STORY_FILE_METADATA_SEL,md,i)<0) return bib; - - bibb=strstr(md,""); - if (!bibb) { free(md); return bib; } - bibe=strstr(bibb,""); - if (bibe) *bibe=0; - t=strstr(bibb,""); - if (t) - { - t+=7; - bibe=strstr(t,""); - if (bibe) - { - *bibe=0; - bib=buffer; - for(i=0;t[i];i++) if (t[i]<0x20 || t[i]>0x7e) t[i]='_'; - sprintf(buffer, "\"%s\" ",t); - *bibe='<'; - } - else strcpy(buffer," "); - } - t=strstr(bibb,""); - if (t) - { - t+=8; - bibe=strstr(t,""); - if (bibe) - { - bib=buffer; - *bibe=0; - for(i=0;t[i];i++) if (t[i]<0x20 || t[i]>0x7e) t[i]='_'; - strcat(buffer, "by "); - strcat(buffer, t); - *bibe='<'; - } - else strcat(buffer, ""); - } - free(md); - return bib; - -} -void babel_story_identify() -{ - int32 i, j, l; - char *b, *cf, *dim; - char buffer[TREATY_MINIMUM_EXTENT]; - - printf("%s\n",get_biblio()); - babel_story_ifid(); - b=babel_get_format(); - if (!b) b="unknown"; - l=babel_get_length() / 1024; - - - i=babel_treaty(GET_STORY_FILE_COVER_EXTENT_SEL,NULL,0); - j=babel_treaty(GET_STORY_FILE_COVER_FORMAT_SEL,NULL,0); - - if (i<=0 || j<=0) - { - cf="no cover"; - } - else - { - char *md=my_malloc(i,"Image buffer"); - if (babel_treaty(GET_STORY_FILE_COVER_SEL,md,i)<0) - { - cf="no cover"; - } - else - { - dim=get_image_dim(md,i,j)+1; - dim[strlen(dim)-1]=0; - if (j==JPEG_COVER_FORMAT) cf="jpeg"; - else if (j==PNG_COVER_FORMAT) cf="png"; - else cf="unknown format"; - sprintf(buffer,"cover %s %s",dim,cf); - cf=buffer; - } - } - printf("%s, %dk, %s\n",b, l,cf); -} - -void babel_story_meta() -{ - deep_babel_ifiction(2); -} - -void babel_story_story() -{ - int32 j,i; - void *p; - FILE *f; - char *ep; - char buffer[TREATY_MINIMUM_EXTENT+20]; - j=babel_get_story_length(); - p=babel_get_story_file(); - if (!j || !p) - { - fprintf(stderr,"A serious error occurred while retrieving the story file.\n"); - return; - } - - i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT); - if (i==0 && !babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT)) - { - fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n"); - return; - } - ep=strchr(buffer, ','); - if (!ep) ep=buffer+strlen(buffer); - *ep=0; - babel_treaty(GET_STORY_FILE_EXTENSION_SEL,ep,19); - f=fopen(buffer,"wb"); - if (!f || !fwrite(p,1,j,f)) - { - fprintf(stderr,"A serious error occurred writing to disk.\n"); - return; - } - fclose(f); - printf("Extracted %s\n",buffer); - - - -} - -void babel_story_unblorb() -{ - deep_babel_ifiction(1); - deep_babel_cover(1); - babel_story_story(); - -} diff --git a/babel/blorb.c b/babel/blorb.c deleted file mode 100644 index ebb84e4..0000000 --- a/babel/blorb.c +++ /dev/null @@ -1,245 +0,0 @@ -/* blorb.c Babel interface to blorb files - * Copyright 2006 by L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends upon treaty_builder.h, misc.c and ifiction.c - * - * Header note: to add support for new executable chunk types, see - * TranslateExec. - * - * This file defines a Treaty of Babel compatable module for handling blorb - * files. However, blorb files are not themselves a babel format. This module - * is used internally by the babel program to handle blorbs. - * - * As a result, if GET_STORY_FILE_IFID_SEL returns NO_REPLY_RV, - * you should check the story file against the babel registry before resorting - * to the default IFID calculation. - * - */ -#define FORMAT blorb -#define HOME_PAGE "http://eblong.com/zarf/blorb" -#define FORMAT_EXT ".blorb,.blb,.zblorb,.zlb,.gblorb,.glb" -#define CONTAINER_FORMAT -#include "treaty_builder.h" -#include -#include - -extern TREATY treaty_registry[]; -/* The following is the translation table of Blorb chunk types to - babel formats. it is NULL-terminated. */ -static char *TranslateExec[] = { "ZCOD", "zcode", - "GLUL", "glulx", - "TAD2", "tads2", - "TAD3", "tads3", - NULL, NULL }; - -void *my_malloc(int32, char *); -int32 ifiction_get_IFID(char *, char *, int32); - -static int32 read_int(void *inp) -{ - unsigned char *mem=(unsigned char *)inp; - int32 i4 = mem[0], - i3 = mem[1], - i2 = mem[2], - i1 = mem[3]; - return i1 | (i2<<8) | (i3<<16) | (i4<<24); -} - - -static int32 blorb_get_chunk(void *blorb_file, int32 extent, char *id, int32 *begin, int32 *output_extent) -{ - int32 i=12, j; - while(i extent) return NO_REPLY_RV; - *begin=i+8; - return 1; - } - - j=read_int((char *)blorb_file+i+4); - if (j%2) j++; - i+=j+8; - - } - return NO_REPLY_RV; -} -static int32 blorb_get_resource(void *blorb_file, int32 extent, char *rid, int32 number, int32 *begin, int32 *output_extent) -{ - int32 ridx_len; - int32 i,j; - void *ridx; - if (blorb_get_chunk(blorb_file, extent,"RIdx",&i,&ridx_len)==NO_REPLY_RV) - return NO_REPLY_RV; - - ridx=(char *)blorb_file+i+4; - ridx_len=read_int((char *)blorb_file+i); - for(i=0;i -#include - -static char elfmagic[] = { 0x7f, 0x45, 0x4c, 0x46, 0 }; -static char javamagic[] = { 0xCA, 0xFE, 0xBA, 0xBE, 0 }; -static char amigamagic[] = { 0, 0, 3, 0xe7, 0 }; -static char machomagic[] = { 0xFE, 0xED, 0xFA, 0xCE, 0}; -struct exetype -{ - char *magic; - char *name; - int len; -}; -static struct exetype magic[]= { { "MZ", "MZ", 2 }, - { elfmagic, "ELF", 4 }, - { javamagic, "JAVA", 4 }, - { amigamagic, "AMIGA", 4 }, - { "#! ", "SCRIPT", 3 }, - { machomagic, "MACHO",4 }, - { "APPL", "MAC",4 }, - { NULL, NULL, 0 } }; - -static char *deduce_magic(void *sf, int32 extent) -{ - int i; - for(i=0;magic[i].magic;i++) - if (extent >= magic[i].len && memcmp(magic[i].magic,sf,magic[i].len)==0) - return magic[i].name; - return NULL; -} - -static int32 claim_story_file(void *sf, int32 extent) -{ - if (deduce_magic(sf,extent)) return VALID_STORY_FILE_RV; - return NO_REPLY_RV; -} -static int32 get_story_file_IFID(void *sf, int32 extent, char *output, int32 output_extent) -{ - char *o; - o=deduce_magic(sf,extent); - if (!o) return 0; - ASSERT_OUTPUT_SIZE((signed) strlen(o)+2); - strcpy(output,o); - strcat(output,"-"); - return INCOMPLETE_REPLY_RV; -} diff --git a/babel/glulx.c b/babel/glulx.c deleted file mode 100644 index ea1b3c4..0000000 --- a/babel/glulx.c +++ /dev/null @@ -1,81 +0,0 @@ -/* glulx.c Treaty of Babel module for Glulx files - * 2006 By L. Ross Raszewski - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file - * may render it noncompliant with the Treaty of Babel - */ - -#define FORMAT glulx -#define HOME_PAGE "http://eblong.com/zarf/glulx" -#define FORMAT_EXT ".ulx" -#define NO_METADATA -#define NO_COVER - -#include "treaty_builder.h" -#include -#include - -static int32 read_int(unsigned char *mem) -{ - int32 i4 = mem[0], - i3 = mem[1], - i2 = mem[2], - i1 = mem[3]; - return i1 | (i2<<8) | (i3<<16) | (i4<<24); -} - - - -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - int32 i,j, k; - char ser[7]; - char buffer[32]; - - - if (extent<256) return INVALID_STORY_FILE_RV; - for(i=0;i -#include - -static int32 get_story_file_IFID(void *s_file, int32 extent, char *output, int32 output_extent) -{ - - int32 i,j; - char ser[9]; - char buffer[32]; - char *story_file = (char *) s_file; - - - if (extent<0x0B) return INVALID_STORY_FILE_RV; - - for(i=0;i0x7e) return INVALID_STORY_FILE_RV; - for(i=0x0b;i<0x18;i+=2) - if (read_hugo_addx(sf+i) * scale > extent) return INVALID_STORY_FILE_RV; - - return VALID_STORY_FILE_RV; -} diff --git a/babel/ifiction.a b/babel/ifiction.a deleted file mode 100644 index 6dda84f..0000000 Binary files a/babel/ifiction.a and /dev/null differ diff --git a/babel/ifiction.c b/babel/ifiction.c deleted file mode 100644 index cb620e7..0000000 --- a/babel/ifiction.c +++ /dev/null @@ -1,534 +0,0 @@ -/* ifiction.c common babel interface for processing ifiction metadata - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends on treaty.h - * - * This file contains common routines for handling ifiction metadata strings - * - * int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent) - * does what the babel treaty function GET_STORY_FILE_IFID_SEL would do for ifiction - * - * void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, - * IFErrorHandler error_handler, void *error_ctx) - * parses the given iFiction metadata. close_tag(struct XMLtag xtg, close_ctx) - * is called for each tag as it is closed, error_handler(char *error, error_ctx) - * is called each time a structural or logical error is found in the iFiction - * This is a very simple XML parser, and probably not as good as any "real" - * XML parser. Its only two benefits are that (1) it's really small, and (2) - * it strictly checks the ifiction record against the Treaty of Babel - * requirements - * - */ - -#include "ifiction.h" -#include -#include -#include -#include - -void *my_malloc(int, char *); -extern char *format_registry[]; - - -static int32 llp; -static char *lnlst; - -static char utfeol[3] = { 0xe2, 0x80, 0xa8 }; -static int32 getln(char *endp) -{ - for(;lnlst"); - if (!ifid_begin) return NO_REPLY_RV; - ifid_begin+=6; - - ifid_end=strstr(ifid_begin,""); - if (!ifid_end) return NO_REPLY_RV; - if (output_extent<=(ifid_end-ifid_begin)) return INVALID_USAGE_RV; - - memcpy(output,ifid_begin,ifid_end-ifid_begin); - - output[ifid_end-ifid_begin]=0; - - return ifid_end-metadata+7; -} - - -int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent) -{ - int32 j=0, k; - - while(*metadata) - { - if ((k=ifiction_get_first_IFID(metadata,output,output_extent)) <= 0) break; - j++; - metadata+=k; - output_extent-=strlen(output)+1; - output+=strlen(output); - *output=','; - output++; - } - if (*(output-1)==',') *(output-1)=0; - return j; -} - - -static char *leaf_tags[] = { "ifid", - "format", - "bafn", - "title", - "author", - "headline", - "firstpublished", - "genre", - "group", - "description", - "leafname", - "url", - "authoremail", - "height", - "width", - - NULL - }; -static char *one_per[] = { "identification", - "bibliographic", - "format", - "title", - "author", - "headline", - "firstpublished", - "genre", - "group", - "description", - "leafname", - "height", - "width", - "forgiveness", - "colophon", - NULL - }; - -static char *required[] = { - "cover", "height", - "cover", "width", - "cover", "format", - "resources", "auxiliary", - "auxiliary", "leafname", - "auxiliary", "description", - "ifiction", "story", - "story", "identification", - "story", "bibliographic", - "identification", "ifid", - "identification", "format", - "bibliographic", "title", - "bibliographic", "author", - "colophon", "generator", - "colophon", "originated", - NULL, NULL - }; -static char *zarfian[] = { - "Merciful", - "Polite", - "Tough", - "Nasty", - "Cruel", - NULL - }; - -struct ifiction_info { - int32 width; - int32 height; - int format; - }; -static void ifiction_validate_tag(struct XMLTag *xtg, struct ifiction_info *xti, IFErrorHandler err_h, void *ectx) -{ - int i; - char ebuf[512]; - struct XMLTag *parent=xtg->next; - if (parent) - { - for(i=0;leaf_tags[i];i++) - if (strcmp(parent->tag,leaf_tags[i])==0) - { - sprintf(ebuf, "Error: (line %d) Tag <%s> is not permitted within tag <%s>", - xtg->beginl,xtg->tag,parent->tag); - err_h(ebuf,ectx); - } - for(i=0;required[i];i+=2) - if (strcmp(required[i],parent->tag)==0 && strcmp(required[i+1],xtg->tag)==0) - parent->rocurrences[i]=1; - for(i=0;one_per[i];i++) - if (strcmp(one_per[i],xtg->tag)==0) - if (parent->occurences[i]) { - sprintf(ebuf,"Error: (line %d) Found more than one <%s> within <%s>",xtg->beginl,xtg->tag, - parent->tag); - err_h(ebuf,ectx); - } - else parent->occurences[i]=1; - } - for(i=0;required[i];i+=2) - if (strcmp(required[i],xtg->tag)==0 && !xtg->rocurrences[i]) - { - sprintf(ebuf,"Error: (line %d) Tag <%s> is required within <%s>",xtg->beginl, required[i+1],xtg->tag); - err_h(ebuf,ectx); - } - if (parent && strcmp(parent->tag,"identification")==0) - { - if (strcmp(xtg->tag,"format")==0) - { - int i; - for(i=0;format_registry[i];i++) if (memcmp(xtg->begin,format_registry[i],strlen(format_registry[i]))==0) break; - if (format_registry[i]) xti->format=i; - else - { - char bf[256]; - memcpy(bf,xtg->begin,xtg->end-xtg->begin); - bf[xtg->end-xtg->begin]=0; - xti->format=-1; - sprintf(ebuf,"Warning: (line %d) Unknown format %s.",xtg->beginl,bf); - err_h(ebuf,ectx); - } - } - } - if (parent && strcmp(parent->tag,"cover")==0) - { - if (strcmp(xtg->tag,"width")==0) - { - int i; - sscanf(xtg->begin,"%d",&i); - if (i<120) - { - sprintf(ebuf,"Warning: (line %d) Cover art width should not be less than 120.",xtg->beginl); - err_h(ebuf,ectx); - } - if (i>1200) - { - sprintf(ebuf,"Warning: (line %d) Cover art width should not exceed 1200.",xtg->beginl); - err_h(ebuf,ectx); - } - if (!xti->width) xti->width=i; - if (xti->height && (xti->width> 2 * xti->height || xti->height > 2 * xti->width)) - { - sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl); - err_h(ebuf,ectx); - } - - } - if (strcmp(xtg->tag,"height")==0) - { - int i; - sscanf(xtg->begin,"%d",&i); - if (i<120) - { - sprintf(ebuf,"Warning: (line %d) Cover art height should not be less than 120.",xtg->beginl); - err_h(ebuf,ectx); - } - if (i>1200) - { - sprintf(ebuf,"Warning: (line %d) Cover art height should not exceed 1200.",xtg->beginl); - err_h(ebuf,ectx); - } - if (!xti->height) xti->height=i; - if (xti->width && (xti->width> 2 * xti->height || xti->height > 2 * xti->width)) - { - sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl); - err_h(ebuf,ectx); - } - - } - if (strcmp(xtg->tag,"format")==0 && memcmp(xtg->begin,"jpg",3) && memcmp(xtg->begin,"png",3)) - { - sprintf(ebuf,"Warning: (line %d) should be one of: png, jpg.",xtg->beginl); - err_h(ebuf,ectx); - } - } - if (parent && strcmp(parent->tag,"bibliographic")==0) - { - char *p; - if (isspace(*xtg->begin)|| isspace(*(xtg->end-1))) - { - sprintf(ebuf,"Warning: (line %d) Extraneous spaces at beginning or end of tag <%s>.",xtg->beginl,xtg->tag); - err_h(ebuf,ectx); - } - for(p=xtg->begin;pend-1;p++) -/* Obsoleted by Revision 6 - if (isspace(*p) && isspace(*(p+1))) - { - sprintf(ebuf,"Warning: (line %d) Extraneous spaces found in tag <%s>.",xtg->beginl, xtg->tag); - err_h(ebuf,ectx); - } - else if (isspace(*p) && *p!=' ') - { - sprintf(ebuf,"Warning: (line %d) Improper whitespace character found in tag <%s>.",xtg->beginl, xtg->tag); - err_h(ebuf,ectx); - - } -*/ - if (strcmp(xtg->tag, "description") && xtg->end-xtg->begin > 240) - { - sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag); - err_h(ebuf,ectx); - } - if (strcmp(xtg->tag, "description")==0 && xtg->end-xtg->begin > 2400) - { - sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag); - err_h(ebuf,ectx); - } - if (strcmp(xtg->tag,"firstpublished")==0) - { - int l=xtg->end-xtg->begin; - if ((l!=4 && l!=10) || - (!isdigit(xtg->begin[0]) || - !isdigit(xtg->begin[1]) || - !isdigit(xtg->begin[2]) || - !isdigit(xtg->begin[3])) || - (l==10 && ( xtg->begin[4]!='-' || - xtg->begin[7]!='-' || - !isdigit(xtg->begin[5]) || - !isdigit(xtg->begin[6]) || - !(xtg->begin[5]=='0' || xtg->begin[5]=='1') || - !(xtg->begin[5]=='0' || xtg->begin[6]<='2') || - !isdigit(xtg->begin[8]) || - !isdigit(xtg->begin[9])))) - { - sprintf(ebuf,"Warning: (line %d) Tag <%s> should be format YYYY or YYYY-MM-DD",xtg->beginl, xtg->tag); - err_h(ebuf,ectx); - } - } - if (strcmp(xtg->tag,"seriesnumber")==0) - { - char *l; - if (*xtg->begin=='0' && xtg->end!=xtg->begin+1) - { - sprintf(ebuf,"Warning: (line %d) Tag <%s> should not use leading zeroes",xtg->beginl, xtg->tag); - err_h(ebuf,ectx); - } - - for(l=xtg->begin;lend;l++) if (!isdigit(*l)) - { - sprintf(ebuf,"Warning: (line %d) Tag <%s> should be a positive number",xtg->beginl, xtg->tag); - err_h(ebuf,ectx); - } - } - if (strcmp(xtg->tag,"forgiveness")==0) - { - int l; - for(l=0;zarfian[l];l++) if (memcmp(xtg->begin,zarfian[l],strlen(zarfian[l]))==0) break; - if (!zarfian[l]) - { - sprintf(ebuf,"Warning: (line %d) should be one of: Merciful, Polite, Tough, Cruel",xtg->beginl); - err_h(ebuf,ectx); - } - } - } - if (xti->format>0) - { - for(i=0;format_registry[i];i++) if (strcmp(xtg->tag,format_registry[i])==0) break; - if (format_registry[i] && xti->format !=i) - { - sprintf(ebuf,"Warning: (line %d) Found <%s> tag, but story is identified as %s.",xtg->beginl, xtg->tag, format_registry[xti->format]); - err_h(ebuf,ectx); - } - } - if (strcmp(xtg->tag,"story")==0) - { - xti->format=-1; - xti->width=0; - xti->height=0; - } - -} - - - -void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, IFErrorHandler error_handler, void *error_ctx) -{ -char *xml, buffer[2400], *aep, *mda=md, ebuffer[512]; -struct XMLTag *parse=NULL, *xtg; -struct ifiction_info xti; -char BOM[3]={ 0xEF, 0xBB, 0xBF}; -xti.width=0; -xti.height=0; -xti.format=-1; -llp=1; -lnlst=md; - -while(*mda && isspace(*mda)) mda++; -if (memcmp(mda,BOM,3)==0) -{ mda+=3; - while(*mda && isspace(*mda)) mda++; -} - - -if (strncmp("",mda, - strlen("")) - && - strncmp("",mda, - strlen("")) - ) -{ - error_handler("Error: XML header not found.",error_ctx); - return; -} - -xml=strstr(md," not found",error_ctx); - return; - } -while(xml && *xml) -{ - char *bp, *ep, *tp; - while(*xml&&*xml!='<') xml++; - if (!*xml) break; - bp=xml; - tp=strchr(bp+1,'<'); - ep=strchr(bp+1,'>'); - if (!ep) break; - if (tp && tp < ep) - { xml=tp; continue; } - if (!tp) tp=ep+1; - if (bp[1]=='/') /* end tag */ - { - strncpy(buffer,bp+2,(ep-bp)-2); - buffer[(ep-bp)-2]=0; - if (parse && strcmp(buffer,parse->tag)==0) - { /* copasetic. Close the tag */ - xtg=parse; - parse=xtg->next; - xtg->end=ep-strlen(buffer)-2; - ifiction_validate_tag(xtg,&xti,error_handler, error_ctx); - close_tag(xtg,close_ctx); - free(xtg); - } - else - { - for(xtg=parse;xtg && strcmp(buffer,xtg->tag);xtg=xtg->next); - if (xtg) /* Intervening unclosed tags */ - { for(xtg=parse;xtg && strcmp(buffer,parse->tag);xtg=parse) - { - xtg->end=xml-1; - parse=xtg->next; - sprintf(ebuffer,"Error: (line %d) unclosed <%s> tag",xtg->beginl,xtg->tag); - error_handler(ebuffer,error_ctx); - ifiction_validate_tag(xtg,&xti,error_handler, error_ctx); - close_tag(xtg,close_ctx); - free(xtg); - } - xtg=parse; - if (xtg) - { - xtg->end=xml-1; - parse=xtg->next; - ifiction_validate_tag(xtg,&xti, error_handler, error_ctx); - close_tag(xtg,close_ctx); - free(xtg); - } - } - else - { - sprintf(ebuffer,"Error: (line %d) saw without <%s>",getln(xml), buffer,buffer); - error_handler(ebuffer,error_ctx); - } - } - - } - else if(*(ep-1)=='/' || bp[1]=='!') /* unterminated tag */ - { - /* Do nothing */ - } - else /* Terminated tag beginning */ - { - int i; - xtg=(struct XMLTag *)my_malloc(sizeof(struct XMLTag),"XML Tag"); - xtg->next=parse; - xtg->beginl=getln(bp); - for(i=0;bp[i+1]=='_' || bp[i+1]=='-' || isalnum(bp[i+1]);i++) - xtg->tag[i]=bp[i+1]; - if (i==0) - { xml=tp; - free(xtg); - continue; - } - parse=xtg; - parse->tag[i]=0; - strncpy(parse->fulltag,bp+1,ep-bp-1); - parse->fulltag[ep-bp-1]=0; - parse->begin=ep+1; - } - xml=tp; -} - while (parse) - { - xtg=parse; - xtg->end=aep-1; - parse=xtg->next; - sprintf(ebuffer,"Error: (line %d) Unclosed tag <%s>",xtg->beginl,xtg->tag); - ifiction_validate_tag(xtg,&xti,error_handler, error_ctx); - close_tag(xtg,close_ctx); - free(xtg); - } -} - -struct get_tag -{ - char *tag; - char *parent; - char *output; - char *target; -}; - -static void ifiction_null_eh(char *e, void *c) -{ - if (e || c) { } - -} - -static void ifiction_find_value(struct XMLTag *xtg, void *xti) -{ - struct get_tag *gt=(struct get_tag *)xti; - - if (gt->output && !gt->target) return; - if (gt->target && gt->output && strcmp(gt->output,gt->target)==0) { gt->target=NULL; free(gt->output); gt->output=NULL; } - if (((!xtg->next && !gt->parent) || (xtg->next && gt->parent && strcmp(xtg->next->tag,gt->parent)==0)) && - strcmp(xtg->tag,gt->tag)==0) - { - int32 l = xtg->end-xtg->begin; - - if (gt->output) free(gt->output); - gt->output=(char *)my_malloc(l+1, "ifiction tag buffer"); - memcpy(gt->output, xtg->begin, l); - gt->output[l]=0; - - } -} - - -char *ifiction_get_tag(char *md, char *p, char *t, char *from) -{ - struct get_tag gt; - gt.output=NULL; - gt.parent=p; - gt.tag=t; - gt.target=from; - ifiction_parse(md,ifiction_find_value,>,ifiction_null_eh,NULL); - if (gt.target){ if (gt.output) free(gt.output); return NULL; } - return gt.output; -} diff --git a/babel/ifiction.h b/babel/ifiction.h deleted file mode 100644 index 75ae946..0000000 --- a/babel/ifiction.h +++ /dev/null @@ -1,45 +0,0 @@ -/* ifiction.h declarations for the babel ifiction API - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - */ - -#ifndef IFICTION_H -#define IFICTION_H - -#include "treaty.h" - -/* Babel's notion of an XML tag */ -struct XMLTag -{ - int32 beginl; /* Beginning line number */ - char tag[256]; /* name of the tag */ - char fulltag[256]; /* Full text of the opening tag */ - char *begin; /* Points to the beginning of the tag's content */ - char *end; /* Points to the end of the tag's content. - setting *end=0 will turn begin into a string - containing the tag's content (But if you do this, you - should restore the original value of *end before - allowing control to return to the ifiction parser) */ - char occurences[256]; /* Tables used internally to find missing required tags */ - char rocurrences[256]; - struct XMLTag *next; /* The tag's parent */ - -}; - -typedef void (*IFCloseTag)(struct XMLTag *, void *); -typedef void (*IFErrorHandler)(char *, void *); - - -void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, IFErrorHandler error_handler, void *error_ctx); -int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent); -char *ifiction_get_tag(char *md, char *p, char *t, char *from); -#endif diff --git a/babel/level9.c b/babel/level9.c deleted file mode 100644 index de855b0..0000000 --- a/babel/level9.c +++ /dev/null @@ -1,495 +0,0 @@ -/* level9.c Treaty of Babel module for Level 9 files - * 2006 By L. Ross Raszewski - * - * Note that this module will handle both bare Level 9 A-Code and - * Spectrum .SNA snapshots. It will not handle compressed .Z80 images. - * - * The Level 9 identification algorithm is based in part on the algorithm - * used by Paul David Doherty's l9cut program. - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file - * may render it noncompliant with the Treaty of Babel - */ - -#define FORMAT level9 -#define HOME_PAGE "http://www.if-legends.org/~l9memorial/html/home.html" -#define FORMAT_EXT ".l9,.sna" -#define NO_METADATA -#define NO_COVER - -#include "treaty_builder.h" -#include -#include -#include - -struct l9rec { - int32 length; - unsigned char chk; - char *ifid; -}; - - -static struct l9rec l9_registry[] = { - { 0x3a31, 0xe5, "LEVEL9-001-1" }, - { 0x8333, 0xb7, "LEVEL9-001-1" }, - { 0x7c6f, 0x0f, "LEVEL9-001-1" }, - { 0x72fa, 0x8b, "LEVEL9-001-1" }, - { 0x38dd, 0x31, "LEVEL9-001-A" }, - { 0x39c0, 0x44, "LEVEL9-001-B" }, - { 0x3a12, 0x8f, "LEVEL9-001-C" }, - { 0x37f1, 0x77, "LEVEL9-001-2" }, - { 0x844d, 0x50, "LEVEL9-001-2" }, - { 0x738e, 0x5b, "LEVEL9-001-2" }, - { 0x3900, 0x1c, "LEVEL9-001-3" }, - { 0x8251, 0x5f, "LEVEL9-001-3" }, - { 0x7375, 0xe5, "LEVEL9-001-3" }, - { 0x3910, 0xac, "LEVEL9-001-4" }, - { 0x7a78, 0x5e, "LEVEL9-001-4" }, - { 0x78d5, 0xe3, "LEVEL9-001-4" }, - { 0x3ad6, 0xa7, "LEVEL9-001-5" }, - { 0x38a5, 0x0f, "LEVEL9-001-6" }, - { 0x361e, 0x7e, "LEVEL9-001-7" }, - { 0x3934, 0x75, "LEVEL9-001-8" }, - { 0x3511, 0xcc, "LEVEL9-001-9" }, - { 0x593a, 0xaf, "LEVEL9-002-1" }, - { 0x7931, 0xb9, "LEVEL9-002-1" }, - { 0x6841, 0x4a, "LEVEL9-002-1" }, - { 0x57e6, 0x8a, "LEVEL9-002-2" }, - { 0x7cdf, 0xa5, "LEVEL9-002-2" }, - { 0x6bc0, 0x62, "LEVEL9-002-2" }, - { 0x5819, 0xcd, "LEVEL9-002-3" }, - { 0x7a0c, 0x97, "LEVEL9-002-3" }, - { 0x692c, 0x21, "LEVEL9-002-3" }, - { 0x579b, 0xad, "LEVEL9-002-4" }, - { 0x7883, 0xe2, "LEVEL9-002-4" }, - { 0x670a, 0x94, "LEVEL9-002-4" }, - { 0x5323, 0xb7, "LEVEL9-003" }, - { 0x6e60, 0x83, "LEVEL9-003" }, - { 0x5b58, 0x50, "LEVEL9-003" }, - { 0x63b6, 0x2e, "LEVEL9-003" }, - { 0x6968, 0x32, "LEVEL9-003" }, - { 0x5b50, 0x66, "LEVEL9-003" }, - { 0x6970, 0xd6, "LEVEL9-003" }, - { 0x5ace, 0x11, "LEVEL9-003" }, - { 0x6e5c, 0xf6, "LEVEL9-003" }, - { 0x1929, 0x00, "LEVEL9-004-DEMO" }, - { 0x40e0, 0x02, "LEVEL9-004-DEMO" }, - { 0x3ebb, 0x00, "LEVEL9-004-en" }, - { 0x3e4f, 0x00, "LEVEL9-004-en" }, - { 0x3e8f, 0x00, "LEVEL9-004-en" }, - { 0x0fd8, 0x00, "LEVEL9-004-en" }, - { 0x14a3, 0x00, "LEVEL9-004-en" }, - { 0x110f, 0x00, "LEVEL9-004-fr" }, - { 0x4872, 0x00, "LEVEL9-004-de" }, - { 0x4846, 0x00, "LEVEL9-004-de" }, - { 0x11f5, 0x00, "LEVEL9-004-de" }, - { 0x11f5, 0x00, "LEVEL9-004-de" }, - { 0x76f4, 0x5e, "LEVEL9-005" }, - { 0x5b16, 0x3b, "LEVEL9-005" }, - { 0x6c8e, 0xb6, "LEVEL9-005" }, - { 0x6f4d, 0xcb, "LEVEL9-005" }, - { 0x6f6a, 0xa5, "LEVEL9-005" }, - { 0x5e31, 0x7c, "LEVEL9-005" }, - { 0x6f70, 0x40, "LEVEL9-005" }, - { 0x6f6e, 0x78, "LEVEL9-005" }, - { 0x5a8e, 0xf2, "LEVEL9-005" }, - { 0x76f4, 0x5a, "LEVEL9-005" }, - { 0x630e, 0x8d, "LEVEL9-006" }, - { 0x630e, 0xbe, "LEVEL9-006" }, - { 0x6f0c, 0x95, "LEVEL9-006" }, - { 0x593a, 0x80, "LEVEL9-006" }, - { 0x6bd2, 0x65, "LEVEL9-006" }, - { 0x6dc0, 0x63, "LEVEL9-006" }, - { 0x58a6, 0x24, "LEVEL9-006" }, - { 0x6de8, 0x4c, "LEVEL9-006" }, - { 0x58a3, 0x38, "LEVEL9-006" }, - { 0x63be, 0xd6, "LEVEL9-007" }, - { 0x378c, 0x8d, "LEVEL9-007" }, - { 0x63be, 0x0a, "LEVEL9-007" }, - { 0x34b3, 0x20, "LEVEL9-008" }, - { 0x34b3, 0xc7, "LEVEL9-008" }, - { 0x34b3, 0x53, "LEVEL9-008" }, - { 0xb1a9, 0x80, "LEVEL9-009-1" }, - { 0x908e, 0x0d, "LEVEL9-009-1" }, - { 0xad41, 0xa8, "LEVEL9-009-1" }, - { 0xb1aa, 0xad, "LEVEL9-009-1" }, - { 0x8aab, 0xc0, "LEVEL9-009-1" }, - { 0xb0ec, 0xc2, "LEVEL9-009-1" }, - { 0xb19e, 0x92, "LEVEL9-009-1" }, - { 0x5ff0, 0xf8, "LEVEL9-009-1" }, - { 0x52aa, 0xdf, "LEVEL9-009-1" }, - { 0xab9d, 0x31, "LEVEL9-009-2" }, - { 0x8f6f, 0x0a, "LEVEL9-009-2" }, - { 0xa735, 0xf7, "LEVEL9-009-2" }, - { 0xab8b, 0xbf, "LEVEL9-009-2" }, - { 0x8ac8, 0x9a, "LEVEL9-009-2" }, - { 0xaf82, 0x83, "LEVEL9-009-2" }, - { 0x6024, 0x01, "LEVEL9-009-2" }, - { 0x6ffa, 0xdb, "LEVEL9-009-2" }, - { 0xae28, 0x87, "LEVEL9-009-3" }, - { 0x9060, 0xbb, "LEVEL9-009-3" }, - { 0xa9c0, 0x9e, "LEVEL9-009-3" }, - { 0xae16, 0x81, "LEVEL9-009-3" }, - { 0x8a93, 0x4f, "LEVEL9-009-3" }, - { 0xb3e6, 0xab, "LEVEL9-009-3" }, - { 0x6036, 0x3d, "LEVEL9-009-3" }, - { 0x723a, 0x69, "LEVEL9-009-3" }, - { 0xd188, 0x13, "LEVEL9-010-1" }, - { 0x9089, 0xce, "LEVEL9-010-1" }, - { 0xb770, 0x03, "LEVEL9-010-1" }, - { 0xd19b, 0xad, "LEVEL9-010-1" }, - { 0x8ab7, 0x68, "LEVEL9-010-1" }, - { 0xd183, 0x83, "LEVEL9-010-1" }, - { 0x5a38, 0xf7, "LEVEL9-010-1" }, - { 0x76a0, 0x3a, "LEVEL9-010-1" }, - { 0xc594, 0x03, "LEVEL9-010-2" }, - { 0x908d, 0x80, "LEVEL9-010-2" }, - { 0xb741, 0xb6, "LEVEL9-010-2" }, - { 0xc5a5, 0xfe, "LEVEL9-010-2" }, - { 0x8b1e, 0x84, "LEVEL9-010-2" }, - { 0xc58f, 0x65, "LEVEL9-010-2" }, - { 0x531a, 0xed, "LEVEL9-010-2" }, - { 0x7674, 0x0b, "LEVEL9-010-2" }, - { 0xd79f, 0xb5, "LEVEL9-010-3" }, - { 0x909e, 0x9f, "LEVEL9-010-3" }, - { 0xb791, 0xa1, "LEVEL9-010-3" }, - { 0xd7ae, 0x9e, "LEVEL9-010-3" }, - { 0x8b1c, 0xa8, "LEVEL9-010-3" }, - { 0xd79a, 0x57, "LEVEL9-010-3" }, - { 0x57e4, 0x19, "LEVEL9-010-3" }, - { 0x765e, 0xba, "LEVEL9-010-3" }, - { 0xbb93, 0x36, "LEVEL9-011-1" }, - { 0x898a, 0x43, "LEVEL9-011-1" }, - { 0x8970, 0x6b, "LEVEL9-011-1" }, - { 0xbb6e, 0xa6, "LEVEL9-011-1" }, - { 0x86d0, 0xb7, "LEVEL9-011-1" }, - { 0xbb6e, 0xad, "LEVEL9-011-1" }, - { 0x46ec, 0x64, "LEVEL9-011-1" }, - { 0x74e0, 0x92, "LEVEL9-011-1" }, - { 0xc58e, 0x4a, "LEVEL9-011-2" }, - { 0x8b9f, 0x61, "LEVEL9-011-2" }, - { 0x8b90, 0x4e, "LEVEL9-011-2" }, - { 0xc58e, 0x43, "LEVEL9-011-2" }, - { 0x8885, 0x22, "LEVEL9-011-2" }, - { 0x6140, 0x18, "LEVEL9-011-2" }, - { 0x6dbc, 0x97, "LEVEL9-011-2" }, - { 0xcb9a, 0x0f, "LEVEL9-011-3" }, - { 0x8af9, 0x61, "LEVEL9-011-3" }, - { 0x8aea, 0x4e, "LEVEL9-011-3" }, - { 0xcb9a, 0x08, "LEVEL9-011-3" }, - { 0x87e5, 0x0e, "LEVEL9-011-3" }, - { 0x640e, 0xc1, "LEVEL9-011-3" }, - { 0x7402, 0x07, "LEVEL9-011-3" }, - { 0xbba4, 0x94, "LEVEL9-012-1" }, - { 0xc0cf, 0x4e, "LEVEL9-012-1" }, - { 0x8afc, 0x07, "LEVEL9-012-1" }, - { 0x8feb, 0xba, "LEVEL9-012-1" }, - { 0xb4c9, 0x94, "LEVEL9-012-1" }, - { 0xc0bd, 0x57, "LEVEL9-012-1" }, - { 0x8ade, 0xf2, "LEVEL9-012-1" }, - { 0x4fd2, 0x9d, "LEVEL9-012-1" }, - { 0x5c7a, 0x44, "LEVEL9-012-1" }, - { 0x768c, 0xe8, "LEVEL9-012-1" }, - { 0xd0c0, 0x56, "LEVEL9-012-2" }, - { 0xd5e9, 0x6a, "LEVEL9-012-2" }, - { 0x8aec, 0x13, "LEVEL9-012-2" }, - { 0x8f6b, 0xfa, "LEVEL9-012-2" }, - { 0xb729, 0x51, "LEVEL9-012-2" }, - { 0xd5d7, 0x99, "LEVEL9-012-2" }, - { 0x8b0e, 0xfb, "LEVEL9-012-2" }, - { 0x4dac, 0xa8, "LEVEL9-012-2" }, - { 0x53a2, 0x1e, "LEVEL9-012-2" }, - { 0x76b0, 0x1d, "LEVEL9-012-2" }, - { 0xb6ac, 0xc6, "LEVEL9-012-3" }, - { 0xbb8f, 0x1a, "LEVEL9-012-3" }, - { 0x8aba, 0x0d, "LEVEL9-012-3" }, - { 0x8f71, 0x2f, "LEVEL9-012-3" }, - { 0xb702, 0xe4, "LEVEL9-012-3" }, - { 0xbb7d, 0x17, "LEVEL9-012-3" }, - { 0x8ab3, 0xc1, "LEVEL9-012-3" }, - { 0x4f96, 0x22, "LEVEL9-012-3" }, - { 0x5914, 0x22, "LEVEL9-012-3" }, - { 0x765e, 0x4f, "LEVEL9-012-3" }, - { 0x5eb9, 0x30, "LEVEL9-013" }, - { 0x5eb9, 0x5d, "LEVEL9-013" }, - { 0x5eb9, 0x6e, "LEVEL9-013" }, - { 0xb257, 0xf8, "LEVEL9-013" }, - { 0xb576, 0x2a, "LEVEL9-013" }, - { 0x8d78, 0x3a, "LEVEL9-013" }, - { 0x9070, 0x43, "LEVEL9-013" }, - { 0xb38c, 0x37, "LEVEL9-013" }, - { 0xb563, 0x6a, "LEVEL9-013" }, - { 0xb57c, 0x44, "LEVEL9-013" }, - { 0xb260, 0xe5, "LEVEL9-013" }, - { 0x8950, 0xa1, "LEVEL9-013" }, - { 0xb579, 0x89, "LEVEL9-013" }, - { 0x579e, 0x97, "LEVEL9-013" }, - { 0x69fe, 0x56, "LEVEL9-013" }, - { 0x6f1e, 0xda, "LEVEL9-013" }, - { 0x5671, 0xbc, "LEVEL9-014" }, - { 0x6fc6, 0x14, "LEVEL9-014" }, - { 0x5aa4, 0xc1, "LEVEL9-014" }, - { 0x7410, 0x5e, "LEVEL9-014" }, - { 0x5aa4, 0xc1, "LEVEL9-014" }, - { 0x5aa4, 0xc1, "LEVEL9-014" }, - { 0xb797, 0x1f, "LEVEL9-014" }, - { 0xbaca, 0x3a, "LEVEL9-014" }, - { 0x8c46, 0xf0, "LEVEL9-014" }, - { 0x8f51, 0xb2, "LEVEL9-014" }, - { 0xb451, 0xa8, "LEVEL9-014" }, - { 0xbab2, 0x87, "LEVEL9-014" }, - { 0xbac7, 0x7f, "LEVEL9-014" }, - { 0xb7a0, 0x7e, "LEVEL9-014" }, - { 0x8a60, 0x2a, "LEVEL9-014" }, - { 0xbac4, 0x80, "LEVEL9-014" }, - { 0x579a, 0x2a, "LEVEL9-014" }, - { 0x5a50, 0xa9, "LEVEL9-014" }, - { 0x6108, 0xdd, "LEVEL9-014" }, - { 0x506c, 0xf0, "LEVEL9-015" }, - { 0x505d, 0x32, "LEVEL9-015" }, - { 0xa398, 0x82, "LEVEL9-015" }, - { 0xa692, 0xd1, "LEVEL9-015" }, - { 0x8d56, 0xd3, "LEVEL9-015" }, - { 0x903f, 0x6b, "LEVEL9-015" }, - { 0xa4e2, 0xa6, "LEVEL9-015" }, - { 0xa67c, 0xb8, "LEVEL9-015" }, - { 0xa69e, 0x6c, "LEVEL9-015" }, - { 0xa3a4, 0xdf, "LEVEL9-015" }, - { 0x8813, 0x11, "LEVEL9-015" }, - { 0xa698, 0x41, "LEVEL9-015" }, - { 0x5500, 0x50, "LEVEL9-015" }, - { 0x6888, 0x8d, "LEVEL9-015" }, - { 0x6da0, 0xb8, "LEVEL9-015" }, - { 0x6064, 0xbd, "LEVEL9-016" }, - { 0x6064, 0x01, "LEVEL9-016" }, - { 0x6047, 0x6c, "LEVEL9-016" }, - { 0x6064, 0xda, "LEVEL9-016" }, - { 0x6064, 0x95, "LEVEL9-016" }, - { 0x60c4, 0x28, "LEVEL9-016" }, - { 0x5cb7, 0xfe, "LEVEL9-016" }, - { 0x5ca1, 0x33, "LEVEL9-016" }, - { 0x5cb7, 0x64, "LEVEL9-016" }, - { 0x7d16, 0xe6, "LEVEL9-016" }, - { 0x639c, 0x8b, "LEVEL9-016" }, - { 0x60f7, 0x68, "LEVEL9-016" }, - { 0x772f, 0xca, "LEVEL9-016" }, - { 0x7cff, 0xf8, "LEVEL9-016" }, - { 0x7cf8, 0x24, "LEVEL9-016" }, - { 0x7d14, 0xe8, "LEVEL9-016" }, - { 0x7c55, 0x18, "LEVEL9-016" }, - { 0x5f43, 0xca, "LEVEL9-016" }, - { 0xc132, 0x14, "LEVEL9-017-1" }, - { 0xbeab, 0x2d, "LEVEL9-017-1" }, - { 0x9058, 0xcf, "LEVEL9-017-1" }, - { 0xbe94, 0xcc, "LEVEL9-017-1" }, - { 0x8a21, 0xf4, "LEVEL9-017-1" }, - { 0x55ce, 0xa1, "LEVEL9-017-1" }, - { 0x5cbc, 0xa5, "LEVEL9-017-1" }, - { 0x762e, 0x82, "LEVEL9-017-1" }, - { 0x99bd, 0x65, "LEVEL9-017-2" }, - { 0x8f43, 0xc9, "LEVEL9-017-2" }, - { 0x8a12, 0xe3, "LEVEL9-017-2" }, - { 0x54a6, 0xa9, "LEVEL9-017-2" }, - { 0x5932, 0x4e, "LEVEL9-017-2" }, - { 0x5bd6, 0x35, "LEVEL9-017-2" }, - { 0xbcb6, 0x7a, "LEVEL9-017-3 (Amiga/PC/ST)" }, - { 0x90ac, 0x68, "LEVEL9-017-3" }, - { 0x8a16, 0xcc, "LEVEL9-017-3" }, - { 0x51bc, 0xe3, "LEVEL9-017-3" }, - { 0x5860, 0x95, "LEVEL9-017-3" }, - { 0x6fa8, 0xa4, "LEVEL9-017-3" }, - { 0x5fab, 0x5c, "LEVEL9-018" }, - { 0x5fab, 0x2f, "LEVEL9-018" }, - { 0x7b31, 0x6e, "LEVEL9-018" }, - { 0x67a3, 0x9d, "LEVEL9-018" }, - { 0x6bf8, 0x3f, "LEVEL9-018" }, - { 0x7363, 0x65, "LEVEL9-018" }, - { 0x7b2f, 0x70, "LEVEL9-018" }, - { 0x7b2f, 0x70, "LEVEL9-018" }, - { 0x6541, 0x02, "LEVEL9-018" }, - { 0x5834, 0x42, "LEVEL9-019-1" }, - { 0x765d, 0xcd, "LEVEL9-019-1" }, - { 0x6ce5, 0x58, "LEVEL9-019-1" }, - { 0x56dd, 0x51, "LEVEL9-019-2" }, - { 0x6e58, 0x07, "LEVEL9-019-2" }, - { 0x68da, 0xc1, "LEVEL9-019-2" }, - { 0x5801, 0x53, "LEVEL9-019-3" }, - { 0x7e98, 0x6a, "LEVEL9-019-3" }, - { 0x6c67, 0x9a, "LEVEL9-019-3" }, - { 0x54a4, 0x01, "LEVEL9-019-4" }, - { 0x81e2, 0xd5, "LEVEL9-019-4" }, - { 0x6d91, 0xb9, "LEVEL9-019-4" }, - { 0x5828, 0xbd, "LEVEL9-020" }, - { 0x6d84, 0xf9, "LEVEL9-020" }, - { 0x6d84, 0xc8, "LEVEL9-020" }, - { 0x6030, 0x47, "LEVEL9-020" }, - { 0x772b, 0xcd, "LEVEL9-020" }, - { 0x546c, 0xb7, "LEVEL9-020" }, - { 0x7cd9, 0x0c, "LEVEL9-020" }, - { 0x60dd, 0xf2, "LEVEL9-020" }, - { 0x6161, 0xf3, "LEVEL9-020" }, - { 0x788d, 0x72, "LEVEL9-020" }, - { 0x7cd7, 0x0e, "LEVEL9-020" }, - { 0x5ebb, 0xf1, "LEVEL9-020" }, - - { 0, 0, NULL } -}; - - - -static int32 read_l9_int(unsigned char *sf) -{ - return ((int32) sf[1]) << 8 | sf[0]; - -} -static int v2_recognition (unsigned char *sf, int32 extent, int32 *l, unsigned char *c) -{ - int32 i, j; - for (i=0;i0x4000) && (*l<=0xdb00))) - if ((*l!=0) && (sf[i+0x0d] == 0)) - for (j=i;j0x0fd0) && (end <= (extent - 2)) && - (((read_l9_int(sf+i+2) + read_l9_int(sf+i+4))==read_l9_int(sf+i+6)) - && (read_l9_int(sf+i+2) != 0) && (read_l9_int(sf+i+4)) != 0) && - (((read_l9_int(sf+i+6) + read_l9_int(sf+i+8)) == read_l9_int(sf+i+10)) - && ((sf[i + 18] == 0x2a) || (sf[i + 18] == 0x2c)) - && (sf[i + 19] == 0) && (sf[i + 20] == 0) && (sf[i + 21] == 0))) - ll = 2; - } - if (ll>1) - { - *c=0; - if (phase==3) ll=1; - else - { char checksum=0; - *c = sf[end]; - for (j=i;j<=end;j++) - checksum += sf[j]; - if (!checksum) ll=1; - else ll=0; - } - } else ll=0; - } - - if (ll) return *l < 0x8500 ? 3:4; - return 0; -} -static char *get_l9_ifid(int32 length, unsigned char chk) -{ - int i; - for(i=0;l9_registry[i].length;i++) - if (length==l9_registry[i].length && chk==l9_registry[i].chk) return l9_registry[i].ifid; - return NULL; -} -static int get_l9_version(unsigned char *sf, int32 extent, char **ifid) -{ - int i; - int32 l; - unsigned char c; - if (v2_recognition(sf,extent, &l, &c)) { *ifid=get_l9_ifid(l,c); return 2; } - l=0; c=0; - i=v3_recognition_phase(1,sf,extent, &l, &c); - if (i) { *ifid=get_l9_ifid(l,c); return i; } - if (v1_recognition(sf,extent, ifid)) return 1; - l=0; c=0; - i=v3_recognition_phase(2,sf,extent, &l, &c); - if (i) { *ifid=get_l9_ifid(l,c); return i; } - i=v3_recognition_phase(3,sf,extent, &l, &c); - *ifid=NULL; - return i; -} - -static int32 claim_story_file(void *story, int32 extent) -{ - char *ifid=NULL; - if (get_l9_version((unsigned char *) story,extent, &ifid)) - if (ifid) return VALID_STORY_FILE_RV; - else return NO_REPLY_RV; - return INVALID_STORY_FILE_RV; -} - - - -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - char *ifid=NULL; - int i=get_l9_version((unsigned char *)story_file, extent, &ifid); - if (!i) return INVALID_STORY_FILE_RV; - if (ifid) - { - ASSERT_OUTPUT_SIZE((signed) strlen(ifid)+1); - strcpy(output,ifid); - return 1; - } - ASSERT_OUTPUT_SIZE(10); - sprintf(output,"LEVEL9-%d-",i); - return INCOMPLETE_REPLY_RV; -} diff --git a/babel/magscrolls.c b/babel/magscrolls.c deleted file mode 100644 index c21c922..0000000 --- a/babel/magscrolls.c +++ /dev/null @@ -1,124 +0,0 @@ -/* magscrolls.c Treaty of Babel module for Z-code files - * 2006 By L. Ross Raszewski - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file - * may render it noncompliant with the Treaty of Babel - */ - -#define FORMAT magscrolls -#define HOME_PAGE "http://www.if-legends.org/~msmemorial/memorial.htm" -#define FORMAT_EXT ".mag" -#define NO_COVER -#define NO_METADATA - -#include "treaty_builder.h" -#include -#include - -struct maginfo -{ - int gv; - char header[21]; - char *title; - int bafn; - int year; - char *ifid; - char *author; - char *meta; -}; - - -static struct maginfo manifest[] = { - { 0, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", - "The Pawn", - 0, - 1985, - "MAGNETIC-1", - "Rob Steggles", - }, - { 1, "\000\004\000\001\007\370\000\000\340\000\000\000\041\064\000\000\040\160\000\000", - "Guild of Thieves", - 0, - 1987, - "MAGNETIC-2", - "Rob Steggles", - }, - { 2, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", - "Jinxter", - 0, - 1987, - "MAGNETIC-3", - "Georgina Sinclair and Michael Bywater", - }, - { 4, "\000\004\000\001\045\140\000\001\000\000\000\000\161\017\000\000\035\210\000\001", - "Corruption", - 0, - 1988, - "MAGNETIC-4", - "Rob Steggles and Hugh Steers", - }, - { 4, "\000\004\000\001\044\304\000\001\000\000\000\000\134\137\000\000\040\230\000\001", - "Fish!", - 0, - 1988, - "MAGNETIC-5", - "John Molloy, Pete Kemp, Phil South, Rob Steggles", - }, - { 4, "\000\003\000\000\377\000\000\000\340\000\000\000\221\000\000\000\036\000\000\001", - "Corruption", - 0, - 1988, - "MAGNETIC-4", - "Rob Steggles and Hugh Steers", - }, - { 4, "\000\003\000\001\000\000\000\000\340\000\000\000\175\000\000\000\037\000\000\001", - "Fish!", - 0, - 1988, - "MAGNETIC-5", - "John Molloy, Pete Kemp, Phil South, Rob Steggles", - }, - { 4, "\000\003\000\000\335\000\000\000\140\000\000\000\064\000\000\000\023\000\000\000", - "Myth", - 0, - 1989, - "MAGNETIC-6", - "Paul Findley", - }, - { 4, "\000\004\000\001\122\074\000\001\000\000\000\000\114\146\000\000\057\240\000\001", - "Wonderland", - 0, - 1990, - "MAGNETIC-7", - "David Bishop", - }, - { 0, "0", NULL, 0, 0, NULL, NULL } - }; - -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - int i; - unsigned char *sf=(unsigned char *)story_file; - if (extent < 42) return INVALID_STORY_FILE_RV; - - for(i=0;manifest[i].title;i++) - if ((sf[13]<3 && manifest[i].gv==sf[13]) || memcmp(manifest[i].header,sf+12,20)==0) - { - ASSERT_OUTPUT_SIZE(((int32) strlen(manifest[i].ifid)+1)); - strcpy(output,manifest[i].ifid); - return 1; - } - strcpy(output,"MAGNETIC-"); - return INCOMPLETE_REPLY_RV; -} - -static int32 claim_story_file(void *story_file, int32 extent) -{ - if (extent<42 || - memcmp(story_file,"MaSc",4) - ) return INVALID_STORY_FILE_RV; - return VALID_STORY_FILE_RV; -} - diff --git a/babel/md5.c b/babel/md5.c deleted file mode 100644 index c35d96c..0000000 --- a/babel/md5.c +++ /dev/null @@ -1,381 +0,0 @@ -/* - Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - L. Peter Deutsch - ghost@aladdin.com - - */ -/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ -/* - Independent implementation of MD5 (RFC 1321). - - This code implements the MD5 Algorithm defined in RFC 1321, whose - text is available at - http://www.ietf.org/rfc/rfc1321.txt - The code is derived from the text of the RFC, including the test suite - (section A.5) but excluding the rest of Appendix A. It does not include - any code or documentation that is identified in the RFC as being - copyrighted. - - The original and principal author of md5.c is L. Peter Deutsch - . Other authors are noted in the change history - that follows (in reverse chronological order): - - 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order - either statically or dynamically; added missing #include - in library. - 2002-03-11 lpd Corrected argument list for main(), and added int return - type, in test program and T value program. - 2002-02-21 lpd Added missing #include in test program. - 2000-07-03 lpd Patched to eliminate warnings about "constant is - unsigned in ANSI C, signed in traditional"; made test program - self-checking. - 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. - 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). - 1999-05-03 lpd Original version. - */ - -#include "md5.h" -#include - -#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ -#ifdef ARCH_IS_BIG_ENDIAN -# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) -#else -# define BYTE_ORDER 0 -#endif - -#define T_MASK ((md5_word_t)~0) -#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) -#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) -#define T3 0x242070db -#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) -#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) -#define T6 0x4787c62a -#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) -#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) -#define T9 0x698098d8 -#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) -#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) -#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) -#define T13 0x6b901122 -#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) -#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) -#define T16 0x49b40821 -#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) -#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) -#define T19 0x265e5a51 -#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) -#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) -#define T22 0x02441453 -#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) -#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) -#define T25 0x21e1cde6 -#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) -#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) -#define T28 0x455a14ed -#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) -#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) -#define T31 0x676f02d9 -#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) -#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) -#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) -#define T35 0x6d9d6122 -#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) -#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) -#define T38 0x4bdecfa9 -#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) -#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) -#define T41 0x289b7ec6 -#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) -#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) -#define T44 0x04881d05 -#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) -#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) -#define T47 0x1fa27cf8 -#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) -#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) -#define T50 0x432aff97 -#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) -#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) -#define T53 0x655b59c3 -#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) -#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) -#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) -#define T57 0x6fa87e4f -#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) -#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) -#define T60 0x4e0811a1 -#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) -#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) -#define T63 0x2ad7d2bb -#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) - - -static void -md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) -{ - md5_word_t - a = pms->abcd[0], b = pms->abcd[1], - c = pms->abcd[2], d = pms->abcd[3]; - md5_word_t t; -#if BYTE_ORDER > 0 - /* Define storage only for big-endian CPUs. */ - md5_word_t X[16]; -#else - /* Define storage for little-endian or both types of CPUs. */ - md5_word_t xbuf[16]; - const md5_word_t *X; -#endif - - { -#if BYTE_ORDER == 0 - /* - * Determine dynamically whether this is a big-endian or - * little-endian machine, since we can use a more efficient - * algorithm on the latter. - */ - static const int w = 1; - - if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ -#endif -#if BYTE_ORDER <= 0 /* little-endian */ - { - /* - * On little-endian machines, we can process properly aligned - * data without copying it. - */ - if (!((data - (const md5_byte_t *)0) & 3)) { - /* data are properly aligned */ - X = (const md5_word_t *)data; - } else { - /* not aligned */ - memcpy(xbuf, data, 64); - X = xbuf; - } - } -#endif -#if BYTE_ORDER == 0 - else /* dynamic big-endian */ -#endif -#if BYTE_ORDER >= 0 /* big-endian */ - { - /* - * On big-endian machines, we must arrange the bytes in the - * right order. - */ - const md5_byte_t *xp = data; - int i; - -# if BYTE_ORDER == 0 - X = xbuf; /* (dynamic only) */ -# else -# define xbuf X /* (static only) */ -# endif - for (i = 0; i < 16; ++i, xp += 4) - xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); - } -#endif - } - -#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) - - /* Round 1. */ - /* Let [abcd k s i] denote the operation - a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ -#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + F(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b - /* Do the following 16 operations. */ - SET(a, b, c, d, 0, 7, T1); - SET(d, a, b, c, 1, 12, T2); - SET(c, d, a, b, 2, 17, T3); - SET(b, c, d, a, 3, 22, T4); - SET(a, b, c, d, 4, 7, T5); - SET(d, a, b, c, 5, 12, T6); - SET(c, d, a, b, 6, 17, T7); - SET(b, c, d, a, 7, 22, T8); - SET(a, b, c, d, 8, 7, T9); - SET(d, a, b, c, 9, 12, T10); - SET(c, d, a, b, 10, 17, T11); - SET(b, c, d, a, 11, 22, T12); - SET(a, b, c, d, 12, 7, T13); - SET(d, a, b, c, 13, 12, T14); - SET(c, d, a, b, 14, 17, T15); - SET(b, c, d, a, 15, 22, T16); -#undef SET - - /* Round 2. */ - /* Let [abcd k s i] denote the operation - a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ -#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + G(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b - /* Do the following 16 operations. */ - SET(a, b, c, d, 1, 5, T17); - SET(d, a, b, c, 6, 9, T18); - SET(c, d, a, b, 11, 14, T19); - SET(b, c, d, a, 0, 20, T20); - SET(a, b, c, d, 5, 5, T21); - SET(d, a, b, c, 10, 9, T22); - SET(c, d, a, b, 15, 14, T23); - SET(b, c, d, a, 4, 20, T24); - SET(a, b, c, d, 9, 5, T25); - SET(d, a, b, c, 14, 9, T26); - SET(c, d, a, b, 3, 14, T27); - SET(b, c, d, a, 8, 20, T28); - SET(a, b, c, d, 13, 5, T29); - SET(d, a, b, c, 2, 9, T30); - SET(c, d, a, b, 7, 14, T31); - SET(b, c, d, a, 12, 20, T32); -#undef SET - - /* Round 3. */ - /* Let [abcd k s t] denote the operation - a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ -#define H(x, y, z) ((x) ^ (y) ^ (z)) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + H(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b - /* Do the following 16 operations. */ - SET(a, b, c, d, 5, 4, T33); - SET(d, a, b, c, 8, 11, T34); - SET(c, d, a, b, 11, 16, T35); - SET(b, c, d, a, 14, 23, T36); - SET(a, b, c, d, 1, 4, T37); - SET(d, a, b, c, 4, 11, T38); - SET(c, d, a, b, 7, 16, T39); - SET(b, c, d, a, 10, 23, T40); - SET(a, b, c, d, 13, 4, T41); - SET(d, a, b, c, 0, 11, T42); - SET(c, d, a, b, 3, 16, T43); - SET(b, c, d, a, 6, 23, T44); - SET(a, b, c, d, 9, 4, T45); - SET(d, a, b, c, 12, 11, T46); - SET(c, d, a, b, 15, 16, T47); - SET(b, c, d, a, 2, 23, T48); -#undef SET - - /* Round 4. */ - /* Let [abcd k s t] denote the operation - a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ -#define I(x, y, z) ((y) ^ ((x) | ~(z))) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + I(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b - /* Do the following 16 operations. */ - SET(a, b, c, d, 0, 6, T49); - SET(d, a, b, c, 7, 10, T50); - SET(c, d, a, b, 14, 15, T51); - SET(b, c, d, a, 5, 21, T52); - SET(a, b, c, d, 12, 6, T53); - SET(d, a, b, c, 3, 10, T54); - SET(c, d, a, b, 10, 15, T55); - SET(b, c, d, a, 1, 21, T56); - SET(a, b, c, d, 8, 6, T57); - SET(d, a, b, c, 15, 10, T58); - SET(c, d, a, b, 6, 15, T59); - SET(b, c, d, a, 13, 21, T60); - SET(a, b, c, d, 4, 6, T61); - SET(d, a, b, c, 11, 10, T62); - SET(c, d, a, b, 2, 15, T63); - SET(b, c, d, a, 9, 21, T64); -#undef SET - - /* Then perform the following additions. (That is increment each - of the four registers by the value it had before this block - was started.) */ - pms->abcd[0] += a; - pms->abcd[1] += b; - pms->abcd[2] += c; - pms->abcd[3] += d; -} - -void -md5_init(md5_state_t *pms) -{ - pms->count[0] = pms->count[1] = 0; - pms->abcd[0] = 0x67452301; - pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; - pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; - pms->abcd[3] = 0x10325476; -} - -void -md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) -{ - const md5_byte_t *p = data; - int left = nbytes; - int offset = (pms->count[0] >> 3) & 63; - md5_word_t nbits = (md5_word_t)(nbytes << 3); - - if (nbytes <= 0) - return; - - /* Update the message length. */ - pms->count[1] += nbytes >> 29; - pms->count[0] += nbits; - if (pms->count[0] < nbits) - pms->count[1]++; - - /* Process an initial partial block. */ - if (offset) { - int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); - - memcpy(pms->buf + offset, p, copy); - if (offset + copy < 64) - return; - p += copy; - left -= copy; - md5_process(pms, pms->buf); - } - - /* Process full blocks. */ - for (; left >= 64; p += 64, left -= 64) - md5_process(pms, p); - - /* Process a final partial block. */ - if (left) - memcpy(pms->buf, p, left); -} - -void -md5_finish(md5_state_t *pms, md5_byte_t digest[16]) -{ - static const md5_byte_t pad[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - md5_byte_t data[8]; - int i; - - /* Save the length before padding. */ - for (i = 0; i < 8; ++i) - data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); - /* Pad to 56 bytes mod 64. */ - md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); - /* Append the length. */ - md5_append(pms, data, 8); - for (i = 0; i < 16; ++i) - digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); -} diff --git a/babel/md5.h b/babel/md5.h deleted file mode 100644 index 698c995..0000000 --- a/babel/md5.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - L. Peter Deutsch - ghost@aladdin.com - - */ -/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ -/* - Independent implementation of MD5 (RFC 1321). - - This code implements the MD5 Algorithm defined in RFC 1321, whose - text is available at - http://www.ietf.org/rfc/rfc1321.txt - The code is derived from the text of the RFC, including the test suite - (section A.5) but excluding the rest of Appendix A. It does not include - any code or documentation that is identified in the RFC as being - copyrighted. - - The original and principal author of md5.h is L. Peter Deutsch - . Other authors are noted in the change history - that follows (in reverse chronological order): - - 2002-04-13 lpd Removed support for non-ANSI compilers; removed - references to Ghostscript; clarified derivation from RFC 1321; - now handles byte order either statically or dynamically. - 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. - 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); - added conditionalization for C++ compilation from Martin - Purschke . - 1999-05-03 lpd Original version. - */ - -#ifndef md5_INCLUDED -# define md5_INCLUDED - -/* - * This package supports both compile-time and run-time determination of CPU - * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be - * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is - * defined as non-zero, the code will be compiled to run only on big-endian - * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to - * run on either big- or little-endian CPUs, but will run slightly less - * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. - */ - -typedef unsigned char md5_byte_t; /* 8-bit byte */ -typedef unsigned int md5_word_t; /* 32-bit word */ - -/* Define the state of the MD5 Algorithm. */ -typedef struct md5_state_s { - md5_word_t count[2]; /* message length in bits, lsw first */ - md5_word_t abcd[4]; /* digest buffer */ - md5_byte_t buf[64]; /* accumulate block */ -} md5_state_t; - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* Initialize the algorithm. */ -void md5_init(md5_state_t *pms); - -/* Append a string to the message. */ -void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); - -/* Finish the message and return the digest. */ -void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); - -#ifdef __cplusplus -} /* end extern "C" */ -#endif - -#endif /* md5_INCLUDED */ diff --git a/babel/misc.c b/babel/misc.c deleted file mode 100644 index 982927f..0000000 --- a/babel/misc.c +++ /dev/null @@ -1,19 +0,0 @@ -/* misc.h : miscellany for babel - * This file is public domain - * 2006 by L. Ross Raszewski - */ - -#include -#include - -void *my_malloc(int size, char *rs) -{ - void *buf=calloc(size,1); - if (size && !buf) - { - fprintf(stderr,"Error: Memory exceeded (%d for %s)!\n",size,rs); - exit(2); - } - return buf; -} - diff --git a/babel/modules.h b/babel/modules.h deleted file mode 100644 index cc799ee..0000000 --- a/babel/modules.h +++ /dev/null @@ -1,70 +0,0 @@ -/* modules.h Declaration of treaty modules for the babel program - * (c) 2006 By L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends upon treaty.h and all the references treaty modules - * - * Persons wishing to add support for a new module to babel need only - * add a line in the form below. New modules should be positioned according - * to their popularity. If this file is being used in tandem with register.c - * (as it is in babel), then being dishonest about the popularity of an added - * system will make the program non-compliant with the treaty of Babel - * - * REGISTER_NAME is used as a placeholder for formats which are specified - * as existing by the treaty but for which no handler yet exists. - * remove the REGISTER_NAME for any format which has a registered treaty. - */ - - -#include "treaty.h" -#undef REGISTER_TREATY -#undef REGISTER_CONTAINER -#undef REGISTER_NAME -#ifdef TREATY_REGISTER -#ifdef CONTAINER_REGISTER -#ifdef FORMAT_REGISTER -#define REGISTER_TREATY(x) #x, -#define REGISTER_NAME(x) #x, -#define REGISTER_CONTAINER(x) -#else -#define REGISTER_TREATY(x) -#define REGISTER_CONTAINER(x) x##_treaty, -#define REGISTER_NAME(x) -#endif -#else -#define REGISTER_TREATY(x) x##_treaty, -#define REGISTER_CONTAINER(x) -#define REGISTER_NAME(x) -#endif -#else -#define REGISTER_TREATY(x) int32 x##_treaty(int32, void *, int32, void *, int32); -#define REGISTER_CONTAINER(x) int32 x##_treaty(int32, void *, int32, void *, int32); -#define REGISTER_NAME(x) -#endif - - -REGISTER_CONTAINER(blorb) -REGISTER_TREATY(zcode) -REGISTER_TREATY(glulx) -REGISTER_TREATY(tads2) -REGISTER_TREATY(tads3) -REGISTER_TREATY(hugo) -REGISTER_TREATY(alan) -REGISTER_TREATY(adrift) -REGISTER_TREATY(level9) -REGISTER_TREATY(agt) -REGISTER_TREATY(magscrolls) -REGISTER_TREATY(advsys) -REGISTER_TREATY(executable) - - - - diff --git a/babel/modules.h.gch b/babel/modules.h.gch deleted file mode 100644 index 99db82c..0000000 Binary files a/babel/modules.h.gch and /dev/null differ diff --git a/babel/register.c b/babel/register.c deleted file mode 100644 index 22bbed5..0000000 --- a/babel/register.c +++ /dev/null @@ -1,36 +0,0 @@ -/* register.c Register modules for the babel handler api - * - * 2006 by L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends on modules.h - * - * The purpose of this file is to create the treaty_registry array. - * This array is a null-terminated list of the known treaty modules. - */ - -#include -#include "modules.h" - - -TREATY treaty_registry[] = { - #define TREATY_REGISTER - #include "modules.h" - NULL - }; - -TREATY container_registry[] = { - #define CONTAINER_REGISTER - #include "modules.h" - NULL - -}; - diff --git a/babel/register_ifiction.c b/babel/register_ifiction.c deleted file mode 100644 index 55fe9e2..0000000 --- a/babel/register_ifiction.c +++ /dev/null @@ -1,29 +0,0 @@ -/* register_ifiction.c Register modules for babel's ifiction API - * - * 2006 by L. Ross Raszewski - * - * This code is freely usable for all purposes. - * - * This work is licensed under the Creative Commons Attribution2.5 License. - * To view a copy of this license, visit - * http://creativecommons.org/licenses/by/2.5/ or send a letter to - * Creative Commons, - * 543 Howard Street, 5th Floor, - * San Francisco, California, 94105, USA. - * - * This file depends on modules.h - * - * This version of register.c is stripped down to include only the - * needed functionality for the ifiction api - */ - -#include -#include "treaty.h" - -char *format_registry[] = { - #define TREATY_REGISTER - #define CONTAINER_REGISTER - #define FORMAT_REGISTER - #include "modules.h" - NULL -}; diff --git a/babel/tads.c b/babel/tads.c deleted file mode 100644 index bde1e5a..0000000 --- a/babel/tads.c +++ /dev/null @@ -1,1827 +0,0 @@ -/* - * tads.c - Treaty of Babel common functions for tads2 and tads3 modules - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file may - * render it noncompliant with the Treaty of Babel - * - * Modified - *. 04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions - *. 04/08/2006 MJRoberts - initial implementation - */ - - -#include "treaty.h" -#include -#include -#include -#include -#include "tads.h" -#include "md5.h" - -#define ASSERT_OUTPUT_SIZE(x) \ - do { if (output_extent < (x)) return INVALID_USAGE_RV; } while (0) - -#define T2_SIGNATURE "TADS2 bin\012\015\032" -#define T3_SIGNATURE "T3-image\015\012\032" - -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -/* ------------------------------------------------------------------------ */ -/* - * private structures - */ - -/* - * resource information structure - this encapsulates the location and size - * of a binary resource object embedded in a story file - */ -typedef struct resinfo resinfo; -struct resinfo -{ - /* pointer and length of the data in the story file buffer */ - const char *ptr; - int32 len; - - /* tads major version (currently, 2 or 3) */ - int tads_version; -}; - -/* - * Name/value pair list entry - */ -typedef struct valinfo valinfo; -struct valinfo -{ - const char *name; - size_t name_len; - - /* value string */ - char *val; - size_t val_len; - - /* next entry in the list */ - valinfo *nxt; -}; - - -/* ------------------------------------------------------------------------ */ -/* - * forward declarations - */ -static valinfo *parse_game_info(const void *story_file, int32 story_len, - int *version); -static int find_resource(const void *story_file, int32 story_len, - const char *resname, resinfo *info); -static int find_cover_art(const void *story_file, int32 story_len, - resinfo *resp, int32 *image_format, - int32 *width, int32 *height); -static int t2_find_res(const void *story_file, int32 story_len, - const char *resname, resinfo *info); -static int t3_find_res(const void *story_file, int32 story_len, - const char *resname, resinfo *info); -static valinfo *find_by_key(valinfo *list_head, const char *key); -static void delete_valinfo_list(valinfo *head); -static int32 generate_md5_ifid(void *story_file, int32 extent, - char *output, int32 output_extent); -static int32 synth_ifiction(valinfo *vals, int tads_version, - char *buf, int32 bufsize, - void *story_file, int32 extent); -static int get_png_dim(const void *img, int32 extent, - int32 *xout, int32 *yout); -static int get_jpeg_dim(const void *img, int32 extent, - int32 *xout, int32 *yout); - - - -/* ------------------------------------------------------------------------ */ -/* - * Get the IFID for a given story file. - */ -int32 tads_get_story_file_IFID(void *story_file, int32 extent, - char *output, int32 output_extent) -{ - valinfo *vals; - - /* if we have GameInfo, try looking for an IFID there */ - if ((vals = parse_game_info(story_file, extent, 0)) != 0) - { - valinfo *val; - int found = 0; - - /* find the "IFID" key */ - if ((val = find_by_key(vals, "IFID")) != 0) - { - char *p; - - /* copy the output as a null-terminated string */ - ASSERT_OUTPUT_SIZE((int32)val->val_len + 1); - memcpy(output, val->val, val->val_len); - output[val->val_len] = '\0'; - - /* - * count up the IFIDs in the buffer - there might be more than - * one, separated by commas - */ - for (found = 1, p = output ; *p != '\0' ; ++p) - { - /* if this is a comma, it delimits a new IFID */ - if (*p == ',') - ++found; - } - } - - /* delete the GameInfo list */ - delete_valinfo_list(vals); - - /* if we found an IFID, indicate how many results we found */ - if (found != 0) - return found; - } - - /* - * we didn't find an IFID in the GameInfo, so generate a default IFID - * using the MD5 method - */ - return generate_md5_ifid(story_file, extent, output, output_extent); -} - -/* - * Get the size of the ifiction metadata for the game - */ -int32 tads_get_story_file_metadata_extent(void *story_file, int32 extent) -{ - valinfo *vals; - int32 ret; - int ver; - - /* - * First, make sure we have a GameInfo record. If we don't, simply - * indicate that there's no metadata to fetch. - */ - if ((vals = parse_game_info(story_file, extent, &ver)) == 0) - return NO_REPLY_RV; - - /* - * Run the ifiction synthesizer with no output buffer, to calculate the - * size we need. - */ - ret = synth_ifiction(vals, ver, 0, 0, story_file, extent); - - /* delete the value list */ - delete_valinfo_list(vals); - - /* return the required size */ - return ret; -} - -/* - * Get the ifiction metadata for the game - */ -int32 tads_get_story_file_metadata(void *story_file, int32 extent, - char *buf, int32 bufsize) -{ - valinfo *vals; - int32 ret; - int ver; - - /* make sure we have metadata to fetch */ - if ((vals = parse_game_info(story_file, extent, &ver)) == 0) - return NO_REPLY_RV; - - /* synthesize the ifiction data into the output buffer */ - ret = synth_ifiction(vals, ver, buf, bufsize, story_file, extent); - - /* if that required more space than we had available, return an error */ - if (ret > bufsize) - ret = INVALID_USAGE_RV; - - /* delete the value list */ - delete_valinfo_list(vals); - - /* return the result */ - return ret; -} - -/* - * Get the size of the cover art - */ -int32 tads_get_story_file_cover_extent(void *story_file, int32 story_len) -{ - resinfo res; - - /* look for the cover art resource */ - if (find_cover_art(story_file, story_len, &res, 0, 0, 0)) - return res.len; - else - return NO_REPLY_RV; -} - -/* - * Get the format of the cover art - */ -int32 tads_get_story_file_cover_format(void *story_file, int32 story_len) -{ - int32 typ; - - /* look for CoverArt.jpg */ - if (find_cover_art(story_file, story_len, 0, &typ, 0, 0)) - return typ; - else - return NO_REPLY_RV; -} - -/* - * Get the cover art data - */ -int32 tads_get_story_file_cover(void *story_file, int32 story_len, - void *outbuf, int32 output_extent) -{ - resinfo res; - - /* look for CoverArt.jpg, then for CoverArt.png */ - if (find_cover_art(story_file, story_len, &res, 0, 0, 0)) - { - /* got it - copy the data to the buffer */ - ASSERT_OUTPUT_SIZE(res.len); - memcpy(outbuf, res.ptr, res.len); - - /* success */ - return res.len; - } - - /* otherwise, we didn't find it */ - return NO_REPLY_RV; -} - -/* ------------------------------------------------------------------------ */ -/* - * Generate a default IFID using the MD5 hash method - */ -static int32 generate_md5_ifid(void *story_file, int32 extent, - char *output, int32 output_extent) -{ - md5_state_t md5; - unsigned char md5_buf[16]; - char *p; - int i; - - /* calculate the MD5 hash of the story file */ - md5_init(&md5); - md5_append(&md5, story_file, extent); - md5_finish(&md5, md5_buf); - - /* make sure we have room to store the result */ - ASSERT_OUTPUT_SIZE(39); - - /* the prefix is "TADS2-" or "TADS3-", depending on the format */ - if (tads_match_sig(story_file, extent, T2_SIGNATURE)) - strcpy(output, "TADS2-"); - else - strcpy(output, "TADS3-"); - - /* the rest is the MD5 hash of the file, as hex digits */ - for (i = 0, p = output + strlen(output) ; i < 16 ; p += 2, ++i) - sprintf(p, "%02X", md5_buf[i]); - - /* indicate that we found one result */ - return 1; -} - -/* ------------------------------------------------------------------------ */ -/* - * Some UTF-8 utility functions and macros. We use our own rather than the - * ctype.h macros because we're parsing UTF-8 text. - */ - -/* is c a space? */ -#define u_isspace(c) ((unsigned char)(c) < 128 && isspace(c)) - -/* is c a horizontal space? */ -#define u_ishspace(c) (u_isspace(c) && (c) != '\n' && (c) != '\r') - -/* is-newline - matches \n, \r, and \u2028 */ -static int u_isnl(const char *p, int32 len) -{ - return (*p == '\n' - || *p == '\r' - || (len >= 3 - && *(unsigned char *)p == 0xe2 - && *(unsigned char *)(p+1) == 0x80 - && *(unsigned char *)(p+2) == 0xa8)); -} - -/* skip to the next utf-8 character */ -static void nextc(const char **p, int32 *len) -{ - /* skip the first byte */ - if (*len != 0) - ++*p, --*len; - - /* skip continuation bytes */ - while (*len != 0 && (**p & 0xC0) == 0x80) - ++*p, --*len; -} - -/* skip to the previous utf-8 character */ -static void prevc(const char **p, int32 *len) -{ - /* move back one byte */ - --*p, ++*len; - - /* keep skipping as long as we're looking at continuation characters */ - while ((**p & 0xC0) == 0x80) - --*p, ++*len; -} - -/* - * Skip a newline sequence. Skips all common conventions, including \n, - * \r, \n\r, \r\n, and \u2028. - */ -static void skip_newline(const char **p, int32 *rem) -{ - /* make sure we have something to skip */ - if (*rem == 0) - return; - - /* check what we have */ - switch (**(const unsigned char **)p) - { - case '\n': - /* skip \n or \n\r */ - nextc(p, rem); - if (**p == '\r') - nextc(p, rem); - break; - - case '\r': - /* skip \r or \r\n */ - nextc(p, rem); - if (**p == '\n') - nextc(p, rem); - break; - - case 0xe2: - /* \u2028 (unicode line separator) - just skip the one character */ - nextc(p, rem); - break; - } -} - -/* - * Skip to the next line - */ -static void skip_to_next_line(const char **p, int32 *rem) -{ - /* look for the next newline */ - for ( ; *rem != 0 ; nextc(p, rem)) - { - /* if this is a newline of some kind, we're at the end of the line */ - if (u_isnl(*p, *rem)) - { - /* skip the newline, and we're done */ - skip_newline(p, rem); - break; - } - } -} - - -/* ------------------------------------------------------------------------ */ -/* - * ifiction synthesizer output context - */ -typedef struct synthctx synthctx; -struct synthctx -{ - /* the current output pointer */ - char *buf; - - /* the number of bytes remaining in the output buffer */ - int32 buf_size; - - /* - * the total number of bytes needed for the output (this might be more - * than we've actually written, since we count up the bytes required - * even if we need more space than the buffer provides) - */ - int32 total_size; - - /* the head of the name/value pair list from the parsed GameInfo */ - valinfo *vals; -}; - -/* initialize a synthesizer context */ -static void init_synthctx(synthctx *ctx, char *buf, int32 bufsize, - valinfo *vals) -{ - /* set up at the beginning of the output buffer */ - ctx->buf = buf; - ctx->buf_size = bufsize; - - /* we haven't written anything to the output buffer yet */ - ctx->total_size = 0; - - /* remember the name/value pair list */ - ctx->vals = vals; -} - -/* - * Write out a chunk to a synthesized ifiction record, updating pointers - * and counters. We won't copy past the end of the buffer, but we'll - * continue counting the output length needed in any case. - */ -static void write_ifiction(synthctx *ctx, const char *src, size_t srclen) -{ - int32 copy_len; - - /* copy as much as we can, up to the remaining buffer size */ - copy_len = srclen; - if (copy_len > ctx->buf_size) - copy_len = ctx->buf_size; - - /* do the copying, if any */ - if (copy_len != 0) - { - /* copy the bytes */ - memcpy(ctx->buf, src, (size_t)copy_len); - - /* adjust the buffer pointer and output buffer size remaining */ - ctx->buf += copy_len; - ctx->buf_size -= copy_len; - } - - /* count this source data in the total size */ - ctx->total_size += srclen; -} - -/* write a null-terminated chunk to the synthesized ifiction record */ -static void write_ifiction_z(synthctx *ctx, const char *src) -{ - write_ifiction(ctx, src, strlen(src)); -} - -/* - * Write a PCDATA string to the synthesized ifiction record. In - * particular, we rewrite '<', '>', and '&' as '<', '>', and '&', - * respectively; we trim off leading and trailing spaces; and we compress - * each run of whitespace down to a single \u0020 (' ') character. - */ -static void write_ifiction_pcdata(synthctx *ctx, const char *p, size_t len) -{ - /* first, skip any leading whitespace */ - for ( ; len != 0 && u_ishspace(*p) ; ++p, --len) ; - - /* keep going until we run out of string */ - for (;;) - { - const char *start; - - /* scan to the next whitespace or markup-significant character */ - for (start = p ; - len != 0 && !u_ishspace(*p) - && *p != '<' && *p != '>' && *p != '&' ; ++p, --len) ; - - /* write the part up to here */ - if (p != start) - write_ifiction(ctx, start, p - start); - - /* if we've reached the end of the string, we can stop */ - if (len == 0) - break; - - /* check what stopped us */ - switch (*p) - { - case '<': - write_ifiction_z(ctx, "<"); - ++p, --len; - break; - - case '>': - write_ifiction_z(ctx, ">"); - ++p, --len; - break; - - case '&': - write_ifiction_z(ctx, "&"); - ++p, --len; - break; - - default: - /* - * The only other thing that could have stopped us is - * whitespace. Skip all consecutive whitespace. - */ - for ( ; len != 0 && u_ishspace(*p) ; ++p, --len); - - /* - * if that's not the end of the string, replace the run of - * whitespace with a single space character in the output; if - * we've reached the end of the string, we don't even want to - * do that, since we want to trim off trailing spaces - */ - if (len != 0) - write_ifiction_z(ctx, " "); - break; - } - } -} - -/* - * Translate a GameInfo keyed value to the corresponding ifiction tagged - * value. We find the GameInfo value keyed by 'gameinfo_key', and write - * out the same string under the ifiction XML tag 'ifiction_tag'. We write - * a complete XML container sequence - value. - * - * If the given GameInfo key doesn't exist, we use the default value string - * 'dflt', if given. If the GameInfo key doesn't exist and 'dflt' is null, - * we don't write anything - we don't even write the open/close tags. - * - * If 'html' is true, we assume the value is in html format, and we write - * it untranslated. Otherwise, we write it as PCDATA, translating markup - * characters into '&' entities and compressing whitespace. - */ -static void write_ifiction_xlat_base(synthctx *ctx, int indent, - const char *gameinfo_key, - const char *ifiction_tag, - const char *dflt, int html) -{ - valinfo *val; - const char *valstr; - size_t vallen; - - /* look up the GameInfo key */ - if ((val = find_by_key(ctx->vals, gameinfo_key)) != 0) - { - /* we found the GameInfo value - use it */ - valstr = val->val; - vallen = val->val_len; - } - else if (dflt != 0) - { - /* the GameInfo value doesn't exist, but we have a default - use it */ - valstr = dflt; - vallen = strlen(dflt); - } - else - { - /* there's no GameInfo value and no default, so write nothing */ - return; - } - - /* write the indentation */ - while (indent != 0) - { - static const char spaces[] = " "; - size_t cur; - - /* figure how much we can write on this round */ - cur = indent; - if (cur > sizeof(spaces) - 1) - cur = sizeof(spaces) - 1; - - /* write it */ - write_ifiction(ctx, spaces, cur); - - /* deduct it from the amount remaining */ - indent -= cur; - } - - /* write the open tag */ - write_ifiction_z(ctx, "<"); - write_ifiction_z(ctx, ifiction_tag); - write_ifiction_z(ctx, ">"); - - /* write the value, applying pcdata translations */ - if (html) - write_ifiction(ctx, valstr, vallen); - else - write_ifiction_pcdata(ctx, valstr, vallen); - - /* write the close tag */ - write_ifiction_z(ctx, "\n"); -} - -#define write_ifiction_xlat(ctx, indent, gikey, iftag, dflt) \ - write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, FALSE) - -#define write_ifiction_xlat_html(ctx, indent, gikey, iftag, dflt) \ - write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, TRUE) - - -/* - * Retrieve the next author name from the GameInfo "Author" format. The - * format is as follows: - * - * name ... ; ... - * - * That is, each author is listed with a name followed by one or more email - * addresses in angle brackets, and multiple authors are separated by - * semicolons. - */ -static int scan_author_name(const char **p, size_t *len, - const char **start, const char **end) -{ - /* keep going until we find a non-empty author name */ - for (;;) - { - /* skip leading spaces */ - for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ; - - /* if we ran out of string, there's definitely no author name */ - if (*len == 0) - return FALSE; - - /* - * Find the end of this author name. The author name ends at the - * next semicolon or angle bracket. - */ - for (*start = *p ; *len != 0 && **p != ';' && **p != '<' ; - ++*p, --*len) ; - - /* trim off any trailing spaces */ - for (*end = *p ; *end > *start && u_ishspace(*(*end - 1)) ; --*end) ; - - /* now skip any email addresses */ - while (*len != 0 && **p == '<') - { - /* skip to the closing bracket */ - for (++*p, --*len ; *len != 0 && **p != '>' ; ++*p, --*len) ; - - /* skip the bracket */ - if (*len != 0) - ++*p, --*len; - - /* skip whitespace */ - for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ; - - /* - * if we're not at a semicolon, another angle bracket, or the - * end of the string, it's a syntax error - */ - if (*len != 0 && **p != '<' && **p != ';') - { - *len = 0; - return FALSE; - } - } - - /* if we're at a semicolon, skip it */ - if (*len != 0 && **p == ';') - ++*p, --*len; - - /* - * if we found a non-empty name, return it; otherwise, continue on - * to the next semicolon section - */ - if (*end != *start) - return TRUE; - } -} - - -/* - * Synthesize an ifiction record for the given GameInfo name/value pair - * list. Returns the number of bytes required for the result, including - * null termination. We'll copy as much as we can to the output buffer, up - * to bufsize; if the buffer size is insufficient to hold the result, we'll - * still indicate the length needed for the full result, but we're careful - * not to actually copy anything past the end of the buffer. - */ -static int32 synth_ifiction(valinfo *vals, int tads_version, - char *buf, int32 bufsize, - void *story_file, int32 extent) -{ - char default_ifid[TREATY_MINIMUM_EXTENT]; - valinfo *ifid = find_by_key(vals, "IFID"); - const char *ifid_val; - size_t ifid_len; - valinfo *author = find_by_key(vals, "AuthorEmail"); - valinfo *url = find_by_key(vals, "Url"); - synthctx ctx; - const char *p; - size_t rem; - int32 art_fmt; - int32 art_wid, art_ht; - - /* initialize the output content */ - init_synthctx(&ctx, buf, bufsize, vals); - - /* make sure the tads version is one we know how to handle */ - if (tads_version != 2 && tads_version != 3) - return NO_REPLY_RV; - - /* - * The IFID is mandatory. If there's not an IFID specifically listed - * in the GameInfo, we need to generate the default IFID based on the - * MD5 hash of the game file. - */ - if (ifid != 0) - { - /* use the explicit IFID(s) listed in the GameInfo */ - ifid_val = ifid->val; - ifid_len = ifid->val_len; - } - else - { - /* generate the default IFID */ - generate_md5_ifid(story_file, extent, - default_ifid, TREATY_MINIMUM_EXTENT); - - /* use this as the IFID */ - ifid_val = default_ifid; - ifid_len = strlen(default_ifid); - } - - /* write the header, and start the section */ - write_ifiction_z( - &ctx, - "\n" - "\n" - " \n" - " \n" - " \n" - " Babel\n" - " " TREATY_VERSION "\n" - " 2006-04-14\n" - " \n" - " \n"); - - /* write each IFID (there might be several) */ - for (p = ifid_val, rem = ifid_len ; rem != 0 ; ) - { - const char *start; - const char *end; - - /* skip leading spaces */ - for ( ; rem != 0 && u_ishspace(*p) ; ++p, --rem) ; - - /* find the end of this IFID */ - for (start = p ; rem != 0 && *p != ',' ; ++p, --rem) ; - - /* remove trailing spaces */ - for (end = p ; end > start && u_ishspace(*(end-1)) ; --end) ; - - /* if we found one, write it out */ - if (end != start) - { - write_ifiction_z(&ctx, " "); - write_ifiction(&ctx, start, end - start); - write_ifiction_z(&ctx, "\n"); - } - - /* skip the comma */ - if (rem != 0 && *p == ',') - ++p, --rem; - } - - /* add the format information */ - write_ifiction_z(&ctx, - tads_version == 2 - ? " tads2\n" - : " tads3\n"); - - /* close the section and start the */ - write_ifiction_z(&ctx, - " \n" - " \n"); - - /* write the various bibliographic data */ - write_ifiction_xlat(&ctx, 6, "Name", "title", "An Interactive Fiction"); - write_ifiction_xlat(&ctx, 6, "Headline", "headline", 0); - write_ifiction_xlat(&ctx, 6, "Desc", "description", 0); - write_ifiction_xlat(&ctx, 6, "Genre", "genre", 0); - write_ifiction_xlat(&ctx, 6, "Forgiveness", "forgiveness", 0); - write_ifiction_xlat(&ctx, 6, "Series", "series", 0); - write_ifiction_xlat(&ctx, 6, "SeriesNumber", "seriesnumber", 0); - write_ifiction_xlat(&ctx, 6, "Language", "language", 0); - write_ifiction_xlat(&ctx, 6, "FirstPublished", "firstpublished", 0); - - /* if there's an author, write the list of author names */ - if (author != 0) - { - int cnt; - int i; - const char *start; - const char *end; - - /* start the tag */ - write_ifiction_z(&ctx, " "); - - /* - * first, count up the number of authors - authors are separated by - * semicolons, so there's one more author than there are semicolons - */ - for (p = author->val, rem = author->val_len, cnt = 1 ; - scan_author_name(&p, &rem, &start, &end) ; ) ; - - /* - * Now generate the list of authors. If there are multiple - * authors, use commas to separate them. - */ - for (p = author->val, rem = author->val_len, i = 0 ; ; ++i) - { - /* scan this author's name */ - if (!scan_author_name(&p, &rem, &start, &end)) - break; - - /* write out this author name */ - write_ifiction_pcdata(&ctx, start, end - start); - - /* if there's another name to come, write a separator */ - if (i + 1 < cnt) - { - /* - * write just "and" to separate two items; write "," - * between items in lists of more than two, with ",and" - * between the last two items - */ - write_ifiction_z(&ctx, - cnt == 2 ? " and " : - i + 2 < cnt ? ", " : ", and "); - } - } - - /* end the tag */ - write_ifiction_z(&ctx, "\n"); - } - - /* end the biblio section */ - write_ifiction_z(&ctx, " \n"); - - /* if there's cover art, add its information */ - if (find_cover_art(story_file, extent, 0, &art_fmt, &art_wid, &art_ht) - && (art_fmt == PNG_COVER_FORMAT || art_fmt == JPEG_COVER_FORMAT)) - { - char buf[200]; - - sprintf(buf, - " \n" - " %s\n" - " %lu\n" - " %lu\n" - " \n", - art_fmt == PNG_COVER_FORMAT ? "png" : "jpg", - (long)art_ht, (long)art_wid); - - write_ifiction_z(&ctx, buf); - } - - /* if there's an author email, include it */ - if (author != 0 || url != 0) - { - const char *p; - size_t rem; - int i; - - /* open the section */ - write_ifiction_z(&ctx, " \n"); - - /* add the author email, if provided */ - if (author != 0) - { - /* write the email list */ - for (i = 0, p = author->val, rem = author->val_len ; ; ++i) - { - const char *start; - - /* skip to the next email address */ - for ( ; rem != 0 && *p != '<' ; ++p, --rem) ; - - /* if we didn't find an email address, we're done */ - if (rem == 0) - break; - - /* find the matching '>' */ - for (++p, --rem, start = p ; rem != 0 && *p != '>' ; - ++p, --rem) ; - - /* - * if this is the first one, open the section; otherwise, - * add a comma - */ - if (i == 0) - write_ifiction_z(&ctx, " "); - else - write_ifiction_z(&ctx, ","); - - /* write this address */ - write_ifiction(&ctx, start, p - start); - - /* - * skip the closing bracket, if there is one; if we're out - * of string, we're done - */ - if (rem != 0) - ++p, --rem; - else - break; - } - - /* if we found any emails to write, end the section */ - if (i != 0) - write_ifiction_z(&ctx, "\n"); - } - - /* if there's a URL, add it */ - if (url != 0) - { - write_ifiction_z(&ctx, " "); - write_ifiction(&ctx, url->val, url->val_len); - write_ifiction_z(&ctx, "\n"); - } - - /* close the section */ - write_ifiction_z(&ctx, " \n"); - } - - /* add the tads-specific section */ - write_ifiction_z(&ctx, " \n"); - - write_ifiction_xlat(&ctx, 6, "Version", "version", 0); - write_ifiction_xlat(&ctx, 6, "ReleaseDate", "releasedate", 0); - write_ifiction_xlat(&ctx, 6, "PresentationProfile", - "presentationprofile", 0); - write_ifiction_xlat(&ctx, 6, "Byline", "byline", 0); - - write_ifiction_z(&ctx, " \n"); - - /* close the story section and the main body */ - write_ifiction_z(&ctx, " \n\n"); - - /* add the null terminator */ - write_ifiction(&ctx, "", 1); - - /* return the total output size */ - return ctx.total_size; -} - -/* ------------------------------------------------------------------------ */ -/* - * Check a data block to see if it starts with the given signature. - */ -int tads_match_sig(const void *buf, int32 len, const char *sig) -{ - /* note the length of the signature string */ - size_t sig_len = strlen(sig); - - /* if matches if the buffer starts with the signature string */ - return (len >= (int32)sig_len && memcmp(buf, sig, sig_len) == 0); -} - - -/* ------------------------------------------------------------------------ */ -/* - * portable-to-native format conversions - */ -#define osbyte(p, ofs) \ - (*(((unsigned char *)(p)) + (ofs))) - -#define osrp1(p) \ - ((unsigned int)osbyte(p, 0)) - -#define osrp2(p) \ - ((unsigned int)osbyte(p, 0) \ - + ((unsigned int)osbyte(p, 1) << 8)) - -#define osrp4(p) \ - (((unsigned long)osbyte(p, 0)) \ - + (((unsigned long)osbyte(p, 1)) << 8) \ - + (((unsigned long)osbyte(p, 2)) << 16) \ - + (((unsigned long)osbyte(p, 3)) << 24)) - - -/* ------------------------------------------------------------------------ */ -/* - * Parse a game file and retrieve the GameInfo data. Returns the head of a - * linked list of valinfo entries. - */ -static valinfo *parse_game_info(const void *story_file, int32 story_len, - int *tads_version) -{ - resinfo res; - const char *p; - int32 rem; - valinfo *val_head = 0; - - /* - * first, find the GameInfo resource - if it's not there, there's no - * game information to parse - */ - if (!find_resource(story_file, story_len, "GameInfo.txt", &res)) - return 0; - - /* if the caller wants the TADS version number, hand it back */ - if (tads_version != 0) - *tads_version = res.tads_version; - - /* parse the data */ - for (p = res.ptr, rem = res.len ; rem != 0 ; ) - { - const char *name_start; - size_t name_len; - const char *val_start; - valinfo *val; - const char *inp; - int32 inlen; - char *outp; - - /* skip any leading whitespace */ - while (rem != 0 && u_isspace(*p)) - ++p, --rem; - - /* if the line starts with '#', it's a comment, so skip it */ - if (rem != 0 && *p == '#') - { - skip_to_next_line(&p, &rem); - continue; - } - - /* we must have the start of a name - note it */ - name_start = p; - - /* skip ahead to a space or colon */ - while (rem != 0 && *p != ':' && !u_ishspace(*p)) - nextc(&p, &rem); - - /* note the length of the name */ - name_len = p - name_start; - - /* skip any whitespace before the presumed colon */ - while (rem != 0 && u_ishspace(*p)) - nextc(&p, &rem); - - /* if we're not at a colon, the line is ill-formed, so skip it */ - if (rem == 0 || *p != ':') - { - /* skip the entire line, and go back for the next one */ - skip_to_next_line(&p, &rem); - continue; - } - - /* skip the colon and any whitespace immediately after it */ - for (nextc(&p, &rem) ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ; - - /* note where the value starts */ - val_start = p; - - /* - * Scan the value to get its length. The value runs from here to - * the next newline that's not followed immediately by a space. - */ - while (rem != 0) - { - const char *nl; - int32 nlrem; - - /* skip to the next line */ - skip_to_next_line(&p, &rem); - - /* if we're at eof, we can stop now */ - if (rem == 0) - break; - - /* note where this line starts */ - nl = p; - nlrem = rem; - - /* - * if we're at a non-whitespace character, it's definitely not - * a continuation line - */ - if (!u_ishspace(*p)) - break; - - /* - * check for spaces followed by a non-space character - this - * would signify a continuation line - */ - for ( ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ; - if (rem == 0 || u_isnl(p, rem)) - { - /* - * we're at end of file, we found a line with nothing but - * whitespace, so this isn't a continuation line; go back - * to the start of this line and end the value here - */ - p = nl; - rem = nlrem; - break; - } - - /* - * We found whitespace followed by non-whitespace, so this is a - * continuation line. Keep going for now. - */ - } - - /* remove any trailing newlines */ - while (p > val_start) - { - /* move back one character */ - prevc(&p, &rem); - - /* - * if it's a newline, keep going; otherwise, keep this - * character and stop trimming - */ - if (!u_isnl(p, rem)) - { - nextc(&p, &rem); - break; - } - } - - /* - * Allocate a new value entry. Make room for the entry itself plus - * a copy of the value. We don't need to make a copy of the name, - * since we can just use the original copy from the story file - * buffer. We do need a copy of the value because we might need to - * transform it slightly, to remove newlines and leading spaces on - * continuation lines. - */ - val = (valinfo *)malloc(sizeof(valinfo) + (p - val_start)); - - /* link it into our list */ - val->nxt = val_head; - val_head = val; - - /* point the name directly to the name in the buffer */ - val->name = name_start; - val->name_len = name_len; - - /* point the value to the space allocated along with the valinfo */ - val->val = (char *)(val + 1); - - /* store the name, removing newlines and continuation-line spaces */ - for (outp = val->val, inp = val_start, inlen = p - val_start ; - inlen != 0 ; ) - { - const char *l; - - /* find the next newline */ - for (l = inp ; inlen != 0 && !u_isnl(inp, inlen) ; - nextc(&inp, &inlen)) ; - - /* copy this line to the output */ - memcpy(outp, l, inp - l); - outp += inp - l; - - /* if we're out of input, we're done */ - if (inlen == 0) - break; - - /* we're at a newline: replace it with a space in the output */ - *outp++ = ' '; - - /* skip the newline and subsequent whitespace in the input */ - for (skip_newline(&inp, &inlen) ; - inlen != 0 && u_ishspace(*inp) ; nextc(&inp, &inlen)) ; - } - - /* set the length of the parsed value */ - val->val_len = outp - val->val; - - /* skip to the next line and continue parsing */ - skip_to_next_line(&p, &rem); - } - - /* return the head of the linked list of value entries */ - return val_head; -} -static int my_memicmp(const void *aa, const void *bb, int l) -{ - int s=0,i; - char *a=(char *) aa; - char *b=(char *) bb; - for(i=0;inxt) - { - /* if this one matches the key we're looking for, return it */ - if (p->name_len == key_len && my_memicmp(p->name, key, key_len) == 0) - return p; - } - - /* no luck */ - return 0; -} - -/* ------------------------------------------------------------------------ */ -/* - * Delete a valinfo list obtained from parse_game_info() - */ -static void delete_valinfo_list(valinfo *head) -{ - /* keep going until we run out of entries */ - while (head != 0) - { - /* remember the next entry, before we delete this one */ - valinfo *nxt = head->nxt; - - /* delete this one */ - free(head); - - /* move on to the next one */ - head = nxt; - } -} - -/* ------------------------------------------------------------------------ */ -/* - * Find the cover art resource. We'll look for CoverArt.jpg and - * CoverArt.png, in that order. - */ -static int find_cover_art(const void *story_file, int32 story_len, - resinfo *resp, int32 *image_format, - int32 *width, int32 *height) -{ - resinfo res; - int32 x, y; - - /* if they didn't want the resource info, provide a placeholder */ - if (resp == 0) - resp = &res; - - /* look for CoverArt.jpg first */ - if (find_resource(story_file, story_len, "CoverArt.jpg", resp)) - { - /* get the width and height */ - if (!get_jpeg_dim(resp->ptr, resp->len, &x, &y)) - return FALSE; - - /* hand back the width and height if it was requested */ - if (width != 0) - *width = x; - if (height != 0) - *height = y; - - /* tell them it's a JPEG image */ - if (image_format != 0) - *image_format = JPEG_COVER_FORMAT; - - /* indicate success */ - return TRUE; - } - - /* look for CoverArt.png second */ - if (find_resource(story_file, story_len, "CoverArt.png", resp)) - { - /* get the width and height */ - if (!get_png_dim(resp->ptr, resp->len, &x, &y)) - return FALSE; - - /* hand back the width and height if it was requested */ - if (width != 0) - *width = x; - if (height != 0) - *height = y; - - /* tell them it's a PNG image */ - if (image_format != 0) - *image_format = PNG_COVER_FORMAT; - - /* indicate success */ - return TRUE; - } - - /* didn't find it */ - return FALSE; -} - -/* ------------------------------------------------------------------------ */ -/* - * Find a resource in a TADS 2 or 3 story file that's been loaded into - * memory. On success, fills in the offset and size of the resource and - * returns TRUE; if the resource isn't found, returns FALSE. - */ -static int find_resource(const void *story_file, int32 story_len, - const char *resname, resinfo *info) -{ - /* if there's no file, there's no resource */ - if (story_file == 0) - return FALSE; - - /* check for tads 2 */ - if (tads_match_sig(story_file, story_len, T2_SIGNATURE)) - { - info->tads_version = 2; - return t2_find_res(story_file, story_len, resname, info); - } - - /* check for tads 3 */ - if (tads_match_sig(story_file, story_len, T3_SIGNATURE)) - { - info->tads_version = 3; - return t3_find_res(story_file, story_len, resname, info); - } - - /* it's not one of ours */ - return FALSE; -} - -/* ------------------------------------------------------------------------ */ -/* - * Find a resource in a tads 2 game file - */ -static int t2_find_res(const void *story_file, int32 story_len, - const char *resname, resinfo *info) -{ - const char *basep = (const char *)story_file; - const char *endp = basep + story_len; - const char *p; - size_t resname_len; - - /* note the length of the name we're seeking */ - resname_len = strlen(resname); - - /* - * skip past the tads 2 file header (13 bytes for the signature, 7 - * bytes for the version header, 2 bytes for the flags, 26 bytes for - * the timestamp) - */ - p = basep + 13 + 7 + 2 + 26; - - /* - * scan the sections in the file; stop on $EOF, and skip everything - * else but HTMLRES, which is the section type that - */ - while (p < endp) - { - unsigned long endofs; - - /* - * We're pointing to a section block header, which looks like this: - * - *. type-length - *. type-name - *. next-section-address - */ - - /* read the ending offset */ - endofs = osrp4(p + 1 + osrp1(p)); - - /* check the type */ - if (p[0] == 7 && memcmp(p + 1, "HTMLRES", 7) == 0) - { - unsigned long found_ofs; - int found; - unsigned long entry_cnt; - - /* we haven't found the resource yet */ - found = FALSE; - - /* - * It's a multimedia resource block. Skip the section block - * header and look at the index table - the index table - * consists of a uint32 giving the number of entries, followed - * by a reserved uint32, followed by the entries. - */ - p += 12; - entry_cnt = osrp4(p); - - /* skip to the first index entry */ - p += 8; - - /* scan the index entries */ - for ( ; entry_cnt != 0 ; --entry_cnt) - { - unsigned long res_ofs; - unsigned long res_siz; - size_t name_len; - - /* - * We're at the next index entry, which looks like this: - * - *. resource-address (bytes from end of index) - *. resource-length (in bytes) - *. name-length - *. name - */ - res_ofs = osrp4(p); - res_siz = osrp4(p + 4); - name_len = osrp2(p + 8); - p += 10; - - /* check for a match to the name we're looking for */ - if (name_len == resname_len - && my_memicmp(resname, p, name_len) == 0) - { - /* - * it's the one we want - note its resource location - * and size, but keep scanning for now, since we need - * to find the end of the index before we'll know where - * the actual resources begin - */ - found = TRUE; - found_ofs = res_ofs; - info->len = res_siz; - } - - /* skip this one's name */ - p += name_len; - } - - /* - * if we found our resource, the current seek position is the - * base of the offset we found in the directory; so we can - * finally fix up the offset to give the actual file location - * and return the result - */ - if (found) - { - /* fix up the offset with the actual file location */ - info->ptr = p + found_ofs; - - /* tell the caller we found it */ - return TRUE; - } - } - else if (p[0] == 4 && memcmp(p + 1, "$EOF", 4) == 0) - { - /* - * that's the end of the file - we've finished without finding - * the resource, so return failure - */ - return FALSE; - } - - /* move to the next section */ - p = basep + endofs; - } - - /* - * reached EOF without an $EOF marker - file must be corrupted; return - * 'not found' - */ - return FALSE; -} - -/* ------------------------------------------------------------------------ */ -/* - * Find a resource in a T3 image file - */ -static int t3_find_res(const void *story_file, int32 story_len, - const char *resname, resinfo *info) -{ - const char *basep = (const char *)story_file; - const char *endp = basep + story_len; - const char *p; - size_t resname_len; - - /* note the length of the name we're seeking */ - resname_len = strlen(resname); - - /* - * skip the file header - 11 bytes for the signature, 2 bytes for the - * format version, 32 reserved bytes, and 24 bytes for the timestamp - */ - p = basep + 11 + 2 + 32 + 24; - - /* scan the data blocks */ - while (p < endp) - { - unsigned long siz; - - /* - * We're at the next block header, which looks like this: - * - *. type-name - *. block-size - *. flags - */ - - /* get the block size */ - siz = osrp4(p + 4); - - /* check the type */ - if (memcmp(p, "MRES", 4) == 0) - { - unsigned int entry_cnt; - unsigned int i; - const char *blockp; - - /* skip the header */ - p += 10; - - /* - * remember the location of the base of the block - the data - * seek location for each index entry is given as an offset - * from this location - */ - blockp = p; - - /* the first thing in the table is the number of entries */ - entry_cnt = osrp2(p); - p += 2; - - /* read the entries */ - for (i = 0 ; i < entry_cnt ; ++i) - { - unsigned long entry_ofs; - unsigned long entry_siz; - size_t entry_name_len; - char namebuf[256]; - char *xp; - size_t xi; - - /* - * Parse this index entry: - * - *. address (as offset from the block base) - *. size (in bytes) - *. name-length - *. name (all bytes XORed with 0xFF) - */ - entry_ofs = osrp4(p); - entry_siz = osrp4(p + 4); - entry_name_len = (unsigned char)p[8]; - - /* unmask the name */ - memcpy(namebuf, p + 9, resname_len); - for (xi = resname_len, xp = namebuf ; xi != 0 ; --xi) - *xp++ ^= 0xFF; - - /* if this is the one we're looking for, return it */ - if (entry_name_len == resname_len - && my_memicmp(resname, namebuf, resname_len) == 0) - { - /* - * fill in the return information - note that the entry - * offset given in the header is an offset from data - * block's starting location, so fix this up to an - * absolute seek location for the return value - */ - info->ptr = blockp + entry_ofs; - info->len = entry_siz; - - /* return success */ - return TRUE; - } - - /* skip this entry (header + name length) */ - p += 9 + entry_name_len; - } - - /* - * if we got this far, we didn't find the name; so skip past - * the MRES section by adding the section length to the base - * pointer, and resume the main file scan - */ - p = blockp + siz; - } - else if (memcmp(p, "EOF ", 4) == 0) - { - /* - * end of file - we've finished without finding the resource, - * so return failure - */ - return FALSE; - } - else - { - /* - * we don't care about anything else - just skip this block and - * keep going; to skip the block, simply seek ahead past the - * block header and then past the block's contents, using the - * size given the in block header - */ - p += siz + 10; - } - } - - /* - * reached EOF without an EOF marker - file must be corrupted; return - * 'not found' - */ - return FALSE; -} - -/* ------------------------------------------------------------------------ */ -/* - * JPEG and PNG information extraction (based on the versions in - * babel_story_functions.c) - */ -static int get_jpeg_dim(const void *img, int32 extent, - int32 *xout, int32 *yout) -{ - const unsigned char *dp=(const unsigned char *) img; - const unsigned char *ep=dp+extent; - unsigned int t1, t2, w, h; - - t1 = *dp++; - t2 = *dp++; - if (t1 != 0xff || t2 != 0xD8) - return FALSE; - - while(1) - { - if (dp>ep) return FALSE; - for(t1=*(dp++);t1!=0xff;t1=*(dp++)) if (dp>ep) return FALSE; - do { t1=*(dp++); if (dp>ep) return FALSE;} while (t1 == 0xff); - - if ((t1 & 0xF0) == 0xC0 && !(t1==0xC4 || t1==0xC8 || t1==0xCC)) - { - dp+=3; - if (dp>ep) return FALSE; - h=*(dp++) << 8; - if (dp>ep) return FALSE; - h|=*(dp++); - if (dp>ep) return FALSE; - w=*(dp++) << 8; - if (dp>ep) return FALSE; - w|=*(dp); - - *xout = w; - *yout = h; - return TRUE; - } - else if (t1==0xD8 || t1==0xD9) - break; - else - { - int l; - - if (dp>ep) return FALSE; - l=*(dp++) << 8; - if (dp>ep) return FALSE; - l|= *(dp++); - l-=2; - dp+=l; - if (dp>ep) return FALSE; - } - } - return FALSE; -} - -static int32 png_read_int(const unsigned char *mem) -{ - int32 i4 = mem[0], - i3 = mem[1], - i2 = mem[2], - i1 = mem[3]; - return i1 | (i2<<8) | (i3<<16) | (i4<<24); -} - - -static int get_png_dim(const void *img, int32 extent, - int32 *xout, int32 *yout) -{ - const unsigned char *dp=(const unsigned char *)img; - - if (extent<33 || - !(dp[0]==137 && dp[1]==80 && dp[2]==78 && dp[3]==71 && - dp[4]==13 && dp[5] == 10 && dp[6] == 26 && dp[7]==10)|| - !(dp[12]=='I' && dp[13]=='H' && dp[14]=='D' && dp[15]=='R')) - return FALSE; - - *xout = png_read_int(dp+16); - *yout = png_read_int(dp+20); - return TRUE; -} - -/* ------------------------------------------------------------------------ */ -/* - * Testing main() - this implements a set of unit tests on the tads - * version. - */ - -#ifdef TADS_TEST - -#include "babel_handler.h" - -void main(int argc, char **argv) -{ - FILE *fp; - int32 siz; - void *buf; - valinfo *head; - int32 rv; - int tadsver; - char outbuf[TREATY_MINIMUM_EXTENT]; - - /* check arguments */ - if (argc != 2) - { - printf("usage: tads \n"); - exit(1); - } - - /* initialize the babel subsystems */ - babel_init(argv[1]); - - /* open the story file */ - if ((fp = fopen(argv[1], "rb")) == 0) - { - printf("error opening input file\n"); - exit(2); - } - - /* check the file size */ - fseek(fp, 0, SEEK_END); - siz = ftell(fp); - fseek(fp, 0, SEEK_SET); - - /* allocate space for it */ - if ((buf = malloc(siz)) == 0) - { - printf("error allocating space to load file\n"); - exit(2); - } - - /* load it */ - if ((int32)fread(buf, 1, siz, fp) != siz) - { - printf("error reading file\n"); - exit(2); - } - - /* done with the file */ - fclose(fp); - - - - /* ===== test 1 - basic parse_game_info() test ===== */ - - /* parse the gameinfo record and print the results */ - if ((head = parse_game_info(buf, siz, &tadsver)) != 0) - { - valinfo *val; - - printf("found GameInfo - tads major version = %d\n", tadsver); - for (val = head ; val != 0 ; val = val->nxt) - { - printf("%.*s=[%.*s]\n", - (int)val->name_len, val->name, - (int)val->val_len, val->val); - } - printf("\n"); - } - else - printf("no GameInfo found\n\n"); - - - - /* ===== test 2 - test the get_story_file_IFID generator ===== */ - rv = tads_get_story_file_IFID(buf, siz, outbuf, TREATY_MINIMUM_EXTENT); - if (rv == 1) - printf("IFID = [%s]\n\n", outbuf); - else - printf("IFID return code = %ld\n", rv); - - - - /* ===== test 3 - test the ifiction synthesizer ===== */ - if ((rv = tads_get_story_file_metadata_extent(buf, siz)) > 0) - { - char *ifbuf; - - /* try allocating the space */ - if ((ifbuf = malloc((size_t)rv)) != 0) - { - /* synthesize the story file */ - rv = tads_get_story_file_metadata(buf, siz, ifbuf, rv); - if (rv > 0) - printf("ifiction metadata:\n=====\n%.*s\n=====\n\n", - (int)rv, ifbuf); - else - printf("tads_get_story_file_metadata result = %ld\n", rv); - } - else - printf("unable to allocate %ld bytes for metadata record\n", rv); - } - else - printf("tads_get_story_file_metadata_extent result code = %ld\n", rv); - - - /* free the loaded story file buffer */ - free(buf); -} - - -#endif TADS_TEST - diff --git a/babel/tads.h b/babel/tads.h deleted file mode 100644 index 296b91d..0000000 --- a/babel/tads.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * tads.h - Treaty of Babel common declarations for tads2 and tads3 modules - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file may - * render it noncompliant with the Treaty of Babel - * - * Modified - *. 04/18/2006 MJRoberts - creation - */ - -#ifndef TADS_H -#define TADS_H - -/* match a TADS file signature */ -int tads_match_sig(const void *buf, int32 len, const char *sig); - -/* get the IFID for a tads story file */ -int32 tads_get_story_file_IFID(void *story_file, int32 extent, - char *output, int32 output_extent); - -/* get the synthesized iFiction record from a tads story file */ -int32 tads_get_story_file_metadata(void *story_file, int32 extent, - char *buf, int32 bufsize); - -/* get the size of the synthesized iFiction record for a tads story file */ -int32 tads_get_story_file_metadata_extent(void *story_file, int32 extent); - -/* get the cover art from a tads story file */ -int32 tads_get_story_file_cover(void *story_file, int32 extent, - void *buf, int32 bufsize); - -/* get the size of the cover art from a tads story file */ -int32 tads_get_story_file_cover_extent(void *story_file, int32 extent); - -/* get the image format (jpeg, png) of the covert art in a tads story file */ -int32 tads_get_story_file_cover_format(void *story_file, int32 extent); - -#endif /* TADS_H */ diff --git a/babel/tads2.c b/babel/tads2.c deleted file mode 100644 index 87c1fcc..0000000 --- a/babel/tads2.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * tads2.c - Treaty of Babel module for Tads 2 files - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file may - * render it noncompliant with the Treaty of Babel - * - * Modified - *. 04/15/2006 LRRaszewski - Separated tads2.c and tads3.c - *. 04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions - *. 04/08/2006 MJRoberts - initial implementation - */ - -#define FORMAT tads2 -#define HOME_PAGE "http://www.tads.org" -#define FORMAT_EXT ".gam" - - -#include "treaty_builder.h" -#include "tads.h" - -#define T2_SIGNATURE "TADS2 bin\012\015\032" - -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -/* - * get a story file's IFID - */ -static int32 get_story_file_IFID(void *story_file, int32 extent, - char *output, int32 output_extent) -{ - /* use the common tads IFID extractor/generator */ - return tads_get_story_file_IFID(story_file, extent, - output, output_extent); -} - -/* - * determine if a given story file is one of ours - */ -static int32 claim_story_file(void *story_file, int32 extent) -{ - /* check our signature */ - if (tads_match_sig(story_file, extent, T2_SIGNATURE)) - return VALID_STORY_FILE_RV; - - /* not one of ours */ - return INVALID_STORY_FILE_RV; -} - -/* - * Get the size of the iFiction metadata for the game - */ -static int32 get_story_file_metadata_extent(void *story_file, int32 extent) -{ - /* use the common tads iFiction synthesizer */ - return tads_get_story_file_metadata_extent(story_file, extent); -} - -/* - * Get the iFiction metadata for the game - */ -static int32 get_story_file_metadata(void *story_file, int32 extent, - char *buf, int32 bufsize) -{ - /* use the common tads iFiction synthesizer */ - return tads_get_story_file_metadata(story_file, extent, buf, bufsize); -} - -static int32 get_story_file_cover_extent(void *story_file, int32 story_len) -{ - /* use the common tads cover file extractor */ - return tads_get_story_file_cover_extent(story_file, story_len); -} - -/* - * Get the format of the cover art - */ -static int32 get_story_file_cover_format(void *story_file, int32 story_len) -{ - /* use the common tads cover file extractor */ - return tads_get_story_file_cover_format(story_file, story_len); -} - -/* - * Get the cover art data - */ -static int32 get_story_file_cover(void *story_file, int32 story_len, - void *outbuf, int32 output_extent) -{ - /* use the common tads cover file extractor */ - return tads_get_story_file_cover(story_file, story_len, - outbuf, output_extent); -} - diff --git a/babel/tads3.c b/babel/tads3.c deleted file mode 100644 index 23d8fb5..0000000 --- a/babel/tads3.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * tads3.c - Treaty of Babel module for Tads 3 files - * - * This file depends on treaty_builder.h - * - * This file is public domain, but note that any changes to this file may - * render it noncompliant with the Treaty of Babel - * - * Modified - *. 04/15/2006 LRRaszewski - Separated tads2.c and tads3.c - *. 04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions - *. 04/08/2006 MJRoberts - initial implementation - */ - -#define FORMAT tads3 -#define HOME_PAGE "http://www.tads.org" -#define FORMAT_EXT ".t3" - - -#include "treaty_builder.h" -#include "tads.h" - -#define T3_SIGNATURE "T3-image\015\012\032" - -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -/* - * get a story file's IFID - */ -static int32 get_story_file_IFID(void *story_file, int32 extent, - char *output, int32 output_extent) -{ - /* use the common tads IFID extractor/generator */ - return tads_get_story_file_IFID(story_file, extent, - output, output_extent); -} - -/* - * determine if a given story file is one of ours - */ -static int32 claim_story_file(void *story_file, int32 extent) -{ - /* check our signature */ - if (tads_match_sig(story_file, extent, T3_SIGNATURE)) - return VALID_STORY_FILE_RV; - - /* not one of ours */ - return INVALID_STORY_FILE_RV; -} - -/* - * Get the size of the iFiction metadata for the game - */ -static int32 get_story_file_metadata_extent(void *story_file, int32 extent) -{ - /* use the common tads iFiction synthesizer */ - return tads_get_story_file_metadata_extent(story_file, extent); -} - -/* - * Get the iFiction metadata for the game - */ -static int32 get_story_file_metadata(void *story_file, int32 extent, - char *buf, int32 bufsize) -{ - /* use the common tads iFiction synthesizer */ - return tads_get_story_file_metadata(story_file, extent, buf, bufsize); -} - -static int32 get_story_file_cover_extent(void *story_file, int32 story_len) -{ - /* use the common tads cover file extractor */ - return tads_get_story_file_cover_extent(story_file, story_len); -} - -/* - * Get the format of the cover art - */ -static int32 get_story_file_cover_format(void *story_file, int32 story_len) -{ - /* use the common tads cover file extractor */ - return tads_get_story_file_cover_format(story_file, story_len); -} - -/* - * Get the cover art data - */ -static int32 get_story_file_cover(void *story_file, int32 story_len, - void *outbuf, int32 output_extent) -{ - /* use the common tads cover file extractor */ - return tads_get_story_file_cover(story_file, story_len, - outbuf, output_extent); -} - diff --git a/babel/treaty.h b/babel/treaty.h deleted file mode 100644 index 7155553..0000000 --- a/babel/treaty.h +++ /dev/null @@ -1,91 +0,0 @@ -/* treaty.h Header file for Treaty of Babel compliant format modules - * By L. Ross Raszewski - * Version 3b - * - * This file is public domain, but please note that derived versions - * may not not be compliant with the Treaty of Babel. - * - * It would be wise for derived works to change the value of - * TREATY_COMPLIANCE to reflect deviations - */ - -#ifndef TREATY_H - -#define TREATY_H - -#define TREATY_COMPLIANCE "Treaty of Babel revision 7" -#define TREATY_VERSION "r7" - -/* return codes */ -#define NO_REPLY_RV 0 -#define INVALID_STORY_FILE_RV -1 -#define UNAVAILABLE_RV -2 -#define INVALID_USAGE_RV -3 -#define INCOMPLETE_REPLY_RV -4 -#define VALID_STORY_FILE_RV 1 - -#define PNG_COVER_FORMAT 1 -#define JPEG_COVER_FORMAT 2 - -/* Treaty bitmasks. These are not required by the treaty, but are here - as a convenience. -*/ -#define TREATY_SELECTOR_INPUT 0x100 -#define TREATY_SELECTOR_OUTPUT 0x200 -#define TREATY_SELECTOR_NUMBER 0xFF - -#define TREATY_CONTAINER_SELECTOR 0x400 - -/* Treaty selectors */ -#define GET_HOME_PAGE_SEL 0x201 -#define GET_FORMAT_NAME_SEL 0x202 -#define GET_FILE_EXTENSIONS_SEL 0x203 -#define CLAIM_STORY_FILE_SEL 0x104 -#define GET_STORY_FILE_METADATA_EXTENT_SEL 0x105 -#define GET_STORY_FILE_COVER_EXTENT_SEL 0x106 -#define GET_STORY_FILE_COVER_FORMAT_SEL 0x107 -#define GET_STORY_FILE_IFID_SEL 0x308 -#define GET_STORY_FILE_METADATA_SEL 0x309 -#define GET_STORY_FILE_COVER_SEL 0x30A -#define GET_STORY_FILE_EXTENSION_SEL 0x30B - -/* Container selectors */ -#define CONTAINER_GET_STORY_FORMAT_SEL 0x710 -#define CONTAINER_GET_STORY_EXTENT_SEL 0x511 -#define CONTAINER_GET_STORY_FILE_SEL 0x711 - - - - -/* Other magic size limits */ -#define TREATY_MINIMUM_EXTENT 512 - - -#include - -/* 32-bit integer types */ -#ifndef VAX -#if SCHAR_MAX >= 0x7FFFFFFFL && SCHAR_MIN <= -0x7FFFFFFFL - typedef signed char int32; -#elif SHRT_MAX >= 0x7FFFFFFFL && SHRT_MIN <= -0x7FFFFFFFL - typedef signed short int int32; -#elif INT_MAX >= 0x7FFFFFFFL && INT_MIN <= -0x7FFFFFFFL - typedef signed int int32; -#elif LONG_MAX >= 0x7FFFFFFFL && LONG_MIN <= -0x7FFFFFFFL - typedef signed long int int32; -#else -#error No type large enough to support 32-bit integers. -#endif -#else - /* VAX C does not provide these limit constants, contrary to ANSI */ - typedef int int32; -#endif - - - -/* Pointer to treaty function. Treaty functions must follow this prototype */ - -typedef int32 (*TREATY)(int32 selector, void *, int32, void *, int32); - -#endif - diff --git a/babel/treaty_builder.h b/babel/treaty_builder.h deleted file mode 100644 index d52ae3e..0000000 --- a/babel/treaty_builder.h +++ /dev/null @@ -1,180 +0,0 @@ -/* treaty_builder.h common macros to build a treaty module - * - * 2006 By L. Ross Raszewski - * - * This file is public domain, but be aware that any changes to it may - * cause it to cease to be compliant with the Treaty of Babel. - * - * This file depends on treaty.h - * - * The purpose of this file is to simplify the building of a treaty - * module. It automatically generates a generic treaty function. - * - * Usage: - * - * #define the following values: - * FORMAT The treaty name of the format - * HOME_PAGE A string containing the URL of the format home page - * FORMAT_EXT A string containing a comma separated list of common - * extensions for game files in this format - * NO_METADATA If the format does not support metadata - * NO_COVER If the format does not support cover art - * CUSTOM_EXTENSION If game files should not always use the first listed extension - * - * (Note: Formats which support metadata and cover art via a container should - * define NO_METADATA and NO_COVER as container support is handled separately) - * - * #include "treaty_builder.h" - * Define the following functions: - * static int32 get_story_file_IFID(void *, int32, char *, int32); - * static int32 claim_story_file(void *, int32); - * Define the following functions if NO_METADATA is not defined: - * static int32 get_story_file_metadata_extent(void *, int32); - * static int32 get_story_file_metadata(void *, int32, char *, int32); - * Define the following functions if NO_COVER is not defined - * static int32 get_story_file_cover_extent(void *, int32); - * static int32 get_story_file_cover_format(void *, int32); - * static int32 get_story_file_cover(void *, int32, void *, int32); - * Define the following if CUSTOM_EXTENSION is defined - * static int32 get_story_file_extension(void *, int32, char *, int32); - * - * The two-parameter functions take the story file and story file extent - * as parameters. The four-parameter ones also take the output - * buffer and its extent. They perform the corresponding task to the - * similarly-named selector. - * - * This file also defines the macro ASSERT_OUTPUT_SIZE(x) which - * returns INVALID_USAGE_RV if output_extent is less than x. - * - * #define CONTAINER_FORMAT before inclusion to generate a container - * module. A container module should define three additional functions: - * static int32 get_story_format(void *, int32, char *, int32); - * static int32 get_story_extent(void *, int32); - * static int32 get_story_file(void *, int32, void *, int32); - * - */ - -#ifndef TREATY_BUILDER -#define TREATY_BUILDER - -#include "treaty.h" -#include - -#define ASSERT_OUTPUT_SIZE(x) do { if (output_extent < (x)) return INVALID_USAGE_RV; } while (0) - -#ifndef NO_METADATA -static int32 get_story_file_metadata_extent(void *, int32); -static int32 get_story_file_metadata(void *, int32, char *, int32); -#endif -#ifndef NO_COVER -static int32 get_story_file_cover_extent(void *, int32); -static int32 get_story_file_cover_format(void *, int32); -static int32 get_story_file_cover(void *, int32, void *, int32); -#endif -static int32 get_story_file_IFID(void *, int32, char *, int32); -static int32 claim_story_file(void *, int32); -#ifdef CONTAINER_FORMAT -static int32 get_story_file(void *, int32, void *, int32); -static int32 get_story_format(void *, int32, char *, int32); -static int32 get_story_extent(void *, int32); -#endif -#ifdef CUSTOM_EXTENSION -static int32 get_story_file_extension(void *, int32, char *, int32); -#else -#include -static int32 get_story_file_extension(void *sf, int32 extent, char *out, int32 output_extent) -{ - int i; - - if (!sf || !extent) return INVALID_STORY_FILE_RV; - - for(i=0;FORMAT_EXT[i] && FORMAT_EXT[i]!=',';i++); - ASSERT_OUTPUT_SIZE(i+1); - memcpy(out,FORMAT_EXT,i); - out[i]=0; - return strlen(out); -} - -#endif - -#define TREATY_FUNCTION(X) DEEP_TREATY_FUNCTION(X) -#define DEEP_TREATY_FUNCTION(X) X ## _treaty -#define dSTRFRY(X) #X -#define STRFRY(X) dSTRFRY(X) - -int32 TREATY_FUNCTION(FORMAT)(int32 selector, - void *story_file, int32 extent, - void *output, int32 output_extent) -{ - int32 ll, csf; - if ((TREATY_SELECTOR_INPUT & selector) && - (csf=claim_story_file(story_file, extent)) -#include - -static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent) -{ - int32 i,j; - char ser[7]; - char buffer[32]; - - - if (extent<0x1D) return INVALID_STORY_FILE_RV; - memcpy(ser, (char *) story_file+0x12, 6); - ser[6]=0; - /* Detect vintage story files */ - if (!(ser[0]=='8' || ser[0]=='9' || - (ser[0]=='0' && ser[1]>='0' && ser[1]<='5'))) - { - for(i=0;i 8 - ) return INVALID_STORY_FILE_RV; - for(i=4;i<=14;i+=2) - { - j=read_zint(sf+i); - if (j>extent || j < 0x40) return INVALID_STORY_FILE_RV; - } - - return VALID_STORY_FILE_RV; -} -static int32 get_story_file_extension(void *sf, int32 extent, char *out, int32 output_extent) -{ - int v; - if (!extent) return INVALID_STORY_FILE_RV; - v= ((char *) sf)[0]; - if (v>9) ASSERT_OUTPUT_SIZE(5); - else ASSERT_OUTPUT_SIZE(4); - sprintf(out,".z%d",v); - return 3+(v>9); - -} diff --git a/bundle/Info-chimara.plist b/bundle/Info-chimara.plist new file mode 100644 index 0000000..7f3a3f6 --- /dev/null +++ b/bundle/Info-chimara.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + chimara + CFBundleGetInfoString + 0.9 (C) 2009-2012 Philip Chimento and Marijn van Vliet http://www.chimara-if.org + CFBundleIconFile + chimara.icns + CFBundleIdentifier + org.chimara.chimara + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.9 + CFBundleSignature + ???? + CFBundleVersion + 0.9 + NSHumanReadableCopyright + Copyright 2009 - 2012 Philip Chimento and Marijn van Vliet, BSD License. + LSMinimumSystemVersion + 10.4 + + diff --git a/bundle/chimara.bundle b/bundle/chimara.bundle new file mode 100644 index 0000000..39329de --- /dev/null +++ b/bundle/chimara.bundle @@ -0,0 +1,150 @@ + + + + + + ${env:JHBUILD_PREFIX} + + + ${env:HOME}/projects/chimara + + + + + + + + + + ${project}/launcher.sh + + + + + gtk+-3.0 + + + + ${project}/Info-chimara.plist + + ${prefix}/bin/chimara + + + + ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/immodules/*.so + + + + ${prefix}/lib/chimara/*.so + + + + + + ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/printbackends/*.so + + + + + ${prefix}/lib/gdk-pixbuf-2.0/${pkg:gdk-pixbuf-2.0:gdk_pixbuf_binary_version}/loaders/*.so + + + + + ${prefix}/share/locale + + + + + + + + + ${prefix}/share/themes + + + + ${prefix}/share/glib-2.0/schemas + + + + + + + + ${project}/gtkrc + + + + + + + ${prefix}/lib/pango/${pkg:pango:pango_module_version}/modules/*.so + + + diff --git a/bundle/chimara_icon_512.png b/bundle/chimara_icon_512.png new file mode 100644 index 0000000..abdb8dd Binary files /dev/null and b/bundle/chimara_icon_512.png differ diff --git a/bundle/gtkrc b/bundle/gtkrc new file mode 100644 index 0000000..e69de29 diff --git a/bundle/launcher.sh b/bundle/launcher.sh new file mode 100755 index 0000000..7408de4 --- /dev/null +++ b/bundle/launcher.sh @@ -0,0 +1,169 @@ +#!/bin/sh + +if test "x$GTK_DEBUG_LAUNCHER" != x; then + set -x +fi + +if test "x$GTK_DEBUG_GDB" != x; then + EXEC="gdb --args" +else + EXEC=exec +fi + +name=`basename "$0"` +tmp="$0" +tmp=`dirname "$tmp"` +tmp=`dirname "$tmp"` +bundle=`dirname "$tmp"` +bundle_contents="$bundle"/Contents +bundle_res="$bundle_contents"/Resources +bundle_lib="$bundle_res"/lib +bundle_bin="$bundle_res"/bin +bundle_data="$bundle_res"/share +bundle_etc="$bundle_res"/etc + +export DYLD_LIBRARY_PATH="$bundle_lib" +export XDG_CONFIG_DIRS="$bundle_etc"/xdg +export XDG_DATA_DIRS="$bundle_data" +export GTK_DATA_PREFIX="$bundle_res" +export GTK_EXE_PREFIX="$bundle_res" +export GTK_PATH="$bundle_res" + +export GTK2_RC_FILES="$bundle_etc/gtk-2.0/gtkrc" +export GTK3_RC_FILES="$bundle_etc/gtk-3.0/gtkrc" +export GTK_IM_MODULE_FILE="$bundle_etc/gtk-3.0/gtk.immodules" +export GDK_PIXBUF_MODULE_FILE="$bundle_etc/gtk-3.0/gdk-pixbuf.loaders" +export PANGO_RC_FILE="$bundle_etc/pango/pangorc" + +APP=name +I18NDIR="$bundle_data/locale" +# Set the locale-related variables appropriately: +unset LANG LC_MESSAGES LC_MONETARY LC_COLLATE + +# Has a language ordering been set? +# If so, set LC_MESSAGES and LANG accordingly; otherwise skip it. +# First step uses sed to clean off the quotes and commas, to change - to _, and change the names for the chinese scripts from "Hans" to CN and "Hant" to TW. +APPLELANGUAGES=`defaults read .GlobalPreferences AppleLanguages | sed -En -e 's/\-/_/' -e 's/Hant/TW/' -e 's/Hans/CN/' -e 's/[[:space:]]*\"?([[:alnum:]_]+)\"?,?/\1/p' ` +if test "$APPLELANGUAGES"; then + # A language ordering exists. + # Test, item per item, to see whether there is an corresponding locale. + for L in $APPLELANGUAGES; do + #test for exact matches: + if test -f "$I18NDIR/${L}/LC_MESSAGES/$APP.mo"; then + export LANG=$L + break + fi + #This is a special case, because often the original strings are in US + #English and there is no translation file. + if test "x$L" == "xen_US"; then + export LANG=$L + break + fi + #OK, now test for just the first two letters: + if test -f "$I18NDIR/${L:0:2}/LC_MESSAGES/$APP.mo"; then + export LANG=${L:0:2} + break + fi + #Same thing, but checking for any english variant. + if test "x${L:0:2}" == "xen"; then + export LANG=$L + break + fi; + done +fi +unset APPLELANGUAGES L + +# If we didn't get a language from the language list, try the Collation preference, in case it's the only setting that exists. +APPLECOLLATION=`defaults read .GlobalPreferences AppleCollationOrder` +if test -z ${LANG} -a -n $APPLECOLLATION; then + if test -f "$I18NDIR/${APPLECOLLATION:0:2}/LC_MESSAGES/$APP.mo"; then + export LANG=${APPLECOLLATION:0:2} + fi +fi +if test ! -z $APPLECOLLATION; then + export LC_COLLATE=$APPLECOLLATION +fi +unset APPLECOLLATION + +# Continue by attempting to find the Locale preference. +APPLELOCALE=`defaults read .GlobalPreferences AppleLocale` + +if test -f "$I18NDIR/${APPLELOCALE:0:5}/LC_MESSAGES/$APP.mo"; then + if test -z $LANG; then + export LANG="${APPLELOCALE:0:5}" + fi + +elif test -z $LANG -a -f "$I18NDIR/${APPLELOCALE:0:2}/LC_MESSAGES/$APP.mo"; then + export LANG="${APPLELOCALE:0:2}" +fi + +#Next we need to set LC_MESSAGES. If at all possilbe, we want a full +#5-character locale to avoid the "Locale not supported by C library" +#warning from Gtk -- even though Gtk will translate with a +#two-character code. +if test -n $LANG; then +#If the language code matches the applelocale, then that's the message +#locale; otherwise, if it's longer than two characters, then it's +#probably a good message locale and we'll go with it. + if test $LANG == ${APPLELOCALE:0:5} -o $LANG != ${LANG:0:2}; then + export LC_MESSAGES=$LANG +#Next try if the Applelocale is longer than 2 chars and the language +#bit matches $LANG + elif test $LANG == ${APPLELOCALE:0:2} -a $APPLELOCALE > ${APPLELOCALE:0:2}; then + export LC_MESSAGES=${APPLELOCALE:0:5} +#Fail. Get a list of the locales in $PREFIX/share/locale that match +#our two letter language code and pick the first one, special casing +#english to set en_US + elif test $LANG == "en"; then + export LC_MESSAGES="en_US" + else + LOC=`find $PREFIX/share/locale -name $LANG???` + for L in $LOC; do + export LC_MESSAGES=$L + done + fi +else +#All efforts have failed, so default to US english + export LANG="en_US" + export LC_MESSAGES="en_US" +fi +CURRENCY=`echo $APPLELOCALE | sed -En 's/.*currency=([[:alpha:]]+).*/\1/p'` +if test "x$CURRENCY" != "x"; then +#The user has set a special currency. Gtk doesn't install LC_MONETARY files, but Apple does in /usr/share/locale, so we're going to look there for a locale to set LC_CURRENCY to. + if test -f /usr/local/share/$LC_MESSAGES/LC_MONETARY; then + if test -a `cat /usr/local/share/$LC_MESSAGES/LC_MONETARY` == $CURRENCY; then + export LC_MONETARY=$LC_MESSAGES + fi + fi + if test -z "$LC_MONETARY"; then + FILES=`find /usr/share/locale -name LC_MONETARY -exec grep -H $CURRENCY {} \;` + if test -n "$FILES"; then + export LC_MONETARY=`echo $FILES | sed -En 's%/usr/share/locale/([[:alpha:]_]+)/LC_MONETARY.*%\1%p'` + fi + fi +fi +#No currency value means that the AppleLocale governs: +if test -z "$LC_MONETARY"; then + LC_MONETARY=${APPLELOCALE:0:5} +fi +#For Gtk, which only looks at LC_ALL: +export LC_ALL=$LC_MESSAGES + +unset APPLELOCALE FILES LOC + +if test -f "$bundle_lib/charset.alias"; then + export CHARSETALIASDIR="$bundle_lib" +fi + +# Extra arguments can be added in environment.sh. +EXTRA_ARGS= +if test -f "$bundle_res/environment.sh"; then + source "$bundle_res/environment.sh" +fi + +# Strip out the argument added by the OS. +if /bin/expr "x$1" : '^x-psn_' > /dev/null; then + shift 1 +fi + +$EXEC "$bundle_contents/MacOS/$name-bin" "$@" $EXTRA_ARGS diff --git a/configure.ac b/configure.ac index bb97b70..54b947f 100644 --- a/configure.ac +++ b/configure.ac @@ -26,10 +26,8 @@ LT_VERSION_INFO="$CHIMARA_CURRENT:$CHIMARA_REVISION:$CHIMARA_AGE" AC_SUBST(LT_VERSION_INFO) ### REQUIREMENTS ############################################################## -# Recommended GTK version: at least 2.12 -# Recommended Glib version: at least 2.16 -GTK_REQUIRED_VERSION=2.6 -GLIB_REQUIRED_VERSION=2.6 +GTK_REQUIRED_VERSION=3.2 +GLIB_REQUIRED_VERSION=2.16 GTK_DOC_REQUIRED_VERSION=1.12 AC_SUBST(GTK_REQUIRED_VERSION) AC_SUBST(GLIB_REQUIRED_VERSION) @@ -77,14 +75,6 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [GETTEXT package name]) GOBJECT_INTROSPECTION_CHECK([0.6.7]) -### ILIAD ##################################################################### -AC_ARG_ENABLE([iliad], - [AS_HELP_STRING([--enable-iliad=@<:@yes/no@:>@], - [Compiles Chimara for the iLiad @<:@default=no@:>@])], - [], - [enable_iliad=no]) -AM_CONDITIONAL(TARGET_ILIAD, $TEST "x$enable_iliad" = xyes) - ### RPM CONFIGURATION ########################################################## # --enable-rpm requires rpm and rpmbuild AC_PATH_PROG([RPMBUILD], [rpmbuild], [notfound]) @@ -118,7 +108,7 @@ AM_CONDITIONAL(BUILDING_VAPI, $TEST "x$VAPIGEN" != xnotfound) # Libraries needed to build Chimara library PKG_CHECK_MODULES([CHIMARA], [ glib-2.0 >= $GLIB_REQUIRED_VERSION - gtk+-2.0 >= $GTK_REQUIRED_VERSION + gtk+-3.0 >= $GTK_REQUIRED_VERSION gthread-2.0 gmodule-2.0 pango @@ -129,12 +119,12 @@ AC_SUBST(CHIMARA_LIBS) # Libraries needed to build Chimara player PKG_CHECK_MODULES([PLAYER], [ glib-2.0 >= $GLIB_REQUIRED_VERSION - gtk+-2.0 >= $GTK_REQUIRED_VERSION + gtk+-3.0 >= $GTK_REQUIRED_VERSION gmodule-2.0 ]) # Libraries needed to build test programs PKG_CHECK_MODULES([TEST], [ - gtk+-2.0 >= $GTK_REQUIRED_VERSION + gtk+-3.0 >= $GTK_REQUIRED_VERSION gmodule-2.0 >= $GLIB_REQUIRED_VERSION ]) @@ -179,6 +169,7 @@ interpreters/frotz/Makefile interpreters/nitfol/Makefile interpreters/glulxe/Makefile interpreters/git/Makefile +interpreters/bocfel/Makefile tests/Makefile player/Makefile player/config.py @@ -187,7 +178,6 @@ docs/reference/Makefile docs/reference/version.xml docs/reference/build-selector-table.pl po/Makefile.in -babel/Makefile ]) # Do it diff --git a/iliad/chimara.png b/iliad/chimara.png deleted file mode 100644 index 05b3cd9..0000000 Binary files a/iliad/chimara.png and /dev/null differ diff --git a/iliad/create_iliad_package.sh b/iliad/create_iliad_package.sh deleted file mode 100755 index 18a5f7c..0000000 --- a/iliad/create_iliad_package.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -VERSION=0.9 - -mkdir -p Chimara/chimara -mkdir -p Chimara/interpreters -mkdir -p Chimara/games - -# Iliad specific files -cp manifest.xml Chimara/manifest.xml -cp iliad_refresh.conf Chimara/iliad_refresh.conf -cp run.sh Chimara/run.sh -cp style.css Chimara/chimara/style.css -cp chimara.png Chimara/chimara.png - -# Chimara lib and player -cp ../libchimara/.libs/libchimara.so.0 Chimara/chimara/ -cp ../player/.libs/chimara_iliad Chimara/chimara/chimara - -# Interpreters -cp ../interpreters/frotz/.libs/frotz.so Chimara/interpreters/ -cp ../interpreters/git/.libs/git.so Chimara/interpreters/ -cp ../interpreters/glulxe/.libs/glulxe.so Chimara/interpreters/ -cp ../interpreters/nitfol/.libs/nitfol.so Chimara/interpreters/ - -# Games -cp ../tests/anchor.z8 Chimara/games/ -cp ../tests/CoSv3.blb Chimara/games/ - -# Create zip file -tar czvf chimara-${VERSION}.tar.gz Chimara - -echo "Iliad package created: chimara-${VERSION}.tar.gz" diff --git a/iliad/iliad_refresh.conf b/iliad/iliad_refresh.conf deleted file mode 100755 index 79a8829..0000000 --- a/iliad/iliad_refresh.conf +++ /dev/null @@ -1 +0,0 @@ -Typing 100 diff --git a/iliad/manifest.xml b/iliad/manifest.xml deleted file mode 100755 index 64da7e7..0000000 --- a/iliad/manifest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Chimara - Interactive Fiction Player - 2009-11-17T15:00:00 - - - - - - run.sh - chimara.png - 000 - 2457600 - - - diff --git a/iliad/run.sh b/iliad/run.sh deleted file mode 100755 index fe44afa..0000000 --- a/iliad/run.sh +++ /dev/null @@ -1,8 +0,0 @@ -export DISPLAY=:0 -export LD_LIBRARY_PATH=. -export scriptdir=`/usr/bin/dirname $0` -cd $scriptdir -cd chimara -export HOME=`pwd` -#./xepdmgr :0 ./iliad ../games/anchor.z8 -./chimara ../games/anchor.z8 diff --git a/iliad/style.css b/iliad/style.css deleted file mode 100644 index 7f0d1fc..0000000 --- a/iliad/style.css +++ /dev/null @@ -1,81 +0,0 @@ -/* Possible windows: - * grid - * buffer - * - * Possible selectors: - * normal - * emphasized - * preformatted - * header - * subheader - * alert - * note - * block-quote - * input - * user1 - * user2 - * hyperlink - * - * Possible style hints: - * font-family (string) - * font-size (float) - * font-weight (normal/bold) - * font-style (normal/italic) - * color (#hex-value) - * background-color (#hex-value) - * text-align (left/right/center) - */ -grid.normal { - font-size: 10; -} - -grid.user1 { - color: #303030; - background-color: #ffffff; -} - -buffer.normal { - font-size: 10; -} - -buffer.header { - font-size: 14; - font-weight: bold; - text-align: center; -} - -buffer.subheader { - font-size: 12; - font-weight: bold; -} - -buffer.alert { - color: #aa0000; - font-weight: bold; -} - -buffer.note { - color: #aaaa00; - font-weight: bold; -} - -buffer.block-quote { - text-align: center; - font-style: italic; -} - -buffer.input { - color: #0000aa; - font-style: italic; -} - -buffer.user1 { -} - -buffer.user2 { -} - -buffer.pager { - color: #ffffff; - background-color: #303030; -} diff --git a/interpreters/Makefile.am b/interpreters/Makefile.am index 0b1cb2e..cd8e930 100644 --- a/interpreters/Makefile.am +++ b/interpreters/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS=nitfol frotz glulxe git +SUBDIRS=nitfol frotz glulxe git bocfel -include $(top_srcdir)/git.mk diff --git a/interpreters/bocfel/BUILDING b/interpreters/bocfel/BUILDING new file mode 100644 index 0000000..30411e8 --- /dev/null +++ b/interpreters/bocfel/BUILDING @@ -0,0 +1,135 @@ +GNU make is required, and all of the examples herein assume that GNU make is +being used. If your native make is not GNU, substitute gmake for make. + +A C99 compiler is required to build Bocfel; or at least a compiler that supports +the C99 features that are used. These include, but are probably not limited to, +VLAs, snprintf(), mixed declarations and code, fixed-width integers, and +compound literals. + +Officially supported compilers are handled in compiler.mk. If you get Bocfel +working with another compiler, I would be happy to add it to compiler.mk. Note +that Bocfel has a few requirements (beyond C99) from its compilers. Compilers +are expected to support the following rather standard Unix command-line +arguments, and are expected to work as linkers, as well: +-D - define a macro +-L - define a library search path +-l - specify a library to link +-o - specify the output file name +-g - build with debugging symbols +-c - compile but don't link, producing a .o file from a .c file +-O - Optimize (but this can be changed via the $OPT variable) + +Compilers are also expected to understand optimization flags while they are +linking; modern compilers can do link-time optimization, and generally require +the same flags when compiling and linking (the system linker is usually not +smart enough to handle the link-time optimized object files). + +The character set used is required to be compatible with ASCII. This is not a +particularly onerous requirement; but if I ever have access to a system that +supports a wildly different character set, I would be willing to think about +extending support beyond ASCII. For now, though, it is much easier to assume +ASCII, because almost everybody uses a character set that is compatible with it, +including the Z-machine. + +A 32-bit (or greater) system is probably a requirement. I have tried to avoid +the assumption that "int" is 32 bits, and it is entirely possible that I have +succeeded and a system with 16-bit ints would work properly. However, there are +some lookup tables that are 65K each, which would probably cause fits for 16-bit +systems. As with the ASCII requirement, if I ever happen upon a 16-bit system +with a C99 compiler, I will probably try to get Bocfel working with it. + +I make use of fixed-width types such as uint8_t and uint16_t. As a practical +side-effect, Bocfel requires a system with 8-, 16-, and 32-bit 2's complement +integers. This is likely not a problem. + +------------------------------------------------------------------------------- + +There are two main types of build: Glk and non-Glk. Glk builds use libraries +based on Andrew Plotkin's Glk standard for I/O. Glk builds can be full- +featured, including timed input and cursor control, allowing games like Border +Zone and Seastalker to run as intended. Non-Glk builds use nothing but standard +C functions. This has the advantage of running on systems where Glk has not +been ported, but the disadvantage of missing important features. Non-Glk builds +are generally not useful. + +The first thing to do is edit config.mk and set $PLATFORM to the proper value. +See the comments in that file for an explanation. There are other variables +that may be set through config.mk, each of which has comments explaining its +use. + +To build a non-Glk interpreter, simply run: +make GLK= + +The $GLK variable may also be set through config.mk. + +Glk builds are slightly more involved. For most Glk libraries (e.g. glkterm(w), +xglk, cheapglk, Windows Glk), you will want to unpack the source distribution +into the current directory (the one containing Bocfel's source), and then build +it. After this is done, simply run +make GLK=glktermw + +to build a Glk-enabled interpreter (where glktermw is the name of the directory +into which the Glk library was unpacked). Note that the Windows Glk +distribution does not unpack into its own directory, so you will want to change +into the winglk directory before unpacking it. + +The presence of a file called Make. in the Glk library +directory is required. Most Glk libraries will include this, but at least +Windows Glk does not. I have included a Make.winglk that should be sufficient +to build a Windows Glk-enabled interpreter, at least with MinGW. + +Bocfel can also be built against Gargoyle's Glk implementation, taking full +advantage of extra features it provides. Ben Cressey, Gargoyle's maintainer, +has imported Bocfel into the Gargoyle source repository, so the easiest way to +obtain Gargoyle support is to check out the latest version of Gargoyle's source +code; see http://code.google.com/p/garglk/source/checkout. If you would prefer +to use Bocfel's build system to build against Gargoyle as you would any other +Glk library, read on. + +The build process for Gargoyle is slightly more involved. Gargoyle includes a +full-featured Glk library, but it is not designed for external use. However, +getting a Gargoyle- (or rather, a garglk-) enabled build is not too difficult. + +The first step is to get and install the Jam build tool from +http://www.perforce.com/jam/jam.html. Your vendor may provide a jam package or +port; consult its documentation. + +Next, get a copy of the Gargoyle source code from http://garglk.googlecode.com/. +Extract it somewhere (it need not be in the current directory). Enter the +directory and run jam. From the build/.release/garglk directory, copy +the files libgarglk.so and libgarglkmain.a to the directory "gargoyle" (which +contains a Make.gargoyle file) in the Bocfel source distribution. Then copy the +files garglk/glk.h, garglk/glkstart.h, and garglk/gi_blorb.h from Gargoyle to +Bocfel's gargoyle directory. You can now build a garglk-enabled interpreter: +make GLK=gargoyle + +If you do not already have Gargoyle installed, you will need to install the +library libgarglk.so somewhere in your library search path. I would, however, +recommend just installing Gargoyle. If you do this, you will want to build the +source code of the same version that you install. If you don't, it's possible +(although unlikely) that ABI or API changes in Gargoyle will cause problems. + +Bocfel is built and tested against the development version of Gargoyle, so +ideally this would be the version to build against. However, Gargoyle does, by +and large, implement a standard API, so the version should not make a large +difference. There are a few Gargoyle-specific extensions that can possibly +change, but they have been stable apart from one change over the past couple of +years. The current (2010.1) release of Gargoyle should work, but the only +officially supported version is whatever was current as of this version of +Bocfel's release. + +------------------------------------------------------------------------------- + +Glk is very portable, and there is very little system-specific code that is +needed. However, there is a small amount. Some libraries are, conventionally, +considered to be Unix (cheapglk, xglk, and glkterm(w)); one Windows (Windows +Glk, naturally). Bocfel needs to know what kind of platform it is running on in +order to provide a few system-specific functions, and it uses this information +to decide what kind of Glk library is in use. While it is theoretically +possible (for example) for a Unix-based Glk library to use a non Unix-style Glk +startup, I have no desire to require users to set both their OS and Glk +platforms. Thus a Windows user who desires to use cheapglk (assuming it builds +on Windows) is out of luck, for example. I consider this to be acceptable +because I expect garglk to be the preferred Glk implementation, and even if it's +not, most Glk uses will work. When building with garglk, Unix-style Glk startup +is forced, because this is what garglk uses, regardless of platform. diff --git a/interpreters/bocfel/COPYING.GPLv2 b/interpreters/bocfel/COPYING.GPLv2 new file mode 100644 index 0000000..a3f6b12 --- /dev/null +++ b/interpreters/bocfel/COPYING.GPLv2 @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/interpreters/bocfel/COPYING.GPLv3 b/interpreters/bocfel/COPYING.GPLv3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/interpreters/bocfel/COPYING.GPLv3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/interpreters/bocfel/Makefile.am b/interpreters/bocfel/Makefile.am new file mode 100644 index 0000000..ecb779e --- /dev/null +++ b/interpreters/bocfel/Makefile.am @@ -0,0 +1,15 @@ +pkglib_LTLIBRARIES = bocfel.la +bocfel_la_SOURCES = blorb.c blorb.h branch.c branch.h dict.c dict.h glkstart.c \ + iff.c iff.h io.c io.h math.c math.h memory.c memory.h objects.c objects.h \ + osdep.c osdep.h process.c process.h random.c random.h screen.c screen.h \ + stack.c stack.h table.c table.h unicode.c unicode.h util.c util.h zoom.c \ + zoom.h zterp.c zterp.h +bocfel_la_CPPFLAGS = -DZTERP_GLK -DZTERP_UNIX \ + -I$(top_srcdir) -I$(top_srcdir)/libchimara +bocfel_la_CFLAGS = -std=c99 $(AM_CFLAGS) +bocfel_la_LDFLAGS = -module $(PLUGIN_LIBTOOL_FLAGS) + +bocfeldocdir = $(datadir)/doc/$(PACKAGE)/bocfel +dist_bocfeldoc_DATA = BUILDING COPYING.GPLv2 COPYING.GPLv3 README + +-include $(top_srcdir)/git.mk diff --git a/interpreters/bocfel/README b/interpreters/bocfel/README new file mode 100644 index 0000000..1d22cd3 --- /dev/null +++ b/interpreters/bocfel/README @@ -0,0 +1,18 @@ +Bocfel is an interpreter for the Z-machine, which means that it plays text +adventure games. For more information on the Z-machine and interpreters, please +see http://en.wikipedia.org/wiki/Z-machine. + +For building instructions, please consult the BUILDING file. + +If you are poking around the source code, you might notice a lot of identifiers +containing "zterp". When I first started this project, I put it in my +Subversion repository, but I wasn't sure it would ever go anywhere. I quickly +picked a rather generic name--zterp--because a name was needed for the +repository path. It turns out, however, that long ago somebody released a +Z-machine interpreter called zterp, so I had to find something new. By the time +I realized this, however, the name "zterp" had become entrenched in the source. +I've changed all user-visible uses of "zterp" to "bocfel", but have kept the old +identifiers in the source. + +Chris Spiegel +http://bocfel.googlecode.com/ diff --git a/interpreters/bocfel/blorb.c b/interpreters/bocfel/blorb.c new file mode 100644 index 0000000..456e976 --- /dev/null +++ b/interpreters/bocfel/blorb.c @@ -0,0 +1,121 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include + +#include "blorb.h" +#include "iff.h" +#include "io.h" + +struct zterp_blorb +{ + struct zterp_io *io; + struct zterp_iff *iff; + + size_t nchunks; + zterp_blorb_chunk *chunks; +}; + +struct zterp_blorb *zterp_blorb_parse(zterp_io *io) +{ + uint32_t size; + uint32_t nresources; + zterp_iff *iff; + struct zterp_blorb *blorb = NULL; + + iff = zterp_iff_parse(io, "IFRS"); + if(!zterp_iff_find(iff, "RIdx", &size)) goto err; + zterp_iff_free(iff); + + if(!zterp_io_read32(io, &nresources)) goto err; + + if((nresources * 12) + 4 != size) goto err; + + blorb = malloc(sizeof *blorb); + if(blorb == NULL) goto err; + + blorb->io = io; + blorb->nchunks = 0; + blorb->chunks = NULL; + + for(uint32_t i = 0; i < nresources; i++) + { + uint32_t usage, number, start, type; + zterp_blorb_chunk *new; + long saved; + uint32_t idx; + + if(!zterp_io_read32(io, &usage) || !zterp_io_read32(io, &number) || !zterp_io_read32(io, &start)) goto err; + + if(usage != BLORB_PICT && usage != BLORB_SND && usage != BLORB_EXEC) goto err; + + saved = zterp_io_tell(io); + if(saved == -1) goto err; + + if(zterp_io_seek(io, start, SEEK_SET) == -1) goto err; + + if(!zterp_io_read32(io, &type) || !zterp_io_read32(io, &size)) goto err; + + if(zterp_io_seek(io, saved, SEEK_SET) == -1) goto err; + + if(type == STRID("FORM")) + { + start -= 8; + size += 8; + } + + /* Not really efficient, but does it matter? */ + new = realloc(blorb->chunks, sizeof *new * ++blorb->nchunks); + if(new == NULL) goto err; + blorb->chunks = new; + + idx = blorb->nchunks - 1; + + new[idx].usage = usage; + new[idx].number = number; + new[idx].type = type; + memcpy(new[idx].name, IDSTR(type), 5); + new[idx].offset = start + 8; + new[idx].size = size; + } + + return blorb; + +err: + zterp_blorb_free(blorb); + + return NULL; +} + +void zterp_blorb_free(struct zterp_blorb *blorb) +{ + if(blorb != NULL) free(blorb->chunks); + free(blorb); +} + +const zterp_blorb_chunk *zterp_blorb_find(struct zterp_blorb *blorb, uint32_t usage, int number) +{ + for(size_t i = 0; i < blorb->nchunks; i++) + { + if(blorb->chunks[i].usage == usage && blorb->chunks[i].number == number) return &blorb->chunks[i]; + } + + return NULL; +} diff --git a/interpreters/bocfel/blorb.h b/interpreters/bocfel/blorb.h new file mode 100644 index 0000000..654becc --- /dev/null +++ b/interpreters/bocfel/blorb.h @@ -0,0 +1,29 @@ +#ifndef ZTERP_BLORB_H +#define ZTERP_BLORB_H + +#include +#include + +#include "io.h" + +#define BLORB_PICT 0x50696374 +#define BLORB_SND 0x536e6420 +#define BLORB_EXEC 0x45786563 + +typedef struct zterp_blorb zterp_blorb; + +typedef struct +{ + uint32_t usage; + int number; + uint32_t type; + char name[5]; + uint32_t offset; + uint32_t size; +} zterp_blorb_chunk; + +zterp_blorb *zterp_blorb_parse(zterp_io *); +void zterp_blorb_free(zterp_blorb *); +const zterp_blorb_chunk *zterp_blorb_find(zterp_blorb *, uint32_t, int); + +#endif diff --git a/interpreters/bocfel/branch.c b/interpreters/bocfel/branch.c new file mode 100644 index 0000000..2af3d29 --- /dev/null +++ b/interpreters/bocfel/branch.c @@ -0,0 +1,91 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include + +#include "branch.h" +#include "memory.h" +#include "process.h" +#include "stack.h" +#include "util.h" +#include "zterp.h" + +void branch_if(int do_branch) +{ + uint8_t branch; + uint16_t offset; + + branch = BYTE(pc++); + + if(!do_branch) branch ^= 0x80; + + offset = branch & 0x3f; + + if((branch & 0x40) == 0) + { + offset = (offset << 8) | BYTE(pc++); + + /* Get the sign right. */ + if(offset & 0x2000) offset |= 0xc000; + } + + if(branch & 0x80) + { + if(offset > 1) + { + pc += (int16_t)offset - 2; + ZASSERT(pc < memory_size, "branch to invalid address 0x%lx", (unsigned long)pc); + } + else + { + do_return(offset); + } + } +} + +void zjump(void) +{ + /* -= 2 because pc has been advanced past the jump instruction. */ + pc += (int16_t)zargs[0]; + pc -= 2; + + ZASSERT(pc < memory_size, "@jump to invalid address 0x%lx", (unsigned long)pc); +} + +void zjz(void) +{ + branch_if(zargs[0] == 0); +} + +void zje(void) +{ + if (znargs == 1) branch_if(0); + else if(znargs == 2) branch_if(zargs[0] == zargs[1]); + else if(znargs == 3) branch_if(zargs[0] == zargs[1] || zargs[0] == zargs[2]); + else branch_if(zargs[0] == zargs[1] || zargs[0] == zargs[2] || zargs[0] == zargs[3]); +} + +void zjl(void) +{ + branch_if((int16_t)zargs[0] < (int16_t)zargs[1]); +} + +void zjg(void) +{ + branch_if((int16_t)zargs[0] > (int16_t)zargs[1]); +} diff --git a/interpreters/bocfel/branch.h b/interpreters/bocfel/branch.h new file mode 100644 index 0000000..709860c --- /dev/null +++ b/interpreters/bocfel/branch.h @@ -0,0 +1,12 @@ +#ifndef ZTERP_BRANCH_H +#define ZTERP_BRANCH_H + +void branch_if(int); + +void zjump(void); +void zjz(void); +void zje(void); +void zjl(void); +void zjg(void); + +#endif diff --git a/interpreters/bocfel/dict.c b/interpreters/bocfel/dict.c new file mode 100644 index 0000000..f184ddb --- /dev/null +++ b/interpreters/bocfel/dict.c @@ -0,0 +1,319 @@ +/*- + * Copyright 2009-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#include "dict.h" +#include "memory.h" +#include "process.h" +#include "unicode.h" +#include "util.h" +#include "zterp.h" + +static uint16_t separators; +static uint8_t num_separators; + +static uint16_t GET_WORD(uint8_t *base) +{ + return (base[0] << 8) | base[1]; +} +static void MAKE_WORD(uint8_t *base, uint16_t val) +{ + base[0] = val >> 8; + base[1] = val & 0xff; +} + +/* Add the character c to the nth position of the encoded text. c is a + * 5-bit value (either a shift character, which selects an alphabet, or + * the index into the current alphabet). + */ +static void add_zchar(int c, int n, uint8_t *encoded) +{ + uint16_t w = GET_WORD(&encoded[2 * (n / 3)]); + + /* From §3.2: + * --first byte------- --second byte--- + * 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * bit --first-- --second--- --third-- + * + * So to figure out which third of the word to store to: + * If n is 0, 3, 6, ... then store to the first (left shift 10). + * If n is 1, 4, 7, ... then store to the second (left shift 5). + * If n is 2, 5, 8, ... then store to the third (left shift 0). + * “Or” into the previous value because, unless this is the first + * character, there are already values we’ve stored there. + */ + w |= (c & 0x1f) << (5 * (2 - (n % 3))); + + MAKE_WORD(&encoded[2 * (n / 3)], w); +} + +/* Encode the text at “s”, of length “len” (there is not necessarily a + * terminating null character) into the buffer “encoded”. + * + * For V3 the encoded text is 6 Z-characters (4 bytes); for V4 and above + * it’s 9 characters (6 bytes). Due to the nature of the loop here, + * it’s possible to encode too many bytes. For example, if the string + * given is "aaa<" in a V3 game, the three 'a' characters will take up a + * word (all three being packed into one), but the single '<' character + * will take up two words (one full word and a third of the next) due to + * the fact that '<' is not in the alphabet table. Thus the encoded + * text will be 7 characters. This is OK because routines that use the + * encoded string are smart enough to only pay attention to the first 6 + * or 9 Z-characters; and partial Z-characters are OK per §3.6.1. + * + * 1.1 of the standard revises the encoding for V1 and V2 games. I am + * not implementing the new rules for two basic reasons: + * 1) It apparently only affects three (unnecessary) dictionary words in + * the known V1-2 games. + * 2) Because of 1, it is not worth the effort to peek ahead and see + * what the next character is to determine whether to shift once or + * to lock. + * + * Z-character 0 is a space (§3.5.1), so theoretically a space should be + * encoded simply with a zero. However, Inform 6.32 encodes space + * (which has the value 32) as a 10-bit ZSCII code, which is the + * Z-characters 5, 6, 1, 0. Assume this is correct. + */ +static void encode_string(const uint8_t *s, size_t len, uint8_t encoded[8]) +{ + int n = 0; + const int res = zversion <= 3 ? 6 : 9; + const int shiftbase = zversion <= 2 ? 1 : 3; + + memset(encoded, 0, 8); + + for(size_t i = 0; i < len && n < res; i++) + { + int pos; + + pos = atable_pos[s[i]]; + if(pos >= 0) + { + int shift = pos / 26; + int c = pos % 26; + + if(shift) add_zchar(shiftbase + shift, n++, encoded); + add_zchar(c + 6, n++, encoded); + } + else + { + add_zchar(shiftbase + 2, n++, encoded); + add_zchar(6, n++, encoded); + add_zchar(s[i] >> 5, n++, encoded); + add_zchar(s[i] & 0x1f, n++, encoded); + } + } + + while(n < res) + { + add_zchar(5, n++, encoded); + } + + /* §3.2: the MSB of the last encoded word must be set. */ + if(zversion <= 3) encoded[2] |= 0x80; + else encoded[4] |= 0x80; +} + +static int dict_compar(const void *a, const void *b) +{ + return memcmp(a, b, zversion <= 3 ? 4 : 6); +} +static uint16_t dict_find(const uint8_t *token, size_t len, uint16_t dictionary) +{ + uint8_t elength; + uint16_t base; + long nentries; + uint8_t *ret = NULL; + uint8_t encoded[8]; + + encode_string(token, len, encoded); + + elength = user_byte(dictionary + num_separators + 1); + nentries = (int16_t)user_word(dictionary + num_separators + 2); + base = dictionary + num_separators + 2 + 2; + + ZASSERT(elength >= (zversion <= 3 ? 4 : 6), "dictionary entry length (%d) too small", elength); + ZASSERT(base + (labs(nentries) * elength) < memory_size, "reported dictionary length extends beyond memory size"); + + if(nentries > 0) + { + ret = bsearch(encoded, &memory[base], nentries, elength, dict_compar); + } + else + { + for(long i = 0; i < -nentries; i++) + { + uint8_t *entry = &memory[base + (i * elength)]; + + if(dict_compar(encoded, entry) == 0) + { + ret = entry; + break; + } + } + } + + if(ret == NULL) return 0; + + return base + (ret - &memory[base]); +} + +static int is_sep(uint8_t c) +{ + if(c == ZSCII_SPACE) return 1; + + for(uint16_t i = 0; i < num_separators; i++) if(user_byte(separators + i) == c) return 1; + + return 0; +} + +static void handle_token(const uint8_t *base, const uint8_t *token, int len, uint16_t parse, uint16_t dictionary, int found, int flag) +{ + uint16_t d; + const uint8_t examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e' }; + const uint8_t again[] = { 'a', 'g', 'a', 'i', 'n' }; + const uint8_t wait[] = { 'w', 'a', 'i', 't' }; + + d = dict_find(token, len, dictionary); + + if(!options.disable_abbreviations && base == token && len == 1) + { + if (*token == 'x') d = dict_find(examine, sizeof examine, dictionary); + else if(*token == 'g') d = dict_find(again, sizeof again, dictionary); + else if(*token == 'z') d = dict_find(wait, sizeof wait, dictionary); + } + + if(flag && d == 0) return; + + parse = parse + 2 + (found * 4); + + user_store_word(parse, d); + + user_store_byte(parse + 2, len); + + if(zversion <= 4) user_store_byte(parse + 3, token - base + 1); + else user_store_byte(parse + 3, token - base + 2); +} + +/* The behavior of tokenize is described in §15 (under the read opcode) + * and §13. + * + * For the text buffer, byte 0 is ignored in both V3/4 and V5+. + * Byte 1 of V3/4 is the start of the string, while in V5+ it is the + * length of the string. + * Byte 2 of V5+ is the start of the string. V3/4 strings have a null + * terminator, while V5+ do not. + * + * For the parse buffer, byte 0 contains the maximum number of tokens + * that can be read. + * The number of tokens found is stored in byte 1. + * Each token is then represented by a 4-byte chunk with the following + * information: + * • The first two bytes are the byte address of the dictionary entry + * for the token, or 0 if the token was not found in the dictionary. + * • The next byte is the length of the token. + * • The final byte is the offset in the string of the token. + */ +void tokenize(uint16_t text, uint16_t parse, uint16_t dictionary, int flag) +{ + const uint8_t *p, *lastp; + uint8_t *string; + uint32_t text_len = 0; + const int maxwords = user_byte(parse); + int in_word = 0; + int found = 0; + + if(dictionary == 0) dictionary = header.dictionary; + + ZASSERT(dictionary != 0, "attempt to tokenize without a valid dictionary"); + + num_separators = user_byte(dictionary); + separators = dictionary + 1; + + if(zversion >= 5) text_len = user_byte(text + 1); + else while(user_byte(text + 1 + text_len) != 0) text_len++; + + ZASSERT(text + 1 + (zversion >= 5) + text_len < memory_size, "attempt to tokenize out-of-bounds string"); + + string = &memory[text + 1 + (zversion >= 5)]; + + for(p = string; p - string < text_len && *p == ZSCII_SPACE; p++); + lastp = p; + + text_len -= (p - string); + + do + { + if(!in_word && text_len != 0 && !is_sep(*p)) + { + in_word = 1; + lastp = p; + } + + if(text_len == 0 || is_sep(*p)) + { + if(in_word) + { + handle_token(string, lastp, p - lastp, parse, dictionary, found++, flag); + } + + /* §13.6.1: Separators (apart from a space) are tokens too. */ + if(text_len != 0 && *p != ZSCII_SPACE) + { + handle_token(string, p, 1, parse, dictionary, found++, flag); + } + + if(found == maxwords) break; + + in_word = 0; + } + + p++; + + } while(text_len--); + + user_store_byte(parse + 1, found); +} + +static void encode_text(uint32_t text, uint16_t len, uint16_t coded) +{ + uint8_t encoded[8]; + + ZASSERT(text + len < memory_size, "reported text length extends beyond memory size"); + + encode_string(&memory[text], len, encoded); + + for(int i = 0; i < 6; i++) user_store_byte(coded + i, encoded[i]); +} + +void ztokenise(void) +{ + if(znargs < 3) zargs[2] = 0; + if(znargs < 4) zargs[3] = 0; + + tokenize(zargs[0], zargs[1], zargs[2], zargs[3]); +} + +void zencode_text(void) +{ + encode_text(zargs[0] + zargs[2], zargs[1], zargs[3]); +} diff --git a/interpreters/bocfel/dict.h b/interpreters/bocfel/dict.h new file mode 100644 index 0000000..e1c8c02 --- /dev/null +++ b/interpreters/bocfel/dict.h @@ -0,0 +1,11 @@ +#ifndef DICTIONARY_H +#define DICTIONARY_H + +#include + +void tokenize(uint16_t, uint16_t, uint16_t, int); + +void ztokenise(void); +void zencode_text(void); + +#endif diff --git a/interpreters/bocfel/glkstart.c b/interpreters/bocfel/glkstart.c new file mode 100644 index 0000000..89cfb27 --- /dev/null +++ b/interpreters/bocfel/glkstart.c @@ -0,0 +1,120 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +/* Even on Win32, Gargoyle provides a glkunix startup. */ +#if defined(ZTERP_UNIX) || defined(GARGLK) +#include + +#include + +#include "util.h" +#include "zterp.h" + +zexternally_visible +glkunix_argumentlist_t glkunix_arguments[] = +{ + { "-a", glkunix_arg_NumberValue, "-a N set the size of the evaluation stack" }, + { "-A", glkunix_arg_NumberValue, "-A N set the size of the call stack" }, + { "-c", glkunix_arg_NoValue, "-c disable color" }, + { "-C", glkunix_arg_NoValue, "-C disable the use of a config file" }, + { "-d", glkunix_arg_NoValue, "-d disable timed input" }, + { "-D", glkunix_arg_NoValue, "-D disable sound effects" }, + { "-e", glkunix_arg_NoValue, "-e enable ANSI escapes in the transcript" }, + { "-E", glkunix_arg_ValueFollows, "-E string set the escape string for -e" }, + { "-f", glkunix_arg_NoValue, "-f disable fixed-width fonts" }, + { "-F", glkunix_arg_NoValue, "-F assume font is fixed-width" }, + { "-g", glkunix_arg_NoValue, "-g disable the character graphics font" }, + { "-G", glkunix_arg_NoValue, "-G enable alternative box-drawing character graphics" }, + { "-i", glkunix_arg_NoValue, "-i display the id of the story file and exit" }, + { "-k", glkunix_arg_NoValue, "-k disable the use of terminating keys (notably used in Beyond Zork)" }, + { "-l", glkunix_arg_NoValue, "-l disable utf-8 transcripts" }, + { "-L", glkunix_arg_NoValue, "-L force utf-8 transcrips" }, + { "-m", glkunix_arg_NoValue, "-m disable meta commands" }, + { "-n", glkunix_arg_NumberValue, "-n N set the interpreter number (see 11.1.3 in The Z-machine Standards Document 1.0)" }, + { "-N", glkunix_arg_NumberValue, "-N N set the interpreter version (see 11.1.3.1 in The Z-machine Standards Document 1.0)" }, + { "-r", glkunix_arg_NoValue, "-r start the story by replaying a command record" }, + { "-R", glkunix_arg_NoValue, "-R filename set the filename to be used if replaying a command record" }, + { "-s", glkunix_arg_NoValue, "-s start the story with command recording on" }, + { "-S", glkunix_arg_NoValue, "-S filename set the filename to be used if command recording is turned on" }, + { "-t", glkunix_arg_NoValue, "-t start the story with transcripting on" }, + { "-T", glkunix_arg_ValueFollows, "-T filename set the filename to be used if transcription is turned on" }, + { "-u", glkunix_arg_NumberValue, "-u N set the maximum number of undo slots" }, + { "-U", glkunix_arg_NoValue, "-U disable compression in undo slots" }, + { "-v", glkunix_arg_NoValue, "-v display version information" }, + { "-x", glkunix_arg_NoValue, "-x disable expansion of abbreviations" }, + { "-X", glkunix_arg_NoValue, "-X enable tandy censorship" }, + { "-y", glkunix_arg_NoValue, "-y when opening a transcript, overwrite rather than append to an existing file" }, + { "-z", glkunix_arg_NumberValue, "-z N set initial random seed" }, + { "-Z", glkunix_arg_ValueFollows, "-Z device read initial random seed from device" }, + { "", glkunix_arg_ValueFollows, "filename file to load" }, + + { NULL, glkunix_arg_End, NULL } +}; + +zexternally_visible +int glkunix_startup_code(glkunix_startup_t *data) +{ + if(!process_arguments(data->argc, data->argv)) return 0; + +#ifdef GARGLK + garglk_set_program_name("Bocfel"); + if(game_file != NULL) + { + char *p = strrchr(game_file, '/'); + garglk_set_story_name(p == NULL ? game_file : p + 1); + } +#endif + + return 1; +} +#elif defined(ZTERP_WIN32) +#include + +#include + +#include "util.h" + +int InitGlk(unsigned int); + +zexternally_visible +int WINAPI WinMain(HINSTANCE instance, HINSTANCE previnstance, LPSTR cmdline, int cmdshow) +{ + /* This works (with a linker message) under MinGW, but I don’t + * know if it’s supposed to; I am unfamiliar with how Windows + * handles command-line arguments. + */ + extern int __argc; + extern char **__argv; + + if(!InitGlk(0x00000700)) exit(EXIT_FAILURE); + + if(!process_arguments(__argc, __argv)) exit(EXIT_FAILURE); + + winglk_app_set_name("Bocfel"); + + glk_main(); + glk_exit(); + + return 0; +} +#else +#error Glk on this platform is not supported. +#endif diff --git a/interpreters/bocfel/iff.c b/interpreters/bocfel/iff.c new file mode 100644 index 0000000..e7fe4e1 --- /dev/null +++ b/interpreters/bocfel/iff.c @@ -0,0 +1,112 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include + +#include "iff.h" +#include "io.h" + +struct zterp_iff +{ + zterp_io *io; + uint32_t tag; + long offset; + uint32_t size; + + struct zterp_iff *next; +}; + +void zterp_iff_free(zterp_iff *iff) +{ + while(iff != NULL) + { + zterp_iff *tmp = iff->next; + free(iff); + iff = tmp; + } +} + +zterp_iff *zterp_iff_parse(zterp_io *io, const char type[4]) +{ + uint32_t tag; + + zterp_iff *iff = NULL, *tail = NULL; + + if(zterp_io_seek(io, 0, SEEK_SET) == -1) goto err; + + if(!zterp_io_read32(io, &tag) || tag != STRID("FORM")) goto err; + + if(zterp_io_seek(io, 4, SEEK_CUR) == -1) goto err; + + if(!zterp_io_read32(io, &tag) || tag != STRID(type)) goto err; + + while(zterp_io_read32(io, &tag)) + { + uint32_t size; + zterp_iff *new; + + if(!zterp_io_read32(io, &size)) goto err; + + new = malloc(sizeof *new); + if(new == NULL) goto err; + + new->tag = tag; + new->io = io; + new->offset = zterp_io_tell(io); + new->size = size; + new->next = NULL; + + if(iff == NULL) iff = new; + else tail->next = new; + + tail = new; + + if(new->offset == -1) goto err; + + if(size & 1) size++; + + if(zterp_io_seek(io, size, SEEK_CUR) == -1) goto err; + } + + return iff; + +err: + zterp_iff_free(iff); + + return NULL; +} + +int zterp_iff_find(zterp_iff *iff, const char tag[4], uint32_t *size) +{ + while(iff != NULL) + { + if(iff->tag == STRID(tag)) + { + if(zterp_io_seek(iff->io, iff->offset, SEEK_SET) == -1) return 0; + *size = iff->size; + + return 1; + } + + iff = iff->next; + } + + return 0; +} diff --git a/interpreters/bocfel/iff.h b/interpreters/bocfel/iff.h new file mode 100644 index 0000000..fd63d26 --- /dev/null +++ b/interpreters/bocfel/iff.h @@ -0,0 +1,31 @@ +#ifndef ZTERP_IFF_H +#define ZTERP_IFF_H + +#include + +#include "io.h" + +typedef struct zterp_iff zterp_iff; + +/* Translate an IFF tag into the corresponding 32-bit integer. */ +#define STRID(s) ( \ + (((uint32_t)(s)[0]) << 24) | \ + (((uint32_t)(s)[1]) << 16) | \ + (((uint32_t)(s)[2]) << 8) | \ + (((uint32_t)(s)[3]) << 0) \ + ) + +/* Reverse of above. */ +#define IDSTR(n) ((char[5]){ \ + ((uint32_t)n >> 24) & 0xff, \ + ((uint32_t)n >> 16) & 0xff, \ + ((uint32_t)n >> 8) & 0xff, \ + ((uint32_t)n >> 0) & 0xff, \ + }) + + +void zterp_iff_free(zterp_iff *); +zterp_iff *zterp_iff_parse(zterp_io *, const char [4]); +int zterp_iff_find(zterp_iff *, const char [4], uint32_t *); + +#endif diff --git a/interpreters/bocfel/io.c b/interpreters/bocfel/io.c new file mode 100644 index 0000000..d51e65a --- /dev/null +++ b/interpreters/bocfel/io.c @@ -0,0 +1,493 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include + +#ifdef ZTERP_GLK +#include +#endif + +#include "io.h" +#include "osdep.h" +#include "unicode.h" + +#define MAX_PATH 4096 + +int use_utf8_io; + +/* Generally speaking, UNICODE_LINEFEED (10) is used as a newline. Glk + * requires this (Glk API 0.7.0 §2.2), and when Unicode is available, we + * write characters out by hand even with stdio, so no translation can + * be done. However, when stdio is being used, Unicode is not + * available, and the file usage will be for a transcript or + * command-script, use '\n' as a newline so translation can be done; + * this is the only case where streams are opened in text mode. + * + * zterp_io_stdio() and zterp_io_stdout() are considered text-mode if + * Unicode is not available, binary otherwise. + */ +#define textmode(io) (!use_utf8_io && ((io->mode) & (ZTERP_IO_TRANS | ZTERP_IO_INPUT))) + +struct zterp_io +{ + enum type { IO_STDIO, IO_GLK } type; + + FILE *fp; + int mode; +#ifdef ZTERP_GLK + strid_t file; +#endif +}; + +/* Glk does not like you to be able to pass a full filename to + * glk_fileref_create_by_name(); this means that Glk cannot be used to + * open arbitrary files. However, Glk is still required to prompt for + * files, such as in a save game situation. To allow zterp_io to work + * for opening files both with and without a prompt, it will use stdio + * when either Glk is not available, or when Glk is available but + * prompting is not necessary. + * + * This is needed because the IFF parser is required for both opening + * games (zblorb files) and for saving/restoring. The former needs to + * be able to access any file on the filesystem, and the latter needs to + * prompt. This is a headache. + * + * Prompting is assumed to be necessary if “filename” is NULL. + */ +zterp_io *zterp_io_open(const char *filename, int mode) +{ + zterp_io *io; + char smode[] = "wb"; + + fprintf(stderr, "zterp_io_open: '%s'\n", filename); + + io = malloc(sizeof *io); + if(io == NULL) goto err; + io->mode = mode; + + if (mode & ZTERP_IO_RDONLY) smode[0] = 'r'; + else if(mode & ZTERP_IO_APPEND) smode[0] = 'a'; + + if(textmode(io)) smode[1] = 0; + +#ifdef ZTERP_GLK + int usage = fileusage_BinaryMode, filemode; + + if (mode & ZTERP_IO_SAVE) usage |= fileusage_SavedGame; + else if(mode & ZTERP_IO_TRANS) usage |= fileusage_Transcript; + else if(mode & ZTERP_IO_INPUT) usage |= fileusage_InputRecord; + else usage |= fileusage_Data; + + if (mode & ZTERP_IO_RDONLY) filemode = filemode_Read; + else if(mode & ZTERP_IO_WRONLY) filemode = filemode_Write; + else if(mode & ZTERP_IO_APPEND) filemode = filemode_WriteAppend; + + else goto err; +#else + const char *prompt; + + if (mode & ZTERP_IO_SAVE) prompt = "Enter filename for save game: "; + else if(mode & ZTERP_IO_TRANS) prompt = "Enter filename for transcript: "; + else if(mode & ZTERP_IO_INPUT) prompt = "Enter filename for command record: "; + else prompt = "Enter filename for data: "; +#endif + + /* No need to prompt. */ + if(filename != NULL) + { + io->type = IO_STDIO; + io->fp = fopen(filename, smode); + if(io->fp == NULL) goto err; + } + /* Prompt. */ + else + { +#ifdef ZTERP_GLK + frefid_t ref; + + ref = glk_fileref_create_by_prompt(usage, filemode, 0); + if(ref == NULL) goto err; + + io->type = IO_GLK; + io->file = glk_stream_open_file(ref, filemode, 0); + glk_fileref_destroy(ref); + if(io->file == NULL) goto err; +#else + char fn[MAX_PATH], *p; + + printf("\n%s", prompt); + fflush(stdout); + if(fgets(fn, sizeof fn, stdin) == NULL || fn[0] == '\n') goto err; + p = strchr(fn, '\n'); + if(p != NULL) *p = 0; + + io->type = IO_STDIO; + io->fp = fopen(fn, smode); + if(io->fp == NULL) goto err; +#endif + } + + return io; + +err: + free(io); + + return NULL; +} + +/* The zterp_os_reopen_binary() calls attempt to reopen stdin/stdout as + * binary streams so that reading/writing UTF-8 doesn’t cause unwanted + * translations. The mode of ZTERP_IO_TRANS is set when Unicode is + * unavailable as a way to signal that these are text streams. + */ +const zterp_io *zterp_io_stdin(void) +{ + static zterp_io io; + + if(io.fp == NULL) + { + io.type = IO_STDIO; + io.mode = ZTERP_IO_RDONLY; + if(use_utf8_io) zterp_os_reopen_binary(stdin); + else io.mode |= ZTERP_IO_TRANS; + io.fp = stdin; + } + + return &io; +} + +const zterp_io *zterp_io_stdout(void) +{ + static zterp_io io; + + if(io.fp == NULL) + { + io.type = IO_STDIO; + io.mode = ZTERP_IO_WRONLY; + if(use_utf8_io) zterp_os_reopen_binary(stdout); + else io.mode |= ZTERP_IO_TRANS; + io.fp = stdout; + } + + return &io; +} + +void zterp_io_close(zterp_io *io) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_stream_close(io->file, NULL); + } + else +#endif + { + fclose(io->fp); + } + + free(io); +} + +int zterp_io_seek(const zterp_io *io, long offset, int whence) +{ + /* To smooth over differences between Glk and standard I/O, don’t + * allow seeking in append-only streams. + */ + if(io->mode & ZTERP_IO_APPEND) return -1; + +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_stream_set_position(io->file, offset, whence == SEEK_SET ? seekmode_Start : whence == SEEK_CUR ? seekmode_Current : seekmode_End); + return 0; /* dammit */ + } + else +#endif + { + return fseek(io->fp, offset, whence); + } +} + +long zterp_io_tell(const zterp_io *io) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + return glk_stream_get_position(io->file); + } + else +#endif + { + return ftell(io->fp); + } +} + +/* zterp_io_read() and zterp_io_write() always operate in terms of + * bytes, whether or not Unicode is available. + */ +size_t zterp_io_read(const zterp_io *io, void *buf, size_t n) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glui32 s = glk_get_buffer_stream(io->file, buf, n); + /* This should only happen if io->file is invalid. */ + if(s == (glui32)-1) s = 0; + return s; + } + else +#endif + { + return fread(buf, 1, n, io->fp); + } +} + +size_t zterp_io_write(const zterp_io *io, const void *buf, size_t n) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_put_buffer_stream(io->file, (char *)buf, n); + return n; /* dammit */ + } + else +#endif + { + return fwrite(buf, 1, n, io->fp); + } +} + +int zterp_io_read16(const zterp_io *io, uint16_t *v) +{ + uint8_t buf[2]; + + if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0; + + *v = (buf[0] << 8) | buf[1]; + + return 1; +} + +int zterp_io_read32(const zterp_io *io, uint32_t *v) +{ + uint8_t buf[4]; + + if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0; + + *v = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + + return 1; +} + +/* Read a byte and make sure it’s part of a valid UTF-8 sequence. */ +static int read_byte(const zterp_io *io, uint8_t *c) +{ + if(zterp_io_read(io, c, sizeof *c) != sizeof *c) return 0; + if((*c & 0x80) != 0x80) return 0; + + return 1; +} + +/* zterp_io_getc() and zterp_io_putc() are meant to operate in terms of + * characters, not bytes. That is, unlike C, bytes and characters are + * not equivalent as far as Zterp’s I/O system is concerned. + */ + +/* Read a UTF-8 character, returning it. + * -1 is returned on EOF. + * + * If there is a problem reading the UTF-8 (either from an invalid + * sequence or from a too-large value), a question mark is returned. + * + * If Unicode is not available, read a single byte (assumed to be + * Latin-1). + * If Unicode is not available, IO_STDIO is in use, and text mode is + * set, do newline translation. Text mode is likely to always be + * set—this function really shouldn’t be used in binary mode. + */ +long zterp_io_getc(const zterp_io *io) +{ + long ret; + + if(!use_utf8_io) + { +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + ret = glk_get_char_stream(io->file); + } + else +#endif + { + int c; + + c = getc(io->fp); + if(c == EOF) ret = -1; + else ret = c; + + if(textmode(io) && c == '\n') ret = UNICODE_LINEFEED; + } + } + else + { + uint8_t c; + + if(zterp_io_read(io, &c, sizeof c) != sizeof c) + { + ret = -1; + } + else if((c & 0x80) == 0) /* One byte. */ + { + ret = c; + } + else if((c & 0xe0) == 0xc0) /* Two bytes. */ + { + ret = (c & 0x1f) << 6; + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= (c & 0x3f); + } + else if((c & 0xf0) == 0xe0) /* Three bytes. */ + { + ret = (c & 0x0f) << 12; + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= ((c & 0x3f) << 6); + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= (c & 0x3f); + } + else if((c & 0xf8) == 0xf0) /* Four bytes. */ + { + /* The Z-machine doesn’t support Unicode this large, but at + * least try not to leave a partial character in the stream. + */ + zterp_io_seek(io, 3, SEEK_CUR); + + ret = UNICODE_QUESTIONMARK; + } + else /* Invalid value. */ + { + ret = UNICODE_QUESTIONMARK; + } + } + + if(ret > UINT16_MAX) ret = UNICODE_QUESTIONMARK; + + return ret; +} + +/* Write a Unicode character as UTF-8. + * + * If Unicode is not available, write the value out as a single Latin-1 + * byte. If it is too large for a byte, write out a question mark. + * + * If Unicode is not available, IO_STDIO is in use, and text mode is + * set, do newline translation. + * + * Text mode is likely to always be set—this function really shouldn’t + * be used in binary mode. + */ +void zterp_io_putc(const zterp_io *io, uint16_t c) +{ + if(!use_utf8_io) + { + if(c > UINT8_MAX) c = UNICODE_QUESTIONMARK; +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_put_char_stream(io->file, c); + } + else +#endif + { + if(textmode(io) && c == UNICODE_LINEFEED) c = '\n'; + putc(c, io->fp); + } + } + else + { + uint8_t hi = c >> 8, lo = c & 0xff; + +#define WRITE(c) zterp_io_write(io, &(uint8_t){ c }, sizeof (uint8_t)) + if(c < 128) + { + WRITE(c); + } + else if(c < 2048) + { + WRITE(0xc0 | (hi << 2) | (lo >> 6)); + WRITE(0x80 | (lo & 0x3f)); + } + else + { + WRITE(0xe0 | (hi >> 4)); + WRITE(0x80 | ((hi << 2) & 0x3f) | (lo >> 6)); + WRITE(0x80 | (lo & 0x3f)); + } +#undef WRITE + } +} + +long zterp_io_readline(const zterp_io *io, uint16_t *buf, size_t len) +{ + long ret; + + if(len > LONG_MAX) return -1; + + for(ret = 0; ret < len; ret++) + { + long c = zterp_io_getc(io); + + /* EOF before newline means there was a problem. */ + if(c == -1) return -1; + + /* Don’t count the newline. */ + if(c == UNICODE_LINEFEED) break; + + buf[ret] = c; + } + + return ret; +} + +long zterp_io_filesize(const zterp_io *io) +{ + if(io->type == IO_STDIO && !textmode(io)) + { + return zterp_os_filesize(io->fp); + } + else + { + return -1; + } +} + +void zterp_io_flush(const zterp_io *io) +{ + if(io == NULL || io->type != IO_STDIO || !(io->mode & (ZTERP_IO_WRONLY | ZTERP_IO_APPEND))) return; + + fflush(io->fp); +} diff --git a/interpreters/bocfel/io.h b/interpreters/bocfel/io.h new file mode 100644 index 0000000..c3b9945 --- /dev/null +++ b/interpreters/bocfel/io.h @@ -0,0 +1,40 @@ +#ifndef ZTERP_IO_H +#define ZTERP_IO_H + +#include +#include + +typedef struct zterp_io zterp_io; + +#define ZTERP_IO_DATA 0x00 +#define ZTERP_IO_SAVE 0x01 +#define ZTERP_IO_TRANS 0x02 +#define ZTERP_IO_INPUT 0x04 +#define ZTERP_IO_RDONLY 0x08 +#define ZTERP_IO_WRONLY 0x10 +#define ZTERP_IO_APPEND 0x20 + +/* This variable controls whether the IO system writes UTF-8 or + * Latin-1; it is distinct from Glk’s Unicode setting. + * If this is set, transcripts will be written in UTF-8, and if + * Glk is not being used, screen output will be written in UTF-8. + */ +extern int use_utf8_io; + +zterp_io *zterp_io_open(const char *, int); +const zterp_io *zterp_io_stdin(void); +const zterp_io *zterp_io_stdout(void); +void zterp_io_close(zterp_io *); +int zterp_io_seek(const zterp_io *, long, int); +long zterp_io_tell(const zterp_io *); +size_t zterp_io_read(const zterp_io *, void *, size_t); +size_t zterp_io_write(const zterp_io *, const void *, size_t); +int zterp_io_read16(const zterp_io *, uint16_t *); +int zterp_io_read32(const zterp_io *, uint32_t *); +long zterp_io_getc(const zterp_io *); +void zterp_io_putc(const zterp_io *, uint16_t); +long zterp_io_readline(const zterp_io *, uint16_t *, size_t); +long zterp_io_filesize(const zterp_io *); +void zterp_io_flush(const zterp_io *); + +#endif diff --git a/interpreters/bocfel/math.c b/interpreters/bocfel/math.c new file mode 100644 index 0000000..925391d --- /dev/null +++ b/interpreters/bocfel/math.c @@ -0,0 +1,166 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include + +#include "math.h" +#include "branch.h" +#include "process.h" +#include "stack.h" +#include "util.h" +#include "zterp.h" + +void zinc(void) +{ + store_variable(zargs[0], variable(zargs[0]) + 1); +} + +void zdec(void) +{ + store_variable(zargs[0], variable(zargs[0]) - 1); +} + +void znot(void) +{ + store(~zargs[0]); +} + +void zdec_chk(void) +{ + int16_t new; + int16_t val = zargs[1]; + + zdec(); + + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) new = *stack_top_element(); + else new = variable(zargs[0]); + + branch_if(new < val); +} + +void zinc_chk(void) +{ + int16_t new; + int16_t val = zargs[1]; + + zinc(); + + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) new = *stack_top_element(); + else new = variable(zargs[0]); + + branch_if(new > val); +} + +void ztest(void) +{ + branch_if( (zargs[0] & zargs[1]) == zargs[1] ); +} + +void zor(void) +{ + store(zargs[0] | zargs[1]); +} + +void zand(void) +{ + store(zargs[0] & zargs[1]); +} + +void zadd(void) +{ + store(zargs[0] + zargs[1]); +} + +void zsub(void) +{ + store(zargs[0] - zargs[1]); +} + +void zmul(void) +{ + store(zargs[0] * zargs[1]); +} + +void zdiv(void) +{ + ZASSERT(zargs[1] != 0, "divide by zero"); + store((int16_t)zargs[0] / (int16_t)zargs[1]); +} + +void zmod(void) +{ + ZASSERT(zargs[1] != 0, "divide by zero"); + store((int16_t)zargs[0] % (int16_t)zargs[1]); +} + +void zlog_shift(void) +{ + int16_t places = zargs[1]; + + /* Shifting more than 15 bits is undefined (as of Standard 1.1), but + * do the most sensible thing. + */ + if(places < -15 || places > 15) + { + store(0); + return; + } + + if(places < 0) store(zargs[0] >> -places); + else store(zargs[0] << places); +} + +void zart_shift(void) +{ + int16_t number = zargs[0], places = zargs[1]; + + /* Shifting more than 15 bits is undefined (as of Standard 1.1), but + * do the most sensible thing. + */ + if(places < -15 || places > 15) + { + store(number < 0 ? -1 : 0); + return; + } + + /* Shifting a negative value in C has some consequences: + * • Shifting a negative value left is undefined. + * • Shifting a negative value right is implementation defined. + * + * Thus these are done by hand. The Z-machine requires a right-shift + * of a negative value to propagate the sign bit. This is easily + * accomplished by complementing the value (yielding a positive + * number), shifting it right (zero filling), and complementing again + * (flip the shifted-in zeroes to ones). + * + * For a left-shift, the result should presumably be the same as a + * logical shift, so do that. + */ + if(number < 0) + { + if(places < 0) store(~(~number >> -places)); + else store(zargs[0] << places); + } + else + { + if(places < 0) store(zargs[0] >> -places); + else store(zargs[0] << places); + } +} diff --git a/interpreters/bocfel/math.h b/interpreters/bocfel/math.h new file mode 100644 index 0000000..94c427a --- /dev/null +++ b/interpreters/bocfel/math.h @@ -0,0 +1,20 @@ +#ifndef ZTERP_MATH_H +#define ZTERP_MATH_H + +void zinc(void); +void zdec(void); +void znot(void); +void zdec_chk(void); +void zinc_chk(void); +void ztest(void); +void zor(void); +void zand(void); +void zadd(void); +void zsub(void); +void zmul(void); +void zdiv(void); +void zmod(void); +void zlog_shift(void); +void zart_shift(void); + +#endif diff --git a/interpreters/bocfel/memory.c b/interpreters/bocfel/memory.c new file mode 100644 index 0000000..e234b88 --- /dev/null +++ b/interpreters/bocfel/memory.c @@ -0,0 +1,76 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include + +#include "memory.h" +#include "screen.h" +#include "util.h" +#include "zterp.h" + +uint8_t *memory, *dynamic_memory; +uint32_t memory_size; + +void user_store_byte(uint16_t addr, uint8_t v) +{ + /* If safety checks are off, there’s no point in checking these + * special cases. */ +#ifndef ZTERP_NO_SAFETY_CHECKS +#ifdef ZTERP_TANDY + if(addr == 0x01) + { + ZASSERT(v == BYTE(addr) || (BYTE(addr) ^ v) == 8, "not allowed to modify any bits but 3 at 0x0001"); + } + else +#endif + + /* 0x10 can’t be modified, but let it slide if the story is storing + * the same value that’s already there. This is useful because the + * flags at 0x10 are stored in a word, so the story possibly could use + * @storew at 0x10 to modify the bits in 0x11. + */ + if(addr == 0x10 && BYTE(addr) == v) + { + return; + } + else +#endif + + if(addr == 0x11) + { + ZASSERT((BYTE(addr) ^ v) < 8, "not allowed to modify bits 3-7 at 0x0011"); + + if(!output_stream((v & FLAGS2_TRANSCRIPT) ? OSTREAM_SCRIPT : -OSTREAM_SCRIPT, 0)) v &= ~FLAGS2_TRANSCRIPT; + + header_fixed_font = v & FLAGS2_FIXED; + set_current_style(); + } + + else + { + ZASSERT(addr >= 0x40 && addr < header.static_start, "attempt to write to read-only address 0x%lx", (unsigned long)addr); + } + + STORE_BYTE(addr, v); +} + +void user_store_word(uint16_t addr, uint16_t v) +{ + user_store_byte(addr + 0, v >> 8); + user_store_byte(addr + 1, v & 0xff); +} diff --git a/interpreters/bocfel/memory.h b/interpreters/bocfel/memory.h new file mode 100644 index 0000000..a53dee3 --- /dev/null +++ b/interpreters/bocfel/memory.h @@ -0,0 +1,55 @@ +#ifndef ZTERP_MEMORY_H +#define ZTERP_MEMORY_H + +#include + +#include "util.h" +#include "zterp.h" + +/* Story files do not have access to memory beyond 64K. If they do + * something that would cause such access, wrap appropriately. This is + * the approach Frotz uses (at least for @loadw/@loadb), and is endorsed + * by Andrew Plotkin (see http://www.intfiction.org/forum/viewtopic.php?f=38&t=2052). + * The standard isn’t exactly clear on the issue, and this appears to be + * the most sensible way to deal with the problem. + */ + +extern uint8_t *memory, *dynamic_memory; +extern uint32_t memory_size; + +#define BYTE(addr) (memory[addr]) +#define STORE_BYTE(addr, val) ((void)(memory[addr] = (val))) + +static inline uint16_t WORD(uint32_t addr) +{ +#ifndef ZTERP_NO_CHEAT + uint16_t cheat_val; + if(cheat_find_freezew(addr, &cheat_val)) return cheat_val; +#endif + return (memory[addr] << 8) | memory[addr + 1]; +} + +static inline void STORE_WORD(uint32_t addr, uint16_t val) +{ + memory[addr + 0] = val >> 8; + memory[addr + 1] = val & 0xff; +} + +static inline uint8_t user_byte(uint16_t addr) +{ + ZASSERT(addr < header.static_end, "attempt to read out-of-bounds address 0x%lx", (unsigned long)addr); + + return BYTE(addr); +} + +static inline uint16_t user_word(uint16_t addr) +{ + ZASSERT(addr < header.static_end - 1, "attempt to read out-of-bounds address 0x%lx", (unsigned long)addr); + + return WORD(addr); +} + +void user_store_byte(uint16_t, uint8_t); +void user_store_word(uint16_t, uint16_t); + +#endif diff --git a/interpreters/bocfel/objects.c b/interpreters/bocfel/objects.c new file mode 100644 index 0000000..ae68839 --- /dev/null +++ b/interpreters/bocfel/objects.c @@ -0,0 +1,436 @@ +/*- + * Copyright 2009-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "objects.h" +#include "branch.h" +#include "memory.h" +#include "process.h" +#include "screen.h" +#include "util.h" +#include "zterp.h" + +static uint16_t OBJECT(uint16_t n) +{ + /* Use 32-bit arithmetic to detect 16-bit overflow. */ + uint32_t base = header.objects, obj = n, addr; + int objsize; + + if(zversion <= 3) + { + ZASSERT(n <= 255, "illegal object %u referenced", (unsigned)n); + addr = base + (31 * 2) + (9 * (obj - 1)); + objsize = 9; + } + else + { + addr = base + (63 * 2) + (14 * (obj - 1)); + objsize = 14; + } + + ZASSERT(addr + objsize < header.static_start, "object %u out of range", (unsigned)n); + + return addr; +} + +#define OFFSET_PARENT (zversion <= 3 ? 4 : 6) +#define OFFSET_SIBLING (zversion <= 3 ? 5 : 8) +#define OFFSET_CHILD (zversion <= 3 ? 6 : 10) +#define OFFSET_PROP (zversion <= 3 ? 7 : 12) + +#define PARENT(object) RELATION(object, OFFSET_PARENT) +#define SIBLING(object) RELATION(object, OFFSET_SIBLING) +#define CHILD(object) RELATION(object, OFFSET_CHILD) + +#define SET_PARENT(obj1, obj2) SET_OBJECT(obj1, obj2, OFFSET_PARENT) +#define SET_SIBLING(obj1, obj2) SET_OBJECT(obj1, obj2, OFFSET_SIBLING) +#define SET_CHILD(obj1, obj2) SET_OBJECT(obj1, obj2, OFFSET_CHILD) + +static uint16_t PROPADDR(uint16_t n) +{ + return WORD(OBJECT(n) + OFFSET_PROP); +} + +static uint16_t RELATION(uint16_t object, int offset) +{ + return zversion <= 3 ? BYTE(OBJECT(object) + offset) : WORD(OBJECT(object) + offset); +} + +/* + * the 32 attribute flags parent sibling child properties + * ---32 bits in 4 bytes--- ---3 bytes------------------ ---2 bytes-- + * + * the 48 attribute flags parent sibling child properties + * ---48 bits in 6 bytes--- ---3 words, i.e. 6 bytes---- ---2 bytes-- + */ +static void SET_OBJECT(uint16_t obj1, uint16_t obj2, int offset) +{ + if(zversion <= 3) STORE_BYTE(OBJECT(obj1) + offset, obj2); + else STORE_WORD(OBJECT(obj1) + offset, obj2); +} + +static void remove_obj(uint16_t object) +{ + uint16_t parent = PARENT(object); + + if(parent != 0) + { + uint16_t child = CHILD(parent); + + /* Direct child */ + if(child == object) + { + /* parent->child = parent->child->sibling */ + SET_CHILD(parent, SIBLING(child)); + } + else + { + while(SIBLING(child) != object) + { + /* child = child->sibling */ + child = SIBLING(child); + } + + /* Now the sibling of child is the object to remove. */ + + /* child->sibling = child->sibling->sibling */ + SET_SIBLING(child, SIBLING(SIBLING(child))); + } + + /* object->parent = 0 */ + SET_PARENT(object, 0); + + /* object->sibling = 0 */ + SET_SIBLING(object, 0); + } +} + +static uint16_t property_length(uint16_t propaddr) +{ + uint16_t length; + /* The address is to the data; the size byte is right before. */ + uint8_t byte = user_byte(propaddr - 1); + + if(zversion <= 3) + { + length = (byte >> 5) + 1; + } + else + { + if(byte & 0x80) + { + length = byte & 0x3f; + if(length == 0) length = 64; + } + else + { + length = (byte & 0x40) ? 2 : 1; + } + } + + return length; +} + +static uint8_t PROPERTY(uint16_t addr) +{ + uint8_t propnum; + + if(zversion <= 3) + { + propnum = user_byte(addr - 1) & 0x1f; + } + else + { + if(user_byte(addr - 1) & 0x80) propnum = user_byte(addr - 2) & 0x3f; + else propnum = user_byte(addr - 1) & 0x3f; + } + + return propnum; +} + +static uint16_t advance_prop_addr(uint16_t propaddr) +{ + uint8_t size; + + size = user_byte(propaddr++); + + if(size == 0) return 0; + + if(zversion >= 4 && (size & 0x80)) propaddr++; + + return propaddr; +} + +static uint16_t first_property(uint16_t object) +{ + uint16_t propaddr = PROPADDR(object); + + propaddr += (2 * user_byte(propaddr)) + 1; + + return advance_prop_addr(propaddr); +} + +static uint16_t next_property(uint16_t propaddr) +{ + propaddr += property_length(propaddr); + + return advance_prop_addr(propaddr); +} + +#define FOR_EACH_PROPERTY(object, addr) for(uint16_t addr = first_property(object); addr != 0; addr = next_property(addr)) + +static int find_prop(uint16_t object, uint16_t property, uint16_t *propaddr, uint16_t *length) +{ + FOR_EACH_PROPERTY(object, addr) + { + if(PROPERTY(addr) == property) + { + *propaddr = addr; + *length = property_length(addr); + return 1; + } + } + + return 0; +} + +static void check_attr(uint16_t attr) +{ + ZASSERT(attr <= (zversion <= 3 ? 31 : 47), "invalid attribute: %u", (unsigned)attr); +} + +static int is_zero(int is_store, int is_jump) +{ + if(zargs[0] == 0) + { + if(is_store) store(0); + if(is_jump) branch_if(0); + + return 1; + } + + return 0; +} + +#define check_zero(store, jump) do { if(is_zero(store, jump)) return; } while(0) + +/* Attributes are stored at the very beginning of an object, so the + * address OBJECT() returns refers directly to the attributes. The + * leftmost bit is attribute 0. Thus these attribute functions need to + * find out first which byte of the attributes to look at; this is done + * by dividing by 8. Attributes 0-7 will be in byte 0, 8-15 in byte 1, + * and so on. Then the particular bit is found. Attributes 0..7 are + * bits 7..0, attributes 8..15 are 7..0, and so on. Taking the + * remainder of the attribute divided by 8 gives the bit position, + * counting from the left, of the attribute. + */ +#define ATTR_BIT(num) (0x80U >> ((num) % 8)) +void ztest_attr(void) +{ + check_zero(0, 1); + check_attr(zargs[1]); + + uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8); + + branch_if(BYTE(addr) & ATTR_BIT(zargs[1])); +} + +void zset_attr(void) +{ + check_zero(0, 0); + check_attr(zargs[1]); + + uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8); + + STORE_BYTE(addr, BYTE(addr) | ATTR_BIT(zargs[1])); +} + +void zclear_attr(void) +{ + check_zero(0, 0); + check_attr(zargs[1]); + + uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8); + + STORE_BYTE(addr, BYTE(addr) & ~ATTR_BIT(zargs[1])); +} +#undef ATTR_BIT + +void zremove_obj(void) +{ + check_zero(0, 0); + + remove_obj(zargs[0]); +} + +void zinsert_obj(void) +{ + check_zero(0, 0); + + remove_obj(zargs[0]); + + SET_SIBLING(zargs[0], CHILD(zargs[1])); + SET_CHILD(zargs[1], zargs[0]); + SET_PARENT(zargs[0], zargs[1]); +} + +void zget_sibling(void) +{ + check_zero(1, 1); + + uint16_t sibling = SIBLING(zargs[0]); + + store(sibling); + branch_if(sibling != 0); +} + +void zget_child(void) +{ + check_zero(1, 1); + + uint16_t child = CHILD(zargs[0]); + + store(child); + branch_if(child != 0); +} + +void zget_parent(void) +{ + check_zero(1, 0); + + store(PARENT(zargs[0])); +} + +void zput_prop(void) +{ + check_zero(0, 0); + + uint16_t propaddr, length; + int found; + + found = find_prop(zargs[0], zargs[1], &propaddr, &length); + + ZASSERT(found, "broken story: no prop"); + ZASSERT(length == 1 || length == 2, "broken story: property too long: %u", (unsigned)length); + + if(length == 1) user_store_byte(propaddr, zargs[2] & 0xff); + else user_store_word(propaddr, zargs[2]); +} + +void zget_prop(void) +{ + check_zero(1, 0); + + uint16_t propaddr, length; + + if(find_prop(zargs[0], zargs[1], &propaddr, &length)) + { + if (length == 1) store(user_byte(propaddr)); + else if(length == 2) store(user_word(propaddr)); + + /* If the length is > 2, the story file is misbehaving. At least + * Christminster does this, and Frotz and Nitfol allow it, reading a + * word, so do that here. + */ + else store(user_word(propaddr)); + } + else + { + uint32_t i; + + ZASSERT(zargs[1] < (zversion <= 3 ? 32 : 64), "invalid property: %u", (unsigned)zargs[1]); + + i = header.objects + (2 * (zargs[1] - 1)); + store(WORD(i)); + } +} + +void zget_prop_len(void) +{ + /* Z-spec 1.1 says @get_prop_len 0 must yield 0. */ + if(zargs[0] == 0) store(0); + else store(property_length(zargs[0])); +} + +void zget_prop_addr(void) +{ + check_zero(1, 0); + + uint16_t propaddr, length; + + if(find_prop(zargs[0], zargs[1], &propaddr, &length)) store(propaddr); + else store(0); +} + +void zget_next_prop(void) +{ + check_zero(1, 0); + + uint16_t object = zargs[0], property = zargs[1]; + int next = 0; + int found_prop = 0; + + FOR_EACH_PROPERTY(object, propaddr) + { + uint8_t propnum = PROPERTY(propaddr); + + if(property == 0 || next) + { + found_prop = propnum; + break; + } + + if(propnum == property) next = 1; + } + + store(found_prop); +} + +void zjin(void) +{ + /* @jin 0 0 is not defined, since @jin requires an object (§15) and + * object 0 is not actually an object (§12.3). However, many + * interpreters yield a true value for this, and Torbjorn Andersson’s + * strictz tester expects it to be true, so go with the flow. + */ + if(zargs[0] == 0 && zargs[1] == 0) + { + branch_if(1); + return; + } + + check_zero(0, 1); + + branch_if(PARENT(zargs[0]) == zargs[1]); +} + +void print_object(uint16_t obj, void (*outc)(uint8_t)) +{ + if(obj == 0) return; + + print_handler(PROPADDR(obj) + 1, outc); +} + +void zprint_obj(void) +{ + check_zero(0, 0); + + print_object(zargs[0], NULL); +} diff --git a/interpreters/bocfel/objects.h b/interpreters/bocfel/objects.h new file mode 100644 index 0000000..eb532ab --- /dev/null +++ b/interpreters/bocfel/objects.h @@ -0,0 +1,24 @@ +#ifndef OBJECTS_H +#define OBJECTS_H + +#include + +void print_object(uint16_t, void (*)(uint8_t)); + +void zget_sibling(void); +void zget_child(void); +void zget_parent(void); +void zremove_obj(void); +void ztest_attr(void); +void zset_attr(void); +void zclear_attr(void); +void zinsert_obj(void); +void zget_prop_len(void); +void zget_prop_addr(void); +void zget_next_prop(void); +void zput_prop(void); +void zget_prop(void); +void zjin(void); +void zprint_obj(void); + +#endif diff --git a/interpreters/bocfel/osdep.c b/interpreters/bocfel/osdep.c new file mode 100644 index 0000000..454c1b2 --- /dev/null +++ b/interpreters/bocfel/osdep.c @@ -0,0 +1,312 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#ifdef ZTERP_UNIX +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include + +#include "osdep.h" +#include "screen.h" + +/* OS-specific functions should all be collected in this file for + * convenience. A sort of poor-man’s “reverse” inheritance is used: for + * each function that a particular operating system provides, it should + * #define a macro of the same name. At the end of the file, a generic + * function is provided for each function that has no associated macro + * definition. + * + * The functions required are as follows: + * + * long zterp_os_filesize(FILE *fp) + * + * Return the size of the file referred to by fp. It is safe to assume + * that the file is opened in binary mode. The file position indicator + * need not be maintained. If the size of the file is larger than + * LONG_MAX, -1 should be returned. + * + * int zterp_os_have_unicode(void) + * + * The main purpose behind this function is to indicate whether + * transcripts will be written in UTF-8 or Latin-1. This is, of course, + * not necessarily an OS matter, but I’ve run into some issues with + * UTF-8 and Windows (at least through Wine), so I want to be as + * sensible as I can with the defaults. The user is able to override + * this value if he so desires. + * If a Glk build is not being used, this function also serves to + * indicate whether all I/O, not just transcripts, should be UTF-8 or + * not. Glk libraries are able to be queried as to their support for + * Unicode so there is no need to make assumptions in that case. + * + * void zterp_os_rcfile(char *s, size_t n) + * + * Different operating systems have different ideas about where + * configuration data should be stored; this function will copy a + * suitable value for the bocfel configuration file into the buffer s + * which is n bytes long. + * + * void zterp_os_reopen_binary(FILE *fp) + * + * Writing UTF-8 requires that no mangling be done, such as might happen + * when a stream is opened in text mode. This function should, if + * necessary, set the mode on the file pointer in fp to be binary. + * + * The following functions are useful for non-Glk builds only. They + * provide for some handling of screen functions that is normally taken + * care of by Glk. + * + * void zterp_os_get_screen_size(unsigned *w, unsigned *h) + * + * The size of the terminal, if known, is written into *w (width) and *h + * (height). If terminal size is unavalable, nothing should be written. + * + * void zterp_os_init_term(void) + * + * If something special needs to be done to prepare the terminal for + * output, it should be done here. This function is called once at + * program startup. + * + * int zterp_os_have_style(int style) + * + * This should return true if the provided style (see style.h for valid + * STYLE_ values) is available. It is safe to assume that styles will + * not be combined; e.g. this will not be called as: + * zterp_os_have_style(STYLE_BOLD | STYLE_ITALIC); + * + * int zterp_os_have_colors(void) + * + * Returns true if the terminal supports colors. + * + * void zterp_os_set_style(int style, int fg, int bg) + * + * Set both a style and foreground/background color. Any previous + * settings should be ignored; for example, if the last call to + * zterp_os_set_style() turned on italics and the current call sets + * bold, the result should be bold, not bold italic. + * Unlike in zterp_os_have_style(), here styles may be combined. See + * the Unix implementation for a reference. + * The colors are Z-machine colors (see §8.3.1), with the following + * note: the only color values that will ever be passed in are 1–9. + */ + +/****************** + * Unix functions * + ******************/ +#ifdef ZTERP_UNIX +#include + +long zterp_os_filesize(FILE *fp) +{ + struct stat buf; + int fd = fileno(fp); + + if(fd == -1 || fstat(fd, &buf) == -1 || !S_ISREG(buf.st_mode) || buf.st_size > LONG_MAX) return -1; + + return buf.st_size; +} +#define zterp_os_filesize + +int zterp_os_have_unicode(void) +{ + return 1; +} +#define zterp_os_have_unicode + +void zterp_os_rcfile(char *s, size_t n) +{ + snprintf(s, n, "%s/.bocfelrc", getenv("HOME") != NULL ? getenv("HOME") : "."); +} +#define zterp_os_rcfile + +#ifndef ZTERP_GLK +#include +#include +#include +#include +#ifdef TIOCGWINSZ +void zterp_os_get_screen_size(unsigned *w, unsigned *h) +{ + struct winsize winsize; + + if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) == 0) + { + *w = winsize.ws_col; + *h = winsize.ws_row; + } +} +#define zterp_os_get_screen_size +#endif + +static const char *ital = NULL, *rev = NULL, *bold = NULL, *none = NULL; +static char *fg_string = NULL, *bg_string = NULL; +static int have_colors = 0; +void zterp_os_init_term(void) +{ + if(setupterm(NULL, STDIN_FILENO, NULL) != OK) return; + + /* prefer italics over underline for emphasized text */ + ital = tgetstr("ZH", NULL); + if(ital == NULL) ital = tgetstr("us", NULL); + rev = tgetstr("mr", NULL); + bold = tgetstr("md", NULL); + none = tgetstr("me", NULL); + + fg_string = tgetstr("AF", NULL); + bg_string = tgetstr("AB", NULL); + + have_colors = none != NULL && fg_string != NULL && bg_string != NULL; +} +#define zterp_os_init_term + +int zterp_os_have_style(int style) +{ + if(none == NULL) return 0; + + if (style == STYLE_ITALIC) return ital != NULL; + else if(style == STYLE_REVERSE) return rev != NULL; + else if(style == STYLE_BOLD) return bold != NULL; + else if(style == STYLE_NONE) return none != NULL; + + return 0; +} +#define zterp_os_have_style + +int zterp_os_have_colors(void) +{ + return have_colors; +} +#define zterp_os_have_colors + +void zterp_os_set_style(int style, int fg, int bg) +{ + /* If the terminal cannot be reset, nothing can be used. */ + if(none == NULL) return; + + putp(none); + + if((style & STYLE_ITALIC) && ital != NULL) putp(ital); + if((style & STYLE_REVERSE) && rev != NULL) putp(rev); + if((style & STYLE_BOLD) && bold != NULL) putp(bold); + + if(have_colors) + { + if(fg > 1) putp(tparm(fg_string, fg - 2, 0, 0, 0, 0, 0, 0, 0, 0)); + if(bg > 1) putp(tparm(bg_string, bg - 2, 0, 0, 0, 0, 0, 0, 0, 0)); + } +} +#define zterp_os_set_style +#endif + +/********************* + * Windows functions * + *********************/ +#elif defined(ZTERP_WIN32) +void zterp_os_rcfile(char *s, size_t n) +{ + char *p; + + p = getenv("APPDATA"); + if(p == NULL) p = getenv("LOCALAPPDATA"); + if(p == NULL) p = "."; + + snprintf(s, n, "%s\\bocfel.ini", p); +} +#define zterp_os_rcfile + +#endif + +/********************* + * Generic functions * + *********************/ +#ifndef zterp_os_filesize +long zterp_os_filesize(FILE *fp) +{ + /* Assume fseek() can seek to the end of binary streams. */ + if(fseek(fp, 0, SEEK_END) == -1) return -1; + + return ftell(fp); +} +#endif + +#ifndef zterp_os_have_unicode +int zterp_os_have_unicode(void) +{ + return 0; +} +#endif + +#ifndef zterp_os_rcfile +void zterp_os_rcfile(char *s, size_t n) +{ + snprintf(s, n, "bocfelrc"); +} +#endif + +/* When UTF-8 output is enabled, special translation of characters (e.g. + * newline) should not be done. Theoretically this function exists to + * set stdin/stdout to binary mode, if necessary. Unix makes no + * text/binary distinction, but Windows does. I’m under the impression + * that there is a setmode() function that should be able to do this, + * but my knowledge of Windows is so small that I do not want to do much + * more than I have, lest I completely break Windows support—assuming it + * even works. + * freopen() should be able to do this, but with my testing under Wine, + * no text gets output in such a case. + */ +#ifndef zterp_os_reopen_binary +void zterp_os_reopen_binary(FILE *fp) +{ +} +#endif + +#ifndef ZTERP_GLK +#ifndef zterp_os_get_screen_size +void zterp_os_get_screen_size(unsigned *w, unsigned *h) +{ +} +#endif + +#ifndef zterp_os_init_term +void zterp_os_init_term(void) +{ +} +#endif + +#ifndef zterp_os_have_style +int zterp_os_have_style(int style) +{ + return 0; +} +#endif + +#ifndef zterp_os_have_colors +int zterp_os_have_colors(void) +{ + return 0; +} +#endif + +#ifndef zterp_os_set_style +void zterp_os_set_style(int style, int fg, int bg) +{ +} +#endif +#endif diff --git a/interpreters/bocfel/osdep.h b/interpreters/bocfel/osdep.h new file mode 100644 index 0000000..de8f062 --- /dev/null +++ b/interpreters/bocfel/osdep.h @@ -0,0 +1,20 @@ +#ifndef ZTERP_OSDEP_H +#define ZTERP_OSDEP_H + +#include +#include + +long zterp_os_filesize(FILE *); +int zterp_os_have_unicode(void); +void zterp_os_rcfile(char *, size_t); +void zterp_os_reopen_binary(FILE *); + +#ifndef ZTERP_GLK +void zterp_os_get_screen_size(unsigned *, unsigned *); +void zterp_os_init_term(void); +int zterp_os_have_style(int); +int zterp_os_have_colors(void); +void zterp_os_set_style(int, int, int); +#endif + +#endif diff --git a/interpreters/bocfel/process.c b/interpreters/bocfel/process.c new file mode 100644 index 0000000..385aee3 --- /dev/null +++ b/interpreters/bocfel/process.c @@ -0,0 +1,416 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#ifdef ZTERP_GLK +#include +#endif + +#include "process.h" +#include "branch.h" +#include "dict.h" +#include "math.h" +#include "memory.h" +#include "objects.h" +#include "random.h" +#include "screen.h" +#include "stack.h" +#include "table.h" +#include "util.h" +#include "zoom.h" +#include "zterp.h" + +uint16_t zargs[8]; +int znargs; + +static jmp_buf *jumps; +static size_t njumps; + +/* Each time an interrupt happens, process_instructions() is called + * (effectively starting a whole new round of interpreting). This + * variable holds the current level of interpreting: 0 for no + * interrupts, 1 if one interrupt has been called, 2 if an interrupt was + * called inside of an interrupt, and so on. + */ +static long ilevel = -1; + +long interrupt_level(void) +{ + return ilevel; +} + +/* When this is called, the interrupt at level “level” will stop + * running: if a single interrupt is running, then break_from(1) will + * stop the interrupt, going back to the main program. Breaking from + * interrupt level 0 (which is not actually an interrupt) will end the + * program. This is how @quit is implemented. + */ +void break_from(long level) +{ + ilevel = level - 1; + longjmp(jumps[level], 1); +} + +/* If a restore happens inside of an interrupt, the level needs to be + * set back to 0, but without a longjmp(), so break_from() cannot be + * used. + */ +void reset_level(void) +{ + ilevel = 0; +} + +/* To signal a restart, longjmp() is called with 2; this advises + * process_instructions() to restart the story file and then continue + * execution, whereas a value of 1 tells it to return immediately. + */ +static void zrestart(void) +{ + ilevel = 0; + longjmp(jumps[0], 2); +} + +/* Returns 1 if decoded, 0 otherwise (omitted) */ +static int decode_base(uint8_t type, uint16_t *loc) +{ + if (type == 0) *loc = WORD(pc++); /* Large constant. */ + else if(type == 1) *loc = BYTE(pc); /* Small constant. */ + else if(type == 2) *loc = variable(BYTE(pc)); /* Variable. */ + else return 0; /* Omitted. */ + + pc++; + + return 1; +} + +static void decode_var(uint8_t types) +{ + uint16_t ret; + + if(!decode_base((types >> 6) & 0x03, &ret)) return; + zargs[znargs++] = ret; + if(!decode_base((types >> 4) & 0x03, &ret)) return; + zargs[znargs++] = ret; + if(!decode_base((types >> 2) & 0x03, &ret)) return; + zargs[znargs++] = ret; + if(!decode_base((types >> 0) & 0x03, &ret)) return; + zargs[znargs++] = ret; +} + +/* op[0] is 0OP, op[1] is 1OP, etc */ +static void (*op[5][256])(void); +static const char *opnames[5][256]; +enum { ZERO, ONE, TWO, VAR, EXT }; + +#define op_call(opt, opnumber) (op[opt][opnumber]()) + +/* This nifty trick is from Frotz. */ +static void zextended(void) +{ + uint8_t opnumber = BYTE(pc++); + + decode_var(BYTE(pc++)); + + /* §14.2.1 + * The exception for 0x80–0x83 is for the Zoom extensions. + * Standard 1.1 implicitly updates §14.2.1 to recommend ignoring + * opcodes in the range EXT:30 to EXT:255, due to the fact that + * @buffer_screen is EXT:29. + */ + if(opnumber > 0x1d && (opnumber < 0x80 || opnumber > 0x83)) return; + + op_call(EXT, opnumber); +} + +static void illegal_opcode(void) +{ +#ifndef ZTERP_NO_SAFETY_CHECKS + die("illegal opcode (pc = 0x%lx)", zassert_pc); +#else + die("illegal opcode"); +#endif +} + +void setup_opcodes(void) +{ + for(int i = 0; i < 5; i++) + { + for(int j = 0; j < 256; j++) + { + op[i][j] = illegal_opcode; + } + } +#define OP(args, opcode, fn) do { op[args][opcode] = fn; opnames[args][opcode] = #fn; } while(0) + OP(ZERO, 0x00, zrtrue); + OP(ZERO, 0x01, zrfalse); + OP(ZERO, 0x02, zprint); + OP(ZERO, 0x03, zprint_ret); + OP(ZERO, 0x04, znop); + if(zversion <= 4) OP(ZERO, 0x05, zsave); + if(zversion <= 4) OP(ZERO, 0x06, zrestore); + OP(ZERO, 0x07, zrestart); + OP(ZERO, 0x08, zret_popped); + if(zversion <= 4) OP(ZERO, 0x09, zpop); + else OP(ZERO, 0x09, zcatch); + OP(ZERO, 0x0a, zquit); + OP(ZERO, 0x0b, znew_line); + if (zversion == 3) OP(ZERO, 0x0c, zshow_status); + else if(zversion >= 4) OP(ZERO, 0x0c, znop); /* §15: Technically illegal in V4+, but a V5 Wishbringer accidentally uses this opcode. */ + if(zversion >= 3) OP(ZERO, 0x0d, zverify); + if(zversion >= 5) OP(ZERO, 0x0e, zextended); + if(zversion >= 5) OP(ZERO, 0x0f, zpiracy); + + OP(ONE, 0x00, zjz); + OP(ONE, 0x01, zget_sibling); + OP(ONE, 0x02, zget_child); + OP(ONE, 0x03, zget_parent); + OP(ONE, 0x04, zget_prop_len); + OP(ONE, 0x05, zinc); + OP(ONE, 0x06, zdec); + OP(ONE, 0x07, zprint_addr); + if(zversion >= 4) OP(ONE, 0x08, zcall_1s); + OP(ONE, 0x09, zremove_obj); + OP(ONE, 0x0a, zprint_obj); + OP(ONE, 0x0b, zret); + OP(ONE, 0x0c, zjump); + OP(ONE, 0x0d, zprint_paddr); + OP(ONE, 0x0e, zload); + if(zversion <= 4) OP(ONE, 0x0f, znot); + else OP(ONE, 0x0f, zcall_1n); + + OP(TWO, 0x01, zje); + OP(TWO, 0x02, zjl); + OP(TWO, 0x03, zjg); + OP(TWO, 0x04, zdec_chk); + OP(TWO, 0x05, zinc_chk); + OP(TWO, 0x06, zjin); + OP(TWO, 0x07, ztest); + OP(TWO, 0x08, zor); + OP(TWO, 0x09, zand); + OP(TWO, 0x0a, ztest_attr); + OP(TWO, 0x0b, zset_attr); + OP(TWO, 0x0c, zclear_attr); + OP(TWO, 0x0d, zstore); + OP(TWO, 0x0e, zinsert_obj); + OP(TWO, 0x0f, zloadw); + OP(TWO, 0x10, zloadb); + OP(TWO, 0x11, zget_prop); + OP(TWO, 0x12, zget_prop_addr); + OP(TWO, 0x13, zget_next_prop); + OP(TWO, 0x14, zadd); + OP(TWO, 0x15, zsub); + OP(TWO, 0x16, zmul); + OP(TWO, 0x17, zdiv); + OP(TWO, 0x18, zmod); + if(zversion >= 4) OP(TWO, 0x19, zcall_2s); + if(zversion >= 5) OP(TWO, 0x1a, zcall_2n); + if(zversion >= 5) OP(TWO, 0x1b, zset_colour); + if(zversion >= 5) OP(TWO, 0x1c, zthrow); + + OP(VAR, 0x00, zcall); + OP(VAR, 0x01, zstorew); + OP(VAR, 0x02, zstoreb); + OP(VAR, 0x03, zput_prop); + OP(VAR, 0x04, zread); + OP(VAR, 0x05, zprint_char); + OP(VAR, 0x06, zprint_num); + OP(VAR, 0x07, zrandom); + OP(VAR, 0x08, zpush); + OP(VAR, 0x09, zpull); + if(zversion >= 3) OP(VAR, 0x0a, zsplit_window); + if(zversion >= 3) OP(VAR, 0x0b, zset_window); + if(zversion >= 4) OP(VAR, 0x0c, zcall_vs2); + if(zversion >= 4) OP(VAR, 0x0d, zerase_window); + if(zversion >= 4) OP(VAR, 0x0e, zerase_line); + if(zversion >= 4) OP(VAR, 0x0f, zset_cursor); + if(zversion >= 4) OP(VAR, 0x10, zget_cursor); + if(zversion >= 4) OP(VAR, 0x11, zset_text_style); + if(zversion >= 4) OP(VAR, 0x12, znop); /* XXX buffer_mode */ + if(zversion >= 3) OP(VAR, 0x13, zoutput_stream); + if(zversion >= 3) OP(VAR, 0x14, zinput_stream); + if(zversion >= 3) OP(VAR, 0x15, zsound_effect); + if(zversion >= 4) OP(VAR, 0x16, zread_char); + if(zversion >= 4) OP(VAR, 0x17, zscan_table); + if(zversion >= 5) OP(VAR, 0x18, znot); + if(zversion >= 5) OP(VAR, 0x19, zcall_vn); + if(zversion >= 5) OP(VAR, 0x1a, zcall_vn2); + if(zversion >= 5) OP(VAR, 0x1b, ztokenise); + if(zversion >= 5) OP(VAR, 0x1c, zencode_text); + if(zversion >= 5) OP(VAR, 0x1d, zcopy_table); + if(zversion >= 5) OP(VAR, 0x1e, zprint_table); + if(zversion >= 5) OP(VAR, 0x1f, zcheck_arg_count); + + if(zversion >= 5) OP(EXT, 0x00, zsave5); + if(zversion >= 5) OP(EXT, 0x01, zrestore5); + if(zversion >= 5) OP(EXT, 0x02, zlog_shift); + if(zversion >= 5) OP(EXT, 0x03, zart_shift); + if(zversion >= 5) OP(EXT, 0x04, zset_font); + if(zversion >= 6) OP(EXT, 0x05, znop); /* XXX draw_picture */ + if(zversion >= 6) OP(EXT, 0x06, zpicture_data); + if(zversion >= 6) OP(EXT, 0x07, znop); /* XXX erase_picture */ + if(zversion >= 6) OP(EXT, 0x08, znop); /* XXX set_margins */ + if(zversion >= 5) OP(EXT, 0x09, zsave_undo); + if(zversion >= 5) OP(EXT, 0x0a, zrestore_undo); + if(zversion >= 5) OP(EXT, 0x0b, zprint_unicode); + if(zversion >= 5) OP(EXT, 0x0c, zcheck_unicode); + if(zversion >= 5) OP(EXT, 0x0d, zset_true_colour); + if(zversion >= 6) OP(EXT, 0x10, znop); /* XXX move_window */ + if(zversion >= 6) OP(EXT, 0x11, znop); /* XXX window_size */ + if(zversion >= 6) OP(EXT, 0x12, znop); /* XXX window_style */ + if(zversion >= 6) OP(EXT, 0x13, zget_wind_prop); + if(zversion >= 6) OP(EXT, 0x14, znop); /* XXX scroll_window */ + if(zversion >= 6) OP(EXT, 0x15, zpop_stack); + if(zversion >= 6) OP(EXT, 0x16, znop); /* XXX read_mouse */ + if(zversion >= 6) OP(EXT, 0x17, znop); /* XXX mouse_window */ + if(zversion >= 6) OP(EXT, 0x18, zpush_stack); + if(zversion >= 6) OP(EXT, 0x19, znop); /* XXX put_wind_prop */ + if(zversion >= 6) OP(EXT, 0x1a, zprint_form); + if(zversion >= 6) OP(EXT, 0x1b, zmake_menu); + if(zversion >= 6) OP(EXT, 0x1c, znop); /* XXX picture_table */ + if(zversion >= 6) OP(EXT, 0x1d, zbuffer_screen); + + /* Zoom extensions. */ + OP(EXT, 0x80, zstart_timer); + OP(EXT, 0x81, zstop_timer); + OP(EXT, 0x82, zread_timer); + OP(EXT, 0x83, zprint_timer); +#undef OP +} + +void process_instructions(void) +{ + if(njumps <= ++ilevel) + { + jumps = realloc(jumps, ++njumps * sizeof *jumps); + if(jumps == NULL) die("unable to allocate memory for jump buffer"); + } + + switch(setjmp(jumps[ilevel])) + { + case 1: /* Normal break from interrupt. */ + return; + case 2: /* Special break: a restart was requested. */ + { + /* §6.1.3: Flags2 is preserved on a restart. */ + uint16_t flags2 = WORD(0x10); + + process_story(); + + STORE_WORD(0x10, flags2); + } + break; + } + + while(1) + { + uint8_t opcode; + +#if defined(ZTERP_GLK) && defined(ZTERP_GLK_TICK) + glk_tick(); +#endif + + ZPC(pc); + + opcode = BYTE(pc++); + + /* long 2OP */ + if(opcode < 0x80) + { + znargs = 2; + + if(opcode & 0x40) zargs[0] = variable(BYTE(pc++)); + else zargs[0] = BYTE(pc++); + + if(opcode & 0x20) zargs[1] = variable(BYTE(pc++)); + else zargs[1] = BYTE(pc++); + + op_call(TWO, opcode & 0x1f); + } + + /* short 1OP */ + else if(opcode < 0xb0) + { + znargs = 1; + + if(opcode < 0x90) /* large constant */ + { + zargs[0] = WORD(pc); + pc += 2; + } + else if(opcode < 0xa0) /* small constant */ + { + zargs[0] = BYTE(pc++); + } + else /* variable */ + { + zargs[0] = variable(BYTE(pc++)); + } + + op_call(ONE, opcode & 0x0f); + } + + /* short 0OP (plus EXT) */ + else if(opcode < 0xc0) + { + znargs = 0; + + op_call(ZERO, opcode & 0x0f); + } + + /* variable 2OP */ + else if(opcode < 0xe0) + { + znargs = 0; + + decode_var(BYTE(pc++)); + + op_call(TWO, opcode & 0x1f); + } + + /* Double variable VAR */ + else if(opcode == 0xec || opcode == 0xfa) + { + uint8_t types1, types2; + + znargs = 0; + + types1 = BYTE(pc++); + types2 = BYTE(pc++); + decode_var(types1); + decode_var(types2); + + op_call(VAR, opcode & 0x1f); + } + + /* variable VAR */ + else + { + znargs = 0; + + read_pc = pc - 1; + + decode_var(BYTE(pc++)); + + op_call(VAR, opcode & 0x1f); + } + } +} diff --git a/interpreters/bocfel/process.h b/interpreters/bocfel/process.h new file mode 100644 index 0000000..e98b2c8 --- /dev/null +++ b/interpreters/bocfel/process.h @@ -0,0 +1,16 @@ +#ifndef ZTERP_PROCESS_h +#define ZTERP_PROCESS_H + +#include + +extern uint16_t zargs[]; +extern int znargs; + +void break_from(long); +void reset_level(void); +long interrupt_level(void); + +void setup_opcodes(void); +void process_instructions(void); + +#endif diff --git a/interpreters/bocfel/random.c b/interpreters/bocfel/random.c new file mode 100644 index 0000000..2d15347 --- /dev/null +++ b/interpreters/bocfel/random.c @@ -0,0 +1,162 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "random.h" +#include "process.h" +#include "zterp.h" + +/* Mersenne Twister. */ +static uint32_t mt[624]; +static uint32_t mt_idx = 0; + +static void zterp_srand(uint32_t s) +{ + mt[0] = s; + for(int i = 1; i < 624; i++) + { + mt[i] = 1812433253UL * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i; + } + mt_idx = 0; +} + +static void mt_gen_num(void) +{ + for(int i = 0; i < 624; i++) + { + uint32_t y; + + y = mt[i] & 0x80000000UL; + y |= (mt[(i + 1) % 624]) & 0x7fffffffUL; + + mt[i] = mt[(i + 397) % 624] ^ (y >> 1); + + if(y % 2 == 1) mt[i] ^= 2567483615UL; + } +} + +static uint32_t zterp_rand(void) +{ + uint32_t y; + + if(mt_idx == 0) mt_gen_num(); + + y = mt[mt_idx]; + y ^= (y >> 11); + y ^= (y << 7) & 2636928640UL; + y ^= (y << 15) & 4022730752UL; + y ^= (y >> 18); + + mt_idx = (mt_idx + 1) % 624; + + return y; +} + +static int rng_interval = 0; +static int rng_counter = 0; + +/* Called with 0, seed the PRNG with either + * a) a user-provided seed (via -z) if available, or + * b) a seed read from a user-provided file/device (via -Z) if + * available, or + * c) a seed derived from a hash of the constituent bytes of the value + * returned by time(NULL) + * + * Called with a value 0 < S < 1000, generate a string of numbers 1, 2, + * 3, ..., S, 1, 2, 3, ... S, ... as recommended in the remarks to §2. + * + * Called with a value >= 1000, use that value as a normal seed. + */ +void seed_random(long value) +{ + if(value == 0) + { + if(options.random_seed == -1) + { + time_t t = time(NULL); + unsigned char *p = (unsigned char *)&t; + uint32_t s = 0; + + /* time_t hashing based on code by Lawrence Kirby. */ + for(size_t i = 0; i < sizeof t; i++) s = s * (UCHAR_MAX + 2U) + p[i]; + + if(options.random_device != NULL) + { + FILE *fp; + uint32_t temp; + + fp = fopen(options.random_device, "r"); + if(fp != NULL) + { + if(fread(&temp, sizeof temp, 1, fp) == 1) s = temp; + fclose(fp); + } + } + + zterp_srand(s); + } + else + { + zterp_srand(options.random_seed); + } + + rng_interval = 0; + } + else if(value < 1000) + { + rng_counter = 0; + rng_interval = value; + } + else + { + zterp_srand(value); + rng_interval = 0; + } +} + +void zrandom(void) +{ + long v = (int16_t)zargs[0]; + + if(v <= 0) + { + seed_random(-v); + store(0); + } + else + { + uint32_t res; + + if(rng_interval != 0) + { + res = rng_counter++; + if(rng_counter == rng_interval) rng_counter = 0; + } + else + { + res = zterp_rand(); + } + + store(res % zargs[0] + 1); + } +} diff --git a/interpreters/bocfel/random.h b/interpreters/bocfel/random.h new file mode 100644 index 0000000..5968432 --- /dev/null +++ b/interpreters/bocfel/random.h @@ -0,0 +1,8 @@ +#ifndef ZTERP_MT_H +#define ZTERP_MT_H + +void seed_random(long); + +void zrandom(void); + +#endif diff --git a/interpreters/bocfel/screen.c b/interpreters/bocfel/screen.c new file mode 100644 index 0000000..35feb56 --- /dev/null +++ b/interpreters/bocfel/screen.c @@ -0,0 +1,2475 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#ifdef ZTERP_GLK +#include +#include +#endif + +#include "screen.h" +#include "branch.h" +#include "dict.h" +#include "io.h" +#include "memory.h" +#include "objects.h" +#include "osdep.h" +#include "process.h" +#include "stack.h" +#include "unicode.h" +#include "util.h" +#include "zterp.h" + +static struct window +{ + unsigned style; + + enum font { FONT_NONE = -1, FONT_PREVIOUS, FONT_NORMAL, FONT_PICTURE, FONT_CHARACTER, FONT_FIXED } font; + enum font prev_font; + +#ifdef ZTERP_GLK + winid_t id; + long x, y; /* Only meaningful for window 1 */ + int pending_read; + union line + { + char latin1[256]; + glui32 unicode[256]; + } *line; + int has_echo; +#endif +} windows[8], *mainwin = &windows[0], *curwin = &windows[0]; +#ifdef ZTERP_GLK +static struct window *upperwin = &windows[1]; +static struct window statuswin; +static long upper_window_height = 0; +static long upper_window_width = 0; +static winid_t errorwin; +#endif + +/* In all versions but 6, styles are global and stored in mainwin. For + * V6, styles are tracked per window and thus stored in each individual + * window. For convenience, this macro expands to the “style window” + * for any version. + */ +#define style_window (zversion == 6 ? curwin : mainwin) + +/* If the window needs to be temporarily switched (@show_status and + * @print_form print to specific windows, and window_change() might + * temporarily need to switch to the upper window), the code that + * requires a specific window can be wrapped in these macros. + */ +#ifdef ZTERP_GLK +#define SWITCH_WINDOW_START(win) { struct window *saved_ = curwin; curwin = (win); glk_set_window((win)->id); +#define SWITCH_WINDOW_END() curwin = saved_; glk_set_window(curwin->id); } +#else +#define SWITCH_WINDOW_START(win) { struct window *saved_ = curwin; curwin = (win); +#define SWITCH_WINDOW_END() curwin = saved_; } +#endif + +/* Output stream bits. */ +#define STREAM_SCREEN (1U << 1) +#define STREAM_TRANS (1U << 2) +#define STREAM_MEMORY (1U << 3) +#define STREAM_SCRIPT (1U << 4) + +static unsigned int streams = STREAM_SCREEN; +static zterp_io *transio, *scriptio; + +static struct +{ + uint16_t table; + uint16_t i; +} stables[16]; +static int stablei = -1; + +static int istream = ISTREAM_KEYBOARD; +static zterp_io *istreamio; + +struct input +{ + enum { INPUT_CHAR, INPUT_LINE } type; + + /* ZSCII value of key read for @read_char. */ + uint8_t key; + + /* Unicode line of chars read for @read. */ + uint32_t *line; + uint8_t maxlen; + uint8_t len; + uint8_t preloaded; + + /* Character used to terminate input. If terminating keys are not + * supported by the Glk implementation being used (or if Glk is not + * used at all) this will be ZSCII_NEWLINE; or in the case of + * cancellation, 0. + */ + uint8_t term; +}; + +/* This macro makes it so that code elsewhere needn’t check have_unicode before printing. */ +#define GLK_PUT_CHAR(c) do { if(!have_unicode) glk_put_char(unicode_to_latin1[c]); else glk_put_char_uni(c); } while(0) + +void show_message(const char *fmt, ...) +{ + va_list ap; + char message[1024]; + + va_start(ap, fmt); + vsnprintf(message, sizeof message, fmt, ap); + va_end(ap); + +#ifdef ZTERP_GLK + static int error_lines = 0; + + if(errorwin != NULL) + { + glui32 w, h; + + /* Allow multiple messages to stack, but force at least 5 lines to + * always be visible in the main window. This is less than perfect + * because it assumes that each message will be less than the width + * of the screen, but it’s not a huge deal, really; even if the + * lines are too long, at least Gargoyle and glktermw are graceful + * enough. + */ + glk_window_get_size(mainwin->id, &w, &h); + + if(h > 5) glk_window_set_arrangement(glk_window_get_parent(errorwin), winmethod_Below | winmethod_Fixed, ++error_lines, errorwin); + glk_put_char_stream(glk_window_get_stream(errorwin), UNICODE_LINEFEED); + } + else + { + errorwin = glk_window_open(mainwin->id, winmethod_Below | winmethod_Fixed, error_lines = 2, wintype_TextBuffer, 0); + } + + /* If windows are not supported (e.g. in cheapglk), messages will not + * get displayed. If this is the case, print to the main window. + */ + if(errorwin != NULL) + { + glk_set_style_stream(glk_window_get_stream(errorwin), style_Alert); + glk_put_string_stream(glk_window_get_stream(errorwin), message); + } + else + { + SWITCH_WINDOW_START(mainwin); + glk_put_string("\12["); + glk_put_string(message); + glk_put_string("]\12"); + SWITCH_WINDOW_END(); + } +#else + /* In Glk messages go to a separate window, but they're interleaved in + * non-Glk. Put brackets around the message in an attempt to offset + * it from the game a bit. + */ + fprintf(stderr, "\n[%s]\n", message); +#endif +} + +/* See §7. + * This returns true if the stream was successfully selected. + * Deselecting a stream is always successful. + */ +int output_stream(int16_t number, uint16_t table) +{ + if(number > 0) + { + streams |= 1U << number; + } + else if(number < 0) + { + if(number != -3 || stablei == 0) streams &= ~(1U << -number); + } + + if(number == 2) + { + STORE_WORD(0x10, WORD(0x10) | FLAGS2_TRANSCRIPT); + if(transio == NULL) + { + transio = zterp_io_open(options.transcript_name, ZTERP_IO_TRANS | (options.overwrite_transcript ? ZTERP_IO_WRONLY : ZTERP_IO_APPEND)); + if(transio == NULL) + { + STORE_WORD(0x10, WORD(0x10) & ~FLAGS2_TRANSCRIPT); + streams &= ~STREAM_TRANS; + warning("unable to open the transcript"); + } + } + } + else if(number == -2) + { + STORE_WORD(0x10, WORD(0x10) & ~FLAGS2_TRANSCRIPT); + } + + if(number == 3) + { + stablei++; + ZASSERT(stablei < 16, "too many stream tables"); + + stables[stablei].table = table; + user_store_word(stables[stablei].table, 0); + stables[stablei].i = 2; + } + else if(number == -3 && stablei >= 0) + { + user_store_word(stables[stablei].table, stables[stablei].i - 2); + stablei--; + } + + if(number == 4) + { + if(scriptio == NULL) + { + scriptio = zterp_io_open(options.record_name, ZTERP_IO_WRONLY | ZTERP_IO_INPUT); + if(scriptio == NULL) + { + streams &= ~STREAM_SCRIPT; + warning("unable to open the script"); + } + } + } + /* XXX v6 has even more handling */ + + return number < 0 || (streams & (1U << number)); +} + +void zoutput_stream(void) +{ + output_stream(zargs[0], zargs[1]); +} + +/* See §10. + * This returns true if the stream was successfully selected. + */ +int input_stream(int which) +{ + istream = which; + + if(istream == ISTREAM_KEYBOARD) + { + if(istreamio != NULL) + { + zterp_io_close(istreamio); + istreamio = NULL; + } + } + else if(istream == ISTREAM_FILE) + { + if(istreamio == NULL) + { + istreamio = zterp_io_open(options.replay_name, ZTERP_IO_INPUT | ZTERP_IO_RDONLY); + if(istreamio == NULL) + { + warning("unable to open the command script"); + istream = ISTREAM_KEYBOARD; + } + } + } + else + { + ZASSERT(0, "invalid input stream: %d", istream); + } + + return istream == which; +} + +void zinput_stream(void) +{ + input_stream(zargs[0]); +} + +/* This does not even pretend to understand V6 windows. */ +static void set_current_window(struct window *window) +{ + curwin = window; + +#ifdef ZTERP_GLK + if(curwin == upperwin && upperwin->id != NULL) + { + upperwin->x = upperwin->y = 0; + glk_window_move_cursor(upperwin->id, 0, 0); + } + + glk_set_window(curwin->id); +#endif + + set_current_style(); +} + +/* Find and validate a window. If window is -3 and the story is V6, + * return the current window. + */ +static struct window *find_window(uint16_t window) +{ + int16_t w = window; + + ZASSERT(zversion == 6 ? w == -3 || (w >= 0 && w < 8) : w == 0 || w == 1, "invalid window selected: %d", w); + + if(w == -3) return curwin; + + return &windows[w]; +} + +#ifdef ZTERP_GLK +/* When resizing the upper window, the screen’s contents should not + * change (§8.6.1); however, the way windows are handled with Glk makes + * this slightly impossible. When an Inform game tries to display + * something with “box”, it expands the upper window, displays the quote + * box, and immediately shrinks the window down again. This is a + * problem under Glk because the window immediately disappears. Other + * games, such as Bureaucracy, expect the upper window to shrink as soon + * as it has been requested. Thus the following system is used: + * + * If a request is made to shrink the upper window, it is granted + * immediately if there has been user input since the last window resize + * request. If there has not been user input, the request is delayed + * until after the next user input is read. + */ +static long delayed_window_shrink = -1; +static int saw_input; + +static void update_delayed(void) +{ + glui32 height; + + if(delayed_window_shrink == -1 || upperwin->id == NULL) return; + + glk_window_set_arrangement(glk_window_get_parent(upperwin->id), winmethod_Above | winmethod_Fixed, delayed_window_shrink, upperwin->id); + upper_window_height = delayed_window_shrink; + + /* Glk might resize the window to a smaller height than was requested, + * so track the actual height, not the requested height. + */ + glk_window_get_size(upperwin->id, NULL, &height); + if(height != upper_window_height) + { + /* This message probably won’t be seen in a window since the upper + * window is likely covering everything, but try anyway. + */ + show_message("Unable to fulfill window size request: wanted %ld, got %lu", delayed_window_shrink, (unsigned long)height); + upper_window_height = height; + } + + delayed_window_shrink = -1; +} + +/* Both the upper and lower windows have their own issues to deal with + * when there is line input. This function ensures that the cursor + * position is properly tracked in the upper window, and if possible, + * aids in the suppression of newline printing on input cancellation in + * the lower window. + */ +static void cleanup_screen(struct input *input) +{ + if(input->type != INPUT_LINE) return; + + /* If the current window is the upper window, the position of the + * cursor needs to be tracked, so after a line has successfully been + * read, advance the cursor to the initial position of the next line, + * or if a terminating key was used or input was canceled, to the end + * of the input. + */ + if(curwin == upperwin) + { + if(input->term != ZSCII_NEWLINE) upperwin->x += input->len; + + if(input->term == ZSCII_NEWLINE || upperwin->x >= upper_window_width) + { + upperwin->x = 0; + if(upperwin->y < upper_window_height) upperwin->y++; + } + + glk_window_move_cursor(upperwin->id, upperwin->x, upperwin->y); + } + + /* If line input echoing is turned off, newlines will not be printed + * when input is canceled, but neither will the input line. Fix that. + */ + if(curwin->has_echo) + { + glk_set_style(style_Input); + for(int i = 0; i < input->len; i++) GLK_PUT_CHAR(input->line[i]); + if(input->term == ZSCII_NEWLINE) glk_put_char(UNICODE_LINEFEED); + set_current_style(); + } +} + +/* In an interrupt, if the story tries to read or write, the previous + * read event (which triggered the interrupt) needs to be canceled. + * This function does the cancellation. + */ +static void cancel_read_events(struct window *window) +{ + if(window->pending_read) + { + event_t ev; + + glk_cancel_char_event(window->id); + glk_cancel_line_event(window->id, &ev); + + /* If the pending read was a line input, zero terminate the string + * so when it’s re-requested the length of the already-loaded + * portion can be discovered. Also deal with cursor positioning in + * the upper window, and line echoing in the lower window. + */ + if(ev.type == evtype_LineInput && window->line != NULL) + { + uint32_t line[ev.val1]; + struct input input = { .type = INPUT_LINE, .line = line, .term = 0, .len = ev.val1 }; + + if(have_unicode) window->line->unicode[ev.val1] = 0; + else window->line->latin1 [ev.val1] = 0; + + for(int i = 0; i < input.len; i++) + { + if(have_unicode) line[i] = window->line->unicode[i]; + else line[i] = window->line->latin1 [i]; + } + + cleanup_screen(&input); + } + + window->pending_read = 0; + window->line = NULL; + } +} + +static void clear_window(struct window *window) +{ + if(window->id == NULL) return; + + /* glk_window_clear() cannot be used while there are pending read requests. */ + cancel_read_events(window); + + glk_window_clear(window->id); + + window->x = window->y = 0; +} +#endif + +/* If restoring from an interrupt (which is a bad idea to begin with), + * it’s entirely possible that there will be pending read events that + * need to be canceled, so allow that. + */ +void cancel_all_events(void) +{ +#ifdef ZTERP_GLK + for(int i = 0; i < 8; i++) cancel_read_events(&windows[i]); +#endif +} + +static void resize_upper_window(long nlines) +{ +#ifdef ZTERP_GLK + if(upperwin->id == NULL) return; + + /* To avoid code duplication, put all window resizing code in + * update_delayed() and, if necessary, call it from here. + */ + delayed_window_shrink = nlines; + if(upper_window_height <= nlines || saw_input) update_delayed(); + + saw_input = 0; + + /* §8.6.1.1.2 */ + if(zversion == 3) clear_window(upperwin); + + /* As in a few other areas, changing the upper window causes reverse + * video to be deactivated, so reapply the current style. + */ + set_current_style(); +#endif +} + +void close_upper_window(void) +{ + /* The upper window is never destroyed; rather, when it’s closed, it + * shrinks to zero height. + */ + resize_upper_window(0); + +#ifdef ZTERP_GLK + delayed_window_shrink = -1; + saw_input = 0; +#endif + + set_current_window(mainwin); +} + +void get_screen_size(unsigned int *width, unsigned int *height) +{ + *width = 80; + *height = 24; + +#ifdef ZTERP_GLK + glui32 w, h; + + /* The main window can be proportional, and if so, its width is not + * generally useful because games tend to care about width with a + * fixed font. If a status window is available, or if an upper window + * is available, use that to calculate the width, because these + * windows will have a fixed-width font. The height is the combined + * height of all windows. + */ + glk_window_get_size(mainwin->id, &w, &h); + *height = h; + if(statuswin.id != NULL) + { + glk_window_get_size(statuswin.id, &w, &h); + *height += h; + } + if(upperwin->id != NULL) + { + glk_window_get_size(upperwin->id, &w, &h); + *height += h; + } + *width = w; +#else + zterp_os_get_screen_size(width, height); +#endif + + /* XGlk does not report the size of textbuffer windows, so here’s a safety net. */ + if(*width == 0) *width = 80; + if(*height == 0) *height = 24; + + /* Terrible hack: Because V6 is not properly supported, the window to + * which Journey writes its story is completely covered up by window + * 1. For the same reason, only the bottom 6 lines of window 1 are + * actually useful, even though the game expands it to cover the whole + * screen. By pretending that the screen height is only 6, the main + * window, where text is actually sent, becomes visible. + */ + if(is_story("83-890706") && *height > 6) *height = 6; +} + +#ifdef GLK_MODULE_LINE_TERMINATORS +static uint32_t *term_keys, term_size, term_nkeys; + +void term_keys_reset(void) +{ + free(term_keys); + term_keys = NULL; + term_size = 0; + term_nkeys = 0; +} + +static void insert_key(uint32_t key) +{ + if(term_nkeys == term_size) + { + term_size += 32; + + term_keys = realloc(term_keys, term_size * sizeof *term_keys); + if(term_keys == NULL) die("unable to allocate memory for terminating keys"); + } + + term_keys[term_nkeys++] = key; +} + +void term_keys_add(uint8_t key) +{ + switch(key) + { + case 129: insert_key(keycode_Up); break; + case 130: insert_key(keycode_Down); break; + case 131: insert_key(keycode_Left); break; + case 132: insert_key(keycode_Right); break; + case 133: insert_key(keycode_Func1); break; + case 134: insert_key(keycode_Func2); break; + case 135: insert_key(keycode_Func3); break; + case 136: insert_key(keycode_Func4); break; + case 137: insert_key(keycode_Func5); break; + case 138: insert_key(keycode_Func6); break; + case 139: insert_key(keycode_Func7); break; + case 140: insert_key(keycode_Func8); break; + case 141: insert_key(keycode_Func9); break; + case 142: insert_key(keycode_Func10); break; + case 143: insert_key(keycode_Func11); break; + case 144: insert_key(keycode_Func12); break; + + /* Keypad 0–9 should be here, but Glk doesn’t support that. */ + case 145: case 146: case 147: case 148: case 149: + case 150: case 151: case 152: case 153: case 154: + break; + + /* Mouse clicks would go here if I supported them. */ + case 252: case 253: case 254: + break; + + case 255: + for(int i = 129; i <= 144; i++) term_keys_add(i); + break; + + default: + ZASSERT(0, "invalid terminating key: %u", (unsigned)key); + break; + } +} +#endif + +/* Print out a character. The character is in “c” and is either Unicode + * or ZSCII; if the former, “unicode” is true. + */ +static void put_char_base(uint16_t c, int unicode) +{ + if(c == 0) return; + + if(streams & STREAM_MEMORY) + { + ZASSERT(stablei != -1, "invalid stream table"); + + /* When writing to memory, ZSCII should always be used (§7.5.3). */ + if(unicode) c = unicode_to_zscii_q[c]; + + user_store_byte(stables[stablei].table + stables[stablei].i++, c); + } + else + { + /* For screen and transcription, always prefer Unicode. */ + if(!unicode) c = zscii_to_unicode[c]; + + if(c != 0) + { + uint8_t zscii = 0; + + /* §16 makes no mention of what a newline in font 3 should map to. + * Other interpreters that implement font 3 assume it stays a + * newline, and this makes the most sense, so don’t do any + * translation in that case. + */ + if(curwin->font == FONT_CHARACTER && !options.disable_graphics_font && c != UNICODE_LINEFEED) + { + zscii = unicode_to_zscii[c]; + + /* These four characters have a “built-in” reverse video (see §16). */ + if(zscii >= 123 && zscii <= 126) + { + style_window->style ^= STYLE_REVERSE; + set_current_style(); + } + + c = zscii_to_font3[zscii]; + } +#ifdef ZTERP_GLK + if((streams & STREAM_SCREEN) && curwin->id != NULL) + { + cancel_read_events(curwin); + + if(curwin == upperwin) + { + /* Interpreters seem to have differing ideas about what + * happens when the cursor reaches the end of a line in the + * upper window. Some wrap, some let it run off the edge (or, + * at least, stop the text at the edge). The standard, from + * what I can see, says nothing on this issue. Follow Windows + * Frotz and don’t wrap. + */ + + if(c == UNICODE_LINEFEED) + { + if(upperwin->y < upper_window_height) + { + /* Glk wraps, so printing a newline when the cursor has + * already reached the edge of the screen will produce two + * newlines. + */ + if(upperwin->x < upper_window_width) GLK_PUT_CHAR(c); + + /* Even if a newline isn’t explicitly printed here + * (because the cursor is at the edge), setting + * upperwin->x to 0 will cause the next character to be on + * the next line because the text will have wrapped. + */ + upperwin->x = 0; + upperwin->y++; + } + } + else if(upperwin->x < upper_window_width && upperwin->y < upper_window_height) + { + upperwin->x++; + GLK_PUT_CHAR(c); + } + } + else + { + GLK_PUT_CHAR(c); + } + } +#else + if((streams & STREAM_SCREEN) && curwin == mainwin) zterp_io_putc(zterp_io_stdout(), c); +#endif + + /* If the reverse video bit was flipped (for the character font), flip it back. */ + if(zscii >= 123 && zscii <= 126) + { + style_window->style ^= STYLE_REVERSE; + set_current_style(); + } + + if((streams & STREAM_TRANS) && curwin == mainwin) zterp_io_putc(transio, c); + } + } +} + +void put_char_u(uint16_t c) +{ + put_char_base(c, 1); +} + +void put_char(uint8_t c) +{ + put_char_base(c, 0); +} + +static void put_string(const char *s) +{ + for(; *s != 0; s++) + { + if(*s == '\n') put_char(ZSCII_NEWLINE); + else put_char(*s); + } +} + +/* Decode and print a zcode string at address “addr”. This can be + * called recursively thanks to abbreviations; the initial call should + * have “in_abbr” set to 0. + * Each time a character is decoded, it is passed to the function + * “outc”. + */ +static int print_zcode(uint32_t addr, int in_abbr, void (*outc)(uint8_t)) +{ + int abbrev = 0, shift = 0, special = 0; + int c, lastc = 0; /* Initialize lastc to shut gcc up */ + uint16_t w; + uint32_t counter = addr; + int current_alphabet = 0; + + do + { + ZASSERT(counter < memory_size - 1, "string runs beyond the end of memory"); + + w = WORD(counter); + + for(int i = 10; i >= 0; i -= 5) + { + c = (w >> i) & 0x1f; + + if(special) + { + if(special == 2) lastc = c; + else outc((lastc << 5) | c); + + special--; + } + + else if(abbrev) + { + uint32_t new_addr; + + new_addr = user_word(header.abbr + 64 * (abbrev - 1) + 2 * c); + + /* new_addr is a word address, so multiply by 2 */ + print_zcode(new_addr * 2, 1, outc); + + abbrev = 0; + } + + else switch(c) + { + case 0: + outc(ZSCII_SPACE); + shift = 0; + break; + case 1: + if(zversion == 1) + { + outc(ZSCII_NEWLINE); + shift = 0; + break; + } + /* fallthrough */ + case 2: case 3: + if(zversion >= 3 || (zversion == 2 && c == 1)) + { + ZASSERT(!in_abbr, "abbreviation being used recursively"); + abbrev = c; + shift = 0; + } + else + { + shift = c - 1; + } + break; + case 4: case 5: + if(zversion <= 2) + { + current_alphabet = (current_alphabet + (c - 3)) % 3; + shift = 0; + } + else + { + shift = c - 3; + } + break; + case 6: + if(zversion <= 2) shift = (current_alphabet + shift) % 3; + + if(shift == 2) + { + shift = 0; + special = 2; + break; + } + /* fallthrough */ + default: + if(zversion <= 2 && c != 6) shift = (current_alphabet + shift) % 3; + + outc(atable[(26 * shift) + (c - 6)]); + shift = 0; + break; + } + } + + counter += 2; + } while((w & 0x8000) == 0); + + return counter - addr; +} + +/* Prints the string at addr “addr”. + * + * Returns the number of bytes the string took up. “outc” is passed as + * the character-print function to print_zcode(); if it is NULL, + * put_char is used. + */ +int print_handler(uint32_t addr, void (*outc)(uint8_t)) +{ + return print_zcode(addr, 0, outc != NULL ? outc : put_char); +} + +void zprint(void) +{ + pc += print_handler(pc, NULL); +} + +void zprint_ret(void) +{ + zprint(); + put_char(ZSCII_NEWLINE); + zrtrue(); +} + +void znew_line(void) +{ + put_char(ZSCII_NEWLINE); +} + +void zerase_window(void) +{ +#ifdef ZTERP_GLK + switch((int16_t)zargs[0]) + { + case -2: + for(int i = 0; i < 8; i++) clear_window(&windows[i]); + break; + case -1: + close_upper_window(); + /* fallthrough */ + case 0: + /* 8.7.3.2.1 says V5+ should have the cursor set to 1, 1 of the + * erased window; V4 the lower window goes bottom left, the upper + * to 1, 1. Glk doesn’t give control over the cursor when + * clearing, and that doesn’t really seem to be an issue; so just + * call glk_window_clear(). + */ + clear_window(mainwin); + break; + case 1: + clear_window(upperwin); + break; + default: + show_message("@erase_window: unhandled window: %d", (int16_t)zargs[0]); + break; + } + + /* glk_window_clear() kills reverse video in Gargoyle. Reapply style. */ + set_current_style(); +#endif +} + +void zerase_line(void) +{ +#ifdef ZTERP_GLK + /* XXX V6 does pixel handling here. */ + if(zargs[0] != 1 || curwin != upperwin || upperwin->id == NULL) return; + + for(long i = upperwin->x; i < upper_window_width; i++) GLK_PUT_CHAR(UNICODE_SPACE); + + glk_window_move_cursor(upperwin->id, upperwin->x, upperwin->y); +#endif +} + +/* XXX This is more complex in V6 and needs to be updated when V6 windowing is implemented. */ +static void set_cursor(uint16_t y, uint16_t x) +{ +#ifdef ZTERP_GLK + /* All the windows in V6 can have their cursor positioned; if V6 ever + * comes about this should be fixed. + */ + if(curwin != upperwin) return; + + /* -1 and -2 are V6 only, but at least Zracer passes -1 (or it’s + * trying to position the cursor to line 65535; unlikely!) + */ + if((int16_t)y == -1 || (int16_t)y == -2) return; + + /* §8.7.2.3 says 1,1 is the top-left, but at least one program (Paint + * and Corners) uses @set_cursor 0 0 to go to the top-left; so + * special-case it. + */ + if(y == 0) y = 1; + if(x == 0) x = 1; + + /* This is actually illegal, but some games (e.g. Beyond Zork) expect it to work. */ + if(y > upper_window_height) resize_upper_window(y); + + if(upperwin->id != NULL) + { + upperwin->x = x - 1; + upperwin->y = y - 1; + + glk_window_move_cursor(upperwin->id, x - 1, y - 1); + } +#endif +} + +void zset_cursor(void) +{ + set_cursor(zargs[0], zargs[1]); +} + +void zget_cursor(void) +{ +#ifdef ZTERP_GLK + user_store_word(zargs[0] + 0, upperwin->y + 1); + user_store_word(zargs[0] + 2, upperwin->x + 1); +#else + user_store_word(zargs[0] + 0, 1); + user_store_word(zargs[0] + 2, 1); +#endif +} + +#ifndef ZTERP_GLK +static int16_t fg_color = 1, bg_color = 1; +#elif defined(GARGLK) +static glui32 zcolor_map[] = { + zcolor_Default, + + 0x000000, /* Black */ + 0xef0000, /* Red */ + 0x00d600, /* Green */ + 0xefef00, /* Yellow */ + 0x006bb5, /* Blue */ + 0xff00ff, /* Magenta */ + 0x00efef, /* Cyan */ + 0xffffff, /* White */ + 0xb5b5b5, /* Light grey */ + 0x8c8c8c, /* Medium grey */ + 0x5a5a5a, /* Dark grey */ +}; +static glui32 fg_color = zcolor_Default, bg_color = zcolor_Default; + +void update_color(int which, unsigned long color) +{ + if(which < 2 || which > 12) return; + + zcolor_map[which - 1] = color; +} +#endif + +/* A window argument may be supplied in V6, and this needs to be implemented. */ +void zset_colour(void) +{ + /* Glk (apart from Gargoyle) has no color support. */ +#if !defined(ZTERP_GLK) || defined(GARGLK) + int16_t fg = zargs[0], bg = zargs[1]; + + /* In V6, each window has its own color settings. Since multiple + * windows are not supported, simply ignore all color requests except + * those in the main window. + */ + if(zversion == 6 && curwin != mainwin) return; + + if(options.disable_color) return; + + /* XXX -1 is a valid color in V6. */ +#ifdef GARGLK + if(fg >= 1 && fg <= (zversion >= 5 ? 12 : 9)) fg_color = zcolor_map[fg - 1]; + if(bg >= 1 && bg <= (zversion >= 5 ? 12 : 9)) bg_color = zcolor_map[bg - 1]; + +#else + if(fg >= 1 && fg <= 9) fg_color = fg; + if(bg >= 1 && bg <= 9) bg_color = bg; +#endif + + set_current_style(); +#endif +} + +#ifdef GARGLK +/* Convert a 15-bit color to a 24-bit color. */ +static glui32 convert_color(unsigned long color) +{ + /* Map 5-bit color values to 8-bit. */ + const uint8_t table[] = { + 0x00, 0x08, 0x10, 0x19, 0x21, 0x29, 0x31, 0x3a, + 0x42, 0x4a, 0x52, 0x5a, 0x63, 0x6b, 0x73, 0x7b, + 0x84, 0x8c, 0x94, 0x9c, 0xa5, 0xad, 0xb5, 0xbd, + 0xc5, 0xce, 0xd6, 0xde, 0xe6, 0xef, 0xf7, 0xff + }; + + return table[(color & 0x001f) >> 0] << 16 | + table[(color & 0x03e0) >> 5] << 8 | + table[(color & 0x7c00) >> 10] << 0; +} +#endif + +void zset_true_colour(void) +{ +#ifdef GARGLK + long fg = (int16_t)zargs[0], bg = (int16_t)zargs[1]; + + if (fg >= 0) fg_color = convert_color(fg); + else if(fg == -1) fg_color = zcolor_Default; + + if (bg >= 0) bg_color = convert_color(bg); + else if(bg == -1) bg_color = zcolor_Default; + + set_current_style(); +#endif +} + +int header_fixed_font; + +#ifdef GARGLK +/* Idea from Nitfol. */ +static const int style_map[] = +{ + style_Normal, + style_Normal, + + style_Subheader, /* Bold */ + style_Subheader, /* Bold */ + style_Emphasized, /* Italic */ + style_Emphasized, /* Italic */ + style_Alert, /* Bold Italic */ + style_Alert, /* Bold Italic */ + style_Preformatted, /* Fixed */ + style_Preformatted, /* Fixed */ + style_User1, /* Bold Fixed */ + style_User1, /* Bold Fixed */ + style_User2, /* Italic Fixed */ + style_User2, /* Italic Fixed */ + style_Note, /* Bold Italic Fixed */ + style_Note, /* Bold Italic Fixed */ +}; +#endif + +/* Yes, there are three ways to indicate that a fixed-width font should be used. */ +#define use_fixed_font() (header_fixed_font || curwin->font == FONT_FIXED || (style & STYLE_FIXED)) + +void set_current_style(void) +{ + unsigned style = style_window->style; +#ifdef ZTERP_GLK + if(curwin->id == NULL) return; + +#ifdef GARGLK + if(use_fixed_font()) style |= STYLE_FIXED; + + if(options.disable_fixed) style &= ~STYLE_FIXED; + + ZASSERT(style < 16, "invalid style selected: %x", (unsigned)style); + + glk_set_style(style_map[style]); + + garglk_set_reversevideo(style & STYLE_REVERSE); + + garglk_set_zcolors(fg_color, bg_color); +#else + /* Glk can’t mix other styles with fixed-width, but the upper window + * is always fixed, so if it is selected, there is no need to + * explicitly request it here. In addition, the user can disable + * fixed-width fonts or tell Bocfel to assume that the output font is + * already fixed (e.g. in an xterm); in either case, there is no need + * to request a fixed font. + * This means that another style can also be applied if applicable. + */ + if(use_fixed_font() && + !options.disable_fixed && + !options.assume_fixed && + curwin != upperwin) + { + glk_set_style(style_Preformatted); + return; + } + + /* According to standard 1.1, if mixed styles aren't available, the + * priority is Fixed, Italic, Bold, Reverse. + */ + if (style & STYLE_ITALIC) glk_set_style(style_Emphasized); + else if(style & STYLE_BOLD) glk_set_style(style_Subheader); + else if(style & STYLE_REVERSE) glk_set_style(style_Alert); + else glk_set_style(style_Normal); +#endif +#else + zterp_os_set_style(style, fg_color, bg_color); +#endif +} + +#undef use_fixed_font + +/* V6 has per-window styles, but all others have a global style; in this + * case, track styles via the main window. + */ +void zset_text_style(void) +{ + /* A style of 0 means all others go off. */ + if(zargs[0] == 0) style_window->style = STYLE_NONE; + else style_window->style |= zargs[0]; + + set_current_style(); +} + +/* Interpreters seem to disagree on @set_font. Given the code + + @set_font 4 -> i; + @set_font 1 -> j; + @set_font 0 -> k; + @set_font 1 -> l; + + * the following values are returned: + * Frotz 2.43: 0, 1, 1, 1 + * Gargoyle r384: 1, 4, 4, 4 + * Fizmo 0.6.5: 1, 4, 1, 0 + * Nitfol 0.5: 1, 4, 0, 1 + * Filfre .987: 1, 4, 0, 1 + * Zoom 1.1.4: 1, 1, 0, 1 + * ZLR 0.07: 0, 1, 0, 1 + * Windows Frotz 1.15: 1, 4, 1, 1 + * XZip 1.8.2: 0, 4, 0, 0 + * + * The standard says that “ID 0 means ‘the previous font’.” (§8.1.2). + * The Frotz 2.43 source code says that “zargs[0] = number of font or 0 + * to keep current font”. + * + * How to implement @set_font turns on the meaning of “previous”. Does + * it mean the previous font _after_ the @set_font call, meaning Frotz + * is right? Or is it the previous font _before_ the @set_font call, + * meaning the identity of two fonts needs to be tracked? + * + * Currently I do the latter. That yields the following: + * 1, 4, 1, 4 + * Almost comically, no interpreters agree with each other. + */ +void zset_font(void) +{ + struct window *win = curwin; + + if(zversion == 6 && znargs == 2 && (int16_t)zargs[1] != -3) + { + ZASSERT(zargs[1] < 8, "invalid window selected: %d", (int16_t)zargs[1]); + win = &windows[zargs[1]]; + } + + /* If no previous font has been stored, consider that an error. */ + if(zargs[0] == FONT_PREVIOUS && win->prev_font != FONT_NONE) + { + zargs[0] = win->prev_font; + zset_font(); + } + else if(zargs[0] == FONT_NORMAL || + (zargs[0] == FONT_CHARACTER && !options.disable_graphics_font) || + (zargs[0] == FONT_FIXED && !options.disable_fixed)) + { + store(win->font); + win->prev_font = win->font; + win->font = zargs[0]; + } + else + { + store(0); + } + + set_current_style(); +} + +void zprint_table(void) +{ + uint16_t text = zargs[0], width = zargs[1], height = zargs[2], skip = zargs[3]; + uint16_t n = 0; + +#ifdef ZTERP_GLK + uint16_t start = 0; /* initialize to appease gcc */ + + if(curwin == upperwin) start = upperwin->x + 1; +#endif + + if(znargs < 3) height = 1; + if(znargs < 4) skip = 0; + + for(uint16_t i = 0; i < height; i++) + { + for(uint16_t j = 0; j < width; j++) + { + put_char(user_byte(text + n++)); + } + + if(i + 1 != height) + { + n += skip; +#ifdef ZTERP_GLK + if(curwin == upperwin) + { + set_cursor(upperwin->y + 2, start); + } + else +#endif + { + put_char(ZSCII_NEWLINE); + } + } + } +} + +void zprint_char(void) +{ + /* Check 32 (space) first: a cursory examination of story files + * indicates that this is the most common value passed to @print_char. + * This appears to be due to V4+ games blanking the upper window. + */ +#define valid_zscii_output(c) ((c) == 32 || (c) == 0 || (c) == 9 || (c) == 11 || (c) == 13 || ((c) > 32 && (c) <= 126) || ((c) >= 155 && (c) <= 251)) + ZASSERT(valid_zscii_output(zargs[0]), "@print_char called with invalid character: %u", (unsigned)zargs[0]); +#undef valid_zscii_output + + put_char(zargs[0]); +} + +void zprint_num(void) +{ + char buf[7]; + int i = 0; + long v = (int16_t)zargs[0]; + + if(v < 0) v = -v; + + do + { + buf[i++] = '0' + (v % 10); + } while(v /= 10); + + if((int16_t)zargs[0] < 0) buf[i++] = '-'; + + while(i--) put_char(buf[i]); +} + +void zprint_addr(void) +{ + print_handler(zargs[0], NULL); +} + +void zprint_paddr(void) +{ + print_handler(unpack(zargs[0], 1), NULL); +} + +/* XXX This is more complex in V6 and needs to be updated when V6 windowing is implemented. */ +void zsplit_window(void) +{ + if(zargs[0] == 0) close_upper_window(); + else resize_upper_window(zargs[0]); +} + +void zset_window(void) +{ + set_current_window(find_window(zargs[0])); +} + +#ifdef ZTERP_GLK +static void window_change(void) +{ + /* When a textgrid (the upper window) in Gargoyle is rearranged, it + * forgets about reverse video settings, so reapply any styles to the + * current window (it doesn’t hurt if the window is a textbuffer). If + * the current window is not the upper window that’s OK, because + * set_current_style() is called when a @set_window is requested. + */ + set_current_style(); + + /* If the new window is smaller, the cursor of the upper window might + * be out of bounds. Pull it back in if so. + */ + if(zversion >= 3 && upperwin->id != NULL && upper_window_height > 0) + { + long x = upperwin->x, y = upperwin->y; + glui32 w, h; + + glk_window_get_size(upperwin->id, &w, &h); + + upper_window_width = w; + upper_window_height = h; + + if(x > w) x = w; + if(y > h) y = h; + + SWITCH_WINDOW_START(upperwin); + set_cursor(y + 1, x + 1); + SWITCH_WINDOW_END(); + } + + /* §8.4 + * Only 0x20 and 0x21 are mentioned; what of 0x22 and 0x24? Zoom and + * Windows Frotz both update the V5 header entries, so do that here, + * too. + * + * Also, no version restrictions are given, but assume V4+ per §11.1. + */ + if(zversion >= 4) + { + unsigned width, height; + + get_screen_size(&width, &height); + + STORE_BYTE(0x20, height > 254 ? 254 : height); + STORE_BYTE(0x21, width > 255 ? 255 : width); + + if(zversion >= 5) + { + STORE_WORD(0x22, width > UINT16_MAX ? UINT16_MAX : width); + STORE_WORD(0x24, height > UINT16_MAX ? UINT16_MAX : height); + } + } + else + { + zshow_status(); + } +} +#endif + +#ifdef ZTERP_GLK +static int timer_running; + +static void start_timer(uint16_t n) +{ + if(!TIMER_AVAILABLE()) return; + + if(timer_running) die("nested timers unsupported"); + glk_request_timer_events(n * 100); + timer_running = 1; +} + +static void stop_timer(void) +{ + if(!TIMER_AVAILABLE()) return; + + glk_request_timer_events(0); + timer_running = 0; +} + +static void request_char(void) +{ + if(have_unicode) glk_request_char_event_uni(curwin->id); + else glk_request_char_event(curwin->id); + + curwin->pending_read = 1; +} + +static void request_line(union line *line, glui32 maxlen, glui32 initlen) +{ + if(have_unicode) glk_request_line_event_uni(curwin->id, line->unicode, maxlen, initlen); + else glk_request_line_event(curwin->id, line->latin1, maxlen, initlen); + + curwin->pending_read = 1; + curwin->line = line; +} +#endif + +#define special_zscii(c) ((c) >= 129 && (c) <= 154) + +/* This is called when input stream 1 (read from file) is selected. If + * it succefully reads a character/line from the file, it fills the + * struct at “input” with the appropriate information and returns true. + * If it fails to read (likely due to EOF) then it sets the input stream + * back to the keyboard and returns false. + */ +static int istream_read_from_file(struct input *input) +{ + if(input->type == INPUT_CHAR) + { + long c; + + c = zterp_io_getc(istreamio); + if(c == -1) + { + input_stream(ISTREAM_KEYBOARD); + return 0; + } + + /* Don’t translate special ZSCII characters (cursor keys, function keys, keypad). */ + if(special_zscii(c)) input->key = c; + else input->key = unicode_to_zscii_q[c]; + } + else + { + long n; + uint16_t line[1024]; + + n = zterp_io_readline(istreamio, line, sizeof line / sizeof *line); + if(n == -1) + { + input_stream(ISTREAM_KEYBOARD); + return 0; + } + + if(n > input->maxlen) n = input->maxlen; + + input->len = n; + +#ifdef ZTERP_GLK + if(curwin->id != NULL) + { + glk_set_style(style_Input); + for(long i = 0; i < n; i++) GLK_PUT_CHAR(line[i]); + GLK_PUT_CHAR(UNICODE_LINEFEED); + set_current_style(); + } +#else + for(long i = 0; i < n; i++) zterp_io_putc(zterp_io_stdout(), line[i]); + zterp_io_putc(zterp_io_stdout(), UNICODE_LINEFEED); +#endif + + for(long i = 0; i < n; i++) input->line[i] = line[i]; + } + +#ifdef ZTERP_GLK + event_t ev; + + /* It’s possible that output is buffered, meaning that until + * glk_select() is called, output will not be displayed. When reading + * from a command-script, flush on each command so that output is + * visible while the script is being replayed. + */ + glk_select_poll(&ev); + switch(ev.type) + { + case evtype_None: + break; + case evtype_Arrange: + window_change(); + break; + default: + /* No other events should arrive. Timers are only started in + * get_input() and are stopped before that function returns. + * Input events will not happen with glk_select_poll(), and no + * other event type is expected to be raised. + */ + break; + } + + saw_input = 1; +#endif + + return 1; +} + +#ifdef GLK_MODULE_LINE_TERMINATORS +/* Glk returns terminating characters as keycode_*, but we need them as + * ZSCII. This should only ever be called with values that are matched + * in the switch, because those are the only ones that Glk was told are + * terminating characters. In the event that another keycode comes + * through, though, treat it as Enter. + */ +static uint8_t zscii_from_glk(glui32 key) +{ + switch(key) + { + case 13: return ZSCII_NEWLINE; + case keycode_Up: return 129; + case keycode_Down: return 130; + case keycode_Left: return 131; + case keycode_Right: return 131; + case keycode_Func1: return 133; + case keycode_Func2: return 134; + case keycode_Func3: return 135; + case keycode_Func4: return 136; + case keycode_Func5: return 137; + case keycode_Func6: return 138; + case keycode_Func7: return 139; + case keycode_Func8: return 140; + case keycode_Func9: return 141; + case keycode_Func10: return 142; + case keycode_Func11: return 143; + case keycode_Func12: return 144; + } + + return ZSCII_NEWLINE; +} +#endif + +#ifdef ZTERP_GLK +/* This is like strlen() but in addition to C strings it can find the + * length of a Unicode string (which is assumed to be zero terminated) + * if Unicode is being used. + */ +static size_t line_len(const union line *line) +{ + size_t i; + + if(!have_unicode) return strlen(line->latin1); + + for(i = 0; line->unicode[i] != 0; i++) + { + } + + return i; +} +#endif + +/* Attempt to read input from the user. The input type can be either a + * single character or a full line. If “timer” is not zero, a timer is + * started that fires off every “timer” tenths of a second (if the value + * is 1, it will timeout 10 times a second, etc.). Each time the timer + * times out the routine at address “routine” is called. If the routine + * returns true, the input is canceled. + * + * The function returns 1 if input was stored, 0 if there was a + * cancellation as described above. + */ +static int get_input(int16_t timer, int16_t routine, struct input *input) +{ + /* If either of these is zero, no timeout should happen. */ + if(timer == 0) routine = 0; + if(routine == 0) timer = 0; + + /* Flush all streams when input is requested. */ +#ifndef ZTERP_GLK + zterp_io_flush(zterp_io_stdout()); +#endif + zterp_io_flush(scriptio); + zterp_io_flush(transio); + + /* Generally speaking, newline will be the reason the line input + * stopped, so set it by default. It will be overridden where + * necessary. + */ + input->term = ZSCII_NEWLINE; + + if(istream == ISTREAM_FILE && istream_read_from_file(input)) return 1; +#ifdef ZTERP_GLK + int status = 0; + union line line; + struct window *saved = NULL; + + /* In V6, input might be requested on an unsupported window. If so, + * switch to the main window temporarily. + */ + if(curwin->id == NULL) + { + saved = curwin; + curwin = mainwin; + glk_set_window(curwin->id); + } + + if(input->type == INPUT_CHAR) + { + request_char(); + } + else + { + for(int i = 0; i < input->preloaded; i++) + { + if(have_unicode) line.unicode[i] = input->line[i]; + else line.latin1 [i] = input->line[i]; + } + + request_line(&line, input->maxlen, input->preloaded); + } + + if(timer != 0) start_timer(timer); + + while(status == 0) + { + event_t ev; + + glk_select(&ev); + + switch(ev.type) + { + case evtype_Arrange: + window_change(); + break; + + case evtype_Timer: + { + ZASSERT(timer != 0, "got unexpected evtype_Timer"); + + struct window *saved2 = curwin; + int ret; + + stop_timer(); + + ret = direct_call(routine); + + /* It’s possible for an interrupt to switch windows; if it + * does, simply switch back. This is the easiest way to deal + * with an undefined bit of the Z-machine. + */ + if(curwin != saved2) set_current_window(saved2); + + if(ret) + { + status = 2; + } + else + { + /* If this got reset to 0, that means an interrupt had to + * cancel the read event in order to either read or write. + */ + if(!curwin->pending_read) + { + if(input->type == INPUT_CHAR) request_char(); + else request_line(&line, input->maxlen, line_len(&line)); + } + + start_timer(timer); + } + } + + break; + + case evtype_CharInput: + ZASSERT(input->type == INPUT_CHAR, "got unexpected evtype_CharInput"); + ZASSERT(ev.win == curwin->id, "got evtype_CharInput on unexpected window"); + + status = 1; + + switch(ev.val1) + { + case keycode_Delete: input->key = 8; break; + case keycode_Return: input->key = 13; break; + case keycode_Escape: input->key = 27; break; + case keycode_Up: input->key = 129; break; + case keycode_Down: input->key = 130; break; + case keycode_Left: input->key = 131; break; + case keycode_Right: input->key = 132; break; + case keycode_Func1: input->key = 133; break; + case keycode_Func2: input->key = 134; break; + case keycode_Func3: input->key = 135; break; + case keycode_Func4: input->key = 136; break; + case keycode_Func5: input->key = 137; break; + case keycode_Func6: input->key = 138; break; + case keycode_Func7: input->key = 139; break; + case keycode_Func8: input->key = 140; break; + case keycode_Func9: input->key = 141; break; + case keycode_Func10: input->key = 142; break; + case keycode_Func11: input->key = 143; break; + case keycode_Func12: input->key = 144; break; + + default: + input->key = ZSCII_QUESTIONMARK; + + if(ev.val1 <= UINT16_MAX) + { + uint8_t c = unicode_to_zscii[ev.val1]; + + if(c != 0) input->key = c; + } + + break; + } + + break; + + case evtype_LineInput: + ZASSERT(input->type == INPUT_LINE, "got unexpected evtype_LineInput"); + ZASSERT(ev.win == curwin->id, "got evtype_LineInput on unexpected window"); + input->len = ev.val1; +#ifdef GLK_MODULE_LINE_TERMINATORS + if(zversion >= 5) input->term = zscii_from_glk(ev.val2); +#endif + status = 1; + break; + } + } + + stop_timer(); + + if(input->type == INPUT_CHAR) + { + glk_cancel_char_event(curwin->id); + } + else + { + /* On cancellation, the buffer still needs to be filled, because + * it’s possible that line input echoing has been turned off and the + * contents will need to be written out. + */ + if(status == 2) + { + event_t ev; + + glk_cancel_line_event(curwin->id, &ev); + input->len = ev.val1; + input->term = 0; + } + + for(glui32 i = 0; i < input->len; i++) + { + if(have_unicode) input->line[i] = line.unicode[i] > UINT16_MAX ? UNICODE_QUESTIONMARK : line.unicode[i]; + else input->line[i] = (uint8_t)line.latin1[i]; + } + } + + curwin->pending_read = 0; + curwin->line = NULL; + + if(status == 1) saw_input = 1; + + if(errorwin != NULL) + { + glk_window_close(errorwin, NULL); + errorwin = NULL; + } + + if(saved != NULL) + { + curwin = saved; + glk_set_window(curwin->id); + } + + return status != 2; +#else + if(input->type == INPUT_CHAR) + { + long n; + uint16_t line[64]; + + n = zterp_io_readline(zterp_io_stdin(), line, sizeof line / sizeof *line); + + /* On error/eof, or if an invalid key was typed, pretend “Enter” was hit. */ + if(n <= 0) + { + input->key = ZSCII_NEWLINE; + } + else + { + input->key = unicode_to_zscii[line[0]]; + if(input->key == 0) input->key = ZSCII_NEWLINE; + } + } + else + { + input->len = input->preloaded; + + if(input->maxlen > input->preloaded) + { + long n; + uint16_t line[1024]; + + n = zterp_io_readline(zterp_io_stdin(), line, sizeof line / sizeof *line); + if(n != -1) + { + if(n > input->maxlen - input->preloaded) n = input->maxlen - input->preloaded; + for(long i = 0; i < n; i++) input->line[i + input->preloaded] = line[i]; + input->len += n; + } + } + } + + return 1; +#endif +} + +void zread_char(void) +{ + uint16_t timer = 0; + uint16_t routine = zargs[2]; + struct input input = { .type = INPUT_CHAR }; + +#ifdef ZTERP_GLK + cancel_read_events(curwin); +#endif + + if(zversion >= 4 && znargs > 1) timer = zargs[1]; + + if(!get_input(timer, routine, &input)) + { + store(0); + return; + } + +#ifdef ZTERP_GLK + update_delayed(); +#endif + + if(streams & STREAM_SCRIPT) + { + /* Values 127 to 159 are not valid Unicode, and these just happen to + * match up to the values needed for special ZSCII keys, so store + * them as-is. + */ + if(special_zscii(input.key)) zterp_io_putc(scriptio, input.key); + else zterp_io_putc(scriptio, zscii_to_unicode[input.key]); + } + + store(input.key); +} + +#ifdef ZTERP_GLK +static void status_putc(uint8_t c) +{ + glk_put_char(zscii_to_unicode[c]); +} +#endif + +void zshow_status(void) +{ +#ifdef ZTERP_GLK + glui32 width, height; + char rhs[64]; + int first = variable(0x11), second = variable(0x12); + + if(statuswin.id == NULL) return; + + glk_window_clear(statuswin.id); + + SWITCH_WINDOW_START(&statuswin); + + glk_window_get_size(statuswin.id, &width, &height); + +#ifdef GARGLK + garglk_set_reversevideo(1); +#else + glk_set_style(style_Alert); +#endif + for(glui32 i = 0; i < width; i++) glk_put_char(ZSCII_SPACE); + + glk_window_move_cursor(statuswin.id, 1, 0); + + /* Variable 0x10 is global variable 1. */ + print_object(variable(0x10), status_putc); + + if(STATUS_IS_TIME()) + { + snprintf(rhs, sizeof rhs, "Time: %d:%02d%s ", (first + 11) % 12 + 1, second, first < 12 ? "am" : "pm"); + if(strlen(rhs) > width) snprintf(rhs, sizeof rhs, "%02d:%02d", first, second); + } + else + { + snprintf(rhs, sizeof rhs, "Score: %d Moves: %d ", first, second); + if(strlen(rhs) > width) snprintf(rhs, sizeof rhs, "%d/%d", first, second); + } + + if(strlen(rhs) <= width) + { + glk_window_move_cursor(statuswin.id, width - strlen(rhs), 0); + glk_put_string(rhs); + } + + SWITCH_WINDOW_END(); +#endif +} + +/* This is strcmp() except that the first string is Unicode. */ +static int unicmp(const uint32_t *s1, const char *s2) +{ + while(*s1 != 0 && *s2 == *s1) + { + s1++; + s2++; + } + + return *s1 - *s2; +} + +uint32_t read_pc; + +/* Try to parse a meta command. Returns true if input should be + * restarted, false to indicate no more input is required. In most + * cases input will be required because the game has requested it, but + * /undo and /restore jump to different locations, so the current @read + * no longer exists. + */ +static int handle_meta_command(const uint32_t *string) +{ + if(unicmp(string, "undo") == 0) + { + uint16_t flags2 = WORD(0x10); + int success = pop_save(); + + if(success != 0) + { + /* §6.1.2. */ + STORE_WORD(0x10, flags2); + + if(zversion >= 5) store(success); + else put_string("[undone]\n\n>"); + + return 0; + } + else + { + put_string("[no save found, unable to undo]"); + } + } + else if(unicmp(string, "scripton") == 0) + { + if(output_stream(OSTREAM_SCRIPT, 0)) put_string("[transcripting on]"); + else put_string("[transcripting failed]"); + } + else if(unicmp(string, "scriptoff") == 0) + { + output_stream(-OSTREAM_SCRIPT, 0); + put_string("[transcripting off]"); + } + else if(unicmp(string, "recon") == 0) + { + if(output_stream(OSTREAM_RECORD, 0)) put_string("[command recording on]"); + else put_string("[command recording failed]"); + } + else if(unicmp(string, "recoff") == 0) + { + output_stream(-OSTREAM_RECORD, 0); + put_string("[command recording off]"); + } + else if(unicmp(string, "replay") == 0) + { + if(input_stream(ISTREAM_FILE)) put_string("[replaying commands]"); + else put_string("[replaying commands failed]"); + } + else if(unicmp(string, "save") == 0) + { + if(interrupt_level() != 0) + { + put_string("[cannot call /save while in an interrupt]"); + } + else + { + uint32_t tmp = pc; + + /* pc is currently set to the next instruction, but the restore + * needs to come back to *this* instruction; so temporarily set + * pc back before saving. + */ + pc = read_pc; + if(do_save(1)) put_string("[saved]"); + else put_string("[save failed]"); + pc = tmp; + } + } + else if(unicmp(string, "restore") == 0) + { + if(do_restore(1)) + { + put_string("[restored]\n\n>"); + return 0; + } + else + { + put_string("[restore failed]"); + } + } + else if(unicmp(string, "help") == 0) + { + put_string( + "/undo: undo a turn\n" + "/scripton: start a transcript\n" + "/scriptoff: stop a transcript\n" + "/recon: start a command record\n" + "/recoff: stop a command record\n" + "/replay: replay a command record\n" + "/save: save the game\n" + "/restore: restore a game saved by /save" + ); + } + else + { + put_string("[unknown command]"); + } + + return 1; +} + +void zread(void) +{ + uint16_t text = zargs[0], parse = zargs[1]; + uint8_t maxchars = zversion >= 5 ? user_byte(text) : user_byte(text) - 1; + uint8_t zscii_string[maxchars]; + uint32_t string[maxchars + 1]; + struct input input = { .type = INPUT_LINE, .line = string, .maxlen = maxchars }; + uint16_t timer = 0; + uint16_t routine = zargs[3]; + +#ifdef ZTERP_GLK + cancel_read_events(curwin); +#endif + + if(zversion <= 3) zshow_status(); + + if(zversion >= 4 && znargs > 2) timer = zargs[2]; + + if(zversion >= 5) + { + int i; + + input.preloaded = user_byte(text + 1); + ZASSERT(input.preloaded <= maxchars, "too many preloaded characters: %d when max is %d", input.preloaded, maxchars); + + for(i = 0; i < input.preloaded; i++) string[i] = zscii_to_unicode[user_byte(text + i + 2)]; + string[i] = 0; + + /* Under garglk, preloaded input works as it’s supposed to. + * Under Glk, it can fail one of two ways: + * 1. The preloaded text is printed out once, but is not editable. + * 2. The preloaded text is printed out twice, the second being editable. + * I have chosen option #2. For non-Glk, option #1 is done by necessity. + */ +#ifdef GARGLK + if(curwin->id != NULL) garglk_unput_string_uni(string); +#endif + } + + if(!get_input(timer, routine, &input)) + { +#ifdef ZTERP_GLK + cleanup_screen(&input); +#endif + if(zversion >= 5) store(0); + return; + } + +#ifdef ZTERP_GLK + cleanup_screen(&input); +#endif + +#ifdef ZTERP_GLK + update_delayed(); +#endif + + if(options.enable_escape && (streams & STREAM_TRANS)) + { + zterp_io_putc(transio, 033); + zterp_io_putc(transio, '['); + for(int i = 0; options.escape_string[i] != 0; i++) zterp_io_putc(transio, options.escape_string[i]); + } + + for(int i = 0; i < input.len; i++) + { + zscii_string[i] = unicode_to_zscii_q[unicode_tolower(string[i])]; + if(streams & STREAM_TRANS) zterp_io_putc(transio, string[i]); + if(streams & STREAM_SCRIPT) zterp_io_putc(scriptio, string[i]); + } + + if(options.enable_escape && (streams & STREAM_TRANS)) + { + zterp_io_putc(transio, 033); + zterp_io_putc(transio, '['); + zterp_io_putc(transio, '0'); + zterp_io_putc(transio, 'm'); + } + + if(streams & STREAM_TRANS) zterp_io_putc(transio, UNICODE_LINEFEED); + if(streams & STREAM_SCRIPT) zterp_io_putc(scriptio, UNICODE_LINEFEED); + + if(!options.disable_meta_commands) + { + string[input.len] = 0; + + if(string[0] == '/') + { + if(handle_meta_command(string + 1)) + { + /* The game still wants input, so try again. */ + put_string("\n\n>"); + zread(); + } + + return; + } + + /* V1–4 do not have @save_undo, so simulate one each time @read is + * called. + * + * pc is currently set to the next instruction, but the undo needs + * to come back to *this* instruction; so temporarily set pc back + * before pushing the save. + */ + if(zversion <= 4) + { + uint32_t tmp_pc = pc; + + pc = read_pc; + push_save(); + pc = tmp_pc; + } + } + + if(zversion >= 5) + { + user_store_byte(text + 1, input.len); /* number of characters read */ + + for(int i = 0; i < input.len; i++) + { + user_store_byte(text + i + 2, zscii_string[i]); + } + + if(parse != 0) tokenize(text, parse, 0, 0); + + store(input.term); + } + else + { + for(int i = 0; i < input.len; i++) + { + user_store_byte(text + i + 1, zscii_string[i]); + } + + user_store_byte(text + input.len + 1, 0); + + tokenize(text, parse, 0, 0); + } +} + +void zprint_unicode(void) +{ + if(valid_unicode(zargs[0])) put_char_u(zargs[0]); + else put_char_u(UNICODE_QUESTIONMARK); +} + +void zcheck_unicode(void) +{ + uint16_t res = 0; + + /* valid_unicode() will tell which Unicode characters can be printed; + * and if the Unicode character is in the Unicode input table, it can + * also be read. If Unicode is not available, then any character >255 + * is invalid for both reading and writing. + */ + if(have_unicode || zargs[0] < 256) + { + if(valid_unicode(zargs[0])) res |= 0x01; + if(unicode_to_zscii[zargs[0]] != 0) res |= 0x02; + } + + store(res); +} + +/* Should picture_data and get_wind_prop be moved to a V6 source file? */ +void zpicture_data(void) +{ + if(zargs[0] == 0) + { + user_store_word(zargs[1] + 0, 0); + user_store_word(zargs[1] + 2, 0); + } + + /* No pictures means no valid pictures, so never branch. */ + branch_if(0); +} + +void zget_wind_prop(void) +{ + uint16_t val; + struct window *win; + + win = find_window(zargs[0]); + + /* These are mostly bald-faced lies. */ + switch(zargs[1]) + { + case 0: /* y coordinate */ + val = 0; + break; + case 1: /* x coordinate */ + val = 0; + break; + case 2: /* y size */ + val = 100; + break; + case 3: /* x size */ + val = 100; + break; + case 4: /* y cursor */ + val = 0; + break; + case 5: /* x cursor */ + val = 0; + break; + case 6: /* left margin size */ + val = 0; + break; + case 7: /* right margin size */ + val = 0; + break; + case 8: /* newline interrupt routine */ + val = 0; + break; + case 9: /* interrupt countdown */ + val = 0; + break; + case 10: /* text style */ + val = win->style; + break; + case 11: /* colour data */ + val = (9 << 8) | 2; + break; + case 12: /* font number */ + val = win->font; + break; + case 13: /* font size */ + val = (10 << 8) | 10; + break; + case 14: /* attributes */ + val = 0; + break; + case 15: /* line count */ + val = 0; + break; + case 16: /* true foreground colour */ + val = 0; + break; + case 17: /* true background colour */ + val = 0; + break; + default: + die("unknown window property: %u", (unsigned)zargs[1]); + } + + store(val); +} + +/* This is not correct, because @output_stream does not work as it + * should with a width argument; however, this does print out the + * contents of a table that was sent to stream 3, so it’s at least + * somewhat useful. + * + * Output should be to the currently-selected window, but since V6 is + * only marginally supported, other windows are not active. Send to the + * main window for the time being. + */ +void zprint_form(void) +{ + SWITCH_WINDOW_START(mainwin); + + for(uint16_t i = 0; i < user_word(zargs[0]); i++) + { + put_char(user_byte(zargs[0] + 2 + i)); + } + + put_char(ZSCII_NEWLINE); + + SWITCH_WINDOW_END(); +} + +void zmake_menu(void) +{ + branch_if(0); +} + +void zbuffer_screen(void) +{ + store(0); +} + +#ifdef GARGLK +/* Glk does not guarantee great control over how various styles are + * going to look, but Gargoyle does. Abusing the Glk “style hints” + * functions allows for quite fine-grained control over style + * appearance. First, clear the (important) attributes for each style, + * and then recreate each in whatever mold is necessary. Re-use some + * that are expected to be correct (emphasized for italic, subheader for + * bold, and so on). + */ +static void set_default_styles(void) +{ + int styles[] = { style_Subheader, style_Emphasized, style_Alert, style_Preformatted, style_User1, style_User2, style_Note }; + + for(int i = 0; i < 7; i++) + { + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Size, 0); + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Weight, 0); + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Oblique, 0); + + /* This sets wintype_TextGrid to be proportional, which of course is + * wrong; but text grids are required to be fixed, so Gargoyle + * simply ignores this hint for those windows. + */ + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Proportional, 1); + } +} +#endif + +int create_mainwin(void) +{ +#ifdef ZTERP_GLK + +#ifdef GARGLK + set_default_styles(); + + /* Bold */ + glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Weight, 1); + + /* Italic */ + glk_stylehint_set(wintype_AllTypes, style_Emphasized, stylehint_Oblique, 1); + + /* Bold Italic */ + glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Weight, 1); + glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Oblique, 1); + + /* Fixed */ + glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Proportional, 0); + + /* Bold Fixed */ + glk_stylehint_set(wintype_AllTypes, style_User1, stylehint_Weight, 1); + glk_stylehint_set(wintype_AllTypes, style_User1, stylehint_Proportional, 0); + + /* Italic Fixed */ + glk_stylehint_set(wintype_AllTypes, style_User2, stylehint_Oblique, 1); + glk_stylehint_set(wintype_AllTypes, style_User2, stylehint_Proportional, 0); + + /* Bold Italic Fixed */ + glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Weight, 1); + glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Oblique, 1); + glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Proportional, 0); +#endif + + mainwin->id = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); + if(mainwin->id == NULL) return 0; + glk_set_window(mainwin->id); + +#ifdef GLK_MODULE_LINE_ECHO + mainwin->has_echo = glk_gestalt(gestalt_LineInputEcho, 0); + if(mainwin->has_echo) glk_set_echo_line_event(mainwin->id, 0); +#endif + + return 1; +#else + return 1; +#endif +} + +int create_statuswin(void) +{ +#ifdef ZTERP_GLK + if(statuswin.id == NULL) statuswin.id = glk_window_open(mainwin->id, winmethod_Above | winmethod_Fixed, 1, wintype_TextGrid, 0); + return statuswin.id != NULL; +#else + return 0; +#endif +} + +int create_upperwin(void) +{ +#ifdef ZTERP_GLK + /* On a restart, this function will get called again. It would be + * possible to try to resize the upper window to 0 if it already + * exists, but it’s easier to just destroy and recreate it. + */ + if(upperwin->id != NULL) glk_window_close(upperwin->id, NULL); + + /* The upper window appeared in V3. */ + if(zversion >= 3) + { + upperwin->id = glk_window_open(mainwin->id, winmethod_Above | winmethod_Fixed, 0, wintype_TextGrid, 0); + upperwin->x = upperwin->y = 0; + upper_window_height = 0; + + if(upperwin->id != NULL) + { + glui32 w, h; + + glk_window_get_size(upperwin->id, &w, &h); + upper_window_width = w; + + if(h != 0 || upper_window_width == 0) + { + glk_window_close(upperwin->id, NULL); + upperwin->id = NULL; + } + } + } + + return upperwin->id != NULL; +#else + return 0; +#endif +} + +void init_screen(void) +{ + for(int i = 0; i < 8; i++) + { + windows[i].style = STYLE_NONE; + windows[i].font = FONT_NORMAL; + windows[i].prev_font = FONT_NONE; + +#ifdef ZTERP_GLK + clear_window(&windows[i]); +#ifdef GLK_MODULE_LINE_TERMINATORS + if(windows[i].id != NULL && term_nkeys != 0 && glk_gestalt(gestalt_LineTerminators, 0)) glk_set_terminators_line_event(windows[i].id, term_keys, term_nkeys); +#endif +#endif + } + + close_upper_window(); + +#ifdef ZTERP_GLK + if(statuswin.id != NULL) glk_window_clear(statuswin.id); + + if(errorwin != NULL) + { + glk_window_close(errorwin, NULL); + errorwin = NULL; + } + + stop_timer(); + +#ifdef GARGLK + fg_color = zcolor_Default; + bg_color = zcolor_Default; +#endif + +#else + fg_color = 1; + bg_color = 1; +#endif + + if(scriptio != NULL) zterp_io_close(scriptio); + scriptio = NULL; + + input_stream(ISTREAM_KEYBOARD); + + streams = STREAM_SCREEN; + stablei = -1; + set_current_window(mainwin); +} diff --git a/interpreters/bocfel/screen.h b/interpreters/bocfel/screen.h new file mode 100644 index 0000000..4e11101 --- /dev/null +++ b/interpreters/bocfel/screen.h @@ -0,0 +1,92 @@ +#ifndef ZTERP_SCREEN_H +#define ZTERP_SCREEN_H + +#include + +#ifdef ZTERP_GLK +#include +#endif + +/* Boolean flag describing whether the header bit meaning “fixed font” is set. */ +extern int header_fixed_font; + +extern uint32_t read_pc; + +void init_screen(void); + +int create_mainwin(void); +int create_statuswin(void); +int create_upperwin(void); +void get_screen_size(unsigned int *, unsigned int *); +void close_upper_window(void); +void cancel_all_events(void); + +/* Text styles. */ +#define STYLE_NONE (0U ) +#define STYLE_REVERSE (1U << 0) +#define STYLE_BOLD (1U << 1) +#define STYLE_ITALIC (1U << 2) +#define STYLE_FIXED (1U << 3) + +void show_message(const char *, ...); + +#ifdef GLK_MODULE_LINE_TERMINATORS +void term_keys_reset(void); +void term_keys_add(uint8_t); +#endif + +#ifdef GARGLK +void update_color(int, unsigned long); +#endif + +/* Output streams. */ +#define OSTREAM_SCREEN 1 +#define OSTREAM_SCRIPT 2 +#define OSTREAM_MEMORY 3 +#define OSTREAM_RECORD 4 + +/* Input streams. */ +#define ISTREAM_KEYBOARD 0 +#define ISTREAM_FILE 1 + +int output_stream(int16_t, uint16_t); +int input_stream(int); + +void set_current_style(void); + +int print_handler(uint32_t, void (*)(uint8_t)); +void put_char_u(uint16_t); +void put_char(uint8_t); + +void zoutput_stream(void); +void zinput_stream(void); +void zprint(void); +void zprint_ret(void); +void znew_line(void); +void zerase_window(void); +void zerase_line(void); +void zset_cursor(void); +void zget_cursor(void); +void zset_colour(void); +void zset_true_colour(void); +void zset_text_style(void); +void zset_font(void); +void zprint_table(void); +void zprint_char(void); +void zprint_num(void); +void zprint_addr(void); +void zprint_paddr(void); +void zsplit_window(void); +void zset_window(void); +void zread_char(void); +void zshow_status(void); +void zread(void); +void zprint_unicode(void); +void zcheck_unicode(void); +void zpicture_data(void); +void zget_wind_prop(void); +void zprint_form(void); +void zmake_menu(void); +void zbuffer_screen(void); + +#endif diff --git a/interpreters/bocfel/stack.c b/interpreters/bocfel/stack.c new file mode 100644 index 0000000..493dd36 --- /dev/null +++ b/interpreters/bocfel/stack.c @@ -0,0 +1,1077 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "stack.h" +#include "branch.h" +#include "iff.h" +#include "io.h" +#include "memory.h" +#include "process.h" +#include "screen.h" +#include "util.h" +#include "zterp.h" + +struct call_frame +{ + uint32_t pc; + uint16_t *sp; + uint8_t nlocals; + uint8_t nargs; + uint16_t where; + uint16_t locals[15]; +}; + +static struct call_frame *frames; +static struct call_frame *fp; + +#define BASE_OF_FRAMES frames +static struct call_frame *TOP_OF_FRAMES; +#define CURRENT_FRAME (fp - 1) +#define NFRAMES ((long)(fp - frames)) + +static uint16_t *stack; +static uint16_t *sp; + +#define BASE_OF_STACK stack +static uint16_t *TOP_OF_STACK; + +static void PUSH_STACK(uint16_t n) { ZASSERT(sp != TOP_OF_STACK, "stack overflow"); *sp++ = n; } +static uint16_t POP_STACK(void) { ZASSERT(sp > CURRENT_FRAME->sp, "stack underflow"); return *--sp; } + +static struct save_state +{ + uint32_t pc; + + uint32_t memsize; + uint8_t *memory; + + uint32_t stack_size; + uint16_t *stack; + + long nframes; + struct call_frame *frames; + + struct save_state *prev, *next; +} *saves_head, *saves_tail; + +static long nsaves; + +static void add_frame(uint32_t pc_, uint16_t *sp_, uint8_t nlocals, uint8_t nargs, uint16_t where) +{ + ZASSERT(fp != TOP_OF_FRAMES, "call stack too deep: %ld", NFRAMES + 1); + + fp->pc = pc_; + fp->sp = sp_; + fp->nlocals = nlocals; + fp->nargs = nargs; + fp->where = where; + + fp++; +} + +void init_stack(void) +{ + /* Allocate space for the evaluation and call stacks. + * Clamp the size between 1 and the largest value that will not + * produce an overflow of size_t when multiplied by the size of the + * type. + * Also, the call stack can be no larger than 0xffff so that the + * result of a @catch will fit into a 16-bit integer. + */ + if(stack == NULL) + { +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define CLAMP(n, a, b) ((n) < (a) ? (a) : (n) > (b) ? (b): (n)) + options.eval_stack_size = CLAMP(options.eval_stack_size, 1, SIZE_MAX / sizeof *stack); + stack = malloc(options.eval_stack_size * sizeof *stack); + if(stack == NULL) die("unable to allocate %lu bytes for the evaluation stack", options.eval_stack_size * (unsigned long)sizeof *stack); + TOP_OF_STACK = &stack[options.eval_stack_size]; + + options.call_stack_size = CLAMP(options.call_stack_size, 1, MIN(0xffff, (SIZE_MAX / sizeof *frames) - sizeof *frames)); + /* One extra to help with saving (thus the subtraction of sizeof *frames above). */ + frames = malloc((options.call_stack_size + 1) * sizeof *frames); + if(frames == NULL) die("unable to allocate %lu bytes for the call stack", (options.call_stack_size + 1) * (unsigned long)sizeof *frames); + TOP_OF_FRAMES = &frames[options.call_stack_size]; +#undef MIN +#undef CLAMP + } + + sp = BASE_OF_STACK; + fp = BASE_OF_FRAMES; + + /* Quetzal requires a dummy frame in non-V6 games, so do that here. */ + if(zversion != 6) add_frame(0, sp, 0, 0, 0); + + /* Free all previous save states (from @save_undo). */ + while(saves_head != NULL) + { + struct save_state *tmp = saves_head; + saves_head = saves_head->next; + free(tmp->stack); + free(tmp->frames); + free(tmp->memory); + free(tmp); + } + saves_tail = NULL; + nsaves = 0; +} + +uint16_t variable(uint16_t var) +{ + ZASSERT(var < 0x100, "unable to decode variable %u", (unsigned)var); + + /* Stack */ + if(var == 0) + { + return POP_STACK(); + } + + /* Locals */ + else if(var <= 0x0f) + { + ZASSERT(var <= CURRENT_FRAME->nlocals, "attempting to read from nonexistent local variable %d: routine has %d", (int)var, CURRENT_FRAME->nlocals); + return CURRENT_FRAME->locals[var - 1]; + } + + /* Globals */ + else if(var <= 0xff) + { + var -= 0x10; + return WORD(header.globals + (var * 2)); + } + + /* This is an “impossible” situation (ie, the game did something wrong). + * It will be caught above if safety checks are turned on, but if they + * are not, do what we can: lie. + */ + return -1; +} + +void store_variable(uint16_t var, uint16_t n) +{ + ZASSERT(var < 0x100, "unable to decode variable %u", (unsigned)var); + + /* Stack. */ + if(var == 0) + { + PUSH_STACK(n); + } + + /* Local variables. */ + else if(var <= 0x0f) + { + ZASSERT(var <= CURRENT_FRAME->nlocals, "attempting to store to nonexistent local variable %d: routine has %d", (int)var, CURRENT_FRAME->nlocals); + CURRENT_FRAME->locals[var - 1] = n; + } + + /* Global variables. */ + else if(var <= 0xff) + { + var -= 0x10; + STORE_WORD(header.globals + (var * 2), n); + } +} + +uint16_t *stack_top_element(void) +{ + ZASSERT(sp > CURRENT_FRAME->sp, "stack underflow"); + + return sp - 1; +} + +void zpush(void) +{ + PUSH_STACK(zargs[0]); +} + +void zpull(void) +{ + uint16_t v; + + if(zversion != 6) + { + v = POP_STACK(); + + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) *stack_top_element() = v; + else store_variable(zargs[0], v); + } + else + { + if(znargs == 0) + { + v = POP_STACK(); + } + else + { + uint16_t slots = user_word(zargs[0]) + 1; + + v = user_word(zargs[0] + (2 * slots)); + + user_store_word(zargs[0], slots); + } + + store(v); + } +} + +void zload(void) +{ + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) store(*stack_top_element()); + else store(variable(zargs[0])); +} + +void zstore(void) +{ + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) *stack_top_element() = zargs[1]; + else store_variable(zargs[0], zargs[1]); +} + +void call(int do_store) +{ + uint32_t jmp_to; + uint8_t nlocals; + uint16_t where; + + if(zargs[0] == 0) + { + /* call(2) should never happen if zargs[0] is 0. */ + if(do_store) store(0); + return; + } + + jmp_to = unpack(zargs[0], 0); + ZASSERT(jmp_to < memory_size - 1, "call to invalid address 0x%lx", (unsigned long)jmp_to); + + nlocals = BYTE(jmp_to++); + ZASSERT(nlocals <= 15, "too many (%d) locals at 0x%lx", nlocals, (unsigned long)jmp_to - 1); + + if(zversion <= 4) ZASSERT(jmp_to + (nlocals * 2) < memory_size, "call to invalid address 0x%lx", (unsigned long)jmp_to); + + switch(do_store) + { + case 1: where = BYTE(pc++); break; /* Where to store return value */ + case 0: where = 0xff + 1; break; /* Or a tag meaning no return value */ + default: where = 0xff + 2; break; /* Or a tag meaning push the return value */ + } + + add_frame(pc, sp, nlocals, znargs - 1, where); + + for(int i = 0; i < nlocals; i++) + { + if(i < znargs - 1) + { + CURRENT_FRAME->locals[i] = zargs[i + 1]; + } + else + { + if(zversion <= 4) CURRENT_FRAME->locals[i] = WORD(jmp_to + (2 * i)); + else CURRENT_FRAME->locals[i] = 0; + } + } + + /* Take care of locals! */ + if(zversion <= 4) jmp_to += nlocals * 2; + + pc = jmp_to; +} + +#ifdef ZTERP_GLK +uint16_t direct_call(uint16_t routine) +{ + uint16_t saved_args[znargs]; + uint16_t saved_nargs; + + memcpy(saved_args, zargs, sizeof saved_args); + saved_nargs = znargs; + + znargs = 1; + zargs[0] = routine; + call(2); + + process_instructions(); + + memcpy(zargs, saved_args, sizeof saved_args); + znargs = saved_nargs; + + return POP_STACK(); +} +#endif + +void zcall_store(void) +{ + call(1); +} +void zcall_nostore(void) +{ + call(0); +} + +void do_return(uint16_t retval) +{ + uint16_t where; + + ZASSERT(NFRAMES > 1, "return attempted outside of a function"); + + pc = CURRENT_FRAME->pc; + sp = CURRENT_FRAME->sp; + where = CURRENT_FRAME->where; + fp--; + + if(where <= 0xff) + { + store_variable(where, retval); + } + else if(where == 0xff + 2) + { + PUSH_STACK(retval); + break_from(interrupt_level()); + } +} + +void zret_popped(void) +{ + do_return(POP_STACK()); +} + +void zpop(void) +{ + POP_STACK(); +} + +void zcatch(void) +{ + ZASSERT(zversion == 6 || NFRAMES > 1, "@catch called outside of a function"); + + /* Must account for the dummy frame in non-V6 stories. */ + store(zversion == 6 ? NFRAMES : NFRAMES - 1); +} + +void zthrow(void) +{ + /* As with @catch, account for the dummy frame. */ + if(zversion != 6) zargs[1]++; + + ZASSERT(zversion == 6 || NFRAMES > 1, "@throw called outside of a function"); + ZASSERT(zargs[1] <= NFRAMES, "unwinding too far"); + + fp = BASE_OF_FRAMES + zargs[1]; + + do_return(zargs[0]); +} + +void zret(void) +{ + do_return(zargs[0]); +} + +void zrtrue(void) +{ + do_return(1); +} + +void zrfalse(void) +{ + do_return(0); +} + +void zcheck_arg_count(void) +{ + ZASSERT(zversion == 6 || NFRAMES > 1, "@check_arg_count called outside of a function"); + + branch_if(zargs[0] <= CURRENT_FRAME->nargs); +} + +void zpop_stack(void) +{ + if(znargs == 1) + { + for(uint16_t i = 0; i < zargs[0]; i++) POP_STACK(); + } + else + { + user_store_word(zargs[1], user_word(zargs[1]) + zargs[0]); + } +} + +void zpush_stack(void) +{ + uint16_t slots = user_word(zargs[1]); + + if(slots == 0) + { + branch_if(0); + return; + } + + user_store_word(zargs[1] + (2 * slots), zargs[0]); + user_store_word(zargs[1], slots - 1); + + branch_if(1); +} + +/* Compress dynamic memory according to Quetzal. Memory is allocated + * for the passed-in pointer, and must be freed by the caller. The + * return value is the size of compressed memory, or 0 on failure. + */ +static uint32_t compress_memory(uint8_t **compressed) +{ + uint32_t ret = 0; + long i = 0; + uint8_t *tmp; + + /* The output buffer needs to be 1.5× the size of dynamic memory for + * the worst-case scenario: every other byte in memory differs from + * the story file. This will cause every other byte to take up two + * bytes in the output, thus creating 3 bytes of output for every 2 of + * input. This should round up for the extreme case of alternating + * zero/non-zero bytes with zeroes at the beginning and end, but due + * to the fact that trailing zeroes are not stored, it does not need + * to. + */ + tmp = malloc((3 * header.static_start) / 2); + if(tmp == NULL) return 0; + + while(1) + { + long run = i; + + /* Count zeroes. Stop counting when: + * • The end of dynamic memory is reached + * • A non-zero value is found + */ + while(i < header.static_start && (BYTE(i) ^ dynamic_memory[i]) == 0) + { + i++; + } + + run = i - run; + + /* A run of zeroes at the end need not be written. */ + if(i == header.static_start) break; + + /* If there has been a run of zeroes, write them out + * 256 at a time. + */ + while(run > 0) + { + tmp[ret++] = 0; + tmp[ret++] = (run > 256 ? 255 : run - 1); + run -= 256; + } + + /* The current byte differs from the story, so write it. */ + tmp[ret++] = BYTE(i) ^ dynamic_memory[i]; + + i++; + } + + *compressed = realloc(tmp, ret); + if(*compressed == NULL) *compressed = tmp; + + return ret; +} + +/* Reverse of the above function. */ +static int uncompress_memory(const uint8_t *compressed, uint32_t size) +{ + uint32_t memory_index = 0; + + memcpy(memory, dynamic_memory, header.static_start); + + for(uint32_t i = 0; i < size; i++) + { + if(compressed[i] != 0) + { + if(memory_index == header.static_start) return -1; + STORE_BYTE(memory_index, BYTE(memory_index) ^ compressed[i]); + memory_index++; + } + else + { + if(++i == size) return -1; + + if(memory_index + (compressed[i] + 1) > header.static_start) return -1; + memory_index += (compressed[i] + 1); + } + } + + return 0; +} + +/* Push the current game state onto the game-state stack. */ +int push_save(void) +{ + struct save_state *new; + + if(options.max_saves == 0) return -1; + + new = malloc(sizeof *new); + if(new == NULL) goto err; + new->stack = NULL; + new->frames = NULL; + + new->pc = pc; + + new->stack_size = sp - BASE_OF_STACK; + new->stack = malloc(new->stack_size * sizeof *new->stack); + if(new->stack == NULL) goto err; + memcpy(new->stack, BASE_OF_STACK, new->stack_size * sizeof *new->stack); + + new->nframes = NFRAMES; + new->frames = malloc(new->nframes * sizeof *new->frames); + if(new->frames == NULL) goto err; + memcpy(new->frames, BASE_OF_FRAMES, new->nframes * sizeof *new->frames); + + if(options.disable_undo_compression) + { + new->memory = malloc(header.static_start); + if(new->memory == NULL) goto err; + memcpy(new->memory, memory, header.static_start); + } + else + { + new->memsize = compress_memory(&new->memory); + if(new->memsize == 0) goto err; + } + + /* If the maximum number has been reached, drop the last element. + * A negative value for max_saves means there is no maximum. + */ + if(options.max_saves > 0 && nsaves == options.max_saves) + { + struct save_state *tmp = saves_tail; + saves_tail = saves_tail->prev; + if(saves_tail == NULL) saves_head = NULL; + else saves_tail->next = NULL; + free(tmp->stack); + free(tmp->frames); + free(tmp->memory); + free(tmp); + nsaves--; + } + + new->next = saves_head; + new->prev = NULL; + if(new->next != NULL) new->next->prev = new; + saves_head = new; + if(saves_tail == NULL) saves_tail = new; + + nsaves++; + + return 1; + +err: + if(new != NULL) + { + free(new->stack); + free(new->frames); + free(new); + } + + return 0; +} + +/* Pop the last-stored game state and jump to it. */ +int pop_save(void) +{ + struct save_state *p; + + if(nsaves == 0) return 0; + + p = saves_head; + + pc = p->pc; + + if(options.disable_undo_compression) + { + memcpy(memory, p->memory, header.static_start); + } + else + { + /* If this fails it’s a bug: unlike Quetzal files, the contents of + * p->memory are known to be good, because the compression was done + * by us with no chance for corruption (apart, again, from bugs). + */ + if(uncompress_memory(p->memory, p->memsize) == -1) die("error uncompressing memory: unable to continue"); + } + + sp = BASE_OF_STACK + p->stack_size; + memcpy(BASE_OF_STACK, p->stack, sizeof *sp * p->stack_size); + + fp = BASE_OF_FRAMES + p->nframes; + memcpy(BASE_OF_FRAMES, p->frames, sizeof *p->frames * p->nframes); + + /* Never pop off the last state. A story has every right to call + * @restore_undo as many times as it called @save_undo. However, if + * there aren’t enough save slots, popping off the last state would + * cause @restore_undo to return failure when it should not. + */ + if(nsaves > 1) + { + saves_head = saves_head->next; + saves_head->prev = NULL; + + free(p->stack); + free(p->frames); + free(p->memory); + free(p); + + nsaves--; + } + + return 2; +} + +void zsave_undo(void) +{ + if(interrupt_level() != 0) die("@save_undo called inside of an interrupt"); + + store(push_save()); +} + +void zrestore_undo(void) +{ + uint16_t flags2; + + /* §6.1.2: Flags 2 should be preserved. */ + flags2 = WORD(0x10); + store(pop_save()); + STORE_WORD(0x10, flags2); +} + +/* Quetzal save/restore functions. */ +static jmp_buf exception; +#define WRITE8(v) do { uint8_t v_ = (v); if(zterp_io_write(savefile, &v_, sizeof v_) != sizeof v_) longjmp(exception, 1); local_written += 1; } while(0) +#define WRITE16(v) do { uint16_t w_ = (v); WRITE8(w_ >> 8); WRITE8(w_ & 0xff); } while(0) +#define WRITE32(v) do { uint32_t x_ = (v); WRITE8(x_ >> 24); WRITE8((x_ >> 16) & 0xff); WRITE8((x_ >> 8) & 0xff); WRITE8(x_ & 0xff); } while(0) +#define WRITEID(v) WRITE32(STRID(v)) + +static size_t quetzal_write_stack(zterp_io *savefile) +{ + size_t local_written = 0; + + /* Add one more “fake” call frame with just enough information to + * calculate the evaluation stack used by the current routine. + */ + fp->sp = sp; + for(struct call_frame *p = BASE_OF_FRAMES; p != fp; p++) + { + uint8_t temp; + + WRITE8((p->pc >> 16) & 0xff); + WRITE8((p->pc >> 8) & 0xff); + WRITE8((p->pc >> 0) & 0xff); + + temp = p->nlocals; + if(p->where > 0xff) temp |= 0x10; + WRITE8(temp); + + if(p->where > 0xff) WRITE8(0); + else WRITE8(p->where); + + WRITE8((1U << p->nargs) - 1); + + /* number of words of evaluation stack used */ + WRITE16((p + 1)->sp - p->sp); + + /* local variables */ + for(int i = 0; i < p->nlocals; i++) WRITE16(p->locals[i]); + + /* evaluation stack */ + for(ptrdiff_t i = 0; i < (p + 1)->sp - p->sp; i++) WRITE16(p->sp[i]); + } + + return local_written; +} + +int save_quetzal(zterp_io *savefile, int is_meta) +{ + if(setjmp(exception) != 0) return 0; + + size_t local_written = 0; + size_t game_len; + uint32_t memsize; + uint8_t *compressed; + uint8_t *mem = memory; + long stks_pos; + size_t stack_size; + + WRITEID("FORM"); + WRITEID(" "); /* to be filled in */ + WRITEID(is_meta ? "BFMS" : "IFZS"); + + WRITEID("IFhd"); + WRITE32(13); + WRITE16(header.release); + zterp_io_write(savefile, header.serial, 6); + local_written += 6; + WRITE16(header.checksum); + WRITE8(pc >> 16); + WRITE8(pc >> 8); + WRITE8(pc & 0xff); + WRITE8(0); /* padding */ + + /* Store the filename in an IntD chunk. */ + game_len = 12 + strlen(game_file); + WRITEID("IntD"); + WRITE32(game_len); + WRITEID("UNIX"); + WRITE8(0x02); + WRITE8(0); + WRITE16(0); + WRITEID(" "); + zterp_io_write(savefile, game_file, game_len - 12); + local_written += (game_len - 12); + if(game_len & 1) WRITE8(0); + + memsize = compress_memory(&compressed); + + /* It is possible for the compressed memory size to be larger than + * uncompressed; in this case, just store the uncompressed memory. + */ + if(memsize > 0 && memsize < header.static_start) + { + mem = compressed; + WRITEID("CMem"); + } + else + { + memsize = header.static_start; + WRITEID("UMem"); + } + WRITE32(memsize); + zterp_io_write(savefile, mem, memsize); + local_written += memsize; + if(memsize & 1) WRITE8(0); /* padding */ + free(compressed); + + WRITEID("Stks"); + stks_pos = zterp_io_tell(savefile); + WRITEID(" "); /* to be filled in */ + stack_size = quetzal_write_stack(savefile); + local_written += stack_size; + if(stack_size & 1) WRITE8(0); /* padding */ + + zterp_io_seek(savefile, 4, SEEK_SET); + WRITE32(local_written - 8); /* entire file size minus 8 (FORM + size) */ + + zterp_io_seek(savefile, stks_pos, SEEK_SET); + WRITE32(stack_size); /* size of the stacks chunk */ + + return 1; +} + +/* Restoring can put the system in an inconsistent state by restoring + * only part of memory: the save file may be corrupt and cause failure + * part way through updating memory, for example. This set of functions + * takes a snapshot of the current state of dynamic memory and the + * stacks so they can be restored on failure. + */ +static uint8_t *memory_backup; +static uint16_t *stack_backup; +static int stack_backup_size; +static struct call_frame *frames_backup; +static int frames_backup_size; + +static void memory_snapshot_free(void) +{ + free(memory_backup); + free(stack_backup); + free(frames_backup); + + memory_backup = NULL; + stack_backup = NULL; + frames_backup = NULL; +} + +static void memory_snapshot(void) +{ + memory_snapshot_free(); + + memory_backup = malloc(header.static_start); + if(memory_backup == NULL) goto err; + + memcpy(memory_backup, memory, header.static_start); + + stack_backup_size = sp - stack; + if(stack_backup_size != 0) + { + stack_backup = malloc(stack_backup_size * sizeof *stack); + if(stack_backup == NULL) goto err; + memcpy(stack_backup, stack, stack_backup_size * sizeof *stack); + } + + frames_backup_size = fp - frames; + if(frames_backup_size != 0) + { + frames_backup = malloc(frames_backup_size * sizeof *frames); + if(frames_backup == NULL) goto err; + memcpy(frames_backup, frames, frames_backup_size * sizeof *frames); + } + + return; + +err: + memory_snapshot_free(); + + return; +} + +static int memory_restore(void) +{ + /* stack_backup and frames_backup will be NULL if the stacks were + * empty, so use memory_backup to determine if a snapshot has been + * taken. + */ + if(memory_backup == NULL) return 0; + + memcpy(memory, memory_backup, header.static_start); + if(stack_backup != NULL) memcpy(stack, stack_backup, stack_backup_size * sizeof *stack); + sp = stack + stack_backup_size; + if(frames_backup != NULL) memcpy(frames, frames_backup, frames_backup_size * sizeof *frames); + fp = frames + frames_backup_size; + + memory_snapshot_free(); + + return 1; +} + +#define goto_err(...) do { show_message("save file error: " __VA_ARGS__); goto err; } while(0) +#define goto_death(...) do { show_message("save file error: " __VA_ARGS__); goto death; } while(0) + +int restore_quetzal(zterp_io *savefile, int is_meta) +{ + zterp_iff *iff; + uint32_t size; + uint32_t n = 0; + uint8_t ifhd[13]; + + iff = zterp_iff_parse(savefile, is_meta ? "BFMS" : "IFZS"); + + if(iff == NULL || + !zterp_iff_find(iff, "IFhd", &size) || + size != 13 || + zterp_io_read(savefile, ifhd, sizeof ifhd) != sizeof ifhd) + { + goto_err("corrupted save file or not a save file at all"); + } + + if(((ifhd[0] << 8) | ifhd[1]) != header.release || + memcmp(&ifhd[2], header.serial, sizeof header.serial) != 0 || + ((ifhd[8] << 8) | ifhd[9]) != header.checksum) + { + goto_err("wrong game or version"); + } + + memory_snapshot(); + + if(zterp_iff_find(iff, "CMem", &size)) + { + uint8_t buf[size]; /* Too big for the stack? */ + + if(zterp_io_read(savefile, buf, size) != size) goto_err("unexpected eof reading compressed memory"); + + if(uncompress_memory(buf, size) == -1) goto_death("memory cannot be uncompressed"); + } + else if(zterp_iff_find(iff, "UMem", &size)) + { + if(size != header.static_start) goto_err("memory size mismatch"); + if(zterp_io_read(savefile, memory, header.static_start) != header.static_start) goto_death("unexpected eof reading memory"); + } + else + { + goto_err("no memory chunk found"); + } + + if(!zterp_iff_find(iff, "Stks", &size)) goto_death("no stacks chunk found"); + + sp = BASE_OF_STACK; + fp = BASE_OF_FRAMES; + + while(n < size) + { + uint8_t frame[8]; + uint8_t nlocals; + uint16_t nstack; + uint8_t nargs = 0; + + if(zterp_io_read(savefile, frame, sizeof frame) != sizeof frame) goto_death("unexpected eof reading stack frame"); + n += sizeof frame; + + nlocals = frame[3] & 0xf; + nstack = (frame[6] << 8) | frame[7]; + frame[5]++; + while(frame[5] >>= 1) nargs++; + + add_frame((frame[0] << 16) | (frame[1] << 8) | frame[2], sp, nlocals, nargs, (frame[3] & 0x10) ? 0xff + 1 : frame[4]); + + for(int i = 0; i < nlocals; i++) + { + uint16_t l; + + if(!zterp_io_read16(savefile, &l)) goto_death("unexpected eof reading local variable"); + CURRENT_FRAME->locals[i] = l; + + n += sizeof l; + } + + for(uint16_t i = 0; i < nstack; i++) + { + uint16_t s; + + if(!zterp_io_read16(savefile, &s)) goto_death("unexpected eof reading stack entry"); + PUSH_STACK(s); + + n += sizeof s; + } + } + + if(n != size) goto_death("stack size mismatch"); + + zterp_iff_free(iff); + memory_snapshot_free(); + + pc = (ifhd[10] << 16) | (ifhd[11] << 8) | ifhd[12]; + + return 1; + +death: + /* At this point, something vital (memory and/or the stacks) has been + * scribbed upon; if there was a successful backup, restore it. + * Otherwise the only course of action is to exit. + */ + if(!memory_restore()) die("the system is likely in an inconsistent state"); + +err: + /* A snapshot may have been taken, but neither memory nor the stacks + * have been overwritten, so just free the snapshot. + */ + memory_snapshot_free(); + zterp_iff_free(iff); + return 0; +} + +#undef goto_err +#undef goto_death + +/* Perform all aspects of a save, apart from storing/branching. + * Returns true if the save was success, false if not. + * “is_meta” is true if this save file is from a meta-save. + */ +int do_save(int is_meta) +{ + zterp_io *savefile; + int success; + + savefile = zterp_io_open(NULL, ZTERP_IO_WRONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + warning("unable to open save file"); + return 0; + } + + success = save_quetzal(savefile, is_meta); + + zterp_io_close(savefile); + + return success; +} + +/* The suggested filename is ignored, because Glk and, at least as of + * right now, zterp_io_open(), do not provide a method to do this. + * The “prompt” argument added by standard 1.1 is thus also ignored. + */ +void zsave(void) +{ + if(interrupt_level() != 0) die("@save called inside of an interrupt"); + + int success = do_save(0); + + if(zversion <= 3) branch_if(success); + else store(success); +} + +/* Perform all aspects of a restore, apart from storing/branching. + * Returns true if the restore was success, false if not. + * “is_meta” is true if this save file is expected to be from a + * meta-save. + */ +int do_restore(int is_meta) +{ + zterp_io *savefile; + uint16_t flags2; + int success; + + savefile = zterp_io_open(NULL, ZTERP_IO_RDONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + warning("unable to open save file"); + return 0; + } + + flags2 = WORD(0x10); + + success = restore_quetzal(savefile, is_meta); + + zterp_io_close(savefile); + + if(success) + { + /* On a successful restore, we are outside of any interrupt (since + * @save cannot be called inside an interrupt), so reset the level + * back to zero. In addition, there may be pending read events that + * need to be canceled, so do that, too. + */ + reset_level(); + cancel_all_events(); + + /* §8.6.1.3 */ + if(zversion == 3) close_upper_window(); + + /* The save might be from a different interpreter with different + * capabilities, so update the header to indicate what the current + * capabilities are... + */ + write_header(); + + /* ...except that flags2 should be preserved (§6.1.2). */ + STORE_WORD(0x10, flags2); + + /* Redraw the status line in games that use one. */ + if(zversion <= 3) zshow_status(); + } + + return success; +} + +void zrestore(void) +{ + int success = do_restore(0); + + if(zversion <= 3) branch_if(success); + else store(success ? 2 : 0); +} diff --git a/interpreters/bocfel/stack.h b/interpreters/bocfel/stack.h new file mode 100644 index 0000000..ab87413 --- /dev/null +++ b/interpreters/bocfel/stack.h @@ -0,0 +1,63 @@ +#ifndef ZTERP_STACK_H +#define ZTERP_STACK_H + +#include + +#include "io.h" + +#define DEFAULT_STACK_SIZE 0x4000 +#define DEFAULT_CALL_DEPTH 0x400 + +void init_stack(void); + +uint16_t variable(uint16_t); +void store_variable(uint16_t, uint16_t); +uint16_t *stack_top_element(void); + +void call(int); +#ifdef ZTERP_GLK +uint16_t direct_call(uint16_t); +#endif +void do_return(uint16_t); + +int save_quetzal(zterp_io *, int); +int restore_quetzal(zterp_io *, int); + +int do_save(int); +int do_restore(int); + +int push_save(void); +int pop_save(void); + +void zpush(void); +void zpull(void); +void zload(void); +void zstore(void); +void zret_popped(void); +void zpop(void); +void zcatch(void); +void zthrow(void); +void zret(void); +void zrtrue(void); +void zrfalse(void); +void zcheck_arg_count(void); +void zpop_stack(void); +void zpush_stack(void); +void zsave_undo(void); +void zrestore_undo(void); +void zsave(void); +void zrestore(void); + +void zcall_store(void); +void zcall_nostore(void); + +#define zcall zcall_store +#define zcall_1n zcall_nostore +#define zcall_1s zcall_store +#define zcall_2n zcall_nostore +#define zcall_2s zcall_store +#define zcall_vn zcall_nostore +#define zcall_vn2 zcall_nostore +#define zcall_vs2 zcall_store + +#endif diff --git a/interpreters/bocfel/table.c b/interpreters/bocfel/table.c new file mode 100644 index 0000000..1de5e9e --- /dev/null +++ b/interpreters/bocfel/table.c @@ -0,0 +1,90 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "table.h" +#include "branch.h" +#include "memory.h" +#include "process.h" +#include "zterp.h" + +void zcopy_table(void) +{ + uint16_t first = zargs[0], second = zargs[1], size = zargs[2]; + + if(second == 0) + { + for(uint16_t i = 0; i < size; i++) user_store_byte(first + i, 0); + } + else if( (first > second) || (int16_t)size < 0 ) + { + long n = labs((int16_t)size); + for(long i = 0; i < n; i++) user_store_byte(second + i, user_byte(first + i)); + } + else + { + for(uint16_t i = 0; i < size; i++) user_store_byte(second + size - i - 1, user_byte(first + size - i - 1)); + } +} + +void zscan_table(void) +{ + uint16_t addr = zargs[1]; + + if(znargs < 4) zargs[3] = 0x82; + + for(uint16_t i = 0; i < zargs[2]; i++) + { + if( + ( (zargs[3] & 0x80) && (user_word(addr) == zargs[0])) || + (!(zargs[3] & 0x80) && (user_byte(addr) == zargs[0])) + ) + { + store(addr); + branch_if(1); + return; + } + + addr += zargs[3] & 0x7f; + } + + store(0); + branch_if(0); +} + +void zloadw(void) +{ + store(user_word(zargs[0] + (2 * zargs[1]))); +} + +void zloadb(void) +{ + store(user_byte(zargs[0] + zargs[1])); +} + +void zstoreb(void) +{ + user_store_byte(zargs[0] + zargs[1], zargs[2]); +} + +void zstorew(void) +{ + user_store_word(zargs[0] + (2 * zargs[1]), zargs[2]); +} diff --git a/interpreters/bocfel/table.h b/interpreters/bocfel/table.h new file mode 100644 index 0000000..0855541 --- /dev/null +++ b/interpreters/bocfel/table.h @@ -0,0 +1,11 @@ +#ifndef ZTERP_TABLE_H +#define ZTERP_TABLE_H + +void zcopy_table(void); +void zscan_table(void); +void zloadw(void); +void zloadb(void); +void zstoreb(void); +void zstorew(void); + +#endif diff --git a/interpreters/bocfel/unicode.c b/interpreters/bocfel/unicode.c new file mode 100644 index 0000000..78f610d --- /dev/null +++ b/interpreters/bocfel/unicode.c @@ -0,0 +1,357 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "unicode.h" +#include "memory.h" +#include "util.h" +#include "zterp.h" + +int have_unicode; + +/* + * The index is the ZSCII value, minus 155 (so entry 0 refers to ZSCII + * value 155); and the value at the index is the Unicode character that + * the ZSCII value maps to. Because Latin-1 and Unicode are equivalent + * for 0–255, this table maps to both Unicode and Latin1, with the + * caveat that values greater than 255 should be considered invalid in + * Latin-1, and are translated as a question mark below in + * setup_tables() where appropriate. + */ +#define UNICODE_TABLE_SIZE 97 +static int unicode_entries = 69; +static uint16_t unicode_table[UNICODE_TABLE_SIZE] = { +0x00e4, 0x00f6, 0x00fc, 0x00c4, 0x00d6, 0x00dc, 0x00df, 0x00bb, 0x00ab, +0x00eb, 0x00ef, 0x00ff, 0x00cb, 0x00cf, 0x00e1, 0x00e9, 0x00ed, 0x00f3, +0x00fa, 0x00fd, 0x00c1, 0x00c9, 0x00cd, 0x00d3, 0x00da, 0x00dd, 0x00e0, +0x00e8, 0x00ec, 0x00f2, 0x00f9, 0x00c0, 0x00c8, 0x00cc, 0x00d2, 0x00d9, +0x00e2, 0x00ea, 0x00ee, 0x00f4, 0x00fb, 0x00c2, 0x00ca, 0x00ce, 0x00d4, +0x00db, 0x00e5, 0x00c5, 0x00f8, 0x00d8, 0x00e3, 0x00f1, 0x00f5, 0x00c3, +0x00d1, 0x00d5, 0x00e6, 0x00c6, 0x00e7, 0x00c7, 0x00fe, 0x00f0, 0x00de, +0x00d0, 0x00a3, 0x0153, 0x0152, 0x00a1, 0x00bf, +}; + +/* If a non-default Unicode table is found, this function is called with + * its address; it updates the Unicode table above. + */ +void parse_unicode_table(uint16_t utable) +{ + if(utable >= memory_size) die("corrupted story: unicode table out of range"); + + unicode_entries = BYTE(utable++); + + if(unicode_entries > UNICODE_TABLE_SIZE) die("corrupted story: too many entries in the unicode table"); + if(utable + (2 * unicode_entries) >= memory_size) die("corrupted story: unicode table out of range"); + + for(int i = 0; i < unicode_entries; i++) + { + unicode_table[i] = WORD(utable + (2 * i)); + } +} + +/* Table used to convert a ZSCII value to Unicode; and since this is + * only used for output, non-output values will be returned as a + * question mark. + */ +uint16_t zscii_to_unicode[UINT8_MAX + 1]; + +/* These tables translate a Unicode or (Latin-1) character into its + * ZSCII equivalent. Only valid Unicode characters are translated (that + * is, those in the range 32–126, or 160 and above). + * + * The first table will translate invalid Unicode characters to zero; + * the second, to a question mark. + */ +uint8_t unicode_to_zscii [UINT16_MAX + 1]; +uint8_t unicode_to_zscii_q[UINT16_MAX + 1]; + +/* Convenience table: pass through all values 0–255, but yield a question mark + * for others. */ +uint8_t unicode_to_latin1[UINT16_MAX + 1]; + +/* Convert ZSCII to Unicode line-drawing/rune characters. */ +uint16_t zscii_to_font3[UINT8_MAX + 1]; + +/* Lookup table to see if a character is in the alphabet table. Key is + * the character, value is the index in the alphabet table, or -1. + */ +int atable_pos[UINT8_MAX + 1]; + +/* Not all fonts provide all characters, so there + * may well be a lot of question marks. + */ +static void build_font3_table(void) +{ + for(int i = 0; i < UINT8_MAX; i++) zscii_to_font3[i] = UNICODE_QUESTIONMARK; + + zscii_to_font3[ 32] = UNICODE_SPACE; + zscii_to_font3[ 33] = 0x2190; /* ← */ + zscii_to_font3[ 34] = 0x2192; /* → */ + zscii_to_font3[ 35] = 0x2571; /* ╱ */ + zscii_to_font3[ 36] = 0x2572; /* ╲ */ + zscii_to_font3[ 37] = UNICODE_SPACE; + zscii_to_font3[ 38] = 0x2500; /* ─ */ + zscii_to_font3[ 39] = 0x2500; /* ─ */ + zscii_to_font3[ 40] = 0x2502; /* │ */ + zscii_to_font3[ 41] = 0x2502; /* │ (this should be slightly offset, but whatever) */ + zscii_to_font3[ 42] = 0x2534; /* ┴ */ + zscii_to_font3[ 43] = 0x252c; /* ┬ */ + zscii_to_font3[ 44] = 0x251c; /* ├ */ + zscii_to_font3[ 45] = 0x2524; /* ┤ */ + zscii_to_font3[ 46] = 0x2514; /* └ */ + zscii_to_font3[ 47] = 0x250c; /* ┌ */ + zscii_to_font3[ 48] = 0x2510; /* ┐ */ + zscii_to_font3[ 49] = 0x2518; /* ┘ */ + + /* There are a few characters that have no box-drawing equivalents. + * These are the pieces that have connections sticking out of them, + * used to link rooms together. There are two options: have filled + * boxes with no connections which makes the rooms look nice but the + * connections look bad, or unfilled boxes with connections which + * results in bad looking rooms but attached connections. The end + * result is something like this: + * + * No connections: Connections: + * ╲ ╱ ╲ ╱ + * ┌─┐ ▗▄▖ ╲─┐ ▗▄╱ + * │ ├─▐█▌─ │ ├─┤█├─ + * └─┘ ▝▀▘ └─┘ ▝┬▘ + * │ │ + * + * By default the former is done, but the latter can be chosen. + */ + zscii_to_font3[ 50] = options.enable_alt_graphics ? 0x2571 : 0x2514; /* ╱ or └ */ + zscii_to_font3[ 51] = options.enable_alt_graphics ? 0x2572 : 0x250c; /* ╲ or ┌ */ + zscii_to_font3[ 52] = options.enable_alt_graphics ? 0x2571 : 0x2510; /* ╱ or ┐ */ + zscii_to_font3[ 53] = options.enable_alt_graphics ? 0x2572 : 0x2518; /* ╲ or ┘ */ + + zscii_to_font3[ 54] = 0x2588; /* █ */ + zscii_to_font3[ 56] = 0x2584; /* ▄ */ + zscii_to_font3[ 55] = 0x2580; /* ▀ */ + zscii_to_font3[ 57] = 0x258c; /* ▌ */ + zscii_to_font3[ 58] = 0x2590; /* ▐ */ + + zscii_to_font3[ 59] = options.enable_alt_graphics ? 0x2534 : 0x2584; /* ┴ or ▄ */ + zscii_to_font3[ 60] = options.enable_alt_graphics ? 0x252c : 0x2580; /* ┬ or ▀ */ + zscii_to_font3[ 61] = options.enable_alt_graphics ? 0x251c : 0x258c; /* ├ or ▌ */ + zscii_to_font3[ 62] = options.enable_alt_graphics ? 0x2524 : 0x2590; /* ┤ or ▐ */ + + zscii_to_font3[ 63] = 0x259d; /* ▝ */ + zscii_to_font3[ 64] = 0x2597; /* ▗ */ + zscii_to_font3[ 65] = 0x2596; /* ▖ */ + zscii_to_font3[ 66] = 0x2598; /* ▘ */ + + zscii_to_font3[ 67] = options.enable_alt_graphics ? 0x2571 : 0x259d; /* ╱ or ▝ */ + zscii_to_font3[ 68] = options.enable_alt_graphics ? 0x2572 : 0x2597; /* ╲ or ▗ */ + zscii_to_font3[ 69] = options.enable_alt_graphics ? 0x2571 : 0x2596; /* ╱ or ▖ */ + zscii_to_font3[ 70] = options.enable_alt_graphics ? 0x2572 : 0x2598; /* ╲ or ▘ */ + + zscii_to_font3[ 75] = 0x2594; /* ▔ */ + zscii_to_font3[ 76] = 0x2581; /* ▁ */ + zscii_to_font3[ 77] = 0x258f; /* ▏ */ + zscii_to_font3[ 78] = 0x2595; /* ▕ */ + + zscii_to_font3[ 79] = UNICODE_SPACE; + zscii_to_font3[ 80] = 0x258f; /* ▏ */ + zscii_to_font3[ 81] = 0x258e; /* ▎ */ + zscii_to_font3[ 82] = 0x258d; /* ▍ */ + zscii_to_font3[ 83] = 0x258c; /* ▌ */ + zscii_to_font3[ 84] = 0x258b; /* ▋ */ + zscii_to_font3[ 85] = 0x258a; /* ▊ */ + zscii_to_font3[ 86] = 0x2589; /* ▉ */ + zscii_to_font3[ 87] = 0x2588; /* █ */ + zscii_to_font3[ 88] = 0x2595; /* ▕ */ + zscii_to_font3[ 89] = 0x258f; /* ▏ */ + + zscii_to_font3[ 90] = 0x2573; /* ╳ */ + zscii_to_font3[ 91] = 0x253c; /* ┼ */ + zscii_to_font3[ 92] = 0x2191; /* ↑ */ + zscii_to_font3[ 93] = 0x2193; /* ↓ */ + zscii_to_font3[ 94] = 0x2195; /* ↕ */ + zscii_to_font3[ 95] = 0x2b1c; /* ⬜ */ + zscii_to_font3[ 96] = UNICODE_QUESTIONMARK; + zscii_to_font3[ 97] = 0x16aa; /* ᚪ */ + zscii_to_font3[ 98] = 0x16d2; /* ᛒ */ + zscii_to_font3[ 99] = 0x16c7; /* ᛇ */ + zscii_to_font3[100] = 0x16de; /* ᛞ */ + zscii_to_font3[101] = 0x16d6; /* ᛖ */ + zscii_to_font3[102] = 0x16a0; /* ᚠ */ + zscii_to_font3[103] = 0x16b7; /* ᚷ */ + zscii_to_font3[104] = 0x16bb; /* ᚻ */ + zscii_to_font3[105] = 0x16c1; /* ᛁ */ + zscii_to_font3[106] = 0x16e8; /* ᛨ */ + zscii_to_font3[107] = 0x16e6; /* ᛦ */ + zscii_to_font3[108] = 0x16da; /* ᛚ */ + zscii_to_font3[109] = 0x16d7; /* ᛗ */ + zscii_to_font3[110] = 0x16be; /* ᚾ */ + zscii_to_font3[111] = 0x16a9; /* ᚩ */ + zscii_to_font3[112] = UNICODE_QUESTIONMARK; /* no good symbol */ + zscii_to_font3[113] = 0x0068; /* Unicode 'h'; close to the rune. */ + zscii_to_font3[114] = 0x16b1; /* ᚱ */ + zscii_to_font3[115] = 0x16cb; /* ᛋ */ + zscii_to_font3[116] = 0x16cf; /* ᛏ */ + zscii_to_font3[117] = 0x16a2; /* ᚢ */ + zscii_to_font3[118] = 0x16e0; /* ᛠ */ + zscii_to_font3[119] = 0x16b9; /* ᚹ */ + zscii_to_font3[120] = 0x16c9; /* ᛉ */ + zscii_to_font3[121] = 0x16a5; /* ᚥ */ + zscii_to_font3[122] = 0x16df; /* ᛟ */ + + /* These are reversed (see §16); a slightly ugly hack in screen.c is + * used to accomplish this. + */ + zscii_to_font3[123] = 0x2191; /* ↑ */ + zscii_to_font3[124] = 0x2193; /* ↓ */ + zscii_to_font3[125] = 0x2195; /* ↕ */ + zscii_to_font3[126] = UNICODE_QUESTIONMARK; +} + +void setup_tables(void) +{ + /*** ZSCII to Unicode table. ***/ + + for(int i = 0; i < UINT8_MAX + 1; i++) zscii_to_unicode[i] = UNICODE_QUESTIONMARK; + zscii_to_unicode[0] = 0; + zscii_to_unicode[ZSCII_NEWLINE] = UNICODE_LINEFEED; + + if(zversion == 6) zscii_to_unicode[ 9] = UNICODE_SPACE; /* Tab. */ + if(zversion == 6) zscii_to_unicode[11] = UNICODE_SPACE; /* Sentence space. */ + + for(int i = 32; i < 127; i++) zscii_to_unicode[i] = i; + for(int i = 0; i < unicode_entries; i++) + { + uint16_t c = unicode_table[i]; + + if(!valid_unicode(c)) c = UNICODE_QUESTIONMARK; + + /* If Unicode is not available, then any values > 255 are invalid. */ + else if(!have_unicode && c > 255) c = UNICODE_QUESTIONMARK; + + zscii_to_unicode[i + 155] = c; + } + + /*** Unicode to ZSCII tables. ***/ + + /* Default values. */ + memset(unicode_to_zscii, 0, sizeof unicode_to_zscii); + memset(unicode_to_zscii_q, ZSCII_QUESTIONMARK, sizeof unicode_to_zscii_q); + + /* First fill up the entries found in the Unicode table. */ + for(int i = 0; i < unicode_entries; i++) + { + uint16_t c = unicode_table[i]; + + if(valid_unicode(c)) + { + unicode_to_zscii [c] = i + 155; + unicode_to_zscii_q[c] = i + 155; + } + } + + /* Now the values that are equivalent in ZSCII and Unicode. */ + for(int i = 32; i < 127; i++) + { + unicode_to_zscii [i] = i; + unicode_to_zscii_q[i] = i; + } + + /* Properly translate a newline. */ + unicode_to_zscii_q[UNICODE_LINEFEED] = ZSCII_NEWLINE; + + /*** Unicode to Latin1 table. ***/ + + memset(unicode_to_latin1, UNICODE_QUESTIONMARK, sizeof unicode_to_latin1); + for(int i = 0; i < 256; i++) unicode_to_latin1[i] = i; + + /*** ZSCII to character graphics table. ***/ + + build_font3_table(); + + /*** Alphabet table. ***/ + + for(int i = 0; i < 256; i++) atable_pos[i] = -1; + + /* 52 is A2 character 6, which is special and should not + * be matched, so skip over it. + */ + for(int i = 0; i < 52 ; i++) atable_pos[atable[i]] = i; + for(int i = 53; i < 26 * 3; i++) atable_pos[atable[i]] = i; +} + +/* This is adapted from Zip2000 (Copyright 2001 Kevin Bracey). */ +uint16_t unicode_tolower(uint16_t c) +{ + static const unsigned char basic_latin[0x100] = + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xd7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + static const unsigned char latin_extended_a[0x80] = + { + 0x01, 0x01, 0x03, 0x03, 0x05, 0x05, 0x07, 0x07, 0x09, 0x09, 0x0b, 0x0b, 0x0d, 0x0d, 0x0f, 0x0f, + 0x11, 0x11, 0x13, 0x13, 0x15, 0x15, 0x17, 0x17, 0x19, 0x19, 0x1b, 0x1b, 0x1d, 0x1d, 0x1f, 0x1f, + 0x21, 0x21, 0x23, 0x23, 0x25, 0x25, 0x27, 0x27, 0x29, 0x29, 0x2b, 0x2b, 0x2d, 0x2d, 0x2f, 0x2f, + 0x00, 0x31, 0x33, 0x33, 0x35, 0x35, 0x37, 0x37, 0x38, 0x3a, 0x3a, 0x3c, 0x3c, 0x3e, 0x3e, 0x40, + 0x40, 0x42, 0x42, 0x44, 0x44, 0x46, 0x46, 0x48, 0x48, 0x49, 0x4b, 0x4b, 0x4d, 0x4d, 0x4f, 0x4f, + 0x51, 0x51, 0x53, 0x53, 0x55, 0x55, 0x57, 0x57, 0x59, 0x59, 0x5b, 0x5b, 0x5d, 0x5d, 0x5f, 0x5f, + 0x61, 0x61, 0x63, 0x63, 0x65, 0x65, 0x67, 0x67, 0x69, 0x69, 0x6b, 0x6b, 0x6d, 0x6d, 0x6f, 0x6f, + 0x71, 0x71, 0x73, 0x73, 0x75, 0x75, 0x77, 0x77, 0x00, 0x7a, 0x7a, 0x7c, 0x7c, 0x7e, 0x7e, 0x7f + }; + static const unsigned char greek[0x50] = + { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0xac, 0x87, 0xad, 0xae, 0xaf, 0x8b, 0xcc, 0x8d, 0xcd, 0xce, + 0x90, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xa2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf + }; + static const unsigned char cyrillic[0x60] = + { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f + }; + + if (c < 0x0100) c = basic_latin[c]; + else if(c == 0x0130) c = 0x0069; /* capital i with dot -> lower case i */ + else if(c == 0x0178) c = 0x00ff; /* capital y diaeresis -> lower case y diaeresis */ + else if(c < 0x0180) c = latin_extended_a[c - 0x100] + 0x100; + else if(c >= 0x380 && c < 0x3d0) c = greek [c - 0x380] + 0x300; + else if(c >= 0x400 && c < 0x460) c = cyrillic [c - 0x400] + 0x400; + + return c; +} diff --git a/interpreters/bocfel/unicode.h b/interpreters/bocfel/unicode.h new file mode 100644 index 0000000..b421fef --- /dev/null +++ b/interpreters/bocfel/unicode.h @@ -0,0 +1,44 @@ +#ifndef ZTERP_TABLES_H +#define ZTERP_TABLES_H + +#include + +#ifdef ZTERP_GLK +#include +#endif + +#define UNICODE_LINEFEED 10 +#define UNICODE_SPACE 32 +#define UNICODE_QUESTIONMARK 63 + +#define ZSCII_NEWLINE 13 +#define ZSCII_SPACE 32 +#define ZSCII_QUESTIONMARK 63 + +/* This variable controls whether Unicode is used for screen + * output. This affects @check_unicode as well as the ZSCII to + * Unicode table. With Glk it is set based on whether the Glk + * implementation supports Unicode (checked with the Unicode + * gestalt), and determines whether Unicode IO functions should + * be used; otherwise, it is kept in parallel with use_utf8_io. + */ +extern int have_unicode; + +extern uint16_t zscii_to_unicode[]; +extern uint8_t unicode_to_zscii[]; +extern uint8_t unicode_to_zscii_q[]; +extern uint8_t unicode_to_latin1[]; +extern uint16_t zscii_to_font3[]; +extern int atable_pos[]; + +void parse_unicode_table(uint16_t); +void setup_tables(void); + +uint16_t unicode_tolower(uint16_t); + +/* Standard 1.1 notes that Unicode characters 0–31 and 127–159 + * are invalid due to the fact that they’re control codes. + */ +static inline int valid_unicode(uint16_t c) { return (c >= 32 && c <= 126) || c >= 160; } + +#endif diff --git a/interpreters/bocfel/util.c b/interpreters/bocfel/util.c new file mode 100644 index 0000000..6cd3d9a --- /dev/null +++ b/interpreters/bocfel/util.c @@ -0,0 +1,260 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#include "util.h" +#include "screen.h" +#include "unicode.h" +#include "zterp.h" + +#ifdef ZTERP_GLK +#include +#include +#endif + +#ifndef ZTERP_NO_SAFETY_CHECKS +unsigned long zassert_pc; + +void assert_fail(const char *fmt, ...) +{ + va_list ap; + char str[1024]; + + va_start(ap, fmt); + vsnprintf(str, sizeof str, fmt, ap); + va_end(ap); + + snprintf(str + strlen(str), sizeof str - strlen(str), " (pc = 0x%lx)", zassert_pc); + + die("%s", str); +} +#endif + +void warning(const char *fmt, ...) +{ + va_list ap; + char str[1024]; + + va_start(ap, fmt); + vsnprintf(str, sizeof str, fmt, ap); + va_end(ap); + + show_message("WARNING: %s", str); +} + +void die(const char *fmt, ...) +{ + va_list ap; + char str[1024]; + + va_start(ap, fmt); + vsnprintf(str, sizeof str, fmt, ap); + va_end(ap); + + show_message("fatal error: %s", str); + +#ifdef ZTERP_GLK +#ifdef GARGLK + fprintf(stderr, "%s\n", str); +#endif + glk_exit(); +#endif + + exit(EXIT_FAILURE); +} + +/* This is not POSIX compliant, but it gets the job done. + * It should not be called more than once. + */ +static int zoptind = 0; +static const char *zoptarg; +static int zgetopt(int argc, char **argv, const char *optstring) +{ + static const char *p = ""; + const char *optp; + int c; + + if(*p == 0) + { + /* No more arguments. */ + if(++zoptind >= argc) return -1; + + p = argv[zoptind]; + + /* No more options. */ + if(p[0] != '-' || p[1] == 0) return -1; + + /* Handle “--” */ + if(*++p == '-') + { + zoptind++; + return -1; + } + } + + c = *p++; + + optp = strchr(optstring, c); + if(optp == NULL) return '?'; + + if(optp[1] == ':') + { + if(*p != 0) zoptarg = p; + else zoptarg = argv[++zoptind]; + + p = ""; + if(zoptarg == NULL) return '?'; + } + + return c; +} + +char *xstrdup(const char *s) +{ + size_t n; + char *r; + + n = strlen(s) + 1; + + r = malloc(n); + if(r != NULL) memcpy(r, s, n); + + return r; +} + +int process_arguments(int argc, char **argv) +{ + int c; + + while( (c = zgetopt(argc, argv, "a:A:cCdDeE:fFgGiklLmn:N:rR:sS:tT:u:UvxXyz:Z:")) != -1 ) + { + switch(c) + { + case 'a': + options.eval_stack_size = strtol(zoptarg, NULL, 10); + break; + case 'A': + options.call_stack_size = strtol(zoptarg, NULL, 10); + break; + case 'c': + options.disable_color = 1; + break; + case 'C': + options.disable_config = 1; + break; + case 'd': + options.disable_timed = 1; + break; + case 'D': + options.disable_sound = 1; + break; + case 'e': + options.enable_escape = 1; + break; + case 'E': + options.escape_string = xstrdup(zoptarg); + break; + case 'f': + options.disable_fixed = 1; + break; + case 'F': + options.assume_fixed = 1; + break; + case 'g': + options.disable_graphics_font = 1; + break; + case 'G': + options.enable_alt_graphics = 1; + break; + case 'i': + options.show_id = 1; + break; + case 'k': + options.disable_term_keys = 1; + break; + case 'l': + options.disable_utf8 = 1; + break; + case 'L': + options.force_utf8 = 1; + break; + case 'm': + options.disable_meta_commands = 1; + break; + case 'n': + options.int_number = strtol(zoptarg, NULL, 10); + break; + case 'N': + options.int_version = zoptarg[0]; + break; + case 'r': + options.replay_on = 1; + break; + case 'R': + options.replay_name = xstrdup(zoptarg); + break; + case 's': + options.record_on = 1; + break; + case 'S': + options.record_name = xstrdup(zoptarg); + break; + case 't': + options.transcript_on = 1; + break; + case 'T': + options.transcript_name = xstrdup(zoptarg); + break; + case 'u': + options.max_saves = strtol(zoptarg, NULL, 10); + break; + case 'U': + options.disable_undo_compression = 1; + break; + case 'v': + options.show_version = 1; + break; + case 'x': + options.disable_abbreviations = 1; + break; + case 'X': + options.enable_censorship = 1; + break; + case 'y': + options.overwrite_transcript = 1; + break; + case 'z': + options.random_seed = strtol(zoptarg, NULL, 10); + break; + case 'Z': + options.random_device = xstrdup(zoptarg); + break; + default: + return 0; + } + } + + /* Just ignore excess stories for now. */ + if(zoptind < argc) game_file = argv[zoptind]; + + return 1; +} diff --git a/interpreters/bocfel/util.h b/interpreters/bocfel/util.h new file mode 100644 index 0000000..a3047a7 --- /dev/null +++ b/interpreters/bocfel/util.h @@ -0,0 +1,64 @@ +#ifndef ZTERP_UTIL_H +#define ZTERP_UTIL_H + +#ifdef ZTERP_GLK +#include +#endif + +#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)) +#define znoreturn __attribute__((__noreturn__)) +#define zprintflike(f, a) __attribute__((__format__(__printf__, f, a))) +#else +#define znoreturn +#define zprintflike(f, a) +#endif + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) +#define zexternally_visible __attribute__((__externally_visible__)) +#else +#define zexternally_visible +#endif + +#ifndef ZTERP_NO_SAFETY_CHECKS +extern unsigned long zassert_pc; +#define ZPC(pc) do { zassert_pc = pc; } while(0) + +zprintflike(1, 2) +znoreturn +void assert_fail(const char *, ...); +#define ZASSERT(expr, ...) do { if(!(expr)) assert_fail(__VA_ARGS__); } while(0) +#else +#define ZPC(pc) ((void)0) +#define ZASSERT(expr, ...) ((void)0) +#endif + +zprintflike(1, 2) +void warning(const char *, ...); + +zprintflike(1, 2) +znoreturn +void die(const char *, ...); + +char *xstrdup(const char *); +int process_arguments(int, char **); + +/* Somewhat ugly hack to get around the fact that some Glk functions may + * not exist. These function calls should all be guarded (e.g. + * if(have_unicode), with have_unicode being set iff GLK_MODULE_UNICODE + * is defined) so they will never be called if the Glk implementation + * being used does not support them, but they must at least exist to + * prevent link errors. + */ +#ifdef ZTERP_GLK +#ifndef GLK_MODULE_UNICODE +#define glk_put_char_uni(...) die("bug %s:%d: glk_put_char_uni() called with no unicode", __FILE__, __LINE__) +#define glk_put_string_uni(...) die("bug %s:%d: glk_put_string_uni() called with no unicode", __FILE__, __LINE__) +#define glk_request_char_event_uni(...) die("bug %s:%d: glk_request_char_event_uni() called with no unicode", __FILE__, __LINE__) +#define glk_request_line_event_uni(...) die("bug %s:%d: glk_request_line_event_uni() called with no unicode", __FILE__, __LINE__) +#endif +#ifndef GLK_MODULE_LINE_ECHO +#define glk_set_echo_line_event(...) die("bug: %s %d: glk_set_echo_line_event() called with no line echo", __FILE__, __LINE__) +#endif +#endif + +#endif diff --git a/interpreters/bocfel/zoom.c b/interpreters/bocfel/zoom.c new file mode 100644 index 0000000..e805762 --- /dev/null +++ b/interpreters/bocfel/zoom.c @@ -0,0 +1,48 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "zoom.h" +#include "screen.h" +#include "zterp.h" + +static clock_t start_clock, end_clock; + +void zstart_timer(void) +{ + start_clock = clock(); +} + +void zstop_timer(void) +{ + end_clock = clock(); +} + +void zread_timer(void) +{ + store(100 * (end_clock - start_clock) / CLOCKS_PER_SEC); +} + +void zprint_timer(void) +{ + char buf[32]; + snprintf(buf, sizeof buf, "%.2f seconds", (end_clock - start_clock) / (double)CLOCKS_PER_SEC); + for(int i = 0; buf[i] != 0; i++) put_char(buf[i]); +} diff --git a/interpreters/bocfel/zoom.h b/interpreters/bocfel/zoom.h new file mode 100644 index 0000000..9611b83 --- /dev/null +++ b/interpreters/bocfel/zoom.h @@ -0,0 +1,9 @@ +#ifndef ZTERP_ZOOM_H +#define ZTERP_ZOOM_H + +void zstart_timer(void); +void zstop_timer(void); +void zread_timer(void); +void zprint_timer(void); + +#endif diff --git a/interpreters/bocfel/zterp.c b/interpreters/bocfel/zterp.c new file mode 100644 index 0000000..49b84a0 --- /dev/null +++ b/interpreters/bocfel/zterp.c @@ -0,0 +1,1053 @@ +/*- + * Copyright 2009-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "zterp.h" +#include "blorb.h" +#include "branch.h" +#include "io.h" +#include "memory.h" +#include "osdep.h" +#include "process.h" +#include "random.h" +#include "screen.h" +#include "stack.h" +#include "unicode.h" +#include "util.h" + +#ifdef ZTERP_GLK +#include +#include +#ifdef GARGLK +#include +#include + +static schanid_t sound_channel = NULL; +#endif +#endif + +#define MAX_LINE 2048 +#define MAX_PATH 4096 + +#define ZTERP_VERSION "0.6.1" + +const char *game_file; +struct options options = { + .eval_stack_size = DEFAULT_STACK_SIZE, + .call_stack_size = DEFAULT_CALL_DEPTH, + .disable_color = 0, + .disable_config = 0, + .disable_sound = 0, + .disable_timed = 0, + .enable_escape = 0, + .escape_string = NULL, + .disable_fixed = 0, + .assume_fixed = 0, + .disable_graphics_font = 0, + .enable_alt_graphics = 0, + .show_id = 0, + .disable_term_keys = 0, + .disable_utf8 = 0, + .force_utf8 = 0, + .disable_meta_commands = 0, + .int_number = 1, /* DEC */ + .int_version = 'C', + .replay_on = 0, + .replay_name = NULL, + .record_on = 0, + .record_name = NULL, + .transcript_on = 0, + .transcript_name = NULL, + .max_saves = 10, + .disable_undo_compression = 0, + .show_version = 0, + .disable_abbreviations = 0, + .enable_censorship = 0, + .overwrite_transcript = 0, + .random_seed = -1, + .random_device = NULL, +}; + +static char story_id[64]; + +uint32_t pc; + +/* zversion stores the Z-machine version of the story: 1–6. + * + * Z-machine versions 7 and 8 are identical to version 5 but for a + * couple of tiny details. They are thus classified as version 5. + * + * zwhich stores the actual version (1–8) for the few rare times where + * this knowledge is necessary. + */ +int zversion; +static int zwhich; + +struct header header; + +static struct +{ + zterp_io *io; + long offset; +} story; + +/* The null character in the alphabet table does not actually signify a + * null character: character 6 from A2 is special in that it specifies + * that the next two characters form a 10-bit ZSCII character (§3.4). + * The code that uses the alphabet table will step around this character + * when necessary, so it’s safe to use a null character here to mean + * “nothing”. + */ +uint8_t atable[26 * 3] = +{ + /* A0 */ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + + /* A1 */ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + + /* A2 */ + 0x0, 0xd, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', + ',', '!', '?', '_', '#', '\'','"', '/', '\\','-', ':', '(', ')', +}; + +void znop(void) +{ +} + +void zquit(void) +{ + break_from(0); +} + +void zverify(void) +{ + uint16_t checksum = 0; + uint32_t remaining = header.file_length - 0x40; + + if(zterp_io_seek(story.io, story.offset + 0x40, SEEK_SET) == -1) + { + branch_if(0); + return; + } + + while(remaining != 0) + { + uint8_t buf[8192]; + uint32_t to_read = remaining < sizeof buf ? remaining : sizeof buf; + + if(zterp_io_read(story.io, buf, to_read) != to_read) + { + branch_if(0); + return; + } + + for(uint32_t i = 0; i < to_read; i++) checksum += buf[i]; + + remaining -= to_read; + } + + branch_if(checksum == header.checksum); +} + +uint32_t unpack(uint16_t addr, int string) +{ + switch(zwhich) + { + case 1: case 2: case 3: + return addr * 2UL; + case 4: case 5: + return addr * 4UL; + case 6: case 7: + return (addr * 4UL) + (string ? header.S_O : header.R_O); + case 8: + return addr * 8UL; + default: + die("unhandled z-machine version: %d", zwhich); + } +} + +void store(uint16_t v) +{ + store_variable(BYTE(pc++), v); +} + +void zsave5(void) +{ + zterp_io *savefile; + size_t n; + + if(znargs == 0) + { + zsave(); + return; + } + + /* This should be able to suggest a filename, but Glk doesn’t support that. */ + savefile = zterp_io_open(NULL, ZTERP_IO_WRONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + store(0); + return; + } + + ZASSERT(zargs[0] + zargs[1] < memory_size, "attempt to save beyond the end of memory"); + n = zterp_io_write(savefile, &memory[zargs[0]], zargs[1]); + + zterp_io_close(savefile); + + store(n == zargs[1]); +} + +void zrestore5(void) +{ + zterp_io *savefile; + uint8_t *buf; + size_t n; + + if(znargs == 0) + { + zrestore(); + return; + } + + savefile = zterp_io_open(NULL, ZTERP_IO_RDONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + store(0); + return; + } + + buf = malloc(zargs[1]); + if(buf == NULL) + { + store(0); + return; + } + + n = zterp_io_read(savefile, buf, zargs[1]); + for(size_t i = 0; i < n; i++) user_store_byte(zargs[0] + i, buf[i]); + + free(buf); + + zterp_io_close(savefile); + + store(n); +} + +void zpiracy(void) +{ + branch_if(1); +} + +void zsound_effect(void) +{ +#ifdef GARGLK + uint8_t repeats, volume; + static uint32_t vols[8] = { + 0x02000, 0x04000, 0x06000, 0x08000, + 0x0a000, 0x0c000, 0x0e000, 0x10000 + }; + + if(sound_channel == NULL || zargs[0] < 3) return; + + repeats = zargs[2] >> 8; + volume = zargs[2] & 0xff; + + if(volume == 0) volume = 1; + if(volume > 8) volume = 8; + + glk_schannel_set_volume(sound_channel, vols[volume - 1]); + + switch(zargs[1]) + { + case 1: /* prepare */ + glk_sound_load_hint(zargs[0], 1); + break; + case 2: /* start */ + glk_schannel_play_ext(sound_channel, zargs[0], repeats == 255 ? -1 : repeats, 0); + break; + case 3: /* stop */ + glk_schannel_stop(sound_channel); + break; + case 4: /* finish with */ + glk_sound_load_hint(zargs[0], 0); + break; + } +#endif +} + +/* Find a story ID roughly in the form of an IFID according to §2.2.2.1 + * of draft 7 of the Treaty of Babel. + * + * This does not add a ZCODE- prefix, and will not search for a manually + * created IFID. + */ +static void find_id(void) +{ + char serial[] = "------"; + + for(int i = 0; i < 6; i++) + { + /* isalnum() cannot be used because it is locale-aware, and this + * must only check for A–Z, a–z, and 0–9. Because ASCII (or a + * compatible charset) is required, testing against 'a', 'z', etc. + * is OK. + */ +#define ALNUM(c) ( ((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= '0' && (c) <= '9') ) + if(ALNUM(header.serial[i])) serial[i] = header.serial[i]; +#undef ALNUM + } + + if(strchr("012345679", serial[0]) != NULL && strcmp(serial, "000000") != 0) + { + snprintf(story_id, sizeof story_id, "%d-%s-%04x", header.release, serial, (unsigned)header.checksum); + } + else + { + snprintf(story_id, sizeof story_id, "%d-%s", header.release, serial); + } +} + +int is_story(const char *id) +{ + return strcmp(story_id, id) == 0; +} + +#ifndef ZTERP_NO_CHEAT +/* The index into these arrays is the address to freeze. + * The first array tracks whether the address is frozen, while the + * second holds the frozen value. + */ +static char freezew_cheat[UINT16_MAX + 1]; +static uint16_t freezew_val [UINT16_MAX + 1]; + +static void cheat(char *how) +{ + char *p; + + p = strtok(how, ":"); + if(p == NULL) return; + + if(strcmp(p, "freezew") == 0) + { + uint16_t addr; + + p = strtok(NULL, ":"); + if(p == NULL) return; + + if(*p == 'G') + { + addr = strtoul(p + 1, NULL, 16); + if(addr > 239) return; + + addr = header.globals + (addr * 2); + } + else + { + addr = strtoul(p, NULL, 16); + } + + p = strtok(NULL, ":"); + if(p == NULL) return; + + freezew_cheat[addr] = 1; + freezew_val [addr] = strtoul(p, NULL, 0); + } +} + +int cheat_find_freezew(uint32_t addr, uint16_t *val) +{ + if(addr > UINT16_MAX || !freezew_cheat[addr]) return 0; + + *val = freezew_val[addr]; + + return 1; +} +#endif + +static void read_config(void) +{ + FILE *fp; + char file[MAX_PATH + 1]; + char line[MAX_LINE]; + char *key, *val, *p; + long n; + int story_matches = 1; + + zterp_os_rcfile(file, sizeof file); + + fp = fopen(file, "r"); + if(fp == NULL) return; + + while(fgets(line, sizeof line, fp) != NULL) + { + line[strcspn(line, "#\n")] = 0; + if(line[0] == 0) continue; + + if(line[0] == '[') + { + p = strrchr(line, ']'); + if(p != NULL && p[1] == 0) + { + *p = 0; + + story_matches = 0; + for(p = strtok(line + 1, " ,"); p != NULL; p = strtok(NULL, " ,")) + { + if(is_story(p)) story_matches = 1; + } + } + + continue; + } + + if(!story_matches) continue; + + key = strtok(line, " \t="); + if(key == NULL) continue; + val = strtok(NULL, "="); + if(val == NULL) continue; + + /* Trim whitespace. */ + while(isspace((unsigned char)*val)) val++; + if(*val == 0) continue; + p = val + strlen(val) - 1; + while(isspace((unsigned char)*p)) *p-- = 0; + + n = strtol(val, NULL, 10); + +#define BOOL(name) else if(strcmp(key, #name) == 0) options.name = (n != 0) +#define NUMBER(name) else if(strcmp(key, #name) == 0) options.name = n +#define STRING(name) else if(strcmp(key, #name) == 0) do { free(options.name); options.name = xstrdup(val); } while(0) +#define CHAR(name) else if(strcmp(key, #name) == 0) options.name = val[0] +#ifdef GARGLK +#define COLOR(name, num)else if(strcmp(key, "color_" #name) == 0) update_color(num, strtol(val, NULL, 16)) +#else +#define COLOR(name, num)else if(0) +#endif + + if(0); + + NUMBER(eval_stack_size); + NUMBER(call_stack_size); + BOOL (disable_color); + BOOL (disable_timed); + BOOL (disable_sound); + BOOL (enable_escape); + STRING(escape_string); + BOOL (disable_fixed); + BOOL (assume_fixed); + BOOL (disable_graphics_font); + BOOL (enable_alt_graphics); + BOOL (disable_term_keys); + BOOL (disable_utf8); + BOOL (force_utf8); + BOOL (disable_meta_commands); + NUMBER(max_saves); + BOOL (disable_undo_compression); + NUMBER(int_number); + CHAR (int_version); + BOOL (replay_on); + STRING(replay_name); + BOOL (record_on); + STRING(record_name); + BOOL (transcript_on); + STRING(transcript_name); + BOOL (disable_abbreviations); + BOOL (enable_censorship); + BOOL (overwrite_transcript); + NUMBER(random_seed); + STRING(random_device); + + COLOR(black, 2); + COLOR(red, 3); + COLOR(green, 4); + COLOR(yellow, 5); + COLOR(blue, 6); + COLOR(magenta, 7); + COLOR(cyan, 8); + COLOR(white, 9); + +#ifndef ZTERP_NO_CHEAT + else if(strcmp(key, "cheat") == 0) cheat(val); +#endif + +#undef BOOL +#undef NUMBER +#undef STRING +#undef CHAR +#undef COLOR + } + + fclose(fp); +} + +static int have_statuswin = 0; +static int have_upperwin = 0; + +/* Various parts of the header (those marked “Rst” in §11) should be + * updated by the interpreter. This function does that. This is also + * used when restoring, because the save file might have come from an + * interpreter with vastly different settings. + */ +void write_header(void) +{ + uint8_t flags1; + + flags1 = BYTE(0x01); + + if(zversion == 3) + { + flags1 |= FLAGS1_NOSTATUS; + flags1 &= ~(FLAGS1_SCREENSPLIT | FLAGS1_VARIABLE); + +#ifdef GARGLK + /* Assume that if Gargoyle is being used, the default font is not fixed. */ + flags1 |= FLAGS1_VARIABLE; +#endif + + if(have_statuswin) flags1 &= ~FLAGS1_NOSTATUS; + if(have_upperwin) flags1 |= FLAGS1_SCREENSPLIT; + if(options.enable_censorship) flags1 |= FLAGS1_CENSOR; + } + else if(zversion >= 4) + { + flags1 |= (FLAGS1_BOLD | FLAGS1_ITALIC | FLAGS1_FIXED); + flags1 &= ~FLAGS1_TIMED; + + if(zversion >= 5) flags1 &= ~FLAGS1_COLORS; + + if(zversion == 6) + { + flags1 &= ~(FLAGS1_PICTURES | FLAGS1_SOUND); +#ifdef GARGLK + if(sound_channel != NULL) flags1 |= FLAGS1_SOUND; +#endif + } + +#ifdef ZTERP_GLK + if(glk_gestalt(gestalt_Timer, 0)) flags1 |= FLAGS1_TIMED; +#ifdef GARGLK + if(zversion >= 5) flags1 |= FLAGS1_COLORS; +#endif +#else + if(!zterp_os_have_style(STYLE_BOLD)) flags1 &= ~FLAGS1_BOLD; + if(!zterp_os_have_style(STYLE_ITALIC)) flags1 &= ~FLAGS1_ITALIC; + if(zversion >= 5 && zterp_os_have_colors()) flags1 |= FLAGS1_COLORS; +#endif + + if(zversion >= 5 && options.disable_color) flags1 &= ~FLAGS1_COLORS; + if(options.disable_timed) flags1 &= ~FLAGS1_TIMED; + if(options.disable_fixed) flags1 &= ~FLAGS1_FIXED; + } + + STORE_BYTE(0x01, flags1); + + if(zversion >= 5) + { + uint16_t flags2 = WORD(0x10); + + flags2 &= ~FLAGS2_MOUSE; +#ifdef GARGLK + if(sound_channel == NULL) flags2 &= ~FLAGS2_SOUND; +#else + flags2 &= ~FLAGS2_SOUND; +#endif + if(zversion >= 6) flags2 &= ~FLAGS2_MENUS; + + if(options.disable_graphics_font) flags2 &= ~FLAGS2_PICTURES; + + if(options.max_saves == 0) flags2 &= ~FLAGS2_UNDO; + + STORE_WORD(0x10, flags2); + } + + if(zversion >= 4) + { + unsigned int width, height; + + /* Interpreter number & version. */ + if(options.int_number < 1 || options.int_number > 11) options.int_number = 1; /* DEC */ + STORE_BYTE(0x1e, options.int_number); + STORE_BYTE(0x1f, options.int_version); + + get_screen_size(&width, &height); + + /* Screen height and width. + * A height of 255 means infinite, so cap at 254. + */ + STORE_BYTE(0x20, height > 254 ? 254 : height); + STORE_BYTE(0x21, width > 255 ? 255 : width); + + if(zversion >= 5) + { + /* Screen width and height in units. */ + STORE_WORD(0x22, width > UINT16_MAX ? UINT16_MAX : width); + STORE_WORD(0x24, height > UINT16_MAX ? UINT16_MAX : height); + + /* Font width and height in units. */ + STORE_BYTE(0x26, 1); + STORE_BYTE(0x27, 1); + + /* Default background and foreground colors. */ + STORE_BYTE(0x2c, 1); + STORE_BYTE(0x2d, 1); + } + } + + /* Standard revision # */ + STORE_BYTE(0x32, 1); + STORE_BYTE(0x33, 1); +} + +void process_story(void) +{ + if(zterp_io_seek(story.io, story.offset, SEEK_SET) == -1) die("unable to rewind story"); + + if(zterp_io_read(story.io, memory, memory_size) != memory_size) die("unable to read from story file"); + + zversion = BYTE(0x00); + if(zversion < 1 || zversion > 8) die("only z-code versions 1-8 are supported"); + + zwhich = zversion; + if(zversion == 7 || zversion == 8) zversion = 5; + + pc = WORD(0x06); + if(pc >= memory_size) die("corrupted story: initial pc out of range"); + + header.release = WORD(0x02); + header.dictionary = WORD(0x08); + header.objects = WORD(0x0a); + header.globals = WORD(0x0c); + header.static_start = WORD(0x0e); + header.abbr = WORD(0x18); + + memcpy(header.serial, &memory[0x12], sizeof header.serial); + + /* There is no explicit “end of static” tag; but it must end by 0xffff + * or the end of the story file, whichever is smaller. + */ + header.static_end = memory_size < 0xffff ? memory_size : 0xffff; + +#define PROPSIZE (zversion <= 3 ? 62L : 126L) + + /* There must be at least enough room in dynamic memory for the header + * (64 bytes), the global variables table (480 bytes), and the + * property defaults table (62 or 126 bytes). + */ + if(header.static_start < 64 + 480 + PROPSIZE) die("corrupted story: dynamic memory too small (%d bytes)", (int)header.static_start); + if(header.static_start >= memory_size) die("corrupted story: static memory out of range"); + + if(header.dictionary != 0 && + header.dictionary < header.static_start) die("corrupted story: dictionary is not in static memory"); + + if(header.objects < 64 || + header.objects + PROPSIZE > header.static_start) + die("corrupted story: object table is not in dynamic memory"); + +#undef PROPSIZE + + if(header.globals < 64 || + header.globals + 480L > header.static_start) die("corrupted story: global variables are not in dynamic memory"); + + if(header.abbr >= memory_size) die("corrupted story: abbreviation table out of range"); + + header.file_length = WORD(0x1a) * (zwhich <= 3 ? 2UL : zwhich <= 5 ? 4UL : 8UL); + if(header.file_length > memory_size) die("story's reported size (%lu) greater than file size (%lu)", (unsigned long)header.file_length, (unsigned long)memory_size); + + header.checksum = WORD(0x1c); + + if(zwhich == 6 || zwhich == 7) + { + header.R_O = WORD(0x28) * 8UL; + header.S_O = WORD(0x2a) * 8UL; + } + + if(dynamic_memory == NULL) + { + dynamic_memory = malloc(header.static_start); + if(dynamic_memory == NULL) die("unable to allocate memory for dynamic memory"); + memcpy(dynamic_memory, memory, header.static_start); + } + +#ifdef GLK_MODULE_LINE_TERMINATORS + if(!options.disable_term_keys) + { + if(zversion >= 5 && WORD(0x2e) != 0) + { + term_keys_reset(); + + for(uint32_t i = WORD(0x2e); i < memory_size && memory[i] != 0; i++) + { + term_keys_add(memory[i]); + } + } + } +#endif + + if(zversion == 1) + { + memcpy(&atable[26 * 2], " 0123456789.,!?_#'\"/\\<-:()", 26); + } + else if(zversion >= 5 && WORD(0x34) != 0) + { + if(WORD(0x34) + 26 * 3 >= memory_size) die("corrupted story: alphabet table out of range"); + + memcpy(atable, &memory[WORD(0x34)], 26 * 3); + + /* Even with a new alphabet table, characters 6 and 7 from A2 must + * remain the same (§3.5.5.1). + */ + atable[52] = 0x00; + atable[53] = 0x0d; + } + + /* Check for a header extension table. */ + if(zversion >= 5) + { + uint16_t etable = WORD(0x36); + + if(etable != 0) + { + uint16_t nentries = user_word(etable); + + if(etable + (2 * nentries) >= memory_size) die("corrupted story: header extension table out of range"); + + /* Unicode table. */ + if(nentries >= 3 && WORD(etable + (2 * 3)) != 0) + { + uint16_t utable = WORD(etable + (2 * 3)); + + parse_unicode_table(utable); + } + + /* Flags3. */ + if(nentries >= 4) STORE_WORD(etable + (2 * 4), 0); + /* True default foreground color. */ + if(nentries >= 5) STORE_WORD(etable + (2 * 5), 0x0000); + /* True default background color. */ + if(nentries >= 6) STORE_WORD(etable + (2 * 6), 0x7fff); + } + } + + /* The configuration file cannot be read until the ID of the current + * story is known, and the ID of the current story is not known until + * the file has been processed; so do both of those here. + */ + find_id(); + if(!options.disable_config) read_config(); + + /* Prevent the configuration file from unexpectedly being reread after + * @restart or @restore. + */ + options.disable_config = 1; + + /* Most options directly set their respective variables, but a few + * require intervention. Delay that intervention until here so that + * the configuration file is taken into account. + */ + if(options.disable_utf8) + { +#ifndef ZTERP_GLK + /* If Glk is not being used, the ZSCII to Unicode table needs to be + * aligned with the IO character set. + */ + have_unicode = 0; +#endif + use_utf8_io = 0; + } + if(options.force_utf8) + { +#ifndef ZTERP_GLK + /* See above. */ + have_unicode = 1; +#endif + use_utf8_io = 1; + } + if(options.escape_string == NULL) options.escape_string = xstrdup("1m"); + +#ifdef GARGLK + if(options.disable_sound && sound_channel != NULL) + { + glk_schannel_destroy(sound_channel); + sound_channel = NULL; + } +#endif + + /* Now that we have a Unicode table and the user’s Unicode + * preferences, build the ZSCII to Unicode and Unicode to ZSCII + * tables. + */ + setup_tables(); + + if(zversion <= 3) have_statuswin = create_statuswin(); + if(zversion >= 3) have_upperwin = create_upperwin(); + + write_header(); + /* Put everything in a clean state. */ + seed_random(0); + init_stack(); + init_screen(); + + /* Unfortunately, Beyond Zork behaves badly when the interpreter + * number is set to DOS: it assumes that it can print out IBM PC + * character codes and get useful results (e.g. it writes out 0x18 + * expecting an up arrow); however, if the pictures bit is set, it + * uses the character graphics font like a good citizen. Thus turn + * that bit on when Beyond Zork is being used and the interpreter is + * set to DOS. It might make sense to do this generally, not just for + * Beyond Zork; but this is such a minor corner of the Z-machine that + * it probably doesn’t matter. For now, peg this to Beyond Zork. + */ + if(options.int_number == 6 && + (is_story("47-870915") || is_story("49-870917") || + is_story("51-870923") || is_story("57-871221"))) + { + STORE_WORD(0x10, WORD(0x10) | FLAGS2_PICTURES); + } + + if(options.transcript_on) + { + STORE_WORD(0x10, WORD(0x10) | FLAGS2_TRANSCRIPT); + options.transcript_on = 0; + } + + if(options.record_on) + { + output_stream(OSTREAM_RECORD, 0); + options.record_on = 0; + } + + if(options.replay_on) + { + input_stream(ISTREAM_FILE); + options.replay_on = 0; + } + + if(zversion == 6) + { + zargs[0] = pc; + call(0); + } +} + +#ifdef ZTERP_GLK +zexternally_visible +void glk_main(void) +#else +int main(int argc, char **argv) +#endif +{ + zterp_blorb *blorb; + +#ifdef ZTERP_GLK + if(!create_mainwin()) return; +#ifdef GLK_MODULE_UNICODE + have_unicode = glk_gestalt(gestalt_Unicode, 0); +#endif +#else + have_unicode = zterp_os_have_unicode(); +#endif + + use_utf8_io = zterp_os_have_unicode(); + +#ifndef ZTERP_GLK + if(!process_arguments(argc, argv)) exit(EXIT_FAILURE); + + zterp_os_init_term(); +#endif + +#ifdef ZTERP_GLK +#define PRINT(s) do { glk_put_string(s); glk_put_char(UNICODE_LINEFEED); } while(0) +#else +#define PRINT(s) puts(s) +#endif + + if(options.show_version) + { + char config[MAX_PATH] = "Configuration file: "; + + PRINT("Bocfel " ZTERP_VERSION); +#ifdef ZTERP_NO_SAFETY_CHECKS + PRINT("Runtime assertions disabled"); +#else + PRINT("Runtime assertions enabled"); +#endif +#ifdef ZTERP_NO_CHEAT + PRINT("Cheat support disabled"); +#else + PRINT("Cheat support enabled"); +#endif +#ifdef ZTERP_TANDY + PRINT("The Tandy bit can be set"); +#else + PRINT("The Tandy bit cannot be set"); +#endif + + zterp_os_rcfile(config + strlen(config), sizeof config - strlen(config)); + PRINT(config); + +#ifdef ZTERP_GLK + glk_exit(); +#else + exit(0); +#endif + } + +#undef PRINT + +#ifdef GARGLK + if(game_file == NULL) + { + frefid_t ref; + + ref = glk_fileref_create_by_prompt(fileusage_Data | fileusage_BinaryMode, filemode_Read, 0); + if(ref != NULL) + { + game_file = xstrdup(garglk_fileref_get_name(ref)); + glk_fileref_destroy(ref); + } + } +#endif + + if(game_file == NULL) die("no story provided"); + + story.io = zterp_io_open(game_file, ZTERP_IO_RDONLY); + if(story.io == NULL) die("cannot open file %s", game_file); + + blorb = zterp_blorb_parse(story.io); + if(blorb != NULL) + { + const zterp_blorb_chunk *chunk; + + chunk = zterp_blorb_find(blorb, BLORB_EXEC, 0); + if(chunk == NULL) die("no EXEC resource found"); + if(strcmp(chunk->name, "ZCOD") != 0) + { + if(strcmp(chunk->name, "GLUL") == 0) die("Glulx stories are not supported (try git or glulxe)"); + + die("unknown story type: %s", chunk->name); + } + + if(chunk->offset > LONG_MAX) die("zcode offset too large"); + + memory_size = chunk->size; + story.offset = chunk->offset; + + zterp_blorb_free(blorb); + } + else + { + long size = zterp_io_filesize(story.io); + + if(size == -1) die("unable to determine file size"); + if(size > UINT32_MAX) die("file too large"); + + memory_size = size; + story.offset = 0; + } + +#ifdef GARGLK + if(glk_gestalt(gestalt_Sound, 0)) + { + /* 5 for the worst case of needing to add .blb to the end plus the + * null character. + */ + char *blorb_file = malloc(strlen(game_file) + 5); + if(blorb_file != NULL) + { + char *p; + strid_t file; + + strcpy(blorb_file, game_file); + p = strrchr(blorb_file, '.'); + if(p != NULL) *p = 0; + strcat(blorb_file, ".blb"); + + file = glkunix_stream_open_pathname(blorb_file, 0, 0); + if(file != NULL) + { + giblorb_set_resource_map(file); + sound_channel = glk_schannel_create(0); + } + + free(blorb_file); + } + } +#endif + + if(memory_size < 64) die("story file too small"); + if(memory_size > SIZE_MAX - 22) die("story file too large"); + + /* It’s possible for a story to be cut short in the middle of an + * instruction. If so, the processing loop will run past the end of + * memory. Either pc needs to be checked each and every time it is + * incremented, or a small guard needs to be placed at the end of + * memory that will trigger an illegal instruction error. The latter + * is done by filling the end of memory with zeroes, which do not + * represent a valid instruction. + * + * There need to be at least 22 bytes for the worst case: 0xec + * (call_vs2) as the last byte in memory. The next two bytes, which + * will be zeroes, indicate that 8 large constants, or 16 bytes, will + * be next. This is a store instruction, so one more byte will be + * read to determine where to store. Another byte is read to + * determine the next opcode; this will be zero, which is nominally a + * 2OP, requiring two more bytes to be read. At this point the opcode + * will be looked up, resulting in an illegal instruction error. + */ + memory = malloc(memory_size + 22); + if(memory == NULL) die("unable to allocate memory for story file"); + memset(memory + memory_size, 0, 22); + + process_story(); + + /* If header transcript/fixed bits have been set, either by the + * story or by the user, this will activate them. + */ + user_store_word(0x10, WORD(0x10)); + + if(options.show_id) + { +#ifdef ZTERP_GLK + glk_put_string(story_id); + glk_exit(); +#else + puts(story_id); + exit(0); +#endif + } + + setup_opcodes(); + + process_instructions(); + +#ifndef ZTERP_GLK + return 0; +#endif +} diff --git a/interpreters/bocfel/zterp.h b/interpreters/bocfel/zterp.h new file mode 100644 index 0000000..2280ef0 --- /dev/null +++ b/interpreters/bocfel/zterp.h @@ -0,0 +1,117 @@ +#ifndef ZTERP_H +#define ZTERP_H + +#include + +struct options +{ + long eval_stack_size; + long call_stack_size; + int disable_color; + int disable_config; + int disable_timed; + int disable_sound; + int enable_escape; + char *escape_string; + int disable_fixed; + int assume_fixed; + int disable_graphics_font; + int enable_alt_graphics; + int show_id; + int disable_term_keys; + int disable_utf8; + int force_utf8; + int disable_meta_commands; + long int_number; + int int_version; + int replay_on; + char *replay_name; + int record_on; + char *record_name; + int transcript_on; + char *transcript_name; + long max_saves; + int disable_undo_compression; + int show_version; + int disable_abbreviations; + int enable_censorship; + int overwrite_transcript; + long random_seed; + char *random_device; +}; + +extern const char *game_file; +extern struct options options; + +/* v3 */ +#define FLAGS1_STATUSTYPE (1U << 1) +#define FLAGS1_STORYSPLIT (1U << 2) +#define FLAGS1_CENSOR (1U << 3) +#define FLAGS1_NOSTATUS (1U << 4) +#define FLAGS1_SCREENSPLIT (1U << 5) +#define FLAGS1_VARIABLE (1U << 6) + +/* v4 */ +#define FLAGS1_COLORS (1U << 0) +#define FLAGS1_PICTURES (1U << 1) +#define FLAGS1_BOLD (1U << 2) +#define FLAGS1_ITALIC (1U << 3) +#define FLAGS1_FIXED (1U << 4) +#define FLAGS1_SOUND (1U << 5) +#define FLAGS1_TIMED (1U << 7) + +#define FLAGS2_TRANSCRIPT (1U << 0) +#define FLAGS2_FIXED (1U << 1) +#define FLAGS2_STATUS (1U << 2) +#define FLAGS2_PICTURES (1U << 3) +#define FLAGS2_UNDO (1U << 4) +#define FLAGS2_MOUSE (1U << 5) +#define FLAGS2_COLORS (1U << 6) +#define FLAGS2_SOUND (1U << 7) +#define FLAGS2_MENUS (1U << 8) + +#define STATUS_IS_TIME() (zversion == 3 && (BYTE(0x01) & FLAGS1_STATUSTYPE)) +#define TIMER_AVAILABLE() (zversion >= 4 && (BYTE(0x01) & FLAGS1_TIMED)) + +struct header +{ + uint16_t release; + uint16_t dictionary; + uint16_t objects; + uint16_t globals; + uint16_t static_start; + uint16_t static_end; + uint16_t abbr; + uint32_t file_length; + uint8_t serial[6]; + uint16_t checksum; + uint32_t R_O; + uint32_t S_O; +}; + +extern uint32_t pc; +extern int zversion; +extern struct header header; +extern uint8_t atable[]; + +int is_story(const char *); + +void write_header(void); + +uint32_t unpack(uint16_t, int); +void store(uint16_t); +void process_story(void); + +#ifndef ZTERP_NO_CHEAT +int cheat_find_freezew(uint32_t, uint16_t *); +#endif + +void znop(void); +void zquit(void); +void zverify(void); +void zpiracy(void); +void zsave5(void); +void zrestore5(void); +void zsound_effect(void); + +#endif diff --git a/interpreters/frotz/Makefile.am b/interpreters/frotz/Makefile.am index 16c91a6..fa0f0b1 100644 --- a/interpreters/frotz/Makefile.am +++ b/interpreters/frotz/Makefile.am @@ -3,15 +3,7 @@ frotz_la_SOURCES = buffer.c err.c fastmem.c files.c input.c main.c math.c \ object.c process.c quetzal.c random.c redirect.c sound.c stream.c table.c \ text.c variable.c glkscreen.c glkmisc.c frotz.h glkfrotz.h glkio.h frotz_la_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/libchimara - -if TARGET_ILIAD -no_pointer_sign = -else -no_pointer_sign = -Wno-pointer-sign -endif - -frotz_la_CFLAGS = $(no_pointer_sign) $(AM_CFLAGS) - +frotz_la_CFLAGS = -Wno-pointer-sign $(AM_CFLAGS) frotz_la_LDFLAGS = -module $(PLUGIN_LIBTOOL_FLAGS) frotzdocdir = $(datadir)/doc/$(PACKAGE)/frotz diff --git a/interpreters/frotz/buffer.c b/interpreters/frotz/buffer.c index bff9572..f454f72 100644 --- a/interpreters/frotz/buffer.c +++ b/interpreters/frotz/buffer.c @@ -16,7 +16,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/err.c b/interpreters/frotz/err.c index ffb5168..81f2f14 100644 --- a/interpreters/frotz/err.c +++ b/interpreters/frotz/err.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/fastmem.c b/interpreters/frotz/fastmem.c index cb4a20e..2e99d8d 100644 --- a/interpreters/frotz/fastmem.c +++ b/interpreters/frotz/fastmem.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /* diff --git a/interpreters/frotz/files.c b/interpreters/frotz/files.c index f407395..5804d7f 100644 --- a/interpreters/frotz/files.c +++ b/interpreters/frotz/files.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/glkmisc.c b/interpreters/frotz/glkmisc.c index c559818..85b13c9 100644 --- a/interpreters/frotz/glkmisc.c +++ b/interpreters/frotz/glkmisc.c @@ -189,53 +189,45 @@ void os_init_screen(void) glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Weight, 0); glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Oblique, 0); - glk_stylehint_set(wintype_TextGrid, style_Preformatted, stylehint_ReverseColor, 1); /* monob */ glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Weight, 1); glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Oblique, 0); - glk_stylehint_set(wintype_TextGrid, style_Subheader, stylehint_ReverseColor, 1); /* monoi */ glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Weight, 0); glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Oblique, 1); - glk_stylehint_set(wintype_TextGrid, style_Alert, stylehint_ReverseColor, 1); /* monoz */ glk_stylehint_set(wintype_AllTypes, style_BlockQuote, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_BlockQuote, stylehint_Weight, 1); glk_stylehint_set(wintype_AllTypes, style_BlockQuote, stylehint_Oblique, 1); - glk_stylehint_set(wintype_TextGrid, style_BlockQuote, stylehint_ReverseColor, 1); /* propr */ glk_stylehint_set(wintype_TextBuffer, style_Normal, stylehint_Proportional, 1); glk_stylehint_set(wintype_TextGrid, style_Normal, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_Weight, 0); glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_Oblique, 0); - glk_stylehint_set(wintype_TextGrid, style_Normal, stylehint_ReverseColor, 1); /* propb */ glk_stylehint_set(wintype_TextBuffer, style_Header, stylehint_Proportional, 1); glk_stylehint_set(wintype_TextGrid, style_Header, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_Header, stylehint_Weight, 1); glk_stylehint_set(wintype_AllTypes, style_Header, stylehint_Oblique, 0); - glk_stylehint_set(wintype_TextGrid, style_Header, stylehint_ReverseColor, 1); /* propi */ glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Proportional, 1); glk_stylehint_set(wintype_TextGrid, style_Emphasized, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_Emphasized, stylehint_Weight, 0); glk_stylehint_set(wintype_AllTypes, style_Emphasized, stylehint_Oblique, 1); - glk_stylehint_set(wintype_TextGrid, style_Emphasized, stylehint_ReverseColor, 1); /* propi */ glk_stylehint_set(wintype_TextBuffer, style_Note, stylehint_Proportional, 1); glk_stylehint_set(wintype_TextGrid, style_Note, stylehint_Proportional, 0); glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Weight, 1); glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Oblique, 1); - glk_stylehint_set(wintype_TextGrid, style_Note, stylehint_ReverseColor, 1); gos_lower = glk_window_open(0, 0, 0, wintype_TextGrid, 0); if (!gos_lower) diff --git a/interpreters/frotz/glkscreen.c b/interpreters/frotz/glkscreen.c index 079373b..2d72bfd 100644 --- a/interpreters/frotz/glkscreen.c +++ b/interpreters/frotz/glkscreen.c @@ -896,6 +896,10 @@ void z_show_status (void) glk_set_window(gos_upper); gos_curwin = gos_upper; +#ifdef GARGLK + garglk_set_reversevideo(TRUE); +#endif /* GARGLK */ + curx = cury = 1; glk_window_move_cursor(gos_upper, 0, 0); diff --git a/interpreters/frotz/input.c b/interpreters/frotz/input.c index f17ec66..ced562f 100644 --- a/interpreters/frotz/input.c +++ b/interpreters/frotz/input.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/main.c b/interpreters/frotz/main.c index 4fbbd1e..671184b 100644 --- a/interpreters/frotz/main.c +++ b/interpreters/frotz/main.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /* diff --git a/interpreters/frotz/math.c b/interpreters/frotz/math.c index 5ff5163..944710f 100644 --- a/interpreters/frotz/math.c +++ b/interpreters/frotz/math.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/object.c b/interpreters/frotz/object.c index a1e86e4..ed21087 100644 --- a/interpreters/frotz/object.c +++ b/interpreters/frotz/object.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/process.c b/interpreters/frotz/process.c index 58c8607..4d9687a 100644 --- a/interpreters/frotz/process.c +++ b/interpreters/frotz/process.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/quetzal.c b/interpreters/frotz/quetzal.c index e41a57b..d48a973 100644 --- a/interpreters/frotz/quetzal.c +++ b/interpreters/frotz/quetzal.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/random.c b/interpreters/frotz/random.c index 6ea14d8..7eed57a 100644 --- a/interpreters/frotz/random.c +++ b/interpreters/frotz/random.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/redirect.c b/interpreters/frotz/redirect.c index 239e9c4..6b0703c 100644 --- a/interpreters/frotz/redirect.c +++ b/interpreters/frotz/redirect.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/sound.c b/interpreters/frotz/sound.c index 9c4d63a..0a29d0b 100644 --- a/interpreters/frotz/sound.c +++ b/interpreters/frotz/sound.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/stream.c b/interpreters/frotz/stream.c index 54cef78..69e6764 100644 --- a/interpreters/frotz/stream.c +++ b/interpreters/frotz/stream.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/table.c b/interpreters/frotz/table.c index eb3a163..9ec990c 100644 --- a/interpreters/frotz/table.c +++ b/interpreters/frotz/table.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/text.c b/interpreters/frotz/text.c index 9cccc2c..fd07b2c 100644 --- a/interpreters/frotz/text.c +++ b/interpreters/frotz/text.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/frotz/variable.c b/interpreters/frotz/variable.c index 3e5c6e0..98356f5 100644 --- a/interpreters/frotz/variable.c +++ b/interpreters/frotz/variable.c @@ -15,7 +15,7 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "frotz.h" diff --git a/interpreters/git/Makefile.am b/interpreters/git/Makefile.am index bbed8c4..f025600 100644 --- a/interpreters/git/Makefile.am +++ b/interpreters/git/Makefile.am @@ -1,7 +1,7 @@ # Automatically generate version.h MAJOR = 1 MINOR = 2 -PATCH = 8 +PATCH = 9 version.h: Makefile.am $(AM_V_GEN)echo "// Automatically generated file -- do not edit!" > version.h $(AM_V_at)echo "#define GIT_MAJOR" $(MAJOR) >> version.h diff --git a/interpreters/git/README.txt b/interpreters/git/README.txt index 2462bcf..4161fdd 100644 --- a/interpreters/git/README.txt +++ b/interpreters/git/README.txt @@ -1,6 +1,6 @@ Git is an interpreter for the Glulx virtual machine. Its homepage is here: - http://diden.net/if/git +http://ifarchive.org/indexes/if-archiveXprogrammingXglulxXinterpretersXgit.html Git's main goal in life is to be fast. It's about five times faster than Glulxe, and about twice as fast as Frotz (using the same Inform source compiled for the @@ -14,9 +14,12 @@ between each prompt. Have fun, and let me know what you think! - Iain Merrick + Iain Merrick (Original author) iain@diden.net + David Kinder (Current maintainer) + davidk.kinder@virgin.net + -------------------------------------------------------------------------------- * Building and installing Git @@ -31,7 +34,8 @@ hard, depending on what kind of computer you're using and whether you want Git to be able to display graphics and play sounds. To find a suitable Glk library, look here: - http://eblong.com/zarf/glk +http://eblong.com/zarf/glk/ +http://ifarchive.org/indexes/if-archiveXprogrammingXglkXimplementations.html Exactly how you build and link everything depends on what platform you're on and which Glk library you're using. The supplied Makefile should work on any Unix @@ -46,8 +50,8 @@ respectively, but I can't guarantee that they're fully up-to-date. It should be possible to build Git with any C compiler, but it works best with GCC, because that has a non-standard extension that Git can use for a big speed -boost. GCC 2.95 actually generates faster code than GCC 3, so if you have a -choice, use the former. (On OS X, this means compiling with 'gcc2'.) +boost. GCC 2.95 actually generates faster code than later versions, so if you +have a choice, use the former. (On OS X, this means compiling with 'gcc2'.) -------------------------------------------------------------------------------- @@ -116,12 +120,9 @@ KB. 256KB is usually enough to store dozens of moves. GCC 3 has bigger problems than I thought. On PowerPC, the direct threading option results in much slower code; and on x86, terp.c crashes GCC itself if -direct threading is used. Therefore, I recommend that you use GCC 2.95 if -possible. If you only have GCC 3, don't define USE_DIRECT_THREADING, at least -until the compiler bug is fixed. - -Since the previous update, GCC 4 has been released, but I haven't evaluated it -yet. If you want to give it a try, let me know how you get on! +direct threading is used. GCC 4 seems to work, given some very limited testing, +but still results in slow code. Therefore, I recommend that you use GCC 2.95 if +possible. If you only have GCC 3, don't define USE_DIRECT_THREADING. Some Glk libraries, such as xglk, can't deal with memory-mapped files. You can tell that this is happening if Git can open .ulx files, but complains that .blb @@ -130,9 +131,9 @@ your startup file, and make sure you're giving it a file stream rather than a memory stream. If you're using the git_unix.c startup file, just make sure USE_MMAP isn't defined. -1-byte and 2-byte local variables are not implemented yet. This means git can't -currently play games created with the Superglus system. This will be fixed at -some point. +1-byte and 2-byte local variables are not implemented. This means git can't +play games created with old versions of the Superglus system. As these small +local variables now deprecated, it is unlikely that this will be fixed. In the search opcodes, direct keys don't work unless they're exactly 4 bytes long. @@ -191,6 +192,12 @@ also to Eliuk Blau for tracking down bugs in the memory management opcodes. * Version History +1.2.9 2011-08-28 Fixed a bug in glkop.c dispatching, to do with optional + array arguments, following a similar fix in Glulxe. + Glk array and string operations are now checked for memory + overflows (though not for ROM writing), following a similar + fix in Glulxe. + 1.2.8 2010-08-25 Fixed a problem with 'undo' when compiled as 64 bit, contributed by Ben Cressey. Fixed a sign problem for the @fceil opcode, following a @@ -267,3 +274,4 @@ also to Eliuk Blau for tracking down bugs in the memory management opcodes. Added gitWithStream() as a workaround for xglk 1.0 2003-10-18 First public release + diff --git a/interpreters/git/git.h b/interpreters/git/git.h index cd1f90e..cfb0a94 100644 --- a/interpreters/git/git.h +++ b/interpreters/git/git.h @@ -124,6 +124,7 @@ extern void startProgram (size_t cacheSize, enum IOMode ioMode); extern int git_init_dispatch(); extern glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist); extern strid_t git_find_stream_by_id(glui32 id); +extern glui32 git_find_id_for_stream(strid_t str); // git_search.c diff --git a/interpreters/git/glkop.c b/interpreters/git/glkop.c index f8c7ff0..fa5b029 100644 --- a/interpreters/git/glkop.c +++ b/interpreters/git/glkop.c @@ -277,6 +277,16 @@ glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist) directly -- instead of bothering with the whole prototype mess. */ + case 0x0047: /* stream_set_current */ + if (numargs != 1) + goto WrongArgNum; + glk_stream_set_current(git_find_stream_by_id(arglist[0])); + break; + case 0x0048: /* stream_get_current */ + if (numargs != 0) + goto WrongArgNum; + retval = git_find_id_for_stream(glk_stream_get_current()); + break; case 0x0080: /* put_char */ if (numargs != 1) goto WrongArgNum; @@ -297,6 +307,16 @@ glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist) 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(git_find_stream_by_id(arglist[0]), arglist[1]); + break; WrongArgNum: fatalError("Wrong number of arguments to Glk function."); @@ -306,7 +326,7 @@ glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist) /* Go through the full dispatcher prototype foo. */ char *proto, *cx; dispatch_splot_t splot; - int argnum; + int argnum, argnum2; /* Grab the string. */ proto = gidispatch_prototype(funcnum); @@ -335,9 +355,11 @@ glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist) gidispatch_call(funcnum, argnum, splot.garglist); /* Phase 3. */ - argnum = 0; + argnum2 = 0; cx = proto; - unparse_glk_args(&splot, &cx, 0, &argnum, 0, 0); + unparse_glk_args(&splot, &cx, 0, &argnum2, 0, 0); + if (argnum != argnum2) + fatalError("Argument counts did not match."); break; } @@ -560,6 +582,12 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, switch (typeclass) { case 'C': + /* This test checks for a giant array length, and cuts it down to + something reasonable. Future releases of this interpreter may + treat this case as a fatal error. */ + if (varglist[ix+1] > gEndMem || varglist[ix]+varglist[ix+1] > gEndMem) + varglist[ix+1] = gEndMem - varglist[ix]; + garglist[gargnum].array = (void*) AddressOfArray(varglist[ix]); gargnum++; ix++; @@ -568,6 +596,10 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, cx++; break; 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; + garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin); gargnum++; ix++; @@ -679,6 +711,8 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth, } else { cx++; + if (isarray) + ix++; } } } @@ -885,6 +919,8 @@ static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth, } else { cx++; + if (isarray) + ix++; } } } @@ -916,6 +952,21 @@ strid_t git_find_stream_by_id(glui32 objid) return classes_get(1, objid); } +/* find_id_for_stream(): + The converse of find_stream_by_id(). + This is only needed in this file, so it's static. +*/ +glui32 git_find_id_for_stream(strid_t str) +{ + gidispatch_rock_t objrock; + + if (!str) + return 0; + + objrock = gidispatch_get_objrock(str, 1); + return ((classref_t *)objrock.ptr)->id; +} + /* Build a hash table to hold a set of Glk objects. */ static classtable_t *new_classtable(glui32 firstid) { diff --git a/interpreters/git/terp.c b/interpreters/git/terp.c index 870b4f1..d157826 100644 --- a/interpreters/git/terp.c +++ b/interpreters/git/terp.c @@ -38,29 +38,29 @@ Opcode* gOpcodeTable; int floatCompare(git_sint32 L1, git_sint32 L2, git_sint32 L3) { - git_float F1, F2; - - if (((L3 & 0x7F800000) == 0x7F800000) && ((L3 & 0x007FFFFF) != 0)) - return 0; - if ((L1 == 0x7F800000 || L1 == 0xFF800000) && (L2 == 0x7F800000 || L2 == 0xFF800000)) - return (L1 == L2); - - F1 = DECODE_FLOAT(L2) - DECODE_FLOAT(L1); - F2 = fabs(DECODE_FLOAT(L3)); - return ((F1 <= F2) && (F1 >= -F2)); + git_float F1, F2; + + if (((L3 & 0x7F800000) == 0x7F800000) && ((L3 & 0x007FFFFF) != 0)) + return 0; + if ((L1 == 0x7F800000 || L1 == 0xFF800000) && (L2 == 0x7F800000 || L2 == 0xFF800000)) + return (L1 == L2); + + F1 = DECODE_FLOAT(L2) - DECODE_FLOAT(L1); + F2 = fabs(DECODE_FLOAT(L3)); + return ((F1 <= F2) && (F1 >= -F2)); } -#ifdef USE_OWN_POWF -float git_powf(float x, float y) -{ - if (x == 1.0f) - return 1.0f; - else if ((y == 0.0f) || (y == -0.0f)) - return 1.0f; - else if ((x == -1.0f) && isinf(y)) - return 1.0f; - return powf(x,y); -} +#ifdef USE_OWN_POWF +float git_powf(float x, float y) +{ + if (x == 1.0f) + return 1.0f; + else if ((y == 0.0f) || (y == -0.0f)) + return 1.0f; + else if ((x == -1.0f) && isinf(y)) + return 1.0f; + return powf(x,y); +} #endif // ------------------------------------------------------------- @@ -231,7 +231,12 @@ do_enter_function_L1: // Arg count is in L2. L6 = memRead8(L1++); // LocalType L5 = memRead8(L1++); // LocalCount if (L6 != 4 && L6 != 0) // We only support 4-byte locals. - fatalError("Local variable wasn't 4 bytes wide"); + { + if (L6 == 1 || L6 == 2) + fatalError("Short local variables are not supported, use Glulxe"); + else + fatalError("Local variable wasn't 4 bytes wide"); + } L4 += L5; // Cumulative local count. } while (L5 != 0); @@ -330,7 +335,7 @@ do_enter_function_L1: // Arg count is in L2. PEEPHOLE_STORE(fadd, F1 = DECODE_FLOAT(L1) + DECODE_FLOAT(L2); S1 = ENCODE_FLOAT(F1)); PEEPHOLE_STORE(fsub, F1 = DECODE_FLOAT(L1) - DECODE_FLOAT(L2); S1 = ENCODE_FLOAT(F1)); PEEPHOLE_STORE(fmul, F1 = DECODE_FLOAT(L1) * DECODE_FLOAT(L2); S1 = ENCODE_FLOAT(F1)); - PEEPHOLE_STORE(fdiv, F1 = DECODE_FLOAT(L1) / DECODE_FLOAT(L2); S1 = ENCODE_FLOAT(F1)); + PEEPHOLE_STORE(fdiv, F1 = DECODE_FLOAT(L1) / DECODE_FLOAT(L2); S1 = ENCODE_FLOAT(F1)); #define PEEPHOLE_LOAD(tag,reg) \ do_ ## tag ## _ ## reg ## _const: reg = READ_PC; goto do_ ## tag; \ @@ -1323,124 +1328,124 @@ do_tailcall: // Floating point (new with glulx spec 3.1.2) - do_numtof: - F1 = (git_float) L1; - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_ftonumz: - F1 = DECODE_FLOAT(L1); - if (!signbit(F1)) { - if (isnan(F1) || isinf(F1) || (F1 > 2147483647.0)) - S1 = 0x7FFFFFFF; - else - S1 = (git_sint32) truncf(F1); - } else { - if (isnan(F1) || isinf(F1) || (F1 < -2147483647.0)) - S1 = 0x80000000; - else - S1 = (git_sint32) truncf(F1); - } - NEXT; - - do_ftonumn: - F1 = DECODE_FLOAT(L1); - if (!signbit(F1)) { - if (isnan(F1) || isinf(F1) || (F1 > 2147483647.0)) - S1 = 0x7FFFFFFF; - else - S1 = (git_sint32) roundf(F1); - } else { - if (isnan(F1) || isinf(F1) || (F1 < -2147483647.0)) - S1 = 0x80000000; - else - S1 = (git_sint32) roundf(F1); - } - NEXT; - - do_ceil: - F1 = ceilf(DECODE_FLOAT(L1)); - L2 = ENCODE_FLOAT(F1); - if ((L2 == 0x0) || (L2 == 0x80000000)) - L2 = L1 & 0x80000000; - S1 = L2; - NEXT; - - do_floor: - F1 = floorf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_sqrt: - F1 = sqrtf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_exp: - F1 = expf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_log: - F1 = logf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_pow: -#ifdef USE_OWN_POWF - F1 = git_powf(DECODE_FLOAT(L1), DECODE_FLOAT(L2)); -#else - F1 = powf(DECODE_FLOAT(L1), DECODE_FLOAT(L2)); -#endif - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_atan2: - F1 = atan2f(DECODE_FLOAT(L1), DECODE_FLOAT(L2)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_fmod: - F1 = DECODE_FLOAT(L1); - F2 = DECODE_FLOAT(L2); - F3 = fmodf(F1, F2); - F4 = (F1 - F3) / F2; - L4 = ENCODE_FLOAT(F4); - if ((L4 == 0) || (L4 == 0x80000000)) - L4 = (L1 ^ L2) & 0x80000000; - S1 = ENCODE_FLOAT(F3); - S2 = L4; - NEXT; - - do_sin: - F1 = sinf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_cos: - F1 = cosf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_tan: - F1 = tanf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_asin: - F1 = asinf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_acos: - F1 = acosf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; - - do_atan: - F1 = atanf(DECODE_FLOAT(L1)); - S1 = ENCODE_FLOAT(F1); - NEXT; + do_numtof: + F1 = (git_float) L1; + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_ftonumz: + F1 = DECODE_FLOAT(L1); + if (!signbit(F1)) { + if (isnan(F1) || isinf(F1) || (F1 > 2147483647.0)) + S1 = 0x7FFFFFFF; + else + S1 = (git_sint32) truncf(F1); + } else { + if (isnan(F1) || isinf(F1) || (F1 < -2147483647.0)) + S1 = 0x80000000; + else + S1 = (git_sint32) truncf(F1); + } + NEXT; + + do_ftonumn: + F1 = DECODE_FLOAT(L1); + if (!signbit(F1)) { + if (isnan(F1) || isinf(F1) || (F1 > 2147483647.0)) + S1 = 0x7FFFFFFF; + else + S1 = (git_sint32) roundf(F1); + } else { + if (isnan(F1) || isinf(F1) || (F1 < -2147483647.0)) + S1 = 0x80000000; + else + S1 = (git_sint32) roundf(F1); + } + NEXT; + + do_ceil: + F1 = ceilf(DECODE_FLOAT(L1)); + L2 = ENCODE_FLOAT(F1); + if ((L2 == 0x0) || (L2 == 0x80000000)) + L2 = L1 & 0x80000000; + S1 = L2; + NEXT; + + do_floor: + F1 = floorf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_sqrt: + F1 = sqrtf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_exp: + F1 = expf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_log: + F1 = logf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_pow: +#ifdef USE_OWN_POWF + F1 = git_powf(DECODE_FLOAT(L1), DECODE_FLOAT(L2)); +#else + F1 = powf(DECODE_FLOAT(L1), DECODE_FLOAT(L2)); +#endif + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_atan2: + F1 = atan2f(DECODE_FLOAT(L1), DECODE_FLOAT(L2)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_fmod: + F1 = DECODE_FLOAT(L1); + F2 = DECODE_FLOAT(L2); + F3 = fmodf(F1, F2); + F4 = (F1 - F3) / F2; + L4 = ENCODE_FLOAT(F4); + if ((L4 == 0) || (L4 == 0x80000000)) + L4 = (L1 ^ L2) & 0x80000000; + S1 = ENCODE_FLOAT(F3); + S2 = L4; + NEXT; + + do_sin: + F1 = sinf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_cos: + F1 = cosf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_tan: + F1 = tanf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_asin: + F1 = asinf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_acos: + F1 = acosf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; + + do_atan: + F1 = atanf(DECODE_FLOAT(L1)); + S1 = ENCODE_FLOAT(F1); + NEXT; // Special Git opcodes diff --git a/interpreters/git/version.h b/interpreters/git/version.h index ed0e7ab..184ed51 100644 --- a/interpreters/git/version.h +++ b/interpreters/git/version.h @@ -1,4 +1,4 @@ // Automatically generated file -- do not edit! #define GIT_MAJOR 1 #define GIT_MINOR 2 -#define GIT_PATCH 8 +#define GIT_PATCH 9 diff --git a/interpreters/glulxe/unixstrt.c b/interpreters/glulxe/unixstrt.c index 38779a2..f4fa205 100644 --- a/interpreters/glulxe/unixstrt.c +++ b/interpreters/glulxe/unixstrt.c @@ -3,22 +3,14 @@ http://eblong.com/zarf/glulx/index.html */ -#include #include "glk.h" #include "glulxe.h" #include "glkstart.h" /* This comes with the Glk library. */ +#include -/* The only command-line argument is the filename. And the profiling switch, - if that's compiled in. The only *two* command-line arguments are... -*/ +/* The only command-line argument is the filename. */ glkunix_argumentlist_t glkunix_arguments[] = { - -#if VM_PROFILING - { "--profile", glkunix_arg_ValueFollows, "Generate profiling information to a file." }, -#endif /* VM_PROFILING */ - { "", glkunix_arg_ValueFollows, "filename: The game file to load." }, - { NULL, glkunix_arg_End, NULL } }; @@ -26,64 +18,35 @@ 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 *cx; unsigned char buf[12]; int res; #ifdef GARGLK - char *cx; garglk_set_program_name("Glulxe 0.4.7"); garglk_set_program_info("Glulxe 0.4.7 by Andrew Plotkin"); #endif - /* 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; - } -#endif /* VM_PROFILING */ - - if (filename) { - init_err = "You must supply exactly one game file."; - return TRUE; - } - filename = data->argv[ix]; - } - - if (!filename) { + if (data->argc <= 1) { init_err = "You must supply the name of a game file."; #ifdef GARGLK return TRUE; /* Hack! but I want error message in glk window */ #endif return FALSE; } + cx = data->argv[1]; - gamefile = glkunix_stream_open_pathname(filename, FALSE, 1); + gamefile = glkunix_stream_open_pathname(cx, FALSE, 1); if (!gamefile) { init_err = "The game file could not be opened."; - init_err2 = filename; + init_err2 = cx; return TRUE; } #ifdef GARGLK - cx = strrchr(filename, '/'); - if (!cx) cx = strrchr(filename, '\\'); - garglk_set_story_name(cx ? cx + 1 : filename); + cx = strrchr(data->argv[1], '/'); + if (!cx) cx = strrchr(data->argv[1], '\\'); + garglk_set_story_name(cx ? cx + 1 : data->argv[1]); #endif /* Now we have to check to see if it's a Blorb file. */ diff --git a/interpreters/nitfol/Makefile.am b/interpreters/nitfol/Makefile.am index a54d42a..1e721b1 100644 --- a/interpreters/nitfol/Makefile.am +++ b/interpreters/nitfol/Makefile.am @@ -1,9 +1,9 @@ -GRAPHICS = no_graph.c no_graph.h -# GRAPHICS = graphics.c graphics.h +#GRAPHICS = no_graph.c no_graph.h +GRAPHICS = graphics.c graphics.h BLORB = blorb.c # BLORB = no_blorb.c -SOUND = no_snd.c no_snd.h -# SOUND = sound.c sound.h +# SOUND = no_snd.c no_snd.h +SOUND = sound.c sound.h dist_noinst_SCRIPTS = copying.awk opt2glkc.pl y2help.pl diff --git a/interpreters/nitfol/automap.c b/interpreters/nitfol/automap.c index b2647b9..bb5c832 100644 --- a/interpreters/nitfol/automap.c +++ b/interpreters/nitfol/automap.c @@ -14,7 +14,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/debug.c b/interpreters/nitfol/debug.c index 2163a76..ea980ee 100644 --- a/interpreters/nitfol/debug.c +++ b/interpreters/nitfol/debug.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/decode.c b/interpreters/nitfol/decode.c index 7f31eac..2c9eb3b 100644 --- a/interpreters/nitfol/decode.c +++ b/interpreters/nitfol/decode.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/errmesg.c b/interpreters/nitfol/errmesg.c index a49332e..d69fefa 100644 --- a/interpreters/nitfol/errmesg.c +++ b/interpreters/nitfol/errmesg.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/globals.c b/interpreters/nitfol/globals.c index fa85d2b..3e33221 100644 --- a/interpreters/nitfol/globals.c +++ b/interpreters/nitfol/globals.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/iff.c b/interpreters/nitfol/iff.c index 4fcfe41..dc0ba81 100644 --- a/interpreters/nitfol/iff.c +++ b/interpreters/nitfol/iff.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/infix.c b/interpreters/nitfol/infix.c index 47b208d..f31da35 100644 --- a/interpreters/nitfol/infix.c +++ b/interpreters/nitfol/infix.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/init.c b/interpreters/nitfol/init.c index 6196d40..23b94ac 100644 --- a/interpreters/nitfol/init.c +++ b/interpreters/nitfol/init.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/io.c b/interpreters/nitfol/io.c index ee29799..41d62f3 100644 --- a/interpreters/nitfol/io.c +++ b/interpreters/nitfol/io.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/linkevil.h b/interpreters/nitfol/linkevil.h index df65116..6ba1aa8 100644 --- a/interpreters/nitfol/linkevil.h +++ b/interpreters/nitfol/linkevil.h @@ -14,7 +14,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at ecr+@andrew.cmu.edu */ diff --git a/interpreters/nitfol/main.c b/interpreters/nitfol/main.c index ce0ceac..79e42bd 100644 --- a/interpreters/nitfol/main.c +++ b/interpreters/nitfol/main.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/nitfol.h b/interpreters/nitfol/nitfol.h index 319a536..1b753cb 100644 --- a/interpreters/nitfol/nitfol.h +++ b/interpreters/nitfol/nitfol.h @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/objects.c b/interpreters/nitfol/objects.c index 185a781..1ae964b 100644 --- a/interpreters/nitfol/objects.c +++ b/interpreters/nitfol/objects.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/op_call.c b/interpreters/nitfol/op_call.c index ebedf4f..d6db12d 100644 --- a/interpreters/nitfol/op_call.c +++ b/interpreters/nitfol/op_call.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/op_jmp.c b/interpreters/nitfol/op_jmp.c index 9da59de..4a36996 100644 --- a/interpreters/nitfol/op_jmp.c +++ b/interpreters/nitfol/op_jmp.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/op_math.c b/interpreters/nitfol/op_math.c index 923bcde..b2fb4d2 100644 --- a/interpreters/nitfol/op_math.c +++ b/interpreters/nitfol/op_math.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/op_save.c b/interpreters/nitfol/op_save.c index 1ccef32..573334d 100644 --- a/interpreters/nitfol/op_save.c +++ b/interpreters/nitfol/op_save.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/op_table.c b/interpreters/nitfol/op_table.c index 9c2e54c..aa0d722 100644 --- a/interpreters/nitfol/op_table.c +++ b/interpreters/nitfol/op_table.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/op_v6.c b/interpreters/nitfol/op_v6.c index b641c3a..fbecba8 100644 --- a/interpreters/nitfol/op_v6.c +++ b/interpreters/nitfol/op_v6.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/oplist.c b/interpreters/nitfol/oplist.c index 454e02a..4bc5676 100644 --- a/interpreters/nitfol/oplist.c +++ b/interpreters/nitfol/oplist.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/opt2glkc.pl b/interpreters/nitfol/opt2glkc.pl index 6f60a64..0a5f255 100644 --- a/interpreters/nitfol/opt2glkc.pl +++ b/interpreters/nitfol/opt2glkc.pl @@ -36,7 +36,7 @@ my $configname = "configname = \"${dirsep}.${appname}rc\";"; # # 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # The author can be reached at nitfol@my-deja.com diff --git a/interpreters/nitfol/portfunc.c b/interpreters/nitfol/portfunc.c index 9c5c552..9a444b6 100644 --- a/interpreters/nitfol/portfunc.c +++ b/interpreters/nitfol/portfunc.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/quetzal.c b/interpreters/nitfol/quetzal.c index 0ddbf36..7fd27da 100644 --- a/interpreters/nitfol/quetzal.c +++ b/interpreters/nitfol/quetzal.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/solve.c b/interpreters/nitfol/solve.c index 33a3e30..9799906 100644 --- a/interpreters/nitfol/solve.c +++ b/interpreters/nitfol/solve.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/sound.c b/interpreters/nitfol/sound.c index 763afcd..4d79cbf 100644 --- a/interpreters/nitfol/sound.c +++ b/interpreters/nitfol/sound.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/stack.c b/interpreters/nitfol/stack.c index 08195c2..3b93209 100644 --- a/interpreters/nitfol/stack.c +++ b/interpreters/nitfol/stack.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/struct.c b/interpreters/nitfol/struct.c index c0a58cf..6108b47 100644 --- a/interpreters/nitfol/struct.c +++ b/interpreters/nitfol/struct.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/tokenise.c b/interpreters/nitfol/tokenise.c index 7db9db6..e32387b 100644 --- a/interpreters/nitfol/tokenise.c +++ b/interpreters/nitfol/tokenise.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/undo.c b/interpreters/nitfol/undo.c index 3f2cb22..5a98da9 100644 --- a/interpreters/nitfol/undo.c +++ b/interpreters/nitfol/undo.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/z_io.c b/interpreters/nitfol/z_io.c index a06fb5a..f7bcbbf 100644 --- a/interpreters/nitfol/z_io.c +++ b/interpreters/nitfol/z_io.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/interpreters/nitfol/zscii.c b/interpreters/nitfol/zscii.c index f7c12d4..0beb9bf 100644 --- a/interpreters/nitfol/zscii.c +++ b/interpreters/nitfol/zscii.c @@ -13,7 +13,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached at nitfol@deja.com */ diff --git a/libchimara/Makefile.am b/libchimara/Makefile.am index 96d861d..efb023d 100644 --- a/libchimara/Makefile.am +++ b/libchimara/Makefile.am @@ -4,12 +4,6 @@ AM_CFLAGS = -Wall lib_LTLIBRARIES = libchimara.la -if TARGET_ILIAD -pluginpath = "../interpreters" -else -pluginpath = $(pkglibdir) -endif - libchimara_la_SOURCES = \ abort.c abort.h \ case.c \ @@ -45,7 +39,7 @@ libchimara_la_SOURCES = \ libchimara_la_CPPFLAGS = $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"Chimara\" \ -DLOCALEDIR=\""$(datadir)/locale"\" \ - -DPLUGINDIR=\""$(pluginpath)"\" \ + -DPLUGINDIR=\""$(pkglibdir)"\" \ -DPLUGINSOURCEDIR=\""$(abs_builddir)/../interpreters"\" \ -I$(top_srcdir) libchimara_la_CFLAGS = @CHIMARA_CFLAGS@ $(AM_CFLAGS) @@ -84,7 +78,7 @@ introspection_sources = \ chimara-if.c chimara-if.h Chimara-1.0.gir: libchimara.la -Chimara_1_0_gir_INCLUDES = GObject-2.0 GLib-2.0 Gtk-2.0 +Chimara_1_0_gir_INCLUDES = GObject-2.0 GLib-2.0 Gtk-3.0 Chimara_1_0_gir_CFLAGS = @CHIMARA_CFLAGS@ -I$(top_srcdir) Chimara_1_0_gir_LIBS = libchimara.la Chimara_1_0_gir_FILES = $(introspection_sources) @@ -110,7 +104,7 @@ dist_vapi_DATA = chimara.vapi if BUILDING_VAPI chimara.vapi: $(INTROSPECTION_GIRS) - $(AM_V_GEN)$(VAPIGEN) --library=chimara --pkg gtk+-2.0 Chimara-1.0.gir && \ + $(AM_V_GEN)$(VAPIGEN) --library=chimara --pkg gtk+-3.0 Chimara-1.0.gir && \ touch $@ endif diff --git a/libchimara/chimara-glk-private.h b/libchimara/chimara-glk-private.h index 0469b8d..01b4a25 100644 --- a/libchimara/chimara-glk-private.h +++ b/libchimara/chimara-glk-private.h @@ -30,7 +30,6 @@ struct _ChimaraGlkPrivate { /* Hashtable containing the current styles set by CSS and GLK */ struct StyleSet *styles; struct StyleSet *glk_styles; - PangoAttrList *pager_attr_list; /* Final message displayed when game exits */ gchar *final_message; /* Image cache */ diff --git a/libchimara/chimara-glk.c b/libchimara/chimara-glk.c index 6a4c0d3..aea0f69 100644 --- a/libchimara/chimara-glk.c +++ b/libchimara/chimara-glk.c @@ -28,27 +28,6 @@ #define CHIMARA_GLK_MIN_WIDTH 0 #define CHIMARA_GLK_MIN_HEIGHT 0 -/* Substitute functions for compiling on iLiad */ - -#if !GTK_CHECK_VERSION(2, 18, 0) -#define gtk_widget_get_allocation(w, a) \ - G_STMT_START { \ - (a)->x = (w)->allocation.x; \ - (a)->y = (w)->allocation.y; \ - (a)->width = (w)->allocation.width; \ - (a)->height = (w)->allocation.height; \ - } G_STMT_END -#define gtk_widget_set_allocation(w, a) \ - G_STMT_START { (w)->allocation = *(a); } G_STMT_END -#define gtk_widget_set_has_window(w, f) \ - G_STMT_START { \ - if(f) \ - GTK_WIDGET_UNSET_FLAGS((w), GTK_NO_WINDOW); \ - else \ - GTK_WIDGET_SET_FLAGS((w), GTK_NO_WINDOW); \ - } G_STMT_END -#endif /* GTK 2.18 */ - /** * SECTION:chimara-glk * @short_description: Widget which executes a Glk program @@ -182,7 +161,6 @@ chimara_glk_init(ChimaraGlk *self) priv->protect = FALSE; priv->styles = g_new0(StyleSet,1); priv->glk_styles = g_new0(StyleSet,1); - priv->pager_attr_list = pango_attr_list_new(); priv->final_message = g_strdup("[ The game has finished ]"); priv->running = FALSE; priv->program = NULL; @@ -291,8 +269,7 @@ chimara_glk_finalize(GObject *object) g_hash_table_destroy(priv->styles->text_grid); g_hash_table_destroy(priv->glk_styles->text_buffer); g_hash_table_destroy(priv->glk_styles->text_grid); - pango_attr_list_unref(priv->pager_attr_list); - + /* Free the event queue */ g_mutex_lock(priv->event_lock); g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL); @@ -345,93 +322,35 @@ chimara_glk_finalize(GObject *object) G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object); } -/* Internal function: Recursively get the Glk window tree's size request */ -static void -request_recurse(winid_t win, GtkRequisition *requisition, guint spacing) +/* Implementation of get_request_mode(): Always request constant size */ +static GtkSizeRequestMode +chimara_glk_get_request_mode(GtkWidget *widget) { - if(win->type == wintype_Pair) - { - /* Get children's size requests */ - GtkRequisition child1, child2; - request_recurse(win->window_node->children->data, &child1, spacing); - request_recurse(win->window_node->children->next->data, &child2, spacing); + return GTK_SIZE_REQUEST_CONSTANT_SIZE; +} - glui32 division = win->split_method & winmethod_DivisionMask; - glui32 direction = win->split_method & winmethod_DirMask; - unsigned border = ((win->split_method & winmethod_BorderMask) == winmethod_NoBorder)? 0 : spacing; +/* Minimal implementation of width request. Allocation in Glk is +strictly top-down, so we just request our current size by returning 1. */ +static void +chimara_glk_get_preferred_width(GtkWidget *widget, int *minimal, int *natural) +{ + g_return_if_fail(widget || CHIMARA_IS_GLK(widget)); + g_return_if_fail(minimal); + g_return_if_fail(natural); - /* If the split is fixed, get the size of the fixed child */ - if(division == winmethod_Fixed) - { - switch(direction) - { - case winmethod_Left: - child1.width = win->key_window? - win->constraint_size * win->key_window->unit_width - : 0; - break; - case winmethod_Right: - child2.width = win->key_window? - win->constraint_size * win->key_window->unit_width - : 0; - break; - case winmethod_Above: - child1.height = win->key_window? - win->constraint_size * win->key_window->unit_height - : 0; - break; - case winmethod_Below: - child2.height = win->key_window? - win->constraint_size * win->key_window->unit_height - : 0; - break; - } - } - - /* Add the children's requests */ - switch(direction) - { - case winmethod_Left: - case winmethod_Right: - requisition->width = child1.width + child2.width + border; - requisition->height = MAX(child1.height, child2.height); - break; - case winmethod_Above: - case winmethod_Below: - requisition->width = MAX(child1.width, child2.width); - requisition->height = child1.height + child2.height + border; - break; - } - } - - /* For non-pair windows, just use the size that GTK requests */ - else - gtk_widget_size_request(win->frame, requisition); + *minimal = *natural = 1; } -/* Overrides gtk_widget_size_request */ +/* Minimal implementation of height request. Allocation in Glk is +strictly top-down, so we just request our current size by returning 1. */ static void -chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition) +chimara_glk_get_preferred_height(GtkWidget *widget, int *minimal, int *natural) { - g_return_if_fail(widget); - g_return_if_fail(requisition); - g_return_if_fail(CHIMARA_IS_GLK(widget)); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); - - guint border_width = gtk_container_get_border_width(GTK_CONTAINER(widget)); - /* For now, just pass the size request on to the root Glk window */ - if(priv->root_window) - { - request_recurse(priv->root_window->data, requisition, priv->spacing); - requisition->width += 2 * border_width; - requisition->height += 2 * border_width; - } - else - { - requisition->width = CHIMARA_GLK_MIN_WIDTH + 2 * border_width; - requisition->height = CHIMARA_GLK_MIN_HEIGHT + 2 * border_width; - } + g_return_if_fail(widget || CHIMARA_IS_GLK(widget)); + g_return_if_fail(minimal); + g_return_if_fail(natural); + + *minimal = *natural = 1; } /* Recursively give the Glk windows their allocated space. Returns a window @@ -552,64 +471,92 @@ allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing) bottom or right area is filled with blanks. */ GtkAllocation widget_allocation; gtk_widget_get_allocation(win->widget, &widget_allocation); - glui32 newwidth = (glui32)(widget_allocation.width / win->unit_width); - glui32 newheight = (glui32)(widget_allocation.height / win->unit_height); - gint line; - GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - GtkTextIter start, end; - - for(line = 0; line < win->height; line++) + glui32 new_width = (glui32)(widget_allocation.width / win->unit_width); + glui32 new_height = (glui32)(widget_allocation.height / win->unit_height); + + if(new_width != win->width || new_height != win->height) { - gtk_text_buffer_get_iter_at_line(textbuffer, &start, line); - /* If this line is going to fall off the bottom, delete it */ - if(line >= newheight) - { - end = start; - gtk_text_iter_forward_to_line_end(&end); - gtk_text_iter_forward_char(&end); - gtk_text_buffer_delete(textbuffer, &start, &end); - break; + // Window has changed size, trim or expand the textbuffer if necessary. + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextIter start, end; + + // Add or remove lines + if(new_height == 0) { + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_delete(buffer, &start, &end); } - /* If this line is not long enough, add spaces on the end */ - if(newwidth > win->width) + else if(new_height < win->height) { - gchar *spaces = g_strnfill(newwidth - win->width, ' '); + // Remove surplus lines + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_get_iter_at_line(buffer, &start, new_height-1); gtk_text_iter_forward_to_line_end(&start); - gtk_text_buffer_insert(textbuffer, &start, spaces, -1); - g_free(spaces); + gtk_text_buffer_delete(buffer, &start, &end); + } - /* But if it's too long, delete characters from the end */ - else if(newwidth < win->width) + else if(new_height > win->height) { + // Add extra lines + gint lines_to_add = new_height - win->height; + gtk_text_buffer_get_end_iter(buffer, &end); + start = end; + + gchar *blanks = g_strnfill(win->width, ' '); + gchar **blanklines = g_new0(gchar *, lines_to_add + 1); + int count; + for(count = 0; count < lines_to_add; count++) + blanklines[count] = blanks; + blanklines[lines_to_add] = NULL; + gchar *vertical_blanks = g_strjoinv("\n", blanklines); + g_free(blanklines); + g_free(blanks); + + if(win->height > 0) + gtk_text_buffer_insert(buffer, &end, "\n", 1); + + gtk_text_buffer_insert(buffer, &end, vertical_blanks, -1); + } + + // Trim or expand lines + if(new_width < win->width) { + gtk_text_buffer_get_start_iter(buffer, &start); end = start; - gtk_text_iter_forward_chars(&start, newwidth); - gtk_text_iter_forward_to_line_end(&end); - gtk_text_buffer_delete(textbuffer, &start, &end); + + gint line; + for(line = 0; line <= new_height; line++) { + // Trim the line + gtk_text_iter_forward_cursor_positions(&start, new_width); + gtk_text_iter_forward_to_line_end(&end); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_iter_forward_line(&start); + end = start; + } + } else if(new_width > win->width) { + gint chars_to_add = new_width - win->width; + gchar *horizontal_blanks = g_strnfill(chars_to_add, ' '); + + gtk_text_buffer_get_start_iter(buffer, &start); + end = start; + + gint line; + for(line = 0; line <= new_height; line++) { + gtk_text_iter_forward_to_line_end(&start); + end = start; + gint start_offset = gtk_text_iter_get_offset(&start); + gtk_text_buffer_insert(buffer, &end, horizontal_blanks, -1); + gtk_text_buffer_get_iter_at_offset(buffer, &start, start_offset); + gtk_text_iter_forward_line(&start); + end = start; + } + + g_free(horizontal_blanks); } - /* Note: if the widths are equal, do nothing */ - } - /* Add blank lines if there aren't enough lines to fit the new size */ - if(newheight > win->height) - { - gchar *blanks = g_strnfill(win->width, ' '); - gchar **blanklines = g_new0(gchar *, (newheight - win->height) + 1); - int count; - for(count = 0; count < newheight - win->height; count++) - blanklines[count] = blanks; - blanklines[newheight - win->height] = NULL; - gchar *text = g_strjoinv("\n", blanklines); - g_free(blanklines); /* not g_strfreev() */ - g_free(blanks); - - gtk_text_buffer_get_end_iter(textbuffer, &start); - gtk_text_buffer_insert(textbuffer, &start, "\n", -1); - gtk_text_buffer_insert(textbuffer, &start, text, -1); - g_free(text); } - gboolean arrange = !(win->width == newwidth && win->height == newheight); - win->width = newwidth; - win->height = newheight; + gboolean arrange = !(win->width == new_width && win->height == new_height); + win->width = new_width; + win->height = new_height; return arrange? win : NULL; } @@ -629,16 +576,11 @@ chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation) ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); gtk_widget_set_allocation(widget, allocation); - + if(priv->root_window) { - GtkAllocation child; - guint border_width = gtk_container_get_border_width(GTK_CONTAINER(widget)); - child.x = allocation->x + border_width; - child.y = allocation->y + border_width; - child.width = CLAMP(allocation->width - 2 * border_width, 0, allocation->width); - child.height = CLAMP(allocation->height - 2 * border_width, 0, allocation->height); + GtkAllocation child = *allocation; winid_t arrange = allocate_recurse(priv->root_window->data, &child, priv->spacing); - + /* arrange points to a window that contains all text grid and graphics windows which have been resized */ g_mutex_lock(priv->arrange_lock); @@ -735,18 +677,6 @@ chimara_glk_iliad_screen_update(ChimaraGlk *self, gboolean typing) /* Default signal handler */ } -/* COMPAT: G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */ -#ifndef G_PARAM_STATIC_STRINGS - -/* COMPAT: G_PARAM_STATIC_NAME and friends only appeared in GTK 2.8 */ -#if GTK_CHECK_VERSION(2,8,0) -#define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB) -#else -#define G_PARAM_STATIC_STRINGS (0) -#endif - -#endif - static void chimara_glk_class_init(ChimaraGlkClass *klass) { @@ -757,11 +687,15 @@ chimara_glk_class_init(ChimaraGlkClass *klass) object_class->finalize = chimara_glk_finalize; GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); - widget_class->size_request = chimara_glk_size_request; + widget_class->get_request_mode = chimara_glk_get_request_mode; + widget_class->get_preferred_width = chimara_glk_get_preferred_width; + widget_class->get_preferred_height = chimara_glk_get_preferred_height; widget_class->size_allocate = chimara_glk_size_allocate; GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); container_class->forall = chimara_glk_forall; + /* Automatically handle the GtkContainer:border-width property */ + gtk_container_class_handle_border_width(container_class); /* Signals */ klass->stopped = chimara_glk_stopped; @@ -1196,6 +1130,15 @@ struct StartupData { ChimaraGlkPrivate *glk_data; }; +static void +free_startup_data(struct StartupData *startup) +{ + int i = 0; + while(i < startup->args.argc) + g_free(startup->args.argv[i++]); + g_free(startup->args.argv); +} + /* glk_enter() is the actual function called in the new thread in which glk_main() runs. */ static gpointer glk_enter(struct StartupData *startup) @@ -1212,14 +1155,11 @@ glk_enter(struct StartupData *startup) startup->glk_data->in_startup = TRUE; int result = startup->glkunix_startup_code(&startup->args); startup->glk_data->in_startup = FALSE; - - int i = 0; - while(i < startup->args.argc) - g_free(startup->args.argv[i++]); - g_free(startup->args.argv); - - if(!result) + + if(!result) { + free_startup_data(startup); return NULL; + } } /* Run main function */ @@ -1229,6 +1169,7 @@ glk_enter(struct StartupData *startup) g_free(startup); g_signal_emit_by_name(startup->glk_data->self, "started"); glk_main(); + free_startup_data(startup); glk_exit(); /* Run shutdown code in glk_exit() even if glk_main() returns normally */ g_assert_not_reached(); /* because glk_exit() calls g_thread_exit() */ return NULL; @@ -1589,28 +1530,6 @@ chimara_glk_get_tag_names(ChimaraGlk *glk, unsigned int *num_tags) return style_get_tag_names(); } -/** - * chimara_glk_update_style: - * @glk: a #ChimaraGlk widget - * - * Processes style updates and updates the widget to reflect the new style. - * Call this every time you change a property of a #GtkTextTag retrieved by - * chimara_glk_get_tag(). - */ -void -chimara_glk_update_style(ChimaraGlk *glk) -{ - CHIMARA_GLK_USE_PRIVATE(glk, priv); - style_update(glk); - - /* Schedule a redraw */ - g_mutex_lock(priv->arrange_lock); - priv->needs_rearrange = TRUE; - priv->ignore_next_arrange_event = TRUE; - g_mutex_unlock(priv->arrange_lock); - gtk_widget_queue_resize( GTK_WIDGET(priv->self) ); -} - /** * chimara_glk_set_resource_load_callback: * @glk: a #ChimaraGlk widget diff --git a/libchimara/chimara-if.c b/libchimara/chimara-if.c index 95f8b93..0554d93 100644 --- a/libchimara/chimara-if.c +++ b/libchimara/chimara-if.c @@ -31,13 +31,13 @@ */ static gboolean supported_formats[CHIMARA_IF_NUM_FORMATS][CHIMARA_IF_NUM_INTERPRETERS] = { - /* Frotz Nitfol Glulxe Git */ - { TRUE, TRUE, FALSE, FALSE }, /* Z5 */ - { TRUE, TRUE, FALSE, FALSE }, /* Z6 */ - { TRUE, TRUE, FALSE, FALSE }, /* Z8 */ - { TRUE, TRUE, FALSE, FALSE }, /* Zblorb */ - { FALSE, FALSE, TRUE, TRUE }, /* Glulx */ - { FALSE, FALSE, TRUE, TRUE } /* Gblorb */ + /* Frotz Nitfol Glulxe Git Bocfel */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Z5 */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Z6 */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Z8 */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Zblorb */ + { FALSE, FALSE, TRUE, TRUE, FALSE }, /* Glulx */ + { FALSE, FALSE, TRUE, TRUE, FALSE } /* Gblorb */ }; static gchar *format_names[CHIMARA_IF_NUM_FORMATS] = { N_("Z-code version 5"), @@ -48,10 +48,10 @@ static gchar *format_names[CHIMARA_IF_NUM_FORMATS] = { N_("Blorbed Glulx") }; static gchar *interpreter_names[CHIMARA_IF_NUM_INTERPRETERS] = { - N_("Frotz"), N_("Nitfol"), N_("Glulxe"), N_("Git") + N_("Frotz"), N_("Nitfol"), N_("Glulxe"), N_("Git"), N_("Bocfel") }; static gchar *plugin_names[CHIMARA_IF_NUM_INTERPRETERS] = { - "frotz", "nitfol", "glulxe", "git" + "frotz", "nitfol", "glulxe", "git", "bocfel" }; typedef enum _ChimaraIFFlags { @@ -286,18 +286,6 @@ chimara_if_command(ChimaraIF *self, gchar *input, gchar *response) /* Default signal handler */ } -/* COMPAT: G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */ -#ifndef G_PARAM_STATIC_STRINGS - -/* COMPAT: G_PARAM_STATIC_NAME and friends only appeared in GTK 2.8 */ -#if GTK_CHECK_VERSION(2,8,0) -#define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB) -#else -#define G_PARAM_STATIC_STRINGS (0) -#endif - -#endif - static void chimara_if_class_init(ChimaraIFClass *klass) { diff --git a/libchimara/chimara-if.h b/libchimara/chimara-if.h index 72d6461..f8ec757 100644 --- a/libchimara/chimara-if.h +++ b/libchimara/chimara-if.h @@ -44,6 +44,7 @@ typedef enum { * @CHIMARA_IF_INTERPRETER_NITFOL: Nitfol * @CHIMARA_IF_INTERPRETER_GLULXE: Glulxe * @CHIMARA_IF_INTERPRETER_GIT: Git + * @CHIMARA_IF_INTERPRETER_BOCFEL: Bocfel * * Constants representing the available interpreter plugins. */ @@ -55,6 +56,7 @@ typedef enum { CHIMARA_IF_INTERPRETER_NITFOL, CHIMARA_IF_INTERPRETER_GLULXE, CHIMARA_IF_INTERPRETER_GIT, + CHIMARA_IF_INTERPRETER_BOCFEL, /*< private >*/ CHIMARA_IF_NUM_INTERPRETERS } ChimaraIFInterpreter; diff --git a/libchimara/fileref.c b/libchimara/fileref.c index d4f2043..5838996 100644 --- a/libchimara/fileref.c +++ b/libchimara/fileref.c @@ -246,11 +246,7 @@ glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock) GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), GTK_FILE_CHOOSER_ACTION_SAVE); - - /* COMPAT: */ -#if GTK_CHECK_VERSION(2,8,0) gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE); -#endif break; case filemode_ReadWrite: case filemode_WriteAppend: diff --git a/libchimara/graphics.c b/libchimara/graphics.c index 1ab5a35..543ad43 100644 --- a/libchimara/graphics.c +++ b/libchimara/graphics.c @@ -386,24 +386,19 @@ glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2, glui3 return draw_image_common(win, info->pixbuf, val1, val2); } -/* Internal function: draws a pixbuf to a graphics window of text buffer */ +/* Internal function: draws a pixbuf to a graphics window or text buffer */ glui32 draw_image_common(winid_t win, GdkPixbuf *pixbuf, glsi32 val1, glsi32 val2) { switch(win->type) { case wintype_Graphics: { - GdkPixmap *canvas; - gdk_threads_enter(); - gtk_image_get_pixmap( GTK_IMAGE(win->widget), &canvas, NULL ); - if(canvas == NULL) { - WARNING("Could not get pixmap"); - return FALSE; - } - - gdk_draw_pixbuf( GDK_DRAWABLE(canvas), NULL, pixbuf, 0, 0, val1, val2, -1, -1, GDK_RGB_DITHER_NONE, 0, 0 ); + cairo_t *cr = cairo_create(win->backing_store); + gdk_cairo_set_source_pixbuf(cr, pixbuf, val1, val2); + cairo_paint(cr); + cairo_destroy(cr); /* Update the screen */ gtk_widget_queue_draw(win->widget); @@ -483,6 +478,16 @@ glk_window_set_background_color(winid_t win, glui32 color) win->background_color = color; } +static void +glkcairo_set_source_glkcolor(cairo_t *cr, glui32 val) +{ + double r, g, b; + r = ((val & 0xff0000) >> 16) / 256.0; + g = ((val & 0x00ff00) >> 8) / 256.0; + b = (val & 0x0000ff) / 256.0; + cairo_set_source_rgb(cr, r, g, b); +} + /** * glk_window_fill_rect: * @win: A graphics window. @@ -504,16 +509,12 @@ glk_window_fill_rect(winid_t win, glui32 color, glsi32 left, glsi32 top, glui32 gdk_threads_enter(); - GdkPixmap *map; - gtk_image_get_pixmap( GTK_IMAGE(win->widget), &map, NULL ); - - GdkGC *gc = gdk_gc_new(map); - GdkColor gdkcolor; - glkcolor_to_gdkcolor(color, &gdkcolor); - gdk_gc_set_rgb_fg_color(gc, &gdkcolor); - gdk_draw_rectangle( GDK_DRAWABLE(map), gc, TRUE, left, top, width, height); + cairo_t *cr = cairo_create(win->backing_store); + glkcairo_set_source_glkcolor(cr, color); + cairo_rectangle(cr, (double)left, (double)top, (double)width, (double)height); + cairo_fill(cr); gtk_widget_queue_draw(win->widget); - g_object_unref(gc); + cairo_destroy(cr); gdk_threads_leave(); } @@ -579,37 +580,54 @@ void glk_window_flow_break(winid_t win) VALID_WINDOW(win, return); } -/*** Called when the graphics window is resized. Resize the backing pixmap if necessary ***/ -void -on_graphics_size_allocate(GtkWidget *widget, GtkAllocation *allocation, winid_t win) -{ - GdkPixmap *oldmap; - gtk_image_get_pixmap( GTK_IMAGE(widget), &oldmap, NULL ); - gint oldwidth = 0; - gint oldheight = 0; - - /* Determine whether a pixmap exists with the correct size */ +/* Called when the graphics window is resized, restacked, or moved. Resize the +backing store if necessary. */ +gboolean +on_graphics_configure(GtkWidget *widget, GdkEventConfigure *event, winid_t win) +{ + int oldwidth = 0, oldheight = 0; + + /* Determine whether the backing store can stay the same size */ gboolean needs_resize = FALSE; - if(oldmap == NULL) + if(win->backing_store == NULL) needs_resize = TRUE; else { - gdk_drawable_get_size( GDK_DRAWABLE(oldmap), &oldwidth, &oldheight ); - if(oldwidth != allocation->width || oldheight != allocation->height) + oldwidth = cairo_image_surface_get_width(win->backing_store); + oldheight = cairo_image_surface_get_height(win->backing_store); + if(oldwidth != event->width || oldheight != event->height) needs_resize = TRUE; } if(needs_resize) { - /* Create a new pixmap */ - GdkPixmap *newmap = gdk_pixmap_new(widget->window, allocation->width, allocation->height, -1); - gdk_draw_rectangle( GDK_DRAWABLE(newmap), widget->style->white_gc, TRUE, 0, 0, allocation->width, allocation->height); - - /* Copy the contents of the old pixmap */ - if(oldmap != NULL) - gdk_draw_drawable( GDK_DRAWABLE(newmap), widget->style->white_gc, GDK_DRAWABLE(oldmap), 0, 0, 0, 0, oldwidth, oldheight); - - /* Use the new pixmap */ - gtk_image_set_from_pixmap( GTK_IMAGE(widget), newmap, NULL ); - g_object_unref(newmap); + /* Create a new backing store */ + cairo_surface_t *new_backing_store = gdk_window_create_similar_surface( gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, gtk_widget_get_allocated_width(widget), gtk_widget_get_allocated_height(widget) ); + cairo_t *cr = cairo_create(new_backing_store); + + /* Clear to background color */ + glkcairo_set_source_glkcolor(cr, win->background_color); + cairo_paint(cr); + + if(win->backing_store != NULL) { + /* Copy the contents of the old backing store */ + cairo_set_source_surface(cr, win->backing_store, 0, 0); + cairo_paint(cr); + cairo_surface_destroy(win->backing_store); + } + + cairo_destroy(cr); + /* Use the new backing store */ + win->backing_store = new_backing_store; } + + return TRUE; /* Event handled, stop processing */ } +/* Draw the backing store to the screen. Called whenever the drawing area is +exposed. */ +gboolean +on_graphics_draw(GtkWidget *widget, cairo_t *cr, winid_t win) +{ + cairo_set_source_surface(cr, win->backing_store, 0, 0); + cairo_paint(cr); + return FALSE; +} diff --git a/libchimara/graphics.h b/libchimara/graphics.h index fe39694..5ca8c60 100644 --- a/libchimara/graphics.h +++ b/libchimara/graphics.h @@ -21,7 +21,8 @@ struct image_info { gboolean scaled; }; -void on_graphics_size_allocate(GtkWidget *widget, GtkAllocation *allocation, winid_t win); +gboolean on_graphics_configure(GtkWidget *widget, GdkEventConfigure *event, winid_t win); +gboolean on_graphics_draw(GtkWidget *widget, cairo_t *cr, winid_t win); void clear_image_cache(struct image_info *data, gpointer user_data); #endif diff --git a/libchimara/input.c b/libchimara/input.c index 7f9b693..fd5aef4 100644 --- a/libchimara/input.c +++ b/libchimara/input.c @@ -143,10 +143,7 @@ text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE); GtkBorder border = { 0, 0, 0, 0 }; - /* COMPAT: */ -#if GTK_CHECK_VERSION(2,10,0) gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border); -#endif gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length); gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length); @@ -497,7 +494,7 @@ on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win gtk_text_buffer_get_selection_bounds(buffer, &selection_start, &selection_end); if(gtk_text_iter_compare(&selection_start, &input_position_iter) < 0) { // Cursor is somewhere else in the text, place it at the end if the user starts typing - if(event->keyval >= GDK_space && event->keyval <= GDK_asciitilde) { + if(event->keyval >= GDK_KEY_space && event->keyval <= GDK_KEY_asciitilde) { gtk_text_buffer_place_cursor(buffer, &end_iter); } else { // User is walking around, let him be. @@ -509,16 +506,16 @@ on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win pager_update(win); /* History up/down */ - if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up - || event->keyval == GDK_Down || event->keyval == GDK_KP_Down) + if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up + || event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) { /* Prevent falling off the end of the history list */ if(win->history == NULL) return TRUE; - if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up) + if( (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) && win->history_pos && win->history_pos->next == NULL) return TRUE; - if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down) + if( (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) && (win->history_pos == NULL || win->history_pos->prev == NULL) ) return TRUE; @@ -531,7 +528,7 @@ on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win gtk_text_buffer_delete(buffer, &input_position_iter, &end_iter); - if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up) + if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) { if(win->history_pos) win->history_pos = g_list_next(win->history_pos); @@ -552,20 +549,20 @@ on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win } /* Move to beginning/end of input field */ - else if(event->keyval == GDK_Home) { + else if(event->keyval == GDK_KEY_Home) { GtkTextIter input_iter; GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position"); gtk_text_buffer_get_iter_at_mark(buffer, &input_iter, input_position); gtk_text_buffer_place_cursor(buffer, &input_iter); return TRUE; } - else if(event->keyval == GDK_End) { + else if(event->keyval == GDK_KEY_End) { gtk_text_buffer_place_cursor(buffer, &end_iter); return TRUE; } /* Handle the line terminators */ - else if(event->keyval == GDK_Return || event->keyval == GDK_KP_Enter + else if(event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter || g_slist_find(win->current_extra_line_terminators, GUINT_TO_POINTER(event->keyval))) { /* Remove signal handlers */ @@ -591,15 +588,15 @@ on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win /* If this is a text grid window, then redirect the key press to the line input GtkEntry */ case wintype_TextGrid: { - if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up - || event->keyval == GDK_Down || event->keyval == GDK_KP_Down - || event->keyval == GDK_Left || event->keyval == GDK_KP_Left - || event->keyval == GDK_Right || event->keyval == GDK_KP_Right - || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab - || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up - || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down - || event->keyval == GDK_Home || event->keyval == GDK_KP_Home - || event->keyval == GDK_End || event->keyval == GDK_KP_End) + if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up + || event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down + || event->keyval == GDK_KEY_Left || event->keyval == GDK_KEY_KP_Left + || event->keyval == GDK_KEY_Right || event->keyval == GDK_KEY_KP_Right + || event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_KP_Tab + || event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_KP_Page_Up + || event->keyval == GDK_KEY_Page_Down || event->keyval == GDK_KEY_KP_Page_Down + || event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_KP_Home + || event->keyval == GDK_KEY_End || event->keyval == GDK_KEY_KP_End) return FALSE; /* Don't redirect these keys */ gtk_widget_grab_focus(win->input_entry); gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1); @@ -805,14 +802,14 @@ GtkEntry in a text grid window. */ gboolean on_input_entry_key_press_event(GtkEntry *input_entry, GdkEventKey *event, winid_t win) { - if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up - || event->keyval == GDK_Down || event->keyval == GDK_KP_Down) + if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up + || event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) { /* Prevent falling off the end of the history list */ - if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up) + if( (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) && win->history_pos && win->history_pos->next == NULL) return TRUE; - if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down) + if( (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) && (win->history_pos == NULL || win->history_pos->prev == NULL) ) return TRUE; @@ -823,7 +820,7 @@ on_input_entry_key_press_event(GtkEntry *input_entry, GdkEventKey *event, winid_ win->history_pos = win->history; } - if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up) + if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) { if(win->history_pos) win->history_pos = g_list_next(win->history_pos); @@ -864,47 +861,47 @@ keyval_to_glk_keycode(guint keyval, gboolean unicode) { glui32 keycode; switch(keyval) { - case GDK_Up: - case GDK_KP_Up: return keycode_Up; - case GDK_Down: - case GDK_KP_Down: return keycode_Down; - case GDK_Left: - case GDK_KP_Left: return keycode_Left; - case GDK_Right: - case GDK_KP_Right: return keycode_Right; - case GDK_Linefeed: - case GDK_Return: - case GDK_KP_Enter: return keycode_Return; - case GDK_Delete: - case GDK_BackSpace: - case GDK_KP_Delete: return keycode_Delete; - case GDK_Escape: return keycode_Escape; - case GDK_Tab: - case GDK_KP_Tab: return keycode_Tab; - case GDK_Page_Up: - case GDK_KP_Page_Up: return keycode_PageUp; - case GDK_Page_Down: - case GDK_KP_Page_Down: return keycode_PageDown; - case GDK_Home: - case GDK_KP_Home: return keycode_Home; - case GDK_End: - case GDK_KP_End: return keycode_End; - case GDK_F1: - case GDK_KP_F1: return keycode_Func1; - case GDK_F2: - case GDK_KP_F2: return keycode_Func2; - case GDK_F3: - case GDK_KP_F3: return keycode_Func3; - case GDK_F4: - case GDK_KP_F4: return keycode_Func4; - case GDK_F5: return keycode_Func5; - case GDK_F6: return keycode_Func6; - case GDK_F7: return keycode_Func7; - case GDK_F8: return keycode_Func8; - case GDK_F9: return keycode_Func9; - case GDK_F10: return keycode_Func10; - case GDK_F11: return keycode_Func11; - case GDK_F12: return keycode_Func12; + case GDK_KEY_Up: + case GDK_KEY_KP_Up: return keycode_Up; + case GDK_KEY_Down: + case GDK_KEY_KP_Down: return keycode_Down; + case GDK_KEY_Left: + case GDK_KEY_KP_Left: return keycode_Left; + case GDK_KEY_Right: + case GDK_KEY_KP_Right: return keycode_Right; + case GDK_KEY_Linefeed: + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: return keycode_Return; + case GDK_KEY_Delete: + case GDK_KEY_BackSpace: + case GDK_KEY_KP_Delete: return keycode_Delete; + case GDK_KEY_Escape: return keycode_Escape; + case GDK_KEY_Tab: + case GDK_KEY_KP_Tab: return keycode_Tab; + case GDK_KEY_Page_Up: + case GDK_KEY_KP_Page_Up: return keycode_PageUp; + case GDK_KEY_Page_Down: + case GDK_KEY_KP_Page_Down: return keycode_PageDown; + case GDK_KEY_Home: + case GDK_KEY_KP_Home: return keycode_Home; + case GDK_KEY_End: + case GDK_KEY_KP_End: return keycode_End; + case GDK_KEY_F1: + case GDK_KEY_KP_F1: return keycode_Func1; + case GDK_KEY_F2: + case GDK_KEY_KP_F2: return keycode_Func2; + case GDK_KEY_F3: + case GDK_KEY_KP_F3: return keycode_Func3; + case GDK_KEY_F4: + case GDK_KEY_KP_F4: return keycode_Func4; + case GDK_KEY_F5: return keycode_Func5; + case GDK_KEY_F6: return keycode_Func6; + case GDK_KEY_F7: return keycode_Func7; + case GDK_KEY_F8: return keycode_Func8; + case GDK_KEY_F9: return keycode_Func9; + case GDK_KEY_F10: return keycode_Func10; + case GDK_KEY_F11: return keycode_Func11; + case GDK_KEY_F12: return keycode_Func12; default: keycode = gdk_keyval_to_unicode(keyval); /* If keycode is 0, then keyval was not recognized; also return @@ -1062,55 +1059,55 @@ keycode_to_gdk_keyval(glui32 keycode) switch (keycode) { case keycode_Left: - return GDK_Left; + return GDK_KEY_Left; case keycode_Right: - return GDK_Right; + return GDK_KEY_Right; case keycode_Up: - return GDK_Up; + return GDK_KEY_Up; case keycode_Down: - return GDK_Down; + return GDK_KEY_Down; case keycode_Return: - return GDK_Return; + return GDK_KEY_Return; case keycode_Delete: - return GDK_Delete; + return GDK_KEY_Delete; case keycode_Escape: - return GDK_Escape; + return GDK_KEY_Escape; case keycode_Tab: - return GDK_Tab; + return GDK_KEY_Tab; case keycode_PageUp: - return GDK_Page_Up; + return GDK_KEY_Page_Up; case keycode_PageDown: - return GDK_Page_Down; + return GDK_KEY_Page_Down; case keycode_Home: - return GDK_Home; + return GDK_KEY_Home; case keycode_End: - return GDK_End; + return GDK_KEY_End; case keycode_Func1: - return GDK_F1; + return GDK_KEY_F1; case keycode_Func2: - return GDK_F2; + return GDK_KEY_F2; case keycode_Func3: - return GDK_F3; + return GDK_KEY_F3; case keycode_Func4: - return GDK_F4; + return GDK_KEY_F4; case keycode_Func5: - return GDK_F5; + return GDK_KEY_F5; case keycode_Func6: - return GDK_F6; + return GDK_KEY_F6; case keycode_Func7: - return GDK_F7; + return GDK_KEY_F7; case keycode_Func8: - return GDK_F8; + return GDK_KEY_F8; case keycode_Func9: - return GDK_F9; + return GDK_KEY_F9; case keycode_Func10: - return GDK_F10; + return GDK_KEY_F10; case keycode_Func11: - return GDK_F11; + return GDK_KEY_F11; case keycode_Func12: - return GDK_F12; + return GDK_KEY_F12; case keycode_Erase: - return GDK_BackSpace; + return GDK_KEY_BackSpace; } unsigned keyval = gdk_unicode_to_keyval(keycode); if(keyval < 0x01000000) /* magic number meaning illegal unicode point */ diff --git a/libchimara/pager.c b/libchimara/pager.c index 194a21d..c36edc7 100644 --- a/libchimara/pager.c +++ b/libchimara/pager.c @@ -2,6 +2,10 @@ #include "pager.h" +/* Not sure if necessary, but this is the margin within which the pager will +stop paging if it's close to the end of the text buffer */ +#define PAGER_FUZZINESS 1.0 + /* Helper function: move the pager to the last visible position in the buffer, and return the distance between the pager and the end of the buffer in buffer coordinates */ @@ -32,11 +36,6 @@ move_pager_and_get_scroll_distance(GtkTextView *textview, gint *view_height, gin gtk_text_view_get_iter_location(textview, &newpager, &pagerpos); gtk_text_view_get_iter_location(textview, &end, &endpos); - /* - g_printerr("View height = %d\n", visiblerect.height); - g_printerr("End - Pager = %d - %d = %d\n", endpos.y, pagerpos.y, endpos.y - pagerpos.y); - */ - *view_height = visiblerect.height; *scroll_distance = endpos.y - pagerpos.y; } @@ -46,7 +45,7 @@ static void start_paging(winid_t win) { win->currently_paging = TRUE; - g_signal_handler_unblock(win->widget, win->pager_expose_handler); + gtk_widget_show(win->pager); g_signal_handler_unblock(win->widget, win->pager_keypress_handler); } @@ -55,10 +54,35 @@ static void stop_paging(winid_t win) { win->currently_paging = FALSE; - g_signal_handler_block(win->widget, win->pager_expose_handler); + gtk_widget_hide(win->pager); g_signal_handler_block(win->widget, win->pager_keypress_handler); } +/* Helper function: If the adjustment is at its maximum value, stop paging */ +static void +check_paging(GtkAdjustment *adj, winid_t win) +{ + double page_size, upper, value; + g_object_get(adj, + "page-size", &page_size, + "upper", &upper, + "value", &value, + NULL); + if(value + PAGER_FUZZINESS >= upper - page_size && win->currently_paging) + stop_paging(win); +} + +void +pager_on_clicked(GtkButton *pager, winid_t win) +{ + GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(win->scrolledwindow) ); + double upper = gtk_adjustment_get_upper(adj); + gtk_adjustment_set_value(adj, upper); + check_paging(adj, win); + /* Give the focus back to the text view */ + gtk_widget_grab_focus(win->widget); +} + /* When the user scrolls up in a textbuffer, start paging. */ void pager_after_adjustment_changed(GtkAdjustment *adj, winid_t win) @@ -68,19 +92,24 @@ pager_after_adjustment_changed(GtkAdjustment *adj, winid_t win) move_pager_and_get_scroll_distance( GTK_TEXT_VIEW(win->widget), &view_height, &scroll_distance, TRUE ); if(scroll_distance > 0 && !win->currently_paging) + { start_paging(win); + return; + } else if(scroll_distance == 0 && win->currently_paging) + { stop_paging(win); - - /* Refresh the widget so that any extra "more" prompts disappear */ - gtk_widget_queue_draw(win->widget); + return; + } + + check_paging(adj, win); } /* Handle key press events in the textview while paging is active */ gboolean pager_on_key_press_event(GtkTextView *textview, GdkEventKey *event, winid_t win) { - GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(win->frame) ); + GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(win->scrolledwindow) ); gdouble page_size, upper, lower, value; g_object_get(adj, "page-size", &page_size, @@ -90,13 +119,19 @@ pager_on_key_press_event(GtkTextView *textview, GdkEventKey *event, winid_t win) NULL); switch (event->keyval) { - case GDK_space: case GDK_KP_Space: - case GDK_Page_Down: case GDK_KP_Page_Down: - case GDK_Return: case GDK_KP_Enter: + case GDK_KEY_space: case GDK_KEY_KP_Space: + case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down: + case GDK_KEY_Return: case GDK_KEY_KP_Enter: gtk_adjustment_set_value(adj, CLAMP(value + page_size, lower, upper - page_size)); + check_paging(adj, win); return TRUE; - case GDK_Page_Up: case GDK_KP_Page_Up: + case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up: gtk_adjustment_set_value(adj, CLAMP(value - page_size, lower, upper - page_size)); + check_paging(adj, win); + return TRUE; + case GDK_KEY_End: case GDK_KEY_KP_End: + gtk_adjustment_set_value(adj, upper - page_size); + check_paging(adj, win); return TRUE; /* don't handle "up" and "down", they're used for input history */ } @@ -104,32 +139,10 @@ pager_on_key_press_event(GtkTextView *textview, GdkEventKey *event, winid_t win) return FALSE; /* if the key wasn't handled here, pass it to other handlers */ } -/* Draw the "more" prompt on top of the buffer, after the regular expose event has run */ -gboolean -pager_on_expose(GtkTextView *textview, GdkEventExpose *event, winid_t win) -{ - /* Calculate the position of the 'more' tag */ - gint promptwidth, promptheight; - pango_layout_get_pixel_size(win->pager_layout, &promptwidth, &promptheight); - - gint winx, winy, winwidth, winheight; - gdk_window_get_position(event->window, &winx, &winy); - gdk_drawable_get_size(GDK_DRAWABLE(event->window), &winwidth, &winheight); - - /* Draw the 'more' tag */ - GdkGC *context = gdk_gc_new(GDK_DRAWABLE(event->window)); - gdk_draw_layout(event->window, context, - winx + winwidth - promptwidth, - winy + winheight - promptheight, - win->pager_layout); - - return FALSE; /* Propagate event further */ -} - /* Check whether paging should be done. This function is called after the * textview has finished validating text positions. */ void -pager_after_size_request(GtkTextView *textview, GtkRequisition *requisition, winid_t win) +pager_after_size_allocate(GtkTextView *textview, GdkRectangle *allocation, winid_t win) { /* Move the pager to the last visible character in the buffer */ gint view_height, scroll_distance; @@ -151,7 +164,7 @@ pager_after_size_request(GtkTextView *textview, GtkRequisition *requisition, win /* Scroll past text already read by user. This is automatic scrolling, so disable the pager_ajustment_handler * first, that acts on the belief the scolling is performed by the user. */ - GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(win->frame)); + GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(win->scrolledwindow)); g_signal_handler_block(adj, win->pager_adjustment_handler); GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget)); GtkTextMark *pager_position = gtk_text_buffer_get_mark(buffer, "pager_position"); @@ -161,13 +174,7 @@ pager_after_size_request(GtkTextView *textview, GtkRequisition *requisition, win if(!win->currently_paging) { if(scroll_distance > view_height) { start_paging(win); - /* Seriously... */ - /* COMPAT: */ -#if GTK_CHECK_VERSION(2,14,0) gdk_window_invalidate_rect(gtk_widget_get_window(win->widget), NULL, TRUE); -#else - gdk_window_invalidate_rect(win->widget->window, NULL, TRUE); -#endif } } } diff --git a/libchimara/pager.h b/libchimara/pager.h index 5918873..4e7bbf0 100644 --- a/libchimara/pager.h +++ b/libchimara/pager.h @@ -5,10 +5,10 @@ #include "glk.h" #include "window.h" -G_GNUC_INTERNAL gboolean pager_on_expose(GtkTextView *textview, GdkEventExpose *event, winid_t win); +G_GNUC_INTERNAL void pager_on_clicked(GtkButton *pager, winid_t win); G_GNUC_INTERNAL gboolean pager_on_key_press_event(GtkTextView *textview, GdkEventKey *event, winid_t win); G_GNUC_INTERNAL void pager_after_adjustment_changed(GtkAdjustment *adj, winid_t win); -G_GNUC_INTERNAL void pager_after_size_request(GtkTextView *textview, GtkRequisition *requisition, winid_t win); +G_GNUC_INTERNAL void pager_after_size_allocate(GtkTextView *textview, GdkRectangle *allocation, winid_t win); G_GNUC_INTERNAL void pager_update(winid_t win); #endif diff --git a/libchimara/resource.c b/libchimara/resource.c index 9c40723..ba47dd4 100644 --- a/libchimara/resource.c +++ b/libchimara/resource.c @@ -1,4 +1,5 @@ #include "resource.h" +#include "stream.h" extern GPrivate *glk_data_key; @@ -38,7 +39,6 @@ giblorb_set_resource_map(strid_t file) glk_data->resource_map = newmap; glk_data->resource_file = file; - //giblorb_print_contents(newmap); return giblorb_err_None; } @@ -114,7 +114,7 @@ giblorb_print_contents(giblorb_map_t *map) } } -gchar* +const char * giblorb_get_error_message(giblorb_err_t err) { switch(err) diff --git a/libchimara/resource.h b/libchimara/resource.h index a732541..6f5b70d 100644 --- a/libchimara/resource.h +++ b/libchimara/resource.h @@ -8,6 +8,6 @@ #include "magic.h" void giblorb_print_contents(giblorb_map_t *map); -gchar* giblorb_get_error_message(giblorb_err_t err); +const char * giblorb_get_error_message(giblorb_err_t err); #endif diff --git a/libchimara/schannel.c b/libchimara/schannel.c index 617adc1..c92e7fc 100644 --- a/libchimara/schannel.c +++ b/libchimara/schannel.c @@ -25,6 +25,11 @@ clean_up_after_playing_sound(schanid_t chan) { if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL)) WARNING_S(_("Could not set GstElement state to"), "NULL"); + if(chan->source) + { + gst_bin_remove(GST_BIN(chan->pipeline), chan->source); + chan->source = NULL; + } if(chan->demux) { gst_bin_remove(GST_BIN(chan->pipeline), chan->demux); @@ -243,12 +248,11 @@ glk_schannel_create_ext(glui32 rock, glui32 volume) gst_object_unref(bus); /* Create GStreamer elements to put in the pipeline */ - s->source = gst_element_factory_make("giostreamsrc", NULL); s->typefind = gst_element_factory_make("typefind", NULL); s->convert = gst_element_factory_make("audioconvert", NULL); s->filter = gst_element_factory_make("volume", NULL); s->sink = gst_element_factory_make("autoaudiosink", NULL); - if(!s->source || !s->typefind || !s->convert || !s->filter || !s->sink) { + if(!s->typefind || !s->convert || !s->filter || !s->sink) { WARNING(_("Could not create one or more GStreamer elements")); goto fail; } @@ -258,9 +262,10 @@ glk_schannel_create_ext(glui32 rock, glui32 volume) /* Put the elements in the pipeline and link as many together as we can without knowing the type of the audio stream */ - gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL); - /* Link elements: Source -> typefinder -> ??? -> Converter -> Volume filter -> Sink */ - if(!gst_element_link(s->source, s->typefind) || !gst_element_link_many(s->convert, s->filter, s->sink, NULL)) { + gst_bin_add_many(GST_BIN(s->pipeline), s->typefind, s->convert, s->filter, s->sink, NULL); + + /* Link elements: ??? -> Converter -> Volume filter -> Sink */ + if(!gst_element_link_many(s->convert, s->filter, s->sink, NULL)) { WARNING(_("Could not link GStreamer elements")); goto fail; } @@ -483,15 +488,24 @@ glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify) stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL); } + chan->source = gst_element_factory_make("giostreamsrc", NULL); + g_object_set(chan->source, "stream", stream, NULL); + g_object_unref(stream); /* Now owned by GStreamer element */ + gst_bin_add(GST_BIN(chan->pipeline), chan->source); + if(!gst_element_link(chan->source, chan->typefind)) { + WARNING(_("Could not link GStreamer elements")); + clean_up_after_playing_sound(chan); + return 0; + } + chan->repeats = repeats; chan->resource = snd; chan->notify = notify; - g_object_set(chan->source, "stream", stream, NULL); - g_object_unref(stream); /* Now owned by GStreamer element */ /* Play the sound; unless the channel is paused, then pause it instead */ if(!gst_element_set_state(chan->pipeline, chan->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) { WARNING_S(_("Could not set GstElement state to"), chan->paused? "PAUSED" : "PLAYING"); + clean_up_after_playing_sound(chan); return 0; } return 1; @@ -591,11 +605,18 @@ glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL); } + chanarray[count]->source = gst_element_factory_make("giostreamsrc", NULL); + g_object_set(chanarray[count]->source, "stream", stream, NULL); + g_object_unref(stream); /* Now owned by GStreamer element */ + gst_bin_add(GST_BIN(chanarray[count]->pipeline), chanarray[count]->source); + if(!gst_element_link(chanarray[count]->source, chanarray[count]->typefind)) { + WARNING(_("Could not link GStreamer elements")); + clean_up_after_playing_sound(chanarray[count]); + } + chanarray[count]->repeats = 1; chanarray[count]->resource = sndarray[count]; chanarray[count]->notify = notify; - g_object_set(chanarray[count]->source, "stream", stream, NULL); - g_object_unref(stream); /* Now owned by GStreamer element */ } /* Start all the sounds as close to each other as possible. */ @@ -608,6 +629,7 @@ glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray if(!gst_element_set_state(chanarray[count]->pipeline, chanarray[count]->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) { WARNING_S(_("Could not set GstElement state to"), chanarray[count]->paused? "PAUSED" : "PLAYING"); skiparray[count] = TRUE; + clean_up_after_playing_sound(chanarray[count]); continue; } successes++; diff --git a/libchimara/style.c b/libchimara/style.c index dc0d4bb..406a884 100644 --- a/libchimara/style.c +++ b/libchimara/style.c @@ -53,7 +53,6 @@ static const gchar* TAG_NAMES[] = { "user1", "user2", "hyperlink", - "pager", "default" }; @@ -123,16 +122,6 @@ glk_set_style_stream(strid_t str, glui32 styl) { str->glk_style = (gchar*) get_glk_tag_name(styl); } -/* Internal function: call this to initialize the layout of the 'more' prompt. */ -void -style_init_more_prompt(winid_t win) -{ - ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); - - win->pager_layout = gtk_widget_create_pango_layout(win->widget, "More"); - pango_layout_set_attributes(win->pager_layout, glk_data->pager_attr_list); -} - /* Internal function: call this to initialize the default styles to a textbuffer. */ void style_init_textbuffer(GtkTextBuffer *buffer) @@ -186,36 +175,43 @@ GtkTextTag * gtk_text_tag_copy(GtkTextTag *tag) { GtkTextTag *copy; + char *tag_name; + GParamSpec **properties; + unsigned nprops, count; g_return_val_if_fail(tag != NULL, NULL); - copy = gtk_text_tag_new(tag->name); - gtk_text_attributes_copy_values(tag->values, copy->values); - - #define _COPY_FLAG(flag) copy->flag = tag->flag - _COPY_FLAG (bg_color_set); - _COPY_FLAG (bg_color_set); - _COPY_FLAG (bg_stipple_set); - _COPY_FLAG (fg_color_set); - _COPY_FLAG (fg_stipple_set); - _COPY_FLAG (justification_set); - _COPY_FLAG (left_margin_set); - _COPY_FLAG (indent_set); - _COPY_FLAG (rise_set); - _COPY_FLAG (strikethrough_set); - _COPY_FLAG (right_margin_set); - _COPY_FLAG (pixels_above_lines_set); - _COPY_FLAG (pixels_below_lines_set); - _COPY_FLAG (pixels_inside_wrap_set); - _COPY_FLAG (tabs_set); - _COPY_FLAG (underline_set); - _COPY_FLAG (wrap_mode_set); - _COPY_FLAG (bg_full_height_set); - _COPY_FLAG (invisible_set); - _COPY_FLAG (editable_set); - _COPY_FLAG (language_set); - _COPY_FLAG (scale_set); - #undef _COPY_FLAG + g_object_get(tag, "name", &tag_name, NULL); + copy = gtk_text_tag_new(tag_name); + g_free(tag_name); + + /* Copy all the original tag's properties to the new tag */ + properties = g_object_class_list_properties( G_OBJECT_GET_CLASS(tag), &nprops ); + for(count = 0; count < nprops; count++) { + + /* Only copy properties that are readable, writable, not construct-only, + and not deprecated */ + GParamFlags flags = properties[count]->flags; + if(flags & G_PARAM_CONSTRUCT_ONLY + || flags & G_PARAM_DEPRECATED + || !(flags & G_PARAM_READABLE) + || !(flags & G_PARAM_WRITABLE)) + continue; + + const char *prop_name = g_param_spec_get_name(properties[count]); + GValue prop_value = G_VALUE_INIT; + + g_value_init( &prop_value, G_PARAM_SPEC_VALUE_TYPE(properties[count]) ); + g_object_get_property( G_OBJECT(tag), prop_name, &prop_value ); + /* Don't copy the PangoTabArray if it is NULL, that prints a warning */ + if(strcmp(prop_name, "tabs") == 0 && g_value_get_boxed(&prop_value) == NULL) { + g_value_unset(&prop_value); + continue; + } + g_object_set_property( G_OBJECT(copy), prop_name, &prop_value ); + g_value_unset(&prop_value); + } + g_free(properties); /* Copy the data that was added manually */ gpointer reverse_color = g_object_get_data( G_OBJECT(tag), "reverse-color" ); @@ -336,15 +332,9 @@ style_init(ChimaraGlk *glk) g_object_set(tag, "foreground", "#0000ff", "foreground-set", TRUE, "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); g_hash_table_insert(default_text_buffer_styles, "hyperlink", tag); - GtkTextTag *pager_tag = gtk_text_tag_new("pager"); - g_object_set(pager_tag, "family", "Monospace", "family-set", TRUE, "foreground", "#ffffff", "foreground-set", TRUE, "background", "#000000", "background-set", TRUE, NULL); - g_hash_table_insert(default_text_buffer_styles, "pager", pager_tag); - text_tag_to_attr_list(pager_tag, priv->pager_attr_list); - priv->styles->text_grid = default_text_grid_styles; priv->styles->text_buffer = default_text_buffer_styles; - /* Initialize the GLK styles to empty tags */ int i; for(i=0; istyles->text_buffer, "pager") ); - text_tag_to_attr_list(pager_tag, priv->pager_attr_list); -} - /* Determine the current colors used to render the text for a given stream. * This can be set in a number of places */ static void @@ -1273,7 +1250,8 @@ style_stream_colors(strid_t str, GdkColor **foreground, GdkColor **background) } } -/* Apply styles to a segment of text in a GtkTextBuffer +/* Apply styles to a segment of text in a GtkTextBuffer, combining multiple + * GtkTextTags. */ void style_apply(winid_t win, GtkTextIter *start, GtkTextIter *end) diff --git a/libchimara/style.h b/libchimara/style.h index ba9939a..ee456d8 100644 --- a/libchimara/style.h +++ b/libchimara/style.h @@ -9,9 +9,7 @@ G_GNUC_INTERNAL void style_init_textbuffer(GtkTextBuffer *buffer); G_GNUC_INTERNAL void style_init_textgrid(GtkTextBuffer *buffer); -G_GNUC_INTERNAL void style_init_more_prompt(winid_t win); G_GNUC_INTERNAL void style_init(ChimaraGlk *glk); -G_GNUC_INTERNAL void style_update(ChimaraGlk *glk); G_GNUC_INTERNAL const gchar** style_get_tag_names(); G_GNUC_INTERNAL void reset_default_styles(ChimaraGlk *glk); G_GNUC_INTERNAL GScanner *create_css_file_scanner(void); @@ -29,7 +27,7 @@ typedef struct StyleSet { GHashTable *text_buffer; } StyleSet; -#define CHIMARA_NUM_STYLES 13 +#define CHIMARA_NUM_STYLES 12 //#define DEBUG_STYLES diff --git a/libchimara/window.c b/libchimara/window.c index ef85e3f..c913293 100644 --- a/libchimara/window.c +++ b/libchimara/window.c @@ -74,8 +74,8 @@ window_close_common(winid_t win, gboolean destroy_node) g_hash_table_destroy(win->hyperlinks); g_free(win->current_hyperlink); - if(win->pager_layout) - g_object_unref(win->pager_layout); + if(win->backing_store) + cairo_surface_destroy(win->backing_store); g_free(win); } @@ -533,12 +533,20 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, case wintype_TextBuffer: { + GtkWidget *overlay = gtk_overlay_new(); GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL); GtkWidget *textview = gtk_text_view_new(); + GtkWidget *pager = gtk_button_new_with_label("More"); + GtkWidget *image = gtk_image_new_from_stock(GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_BUTTON); GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC ); - + + gtk_button_set_image( GTK_BUTTON(pager), image ); + gtk_widget_set_halign(pager, GTK_ALIGN_END); + gtk_widget_set_valign(pager, GTK_ALIGN_END); + gtk_widget_set_no_show_all(pager, TRUE); + gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR ); gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE ); gtk_text_view_set_pixels_inside_wrap( GTK_TEXT_VIEW(textview), 3 ); @@ -546,16 +554,19 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, gtk_text_view_set_right_margin( GTK_TEXT_VIEW(textview), 20 ); gtk_container_add( GTK_CONTAINER(scrolledwindow), textview ); - gtk_widget_show_all(scrolledwindow); + gtk_container_add( GTK_CONTAINER(overlay), scrolledwindow ); + gtk_overlay_add_overlay( GTK_OVERLAY(overlay), pager ); + gtk_widget_show_all(overlay); win->widget = textview; - win->frame = scrolledwindow; - + win->scrolledwindow = scrolledwindow; + win->pager = pager; + win->frame = overlay; + /* Create the styles available to the window stream */ style_init_textbuffer(textbuffer); - style_init_more_prompt(win); gtk_widget_modify_font( textview, get_current_font(wintype) ); - + /* Determine the size of a "0" character in pixels */ PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); pango_layout_set_font_description( zero, get_current_font(wintype) ); @@ -565,13 +576,12 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, /* Connect signal handlers */ /* Pager */ - g_signal_connect_after( textview, "size-request", G_CALLBACK(pager_after_size_request), win ); - win->pager_expose_handler = g_signal_connect_after( textview, "expose-event", G_CALLBACK(pager_on_expose), win ); - g_signal_handler_block(textview, win->pager_expose_handler); + g_signal_connect_after( textview, "size-allocate", G_CALLBACK(pager_after_size_allocate), win ); win->pager_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(pager_on_key_press_event), win ); g_signal_handler_block(textview, win->pager_keypress_handler); GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow)); win->pager_adjustment_handler = g_signal_connect_after(adj, "value-changed", G_CALLBACK(pager_after_adjustment_changed), win); + g_signal_connect(pager, "clicked", G_CALLBACK(pager_on_clicked), win); /* Char and line input */ win->char_input_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_char_input_key_press_event), win ); @@ -603,7 +613,7 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, case wintype_Graphics: { - GtkWidget *image = gtk_image_new_from_pixmap(NULL, NULL); + GtkWidget *image = gtk_drawing_area_new(); gtk_widget_show(image); win->unit_width = 1; @@ -611,13 +621,15 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, win->widget = image; win->frame = image; win->background_color = 0x00FFFFFF; - + win->backing_store = NULL; + /* Connect signal handlers */ win->button_press_event_handler = g_signal_connect(image, "button-press-event", G_CALLBACK(on_window_button_press), win); g_signal_handler_block(image, win->button_press_event_handler); win->shutdown_keypress_handler = g_signal_connect(image, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win); g_signal_handler_block(image, win->shutdown_keypress_handler); - win->size_allocate_handler = g_signal_connect(image, "size-allocate", G_CALLBACK(on_graphics_size_allocate), win); + g_signal_connect(image, "configure-event", G_CALLBACK(on_graphics_configure), win); + g_signal_connect(image, "draw", G_CALLBACK(on_graphics_draw), win); } break; @@ -967,24 +979,7 @@ glk_window_clear(winid_t win) GtkTextIter start, end; gtk_text_buffer_get_start_iter(textbuffer, &start); gtk_text_buffer_get_end_iter(textbuffer, &end); - - /* Determine default style */ - GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(textbuffer); - GtkTextTag *default_tag = gtk_text_tag_table_lookup(tags, "default"); - GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, "normal"); - GtkTextTag *glk_style_tag = gtk_text_tag_table_lookup(tags, "normal"); - - // Default style - gtk_text_buffer_apply_tag(textbuffer, default_tag, &start, &end); - - // Player's style overrides - gtk_text_buffer_apply_tag(textbuffer, style_tag, &start, &end); - - // GLK Program's style overrides - gtk_text_buffer_apply_tag(textbuffer, glk_style_tag, &start, &end); - - if(win->zcolor != NULL) - gtk_text_buffer_apply_tag(textbuffer, win->zcolor, &start, &end); + style_apply(win, &start, &end); gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &start); @@ -1008,13 +1003,19 @@ glk_window_clear(winid_t win) case wintype_Graphics: { + GtkAllocation allocation; + /* Wait for the window's size to be updated */ g_mutex_lock(glk_data->arrange_lock); if(glk_data->needs_rearrange) g_cond_wait(glk_data->rearranged, glk_data->arrange_lock); g_mutex_unlock(glk_data->arrange_lock); - glk_window_erase_rect(win, 0, 0, win->widget->allocation.width, win->widget->allocation.height); + gdk_threads_enter(); + gtk_widget_get_allocation(win->widget, &allocation); + gdk_threads_leave(); + + glk_window_erase_rect(win, 0, 0, allocation.width, allocation.height); } break; @@ -1134,6 +1135,7 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) { VALID_WINDOW(win, return); + GtkAllocation allocation; ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); switch(win->type) @@ -1154,9 +1156,10 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) g_mutex_unlock(glk_data->arrange_lock); gdk_threads_enter(); + gtk_widget_get_allocation(win->widget, &allocation); /* Cache the width and height */ - win->width = (glui32)(win->widget->allocation.width / win->unit_width); - win->height = (glui32)(win->widget->allocation.height / win->unit_height); + win->width = (glui32)(allocation.width / win->unit_width); + win->height = (glui32)(allocation.height / win->unit_height); gdk_threads_leave(); if(widthptr != NULL) @@ -1173,10 +1176,11 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) g_mutex_unlock(glk_data->arrange_lock); gdk_threads_enter(); + gtk_widget_get_allocation(win->widget, &allocation); if(widthptr != NULL) - *widthptr = (glui32)(win->widget->allocation.width / win->unit_width); + *widthptr = (glui32)(allocation.width / win->unit_width); if(heightptr != NULL) - *heightptr = (glui32)(win->widget->allocation.height / win->unit_height); + *heightptr = (glui32)(allocation.height / win->unit_height); gdk_threads_leave(); break; @@ -1188,10 +1192,11 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) g_mutex_unlock(glk_data->arrange_lock); gdk_threads_enter(); + gtk_widget_get_allocation(win->widget, &allocation); if(widthptr != NULL) - *widthptr = (glui32)(win->widget->allocation.width); + *widthptr = (glui32)(allocation.width); if(heightptr != NULL) - *heightptr = (glui32)(win->widget->allocation.height); + *heightptr = (glui32)(allocation.height); gdk_threads_leave(); break; diff --git a/libchimara/window.h b/libchimara/window.h index c67654a..aa32502 100644 --- a/libchimara/window.h +++ b/libchimara/window.h @@ -42,6 +42,10 @@ struct glk_window_struct /* "frame" is the widget that is the child of the ChimaraGlk container, such as a scroll window. It may be the same as "widget". */ GtkWidget *frame; + /* In text buffer windows, the scrolled window and the pager are extra + widgets that are neither "widget" nor "frame" */ + GtkWidget *scrolledwindow; + GtkWidget *pager; /* Width and height of the window's size units, in pixels */ int unit_width; int unit_height; @@ -82,7 +86,6 @@ struct glk_window_struct gulong shutdown_keypress_handler; gulong button_press_event_handler; gulong size_allocate_handler; - gulong pager_expose_handler; gulong pager_keypress_handler; gulong pager_adjustment_handler; /* Window buffer */ @@ -95,9 +98,9 @@ struct glk_window_struct gboolean hyperlink_event_requested; /* Graphics */ glui32 background_color; + cairo_surface_t *backing_store; /* Pager (textbuffer only) */ gboolean currently_paging; - PangoLayout *pager_layout; }; #endif diff --git a/player/Makefile.am b/player/Makefile.am index e7aee92..6ef3b76 100644 --- a/player/Makefile.am +++ b/player/Makefile.am @@ -3,16 +3,6 @@ AM_CPPFLAGS = -I$(top_srcdir) PLUGIN_LIBTOOL_FLAGS=-module -avoid-version -export-symbols-regex "^glk_main$$" -if TARGET_ILIAD - -chimara_iliad_SOURCES = iliad.c xepdmgrclient.c xepdmgrclient.h -chimara_iliad_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) -chimara_iliad_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la - -bin_PROGRAMS = chimara_iliad - -else - dist_pkgdata_DATA = chimara.ui chimara.menus style.css bin_PROGRAMS = chimara @@ -27,8 +17,6 @@ chimara_LDADD = @PLAYER_LIBS@ $(top_builddir)/libchimara/libchimara.la gsettings_SCHEMAS = org.chimara-if.gschema.xml @GSETTINGS_RULES@ -endif - CLEANFILES = config.pyc DISTCLEANFILES = config.py diff --git a/player/callbacks.c b/player/callbacks.c index a4cc173..94b9e5b 100644 --- a/player/callbacks.c +++ b/player/callbacks.c @@ -79,7 +79,18 @@ search_for_graphics_file(const char *filename, ChimaraIF *glk) /* First get the name of the story file */ char *scratch = g_path_get_basename(filename); - *(strrchr(scratch, '.')) = '\0'; + char *ext = strrchr(scratch, '.'); + if(strcmp(ext, ".zlb") == 0 || + strcmp(ext, ".zblorb") == 0 || + strcmp(ext, ".glb") == 0 || + strcmp(ext, ".gblorb") == 0 || + strcmp(ext, ".blorb") == 0 || + strcmp(ext, ".blb") == 0) + { + g_object_set(glk, "graphics-file", NULL, NULL); + return; + } + *ext = '\0'; /* Check in the stored resource path, if set */ char *resource_path; diff --git a/player/config.py.in b/player/config.py.in index 1d4261d..260ac8d 100644 --- a/player/config.py.in +++ b/player/config.py.in @@ -1 +1,8 @@ PACKAGE_VERSION = '''@PACKAGE_VERSION@''' +GETTEXT_PACKAGE = '''@GETTEXT_PACKAGE@''' +datarootdir = '''@datarootdir@'''.replace('${prefix}', '''@prefix@''') +PACKAGE_DATA_DIR = datarootdir + '''/@PACKAGE@''' +PACKAGE_SRC_DIR = '''@srcdir@''' +PACKAGE_LOCALE_DIR = datarootdir + '/locale' +ENABLE_NLS = ('''@USE_NLS@''' == 'yes') +DEBUG = ('-DDEBUG' in '''@CPPFLAGS@''') diff --git a/player/iliad.c b/player/iliad.c deleted file mode 100644 index abb2ad1..0000000 --- a/player/iliad.c +++ /dev/null @@ -1,227 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ -/* - * iliad.c - * Copyright (C) Philip en Marijn 2008 <> - * - * iliad.c is free software copyrighted by Philip en Marijn. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name ``Philip en Marijn'' nor the name of any other - * contributor may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * main.c IS PROVIDED BY Philip en Marijn ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL Philip en Marijn OR ANY OTHER CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "error.h" -#include -#include - -/* Iliad includes */ -#include -#include -#include -#include -/*#include "xepdmgrclient.h"*/ - - -/* Global pointers to widgets */ -GtkWidget *window = NULL; -GtkWidget *glk = NULL; - -/* Display manager */ -/* sEpd *epd = NULL;*/ - -static erClientChannel_t erbusyChannel; -static erClientChannel_t ertoolbarChannel; - -static void -on_started(ChimaraGlk *glk) -{ - g_printerr("Started!\n"); -} - -static void -on_stopped(ChimaraGlk *glk) -{ - g_printerr("Stopped!\n"); -} - -static void -on_restore() -{ - chimara_glk_feed_line_input( CHIMARA_GLK(glk), "restore" ); -} - -static void -on_save() -{ - chimara_glk_feed_line_input( CHIMARA_GLK(glk), "save" ); -} - -gboolean -update_screen(gpointer data) -{ - printf("Update screen from idle handler\n"); - dmDisplay(dmCmdPriorNormal, dmQFull); - - return FALSE; -} - -static void -on_iliad_screen_update(ChimaraGlk *glk, gboolean typing) -{ - printf("Update screen\n"); - g_idle_add_full(G_PRIORITY_DEFAULT_IDLE+100, update_screen, NULL, NULL); -} - - -static void -create_window(void) -{ - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - glk = chimara_if_new(); - //chimara_if_set_preferred_interpreter( CHIMARA_IF(glk), CHIMARA_IF_FORMAT_Z8, CHIMARA_IF_INTERPRETER_NITFOL); - - gtk_widget_set_size_request(window, 800, 800); - g_object_set(glk, - "border-width", 6, - "spacing", 6, - "ignore-errors", TRUE, - "style-sheet", "style.css", - NULL); - - g_signal_connect(glk, "started", G_CALLBACK(on_started), NULL); - g_signal_connect(glk, "stopped", G_CALLBACK(on_stopped), NULL); - g_signal_connect(glk, "iliad-screen-update", G_CALLBACK(on_iliad_screen_update), NULL); - - GtkWidget *vbox = gtk_vbox_new(FALSE, 0); - GtkWidget *toolbar = gtk_toolbar_new(); - - GtkToolItem *restore_button = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN); - g_signal_connect(restore_button, "clicked", G_CALLBACK(on_restore), NULL); - gtk_toolbar_insert( GTK_TOOLBAR(toolbar), restore_button, 0 ); - - GtkToolItem *save_button = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE); - g_signal_connect(save_button, "clicked", G_CALLBACK(on_save), NULL); - gtk_toolbar_insert( GTK_TOOLBAR(toolbar), save_button, 0 ); - - GtkToolItem *quit_button = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT); - g_signal_connect(quit_button, "clicked", G_CALLBACK(gtk_main_quit), NULL); - gtk_toolbar_insert( GTK_TOOLBAR(toolbar), quit_button, 0 ); - - GtkWidget *spacer = gtk_vbox_new(FALSE, 0); - gtk_widget_set_size_request(spacer, -1, 250); - - gtk_box_pack_start( GTK_BOX(vbox), toolbar, FALSE, FALSE, 0 ); - gtk_box_pack_start( GTK_BOX(vbox), glk, TRUE, TRUE, 0 ); - gtk_box_pack_end( GTK_BOX(vbox), spacer, FALSE, FALSE, 0 ); - - gtk_container_add( GTK_CONTAINER(window), vbox ); -} - -static void -iliad_init_toolbar() -{ - erIpcStartClient(ER_TOOLBAR_CHANNEL, &ertoolbarChannel); - tbSelectIconSet(ertoolbarChannel, ER_PDF_VIEWER_UA_ID); - tbClearIconSet(ertoolbarChannel, ER_PDF_VIEWER_UA_ID); - - // Turn off trashcan - tbAppendPlatformIcon( ertoolbarChannel, ER_PDF_VIEWER_UA_ID, iconID_trashcan, -1); - tbSetStatePlatformIcon(ertoolbarChannel, ER_PDF_VIEWER_UA_ID, iconID_trashcan, iconState_grey ); - - // Enable then pop up keyboard - tbAppendPlatformIcon( ertoolbarChannel, ER_PDF_VIEWER_UA_ID, iconID_keyboard, -1); - tbSetStatePlatformIcon(ertoolbarChannel, ER_PDF_VIEWER_UA_ID, iconID_keyboard, iconState_selected); -} - -static void -iliad_clear_toolbar() -{ - // Turn on trashcan - tbSetStatePlatformIcon(ertoolbarChannel, ER_PDF_VIEWER_UA_ID, iconID_trashcan, iconState_normal ); - - // Disable the keyboard - tbSetStatePlatformIcon(ertoolbarChannel, ER_PDF_VIEWER_UA_ID, iconID_keyboard, iconState_normal); -} - -int -main(int argc, char *argv[]) -{ - GError *error = NULL; - -#ifdef ENABLE_NLS - bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); - bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); - textdomain(GETTEXT_PACKAGE); -#endif - - /* Setup connection to display manager deamon */ - /* - if( (epd = EpdInit(NULL)) == NULL ) { - g_critical("Could not connect to xepdmgr server\n"); - return 1; - } - EpdRefreshAuto(epd, 0); - */ - - if( !g_thread_supported() ) - g_thread_init(NULL); - gdk_threads_init(); - gtk_init(&argc, &argv); - - create_window(); - gtk_widget_show_all(window); - - if(argc < 2) { - g_printerr("Must provide a game file\n"); - return 1; - } - - if( !chimara_if_run_game(CHIMARA_IF(glk), argv[1], &error) ) { - g_printerr("Error starting Glk library: %s\n", error->message); - return 1; - } - //chimara_glk_run( CHIMARA_GLK(glk), ".libs/multiwin.so", argc, argv, NULL); - - iliad_init_toolbar(); - - gdk_threads_enter(); - gtk_main(); - gdk_threads_leave(); - - chimara_glk_stop(CHIMARA_GLK(glk)); - chimara_glk_wait(CHIMARA_GLK(glk)); - - iliad_clear_toolbar(); - - return 0; -} diff --git a/player/main.c b/player/main.c index d76ef56..b919203 100644 --- a/player/main.c +++ b/player/main.c @@ -165,7 +165,7 @@ create_window(void) /* DON'T UNCOMMENT THIS your eyes will burn but it is a good test of programmatically altering just one style chimara_glk_set_css_from_string(CHIMARA_GLK(glk), - "buffer.normal { font-family: 'Comic Sans MS'; }");*/ + "buffer { font-family: 'Comic Sans MS'; }");*/ GtkBox *vbox = GTK_BOX( gtk_builder_get_object(builder, "vbox") ); if(vbox == NULL) diff --git a/player/player.py b/player/player.py index 82a6195..c153c68 100644 --- a/player/player.py +++ b/player/player.py @@ -2,26 +2,52 @@ import sys import os.path +import argparse from gi.repository import GObject, GLib, Gdk, Gio, Gtk, Chimara import config -# FIXME: Dummy translation function, for now -_ = lambda x: x +if config.ENABLE_NLS: + import gettext + gettext.install(config.GETTEXT_PACKAGE, config.PACKAGE_LOCALE_DIR, + unicode=True, codeset='UTF-8') +else: + _ = lambda x: x class Player(GObject.GObject): __gtype_name__ = 'ChimaraPlayer' - def __init__(self): + def __init__(self, graphics_file=None): super(Player, self).__init__() - # FIXME: should use the Keyfile backend, but that's not available from - # Python - self.prefs_settings = Gio.Settings('org.chimara-if.player.preferences') - self.state_settings = Gio.Settings('org.chimara-if.player.state') + # Initialize settings file; it can be overridden by a "chimara-config" + # file in the current directory + if os.path.exists('chimara-config'): + keyfile = 'chimara-config' + else: + keyfile = os.path.expanduser('~/.chimara/config') + try: + # This only works on my custom-built gobject-introspection; opened + # bug #682702 + backend = Gio.keyfile_settings_backend_new(keyfile, + "/org/chimara-if/player/", None) + except AttributeError: + backend = None + self.prefs_settings = Gio.Settings('org.chimara-if.player.preferences', + backend=backend) + self.state_settings = Gio.Settings('org.chimara-if.player.state', + backend=backend) builder = Gtk.Builder() - builder.add_from_file('chimara.ui') + try: + builder.add_from_file(os.path.join(config.PACKAGE_DATA_DIR, + 'chimara.ui')) + except GLib.GError: + if config.DEBUG: + builder.add_from_file(os.path.join(config.PACKAGE_SRC_DIR, + 'chimara.ui')) + else: + raise self.window = builder.get_object('chimara') self.aboutwindow = builder.get_object('aboutwindow') self.prefswindow = builder.get_object('prefswindow') @@ -37,6 +63,8 @@ class Player(GObject.GObject): 'active', Gio.SettingsBindFlags.SET) filt = Gtk.RecentFilter() + # TODO: Use mimetypes and construct the filter dynamically depending on + # what plugins are installed for pattern in ['*.z[1-8]', '*.[zg]lb', '*.[zg]blorb', '*.ulx', '*.blb', '*.blorb']: filt.add_pattern(pattern) @@ -44,8 +72,16 @@ class Player(GObject.GObject): recent.add_filter(filt) uimanager = Gtk.UIManager() - uimanager.add_ui_from_file('chimara.menus') - uimanager.insert_action_group(actiongroup, 0) + try: + uimanager.add_ui_from_file(os.path.join(config.PACKAGE_DATA_DIR, + 'chimara.menus')) + except GLib.GError: + if config.DEBUG: + uimanager.add_ui_from_file(os.path.join(config.PACKAGE_SRC_DIR, + 'chimara.menus')) + else: + raise + uimanager.insert_action_group(actiongroup) menubar = uimanager.get_widget('/menubar') toolbar = uimanager.get_widget('/toolbar') toolbar.no_show_all = True @@ -58,40 +94,24 @@ class Player(GObject.GObject): accels = uimanager.get_accel_group() self.window.add_accel_group(accels) - self.glk = Chimara.IF() - self.glk.props.ignore_errors = True - self.glk.set_css_from_file('style.css') + self.glk = Chimara.IF(ignore_errors=True, + # interpreter_number=Chimara.IFZmachineVersion.TANDY_COLOR, + graphics_file=graphics_file) + css_file = _maybe(self.prefs_settings.get_value('css-file')) + if css_file is None: + css_file = 'style.css' + self.glk.set_css_from_file(css_file) + + # DON'T UNCOMMENT THIS your eyes will burn + # but it is a good test of programmatically altering just one style + # self.glk.set_css_from_string("buffer{font-family: 'Comic Sans MS';}") vbox = builder.get_object('vbox') vbox.pack_end(self.glk, True, True, 0) vbox.pack_start(menubar, False, False, 0) vbox.pack_start(toolbar, False, False, 0) - #builder.connect_signals(self) # FIXME Segfaults?! - builder.get_object('open').connect('activate', self.on_open_activate) - builder.get_object('restore').connect('activate', - self.on_restore_activate) - builder.get_object('save').connect('activate', self.on_save_activate) - builder.get_object('stop').connect('activate', self.on_stop_activate) - builder.get_object('recent').connect('item-activated', - self.on_recent_item_activated) - builder.get_object('undo').connect('activate', self.on_undo_activate) - builder.get_object('quit').connect('activate', self.on_quit_activate) - builder.get_object('copy').connect('activate', self.on_copy_activate) - builder.get_object('paste').connect('activate', self.on_paste_activate) - builder.get_object('preferences').connect('activate', - self.on_preferences_activate) - builder.get_object('about').connect('activate', self.on_about_activate) - toolbar_action.connect('toggled', self.on_toolbar_toggled) - self.aboutwindow.connect('response', lambda x, *args: x.hide()) - self.aboutwindow.connect('delete-event', - lambda x, *args: x.hide_on_delete()) - self.window.connect('delete-event', self.on_window_delete_event) - self.prefswindow.connect('response', lambda x, *args: x.hide()) - self.prefswindow.connect('delete-event', - lambda x, *args: x.hide_on_delete()) - # FIXME Delete to here when above bug is fixed - + builder.connect_signals(self) self.glk.connect('notify::program-name', self.change_window_title) self.glk.connect('notify::story-name', self.change_window_title) @@ -169,13 +189,13 @@ class Player(GObject.GObject): manager = Gtk.RecentManager.get_default() manager.add_item(uri) - def on_stop_activate(self, action, data=None): + def on_stop_activate(self, *args): self.glk.stop() - def on_quit_chimara_activate(self, action, data=None): + def on_quit_chimara_activate(self, *args): Gtk.main_quit() - def on_copy_activate(self, action, data=None): + def on_copy_activate(self, *args): focus = self.window.get_focus() # Call "copy clipboard" on any widget that defines it if (isinstance(focus, Gtk.Label) @@ -183,41 +203,41 @@ class Player(GObject.GObject): or isinstance(focus, Gtk.TextView)): focus.emit('copy-clipboard') - def on_paste_activate(self, action, data=None): + def on_paste_activate(self, *args): focus = self.window.get_focus() # Call "paste clipboard" on any widget that defines it if isinstance(focus, Gtk.Entry) or isinstance(focus, Gtk.TextView): focus.emit('paste-clipboard') - def on_preferences_activate(self, action, data=None): + def on_preferences_activate(self, *args): self.prefswindow.present() - def on_toolbar_toggled(self, action, data=None): + def on_toolbar_toggled(self, action, *args): if action.get_active(): self.toolbar.show() else: self.toolbar.hide() - def on_undo_activate(self, action, data=None): + def on_undo_activate(self, *args): self.glk.feed_line_input('undo') - def on_save_activate(self, action, data=None): + def on_save_activate(self, *args): self.glk.feed_line_input('save') - def on_restore_activate(self, action, data=None): + def on_restore_activate(self, *args): self.glk.feed_line_input('restore') - def on_restart_activate(self, action, data=None): + def on_restart_activate(self, *args): self.glk.feed_line_input('restart') - def on_quit_activate(self, action, data=None): + def on_quit_activate(self, *args): self.glk.feed_line_input('quit') - def on_about_activate(self, action, data=None): + def on_about_activate(self, *args): self.aboutwindow.set_version(config.PACKAGE_VERSION) self.aboutwindow.present() - def on_window_delete_event(self, widget, event, data=None): + def on_window_delete_event(self, *args): Gtk.main_quit() return True @@ -267,6 +287,30 @@ class Player(GObject.GObject): if os.path.exists(blorbfile): self.glk.graphics_file = blorbfile + # Various signal handlers for GtkBuilder file + def gtk_widget_hide(self, widget, *args): + return Gtk.Widget.hide(widget) + + def gtk_widget_hide_on_delete(self, widget, *args): + return Gtk.Widget.hide_on_delete(widget) + + def dummy_handler(self, *args): + pass + + on_resource_file_set = dummy_handler + on_interpreter_cell_changed = dummy_handler + on_toggle_underline = dummy_handler + on_toggle_italic = dummy_handler + on_toggle_bold = dummy_handler + on_toggle_justify = dummy_handler + on_toggle_right = dummy_handler + on_toggle_center = dummy_handler + on_toggle_left = dummy_handler + on_background_color_set = dummy_handler + on_foreground_color_set = dummy_handler + on_font_set = dummy_handler + on_css_filechooser_file_set = dummy_handler + def _maybe(variant): """Gets a maybe value from a GVariant - not handled in PyGI""" @@ -283,16 +327,28 @@ def error_dialog(parent, message): dialog.destroy() if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('game_file', nargs='?', default=None, + metavar='GAME FILE', help='the game file to load and start') + parser.add_argument('graphics_file', nargs='?', default=None, + metavar='GRAPHICS FILE', help='a Blorb resource file to include') + args = parser.parse_args() + Gdk.threads_init() - player = Player() + # Create configuration dir ~/.chimara + try: + os.mkdir(os.path.expanduser('~/.chimara')) + except OSError: + # already exists + assert os.path.isdir(os.path.expanduser('~/.chimara')) + + player = Player(graphics_file=args.graphics_file) player.window.show_all() - if len(sys.argv) == 3: - player.glk.props.graphics_file = sys.argv[2] - if len(sys.argv) >= 2: + if args.game_file is not None: try: - player.glk.run_game(sys.argv[1]) + player.glk.run_game(args.game_file) except GLib.Error as e: error_dialog(player.window, _("Error starting Glk library: {errmsg}").format( diff --git a/player/preferences.c b/player/preferences.c index 9154f4e..a9f5f43 100644 --- a/player/preferences.c +++ b/player/preferences.c @@ -104,11 +104,13 @@ parse_interpreter(const char *interp) return CHIMARA_IF_INTERPRETER_GLULXE; if(strcmp(interp, "git") == 0) return CHIMARA_IF_INTERPRETER_GIT; + if(strcmp(interp, "bocfel") == 0) + return CHIMARA_IF_INTERPRETER_BOCFEL; return CHIMARA_IF_INTERPRETER_NONE; } static const char *interpreter_strings[CHIMARA_IF_NUM_INTERPRETERS] = { - "frotz", "nitfol", "glulxe", "git" + "frotz", "nitfol", "glulxe", "git", "bocfel" }; static const char * @@ -123,7 +125,8 @@ static const char *interpreter_display_strings[CHIMARA_IF_NUM_INTERPRETERS] = { N_("Frotz"), N_("Nitfol"), N_("Glulxe"), - N_("Git") + N_("Git"), + N_("Bocfel") }; static const char * @@ -252,7 +255,6 @@ on_toggle_left(GtkToggleButton *button, ChimaraGlk *glk) { if( !gtk_toggle_button_get_active(button) ) return; g_object_set(current_tag, "justification", GTK_JUSTIFY_LEFT, "justification-set", TRUE, NULL); - chimara_glk_update_style(glk); } void @@ -260,7 +262,6 @@ on_toggle_center(GtkToggleButton *button, ChimaraGlk *glk) { if( !gtk_toggle_button_get_active(button) ) return; g_object_set(current_tag, "justification", GTK_JUSTIFY_CENTER, "justification-set", TRUE, NULL); - chimara_glk_update_style(glk); } void @@ -268,7 +269,6 @@ on_toggle_right(GtkToggleButton *button, ChimaraGlk *glk) { if( !gtk_toggle_button_get_active(button) ) return; g_object_set(current_tag, "justification", GTK_JUSTIFY_RIGHT, "justification-set", TRUE, NULL); - chimara_glk_update_style(glk); } void @@ -276,7 +276,6 @@ on_toggle_justify(GtkToggleButton *button, ChimaraGlk *glk) { if( !gtk_toggle_button_get_active(button) ) return; g_object_set(current_tag, "justification", GTK_JUSTIFY_FILL, "justification-set", TRUE, NULL); - chimara_glk_update_style(glk); } void @@ -285,8 +284,6 @@ on_toggle_bold(GtkToggleButton *button, ChimaraGlk *glk) { g_object_set(current_tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL); else g_object_set(current_tag, "weight", PANGO_WEIGHT_NORMAL, "weight-set", TRUE, NULL); - - chimara_glk_update_style(glk); } void @@ -295,8 +292,6 @@ on_toggle_italic(GtkToggleButton *button, ChimaraGlk *glk) { g_object_set(current_tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL); else g_object_set(current_tag, "style", PANGO_STYLE_NORMAL, "style-set", TRUE, NULL); - - chimara_glk_update_style(glk); } void @@ -305,8 +300,6 @@ on_toggle_underline(GtkToggleButton *button, ChimaraGlk *glk) { g_object_set(current_tag, "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); else g_object_set(current_tag, "underline", PANGO_UNDERLINE_NONE, "underline-set", TRUE, NULL); - - chimara_glk_update_style(glk); } void @@ -315,7 +308,6 @@ on_foreground_color_set(GtkColorButton *button, ChimaraGlk *glk) GdkColor color; gtk_color_button_get_color(button, &color); g_object_set(current_tag, "foreground-gdk", &color, "foreground-set", TRUE, NULL); - chimara_glk_update_style(glk); } void @@ -324,7 +316,6 @@ on_background_color_set(GtkColorButton *button, ChimaraGlk *glk) GdkColor color; gtk_color_button_get_color(button, &color); g_object_set(current_tag, "background-gdk", &color, "background-set", TRUE, NULL); - chimara_glk_update_style(glk); } void @@ -333,7 +324,6 @@ on_font_set(GtkFontButton *button, ChimaraGlk *glk) const gchar *font_name = gtk_font_button_get_font_name(button); PangoFontDescription *font_description = pango_font_description_from_string(font_name); g_object_set(current_tag, "font-desc", font_description, NULL); - chimara_glk_update_style(glk); } void diff --git a/player/style.css b/player/style.css index 87f5026..37cc434 100644 --- a/player/style.css +++ b/player/style.css @@ -78,8 +78,3 @@ buffer.user1 { buffer.user2 { } - -buffer.pager { - color: #ffffff; - background-color: #303030; -} diff --git a/player/xepdmgrclient.c b/player/xepdmgrclient.c deleted file mode 100644 index e69de29..0000000 diff --git a/player/xepdmgrclient.h b/player/xepdmgrclient.h deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Makefile.am b/tests/Makefile.am index 1fed105..aacfc6e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -10,7 +10,7 @@ TEST_PLUGIN_LIBTOOL_FLAGS = \ -export-symbols-regex "^glk_main$$" \ -rpath $(abs_builddir) -noinst_PROGRAMS = test-multisession glulxercise plugin-loader test-close babeltest +noinst_PROGRAMS = test-multisession glulxercise plugin-loader test-close csstest test_multisession_SOURCES = test-multisession.c test_multisession_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) @@ -29,9 +29,9 @@ test_close_SOURCES = test-close.c test_close_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) test_close_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la -babeltest_SOURCES = babeltest.c -babeltest_CFLAGS = @PLAYER_CFLAGS@ $(AM_CFLAGS) -babeltest_LDADD = @PLAYER_LIBS@ $(top_builddir)/babel/libbabel_functions.la $(top_builddir)/babel/libbabel.la $(top_builddir)/babel/libifiction.la +csstest_SOURCES = csstest.c +csstest_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) +csstest_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la noinst_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la \ styletest.la soundtest.la test-userstyle.la fileio.la diff --git a/tests/babeltest.c b/tests/babeltest.c deleted file mode 100644 index 2e8827e..0000000 --- a/tests/babeltest.c +++ /dev/null @@ -1,231 +0,0 @@ -#include "babel/babel_handler.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -typedef struct _metadata { - const gchar *element_name; - gchar *ifid; - gchar *title; - gchar *author; - gchar *firstpublished; - gboolean error; - gchar *error_message; - gchar *error_code; -} metadata; - -void start_element( - GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer data, - GError **error) -{ - metadata *md = (metadata*) data; - md->element_name = element_name; - - if( !strcmp(element_name, "errorCode") ) { - md->error = 1; - md->error_message = ""; - md->error_code = ""; - } - - if( !strcmp(element_name, "ifindex") ) { - md->ifid = ""; - md->title = ""; - md->author = ""; - md->firstpublished = ""; - } -} - -void text( - GMarkupParseContext *context, - const gchar *text, - gsize text_len, - gpointer data, - GError **error) -{ - metadata *md = (metadata*) data; - - if( !strcmp(md->element_name, "errorCode") ) { - md->error_code = g_strndup(text, text_len); - } - else if( !strcmp(md->element_name, "errorMessage") ) { - md->error_message = g_strndup(text, text_len); - } - else if( !strcmp(md->element_name, "ifid") ) { - if( strlen(md->ifid) < text_len ) - md->ifid = g_strndup(text, text_len); - } - else if( !strcmp(md->element_name, "title") ) { - if( strlen(md->title) < text_len ) - md->title = g_strndup(text, text_len); - } - else if( !strcmp(md->element_name, "author") ) { - if( strlen(md->author) < text_len ) - md->author = g_strndup(text, text_len); - } - else if( !strcmp(md->element_name, "firstpublished") ) { - if( strlen(md->firstpublished) < text_len ) - md->firstpublished = g_strndup(text, text_len); - } -} - -void end_element( - GMarkupParseContext *context, - const gchar *element_name, - gpointer data, - GError **error) -{ - if( !strcmp(element_name, "ifindex") ) { - metadata *md = (metadata*) data; - printf("IFID: %s\nTitle: %s\nAuthor: %s\nFirst published: %s\n", md->ifid, md->title, md->author, md->firstpublished); - } -} - -/* - * run a non SELECT command and stops if an error occurs - */ -void -run_sql_non_select(GdaConnection *cnc, const gchar *sql) -{ - GdaStatement *stmt; - GError *error = NULL; - gint nrows; - const gchar *remain; - GdaSqlParser *parser; - - parser = g_object_get_data(G_OBJECT(cnc), "parser"); - stmt = gda_sql_parser_parse_string(parser, sql, &remain, &error); - if(remain) - g_print ("REMAINS: %s\n", remain); - - nrows = gda_connection_statement_execute_non_select(cnc, stmt, NULL, NULL, &error); - if(nrows == -1) - g_error("NON SELECT error: %s\n", error && error->message ? error->message : "no detail"); - g_object_unref(stmt); -} - -int main(int argc, char **argv) { - GError *err = NULL; - metadata data; - data.error = 0; - - if(argc < 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } - - g_type_init(); - - babel_init(argv[1]); - int len = babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL, NULL, 0); - gchar *ifiction; - if(len) { - printf("Metadata found in file.\n"); - gchar *buffer = malloc(len * sizeof(gchar)); - babel_treaty(GET_STORY_FILE_METADATA_SEL, buffer, len); - ifiction = g_strndup(buffer, len); - g_free(buffer); - } else { - printf("No metadata found in file, performing IFDB lookup.\n"); - gchar *ifid = malloc(TREATY_MINIMUM_EXTENT * sizeof(gchar)); - if( !babel_treaty(GET_STORY_FILE_IFID_SEL, ifid, TREATY_MINIMUM_EXTENT) ) { - fprintf(stderr, "Unable to create an IFID (A serious problem occurred while loading the file).\n"); - babel_release(); - return 1; - } - printf("Looking up IFID: %s.\n", ifid); - babel_release(); - - SoupSession *session = soup_session_async_new(); - char *uri_string = g_strconcat("http://ifdb.tads.org/viewgame?ifiction&ifid=", ifid, NULL); - SoupMessage *message = soup_message_new("GET", uri_string); - g_free(uri_string); - soup_message_headers_append(message->request_headers, "Connection", "close"); - if(soup_session_send_message(session, message) != 200) - g_printerr("ERROR: did not get HTTP status 200\n"); - ifiction = g_strndup(message->response_body->data, message->response_body->length); - g_object_unref(message); - g_object_unref(session); - } - - ifiction = g_strchomp(ifiction); - - GMarkupParser xml_parser = {start_element, end_element, text, NULL, NULL}; - GMarkupParseContext *context = g_markup_parse_context_new(&xml_parser, 0, &data, NULL); - - if( g_markup_parse_context_parse(context, ifiction, strlen(ifiction), &err) == FALSE ) { - fprintf(stderr, "Metadata parse failed: %s\n", err->message); - } - - g_markup_parse_context_free(context); - g_free(ifiction); - - babel_release(); - - // Check for errors - if(data.error) { - fprintf(stderr, "ERROR %s: %s\n", data.error_code, data.error_message); - return 1; - } - - // Open DB connection - GdaConnection *cnc; - GdaSqlParser *sql_parser; - - gda_init(); - cnc = gda_connection_open_from_string("SQLite", "DB_DIR=.;DB_NAME=library", NULL, GDA_CONNECTION_OPTIONS_NONE, &err); - if(!cnc) { - fprintf(stderr, "Could not open connection to SQLite database in library.db file: %s\n", err && err->message ? err->message : "No details"); - return 1; - } - - sql_parser = gda_connection_create_parser(cnc); - if(!sql_parser) // cnc does not provide its own parser, use default one - sql_parser = gda_sql_parser_new(); - - g_object_set_data_full(G_OBJECT(cnc), "parser", sql_parser, g_object_unref); - - // Create stories table - //run_sql_non_select(cnc, "DROP TABLE IF EXISTS stories"); - run_sql_non_select(cnc, "CREATE TABLE IF NOT EXISTS stories (ifid text not null primary key, title text, author text, firstpublished text)"); - - // Populate the table - GValue *v1, *v2, *v3, *v4; - v1 = gda_value_new_from_string(data.ifid, G_TYPE_STRING); - v2 = gda_value_new_from_string(data.title, G_TYPE_STRING); - v3 = gda_value_new_from_string(data.author, G_TYPE_STRING); - v4 = gda_value_new_from_string(data.firstpublished, G_TYPE_STRING); - - if( !gda_insert_row_into_table(cnc, "stories", &err, "ifid", v1, "title", v2, "author", v3, "firstpublished", v4, NULL) ) { - g_warning("Could not INSERT data into the 'stories' table: %s\n", err && err->message ? err->message : "No details"); - } - - gda_value_free(v1); - gda_value_free(v2); - gda_value_free(v3); - gda_value_free(v4); - - // Dump the table contents - GdaDataModel *data_model; - GdaStatement *stmt = gda_sql_parser_parse_string(sql_parser, "SELECT * FROM stories", NULL, NULL); - data_model = gda_connection_statement_execute_select(cnc, stmt, NULL, &err); - if(!data_model) - g_error("Could not get the contents of the 'stories' table: %s\n", err && err->message ? err->message : "No details"); - printf("Dumping library table:\n"); - gda_data_model_dump(data_model, stdout); - - g_object_unref(stmt); - g_object_unref(data_model); - - gda_connection_close(cnc); - return 0; -} diff --git a/tests/csstest.c b/tests/csstest.c new file mode 100644 index 0000000..28cbabe --- /dev/null +++ b/tests/csstest.c @@ -0,0 +1,105 @@ +#include +#include + +/* This is a test program for CSS styling of the Glk program, which is not +implemented so far. */ + +/* Style the GUI funky */ +void +style1(GtkButton *button, GtkStyleProvider *funky_provider) +{ + gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), funky_provider, GTK_STYLE_PROVIDER_PRIORITY_USER); +} + +/* Style the GUI nicely */ +void +style2(GtkButton *button, GtkStyleProvider *funky_provider) +{ + gtk_style_context_remove_provider_for_screen(gdk_screen_get_default(), funky_provider); +} + +int +main(int argc, char **argv) +{ + gdk_threads_init(); + gtk_init(&argc, &argv); + + /* Create widgets */ + GtkWidget *win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GtkWidget *grid = gtk_grid_new(); + GtkWidget *glk = chimara_glk_new(); + GtkWidget *stylebutton1 = gtk_button_new_with_label("Style 1"); + GtkWidget *stylebutton2 = gtk_button_new_with_label("Style 2"); + GtkCssProvider *funky_provider = gtk_css_provider_new(); + + /* Set properties on widgets */ + gtk_widget_set_size_request(win, 400, 400); + g_object_set(glk, "expand", TRUE, NULL); + GError *error = NULL; + gboolean res = gtk_css_provider_load_from_data(funky_provider, + ".glk grid {" + " font-size: 14;" + " color: #303030;" + " font-family: \"Andale Mono\";" + "}\n" + ".glk buffer {" + " color: #303030;" + " font-size: 14;" + " margin-bottom: 5px;" + " font-family: \"Book Antiqua\";" + "}\n" + ".glk buffer.header { font-weight: bold; }\n" + ".glk buffer.alert {" + " color: #aa0000;" + " font-weight: bold;" + "}\n" + ".glk buffer.note {" + " color: #aaaa00;" + " font-weight: bold;" + "}\n" + ".glk buffer.block-quote {" + " /*text-align: center;*/" + " font-style: italic;" + "}\n" + ".glk buffer.input {" + " color: #0000aa;" + " font-style: italic;" + "}\n" + ".glk blank { background-color: #4e702a; }\n" + ".glk graphics {" + " background-image: -gtk-gradient(linear, 0 0, 0 1," + " color-stop(0, @yellow)," + " color-stop(0.2, @blue)," + " color-stop(1, #0f0));" + "}", + -1, &error); + if(!res) + g_printerr("Error: %s\n", error->message); + + /* Put widgets together */ + gtk_grid_attach(GTK_GRID(grid), stylebutton1, 0, 0, 1, 1); + gtk_grid_attach_next_to(GTK_GRID(grid), stylebutton2, NULL, GTK_POS_RIGHT, 1, 1); + gtk_grid_attach(GTK_GRID(grid), glk, 0, 1, 2, 1); + gtk_container_add(GTK_CONTAINER(win), grid); + + /* Connect signals */ + g_signal_connect(win, "delete-event", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(stylebutton1, "clicked", G_CALLBACK(style1), funky_provider); + g_signal_connect(stylebutton2, "clicked", G_CALLBACK(style2), funky_provider); + + /* Go! */ + gtk_widget_show_all(win); + g_object_ref(glk); + char *plugin_argv[] = { "styletest" }; + chimara_glk_run(CHIMARA_GLK(glk), ".libs/styletest.so", 1, plugin_argv, NULL); + + gdk_threads_enter(); + gtk_main(); + gdk_threads_leave(); + + chimara_glk_stop(CHIMARA_GLK(glk)); + chimara_glk_wait(CHIMARA_GLK(glk)); + g_object_unref(glk); + g_object_unref(funky_provider); + return 0; +} \ No newline at end of file diff --git a/tests/glulxercise.c b/tests/glulxercise.c index 54113a4..fc9a9b1 100644 --- a/tests/glulxercise.c +++ b/tests/glulxercise.c @@ -94,7 +94,7 @@ main(int argc, char *argv[]) w->stop = LOAD_WIDGET("stop"); w->interp = chimara_if_new(); gtk_widget_set_size_request(w->interp, 500, 600); - gtk_box_pack_end_defaults(GTK_BOX(vbox), w->interp); + gtk_box_pack_end(GTK_BOX(vbox), w->interp, TRUE, TRUE, 0); chimara_glk_set_css_from_string(CHIMARA_GLK(w->interp), "buffer { font-size: 12; } buffer.input { color: #00a; font-style: italic; }"); chimara_glk_set_spacing(CHIMARA_GLK(w->interp), 1);