Merge branch 'gtk3'
authorMarijn van Vliet <w.m.vanvliet@student.utwente.nl>
Mon, 24 Sep 2012 09:38:10 +0000 (11:38 +0200)
committerMarijn van Vliet <w.m.vanvliet@student.utwente.nl>
Mon, 24 Sep 2012 09:38:10 +0000 (11:38 +0200)
191 files changed:
COPYING
ChangeLog
Makefile.am
NEWS
babel/MANIFEST [deleted file]
babel/Makefile.am [deleted file]
babel/README [deleted file]
babel/adrift.c [deleted file]
babel/advsys.c [deleted file]
babel/agt.c [deleted file]
babel/alan.c [deleted file]
babel/babel [deleted file]
babel/babel-makefile [deleted file]
babel/babel.a [deleted file]
babel/babel.c [deleted file]
babel/babel.h [deleted file]
babel/babel_functions.a [deleted file]
babel/babel_handler.c [deleted file]
babel/babel_handler.h [deleted file]
babel/babel_ifiction_functions.c [deleted file]
babel/babel_multi_functions.c [deleted file]
babel/babel_story_functions.c [deleted file]
babel/blorb.c [deleted file]
babel/executable.c [deleted file]
babel/glulx.c [deleted file]
babel/hugo.c [deleted file]
babel/ifiction.a [deleted file]
babel/ifiction.c [deleted file]
babel/ifiction.h [deleted file]
babel/level9.c [deleted file]
babel/magscrolls.c [deleted file]
babel/md5.c [deleted file]
babel/md5.h [deleted file]
babel/misc.c [deleted file]
babel/modules.h [deleted file]
babel/modules.h.gch [deleted file]
babel/register.c [deleted file]
babel/register_ifiction.c [deleted file]
babel/tads.c [deleted file]
babel/tads.h [deleted file]
babel/tads2.c [deleted file]
babel/tads3.c [deleted file]
babel/treaty.h [deleted file]
babel/treaty_builder.h [deleted file]
babel/zcode.c [deleted file]
bundle/Info-chimara.plist [new file with mode: 0644]
bundle/chimara.bundle [new file with mode: 0644]
bundle/chimara_icon_512.png [new file with mode: 0644]
bundle/gtkrc [new file with mode: 0644]
bundle/launcher.sh [new file with mode: 0755]
configure.ac
iliad/chimara.png [deleted file]
iliad/create_iliad_package.sh [deleted file]
iliad/iliad_refresh.conf [deleted file]
iliad/manifest.xml [deleted file]
iliad/run.sh [deleted file]
iliad/style.css [deleted file]
interpreters/Makefile.am
interpreters/bocfel/BUILDING [new file with mode: 0644]
interpreters/bocfel/COPYING.GPLv2 [new file with mode: 0644]
interpreters/bocfel/COPYING.GPLv3 [new file with mode: 0644]
interpreters/bocfel/Makefile.am [new file with mode: 0644]
interpreters/bocfel/README [new file with mode: 0644]
interpreters/bocfel/blorb.c [new file with mode: 0644]
interpreters/bocfel/blorb.h [new file with mode: 0644]
interpreters/bocfel/branch.c [new file with mode: 0644]
interpreters/bocfel/branch.h [new file with mode: 0644]
interpreters/bocfel/dict.c [new file with mode: 0644]
interpreters/bocfel/dict.h [new file with mode: 0644]
interpreters/bocfel/glkstart.c [new file with mode: 0644]
interpreters/bocfel/iff.c [new file with mode: 0644]
interpreters/bocfel/iff.h [new file with mode: 0644]
interpreters/bocfel/io.c [new file with mode: 0644]
interpreters/bocfel/io.h [new file with mode: 0644]
interpreters/bocfel/math.c [new file with mode: 0644]
interpreters/bocfel/math.h [new file with mode: 0644]
interpreters/bocfel/memory.c [new file with mode: 0644]
interpreters/bocfel/memory.h [new file with mode: 0644]
interpreters/bocfel/objects.c [new file with mode: 0644]
interpreters/bocfel/objects.h [new file with mode: 0644]
interpreters/bocfel/osdep.c [new file with mode: 0644]
interpreters/bocfel/osdep.h [new file with mode: 0644]
interpreters/bocfel/process.c [new file with mode: 0644]
interpreters/bocfel/process.h [new file with mode: 0644]
interpreters/bocfel/random.c [new file with mode: 0644]
interpreters/bocfel/random.h [new file with mode: 0644]
interpreters/bocfel/screen.c [new file with mode: 0644]
interpreters/bocfel/screen.h [new file with mode: 0644]
interpreters/bocfel/stack.c [new file with mode: 0644]
interpreters/bocfel/stack.h [new file with mode: 0644]
interpreters/bocfel/table.c [new file with mode: 0644]
interpreters/bocfel/table.h [new file with mode: 0644]
interpreters/bocfel/unicode.c [new file with mode: 0644]
interpreters/bocfel/unicode.h [new file with mode: 0644]
interpreters/bocfel/util.c [new file with mode: 0644]
interpreters/bocfel/util.h [new file with mode: 0644]
interpreters/bocfel/zoom.c [new file with mode: 0644]
interpreters/bocfel/zoom.h [new file with mode: 0644]
interpreters/bocfel/zterp.c [new file with mode: 0644]
interpreters/bocfel/zterp.h [new file with mode: 0644]
interpreters/frotz/Makefile.am
interpreters/frotz/buffer.c
interpreters/frotz/err.c
interpreters/frotz/fastmem.c
interpreters/frotz/files.c
interpreters/frotz/glkmisc.c
interpreters/frotz/glkscreen.c
interpreters/frotz/input.c
interpreters/frotz/main.c
interpreters/frotz/math.c
interpreters/frotz/object.c
interpreters/frotz/process.c
interpreters/frotz/quetzal.c
interpreters/frotz/random.c
interpreters/frotz/redirect.c
interpreters/frotz/sound.c
interpreters/frotz/stream.c
interpreters/frotz/table.c
interpreters/frotz/text.c
interpreters/frotz/variable.c
interpreters/git/Makefile.am
interpreters/git/README.txt
interpreters/git/git.h
interpreters/git/glkop.c
interpreters/git/terp.c
interpreters/git/version.h
interpreters/glulxe/unixstrt.c
interpreters/nitfol/Makefile.am
interpreters/nitfol/automap.c
interpreters/nitfol/debug.c
interpreters/nitfol/decode.c
interpreters/nitfol/errmesg.c
interpreters/nitfol/globals.c
interpreters/nitfol/iff.c
interpreters/nitfol/infix.c
interpreters/nitfol/init.c
interpreters/nitfol/io.c
interpreters/nitfol/linkevil.h
interpreters/nitfol/main.c
interpreters/nitfol/nitfol.h
interpreters/nitfol/objects.c
interpreters/nitfol/op_call.c
interpreters/nitfol/op_jmp.c
interpreters/nitfol/op_math.c
interpreters/nitfol/op_save.c
interpreters/nitfol/op_table.c
interpreters/nitfol/op_v6.c
interpreters/nitfol/oplist.c
interpreters/nitfol/opt2glkc.pl
interpreters/nitfol/portfunc.c
interpreters/nitfol/quetzal.c
interpreters/nitfol/solve.c
interpreters/nitfol/sound.c
interpreters/nitfol/stack.c
interpreters/nitfol/struct.c
interpreters/nitfol/tokenise.c
interpreters/nitfol/undo.c
interpreters/nitfol/z_io.c
interpreters/nitfol/zscii.c
libchimara/Makefile.am
libchimara/chimara-glk-private.h
libchimara/chimara-glk.c
libchimara/chimara-if.c
libchimara/chimara-if.h
libchimara/fileref.c
libchimara/graphics.c
libchimara/graphics.h
libchimara/input.c
libchimara/pager.c
libchimara/pager.h
libchimara/resource.c
libchimara/resource.h
libchimara/schannel.c
libchimara/style.c
libchimara/style.h
libchimara/window.c
libchimara/window.h
player/Makefile.am
player/callbacks.c
player/config.py.in
player/iliad.c [deleted file]
player/main.c
player/player.py
player/preferences.c
player/style.css
player/xepdmgrclient.c [deleted file]
player/xepdmgrclient.h [deleted file]
tests/Makefile.am
tests/babeltest.c [deleted file]
tests/csstest.c [new file with mode: 0644]
tests/glulxercise.c

diff --git a/COPYING b/COPYING
index eb40986d6c1f08057d8ce1b83c5b56e5d9786634..976f4da1de5759d4837a62220da452f08fe7c873 100644 (file)
--- 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.
index 17318ff5b2e9cfea9bef72d1b39c0f2d835f88a9..a366387fb439f40b336ed7aab5031f3769a0517f 100644 (file)
--- 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
index a43dbaca79dcc13f3f02c001c3ff771847279512..d0d290d371a83d72597f41f791e54a8703ba5ace 100644 (file)
@@ -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 17318ff5b2e9cfea9bef72d1b39c0f2d835f88a9..0fde9c942ab75bb633d1a6b663678e5d6d927ce2 100644 (file)
--- 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 (file)
index 09048bd..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-adrift.c                        Treaty of Babel module for Adrift\r
-advsys.c                        Treaty of Babel module for AdvSys\r
-alan.c                          Treaty of Babel module for Alan\r
-agt.c                           Treaty of Babel module for AGT\r
-babel.h                         babel program header\r
-md5.h                           L. Peter Deutsch's md5 header\r
-modules.h                       babel module registry\r
-treaty.h                        Treaty of Babel header\r
-treaty_builder.h                Macros to build treaty modules\r
-babel.c                         Babel main program\r
-babel_handler.h                 Babel handler header file\r
-babel_handler.c                 The babel handler api\r
-babel_ifiction_functions.c      Babel program-specific ifiction operations\r
-babel_multi_functions.c         Babel program-specific multi operations\r
-babel_story_functions.c         Babel program-specific story operations\r
-blorb.c                         babel handler blorb module\r
-executable.c                    Treaty of Bable module for executables \r
-glulx.c                         Treaty of Babel module for glulx\r
-hugo.c                          Draft Treaty of Babel module for hugo\r
-ifiction.h                      babel ifiction header\r
-ifiction.c                      babel ifiction api\r
-level9.c                        Treaty of Babel module for level9\r
-magscrolls.c                    Magnetic Scrolls treaty module\r
-makefile                        Provisional makefile\r
-md5.c                           L. Peter Deutsch's md5 implementation\r
-misc.c                          babel memory allocator\r
-register.c                      babel module registry\r
-register_ifiction.c             babel module registry for ifiction API\r
-tads.h                          Prototypes for tads2.c and tads3.c\r
-tads.c                          Common functions for TADS modules\r
-tads2.c                         Treaty of Babel module for tads2\r
-tads3.c                         Treaty of Babel module for tads3\r
-zcode.c                         Treaty of Babel module for zcode\r
-README                          documentation\r
-MANIFEST                        this file\r
-extras/babel-cache.pl           Perl demo of babel interaction\r
-extras/babel-infocom.pl         Special bundler for the infocom corpus\r
-extras/babel-list.c             Babel API demo\r
-extras/babel-marry.pl           Perl simple blorb encapsulator\r
-extras/babel-wed.pl             Perl single file blorb encapsulator\r
-extras/hotload.c                Dynamic loader replacement for register.c\r
-extras/hotload.h                Header file for hotload.c\r
-extras/ifiction-aggregate.c     Utility to combine multiple ifiction files\r
-extras/ifiction-xtract.c        Ifiction API demo\r
-extras/simple-marry.c           Simplified C version of babel-marry\r
-babel-get/babel-get.c           The babel-get application\r
-babel-get/get_dir.c             Directory source\r
-babel-get/get_ifiction.c        ifiction source\r
-babel-get/get_story.c           story file source\r
-babel-get/get_url.c             URL source\r
-babel-get/makefile              Makefile for babel-get\r
diff --git a/babel/Makefile.am b/babel/Makefile.am
deleted file mode 100644 (file)
index 1e0d78d..0000000
+++ /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 (file)
index b754cab..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-Version 0.2b, Treaty of Babel Revision 7\r
-This is the source code for babel, the Treaty of Babel analysis tool.\r
-\r
-Most of this code is (c) 2006 by L. Ross Raszewski\r
-\r
-The following files are public domain:\r
-zcode.c\r
-glulx.c\r
-executable.c\r
-level9.c\r
-magscrolls.c\r
-agt.c\r
-hugo.c\r
-advsys.c\r
-misc.c\r
-alan.c\r
-adrift.c\r
-treaty.h\r
-treaty_builder.h\r
-\r
-The following files are Copyright (C) 1999, 2000, 2002 Aladdin Enterprises:\r
-md5.c\r
-md5.h\r
-\r
-And are used in accordance with their licenses.\r
-\r
-All other files are (c) 2006 by L. Ross Raszewski and are released under\r
-the Creative Commons Attribution2.5 License.\r
-\r
-To view a copy of this license, visit\r
-http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
-\r
-Creative Commons,\r
-543 Howard Street, 5th Floor,\r
-San Francisco, California, 94105, USA.\r
-\r
-\r
-To build babel:\r
-\r
-1. compile all the source files in this directory\r
-2. link them together\r
-3. the end\r
-\r
-For folks who find makefiles more useful than generalizations, there is a\r
-makefile provided for babel.  The makefile is currently configured for\r
-Borland's 32-bit C compiler.  Comment out those lines and uncomment the block\r
-which follows for gcc.\r
-\r
-To compile babel-get, first compile babel, then do the same thing in the\r
-babel-get directory.\r
-\r
-To compile ifiction-aggregate, ifiction-xtract, babel-list, and simple-marry,\r
-first compile babel, then compile the relevant C file in the extras/ directory\r
-(These may rely on #include files from the babel directory, so, for example,\r
-to compile ifiction-aggregate, "gcc -c -I.. ifiction-aggregate.c"), then link the\r
-opbject file to the babel and ifiction libraries (babel.lib and ifiction.lib\r
-under Windows, babel.a and ifiction.a most everywhere else.  eg.\r
-"gcc -o ifiction-aggregate ifiction-aggregate.o ../babel.a ../ifiction.a")\r
-\r
-Babel is intended to accept contributions in the form of treaty modules\r
-as defined by the treaty of babel section 2.3.2.\r
-\r
-These modules should use the declarations made in treaty.h.\r
-The file treaty_builder.h generates a generic framework which simplifies\r
-the task of writing treaty modules.  Its use is not required for treaty\r
-compliance, but it should prove useful.\r
-\r
-Parts of babel are intended for use in other programs.  When adapting\r
-babel's source, the files babel.c, babel_story_functions.c and\r
-babel_ifiction_functions.c will probably not prove useful.  However, you\r
-may wish to use babel_handler, which provides a framework for loading a\r
-story file, selecting the proper treaty modules, and seamlessly handling\r
-blorb-wrapped files.\r
-\r
diff --git a/babel/adrift.c b/babel/adrift.c
deleted file mode 100644 (file)
index 3718d6a..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/* adrift.c  Treaty of Babel module for Adrift files\r
- *\r
- * PROVISIONAL - Hold for someone else\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT adrift\r
-#define HOME_PAGE "http://www.adrift.org.uk"\r
-#define FORMAT_EXT ".taf"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-\r
-#include <stdio.h>\r
-#include <limits.h>\r
-#include <stdlib.h>\r
-\r
-/* VB RNG constants */\r
-#define  VB_RAND1      0x43FD43FD\r
-#define  VB_RAND2      0x00C39EC3\r
-#define  VB_RAND3      0x00FFFFFF\r
-#define  VB_INIT       0x00A09E86\r
-static int32 vbr_state;\r
-\r
-/*\r
-  Unobfuscates one byte from a taf file. This should be called on each byte\r
-  in order, as the ADRIFT obfuscation function is stately.\r
-\r
-  The de-obfuscation algorithm works by xoring the byte with the next\r
-  byte in the sequence produced by the Visual Basic pseudorandom number\r
-  generator, which is simulated here.\r
-*/\r
-static unsigned char taf_translate (unsigned char c)\r
-{\r
- int32 r;\r
-\r
- vbr_state = (vbr_state*VB_RAND1+VB_RAND2) & VB_RAND3;\r
- r=UCHAR_MAX * (unsigned) vbr_state;\r
- r/=((unsigned) VB_RAND3)+1;\r
- return r^c;\r
-}\r
-\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- int adv;\r
- unsigned char buf[4];\r
- unsigned char *sf=(unsigned char *)story_file;\r
- vbr_state=VB_INIT;\r
-\r
- if (extent <12) return INVALID_STORY_FILE_RV;\r
-\r
- buf[3]=0;\r
- /* Burn the first 8 bytes of translation */\r
- for(adv=0;adv<8;adv++) taf_translate(0);\r
- /* Bytes 8-11 contain the Adrift version number in the formay N.NN */\r
- buf[0]=taf_translate(sf[8]);\r
- taf_translate(0);\r
- buf[1]=taf_translate(sf[10]);\r
- buf[2]=taf_translate(sf[11]);\r
- adv=atoi((char *) buf);\r
- ASSERT_OUTPUT_SIZE(12);\r
- sprintf(output,"ADRIFT-%03d-",adv);\r
- return INCOMPLETE_REPLY_RV;\r
-\r
-}\r
-\r
-/* The claim algorithm for ADRIFT is to unobfuscate the first\r
-   seven bytes, and check for the word "Version".\r
-   It seems fairly unlikely that the obfuscated form of that\r
-   word would occur in the wild\r
-*/\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- unsigned char buf[8];\r
- int i;\r
- unsigned char *sf=(unsigned char *)story_file;\r
- buf[7]=0;\r
- vbr_state=VB_INIT;\r
- if (extent<12) return INVALID_STORY_FILE_RV;\r
- for(i=0;i<7;i++) buf[i]=taf_translate(sf[i]);\r
- if (strcmp((char *)buf,"Version")) return INVALID_STORY_FILE_RV;\r
- return VALID_STORY_FILE_RV;\r
-\r
-}\r
diff --git a/babel/advsys.c b/babel/advsys.c
deleted file mode 100644 (file)
index e0e1cf8..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/* advsys.c  Treaty of Babel module for AdvSys files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT advsys\r
-#define HOME_PAGE "http://www.ifarchive.org/if-archive/programming/advsys/"\r
-#define FORMAT_EXT ".dat"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-/* IFIDs for AdvSys are formed by prepending ADVSYS- to the default\r
-   MD5 ifid\r
-*/\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- /* This line suppresses a warning from the borland compiler */\r
- if (story_file || extent) { }\r
- ASSERT_OUTPUT_SIZE(8);\r
- strcpy(output,"ADVSYS-");\r
- return INCOMPLETE_REPLY_RV;\r
-\r
-}\r
-\r
-/* The Advsys claim algorithm: bytes 2-8 of the file contain the\r
-   text "ADVSYS", unobfuscated in the following way:\r
-     30 is added to each byte, then the bits are reversed\r
-*/\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- char buf[7];\r
- int i;\r
- if (extent >=8)\r
- { \r
-   for(i=0;i<6;i++)\r
-    buf[i]=~(((char *)story_file)[i+2]+30);\r
-   buf[6]=0;\r
-   if (strcmp(buf,"ADVSYS")==0) return VALID_STORY_FILE_RV;\r
- }\r
- return INVALID_STORY_FILE_RV;\r
-}\r
diff --git a/babel/agt.c b/babel/agt.c
deleted file mode 100644 (file)
index 66b6575..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/* agt.c  Treaty of Babel module for AGX-encapsulated AGT files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT agt\r
-#define HOME_PAGE "http://www.ifarchive.org/indexes/if-archiveXprogrammingXagt"\r
-#define FORMAT_EXT ".agx"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-\r
-static char AGX_MAGIC[4] = { 0x58, 0xC7, 0xC1, 0x51 };\r
-\r
-/* Helper functions to unencode integers from AGT source */\r
-static int32 read_agt_short(unsigned char *sf)\r
-{\r
- return sf[0] | (int32) sf[1]<<8;\r
-}\r
-static int32 read_agt_int(unsigned char *sf)\r
-{\r
- return (read_agt_short(sf+2) << 16) | read_agt_short(sf);\r
-\r
-}\r
-\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- int32 l, game_version, game_sig;\r
- unsigned char *sf=(unsigned char *)story_file;\r
-\r
- /* Read the position of the game desciption block */\r
- l=read_agt_int(sf+32);\r
- if (extent<l+6) return INVALID_STORY_FILE_RV;\r
- game_version = read_agt_short(sf+l);\r
- game_sig=read_agt_int(sf+l+2);\r
- ASSERT_OUTPUT_SIZE(19);\r
- sprintf(output,"AGT-%05d-%08X",game_version,game_sig);\r
- return 1;\r
-}\r
-\r
-/* The claim algorithm for AGT is to check for the magic word\r
-   defined above\r
-*/\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
-\r
-\r
- if (extent<36 || memcmp(story_file,AGX_MAGIC,4)) return INVALID_STORY_FILE_RV;\r
- return VALID_STORY_FILE_RV;\r
-\r
-}\r
diff --git a/babel/alan.c b/babel/alan.c
deleted file mode 100644 (file)
index fb57dbe..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/* alan.c  Treaty of Babel module for ALAN files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT alan\r
-#define HOME_PAGE "http://www.alanif.se/"\r
-#define FORMAT_EXT ".acd"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-static int32 read_alan_int(unsigned char *from)\r
-{\r
- return ((unsigned long int) from[3])| ((unsigned long int)from[2] << 8) |\r
-       ((unsigned long int) from[1]<<16)| ((unsigned long int)from[0] << 24);\r
-}\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
-\r
- if (story_file || extent) { }\r
- ASSERT_OUTPUT_SIZE(6);\r
- strcpy(output,"ALAN-");\r
- return INCOMPLETE_REPLY_RV;\r
-}\r
-/*\r
-  The claim algorithm for Alan files is:\r
-   * For Alan 3, check for the magic word\r
-   * load the file length in blocks\r
-   * check that the file length is correct\r
-   * For alan 2, each word between byte address 24 and 81 is a\r
-      word address within the file, so check that they're all within\r
-      the file\r
-   * Locate the checksum and verify that it is correct\r
-*/\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- unsigned char *sf = (unsigned char *) story_file;\r
- int32 bf, i, crc=0;\r
- if (extent < 160) return INVALID_STORY_FILE_RV;\r
- if (memcmp(sf,"ALAN",4))\r
- { /* Identify Alan 2.x */\r
- bf=read_alan_int(sf+4);\r
- if (bf > extent/4) return INVALID_STORY_FILE_RV;\r
- for (i=24;i<81;i+=4)\r
- if (read_alan_int(sf+i) > extent/4) return INVALID_STORY_FILE_RV;\r
- for (i=160;i<(bf*4);i++)\r
- crc+=sf[i];\r
- if (crc!=read_alan_int(sf+152)) return INVALID_STORY_FILE_RV;\r
- return VALID_STORY_FILE_RV;\r
- }\r
- else\r
- { /* Identify Alan 3 */\r
-   bf=read_alan_int(sf+12);\r
-   if (bf > (extent/4)) return INVALID_STORY_FILE_RV;\r
-   for (i=184;i<(bf*4);i++)\r
-    crc+=sf[i];\r
- if (crc!=read_alan_int(sf+176)) return INVALID_STORY_FILE_RV;\r
-\r
- }\r
- return INVALID_STORY_FILE_RV;\r
-}\r
diff --git a/babel/babel b/babel/babel
deleted file mode 100644 (file)
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 (file)
index 1b31887..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-# provisional makefile for babel\r
-#\r
-# Note that to compile babel, it is necessary only to compile all the .c\r
-# files in this distribution and link them.\r
-#\r
-# This makefile is provided purely as a convenience.\r
-#\r
-# The following targets are available:\r
-#  babel:              make babel\r
-#  babel.lib:          make babel handler library (for Borland)\r
-#  ifiction.lib:       make babel ifiction library (for Borland)\r
-#  babel.a:            make babel handler library (for gcc)\r
-#  ifiction.a:         make babel ifiction library (for gcc)\r
-#  dist:               make babel.zip, the babel source distribution\r
-#\r
-# Note that this is a GNU makefile, and may not work with other makes\r
-#\r
-# Comment/uncomment the following lines to make the program work\r
-\r
-#CC=bcc32\r
-#OBJ=.obj\r
-#BABEL_LIB=babel.lib\r
-#IFICTION_LIB=ifiction.lib\r
-#BABEL_FLIB=babel_functions.lib\r
-#OUTPUT_BABEL=\r
-\r
-CC=gcc -g\r
-OBJ=.o\r
-BABEL_LIB=babel.a\r
-BABEL_FLIB=babel_functions.a\r
-IFICTION_LIB=ifiction.a\r
-OUTPUT_BABEL=-o babel\r
-\r
-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}\r
-bh_objs = babel_handler${OBJ} register${OBJ} misc${OBJ} md5${OBJ} ${treaty_objs}\r
-ifiction_objs = ifiction${OBJ} register_ifiction${OBJ}\r
-babel_functions =  babel_story_functions${OBJ} babel_ifiction_functions${OBJ} babel_multi_functions${OBJ}\r
-babel_objs = babel${OBJ} $(BABEL_FLIB) $(IFICTION_LIB) $(BABEL_LIB)\r
-\r
-babel: ${babel_objs} \r
-       ${CC} ${OUTPUT_BABEL} ${babel_objs}\r
-\r
-%${OBJ} : %.c\r
-       ${CC} -c $^\r
-\r
-register${OBJ}: modules.h\r
-\r
-babel.lib: ${foreach dep,${bh_objs},${dep}.bl}\r
-\r
-ifiction.lib: ${foreach dep,${ifiction_objs},${dep}.il}\r
-\r
-babel_functions.lib: ${foreach dep,${babel_functions},${dep}.fl}\r
-\r
-%.obj.bl: %.obj\r
-       tlib babel.lib +-$^\r
-       echo made > $@\r
-\r
-%.obj.il: %.obj\r
-       tlib ifiction.lib +-$^\r
-       echo made > $@\r
-%.obj.fl: %.obj\r
-       tlib babel_functions.lib +-$^\r
-       echo made > $@\r
-\r
-babel.a: $(bh_objs)\r
-       ar -r babel.a $^\r
-\r
-ifiction.a: $(ifiction_objs)\r
-       ar -r ifiction.a $^\r
-\r
-babel_functions.a: $(babel_functions)\r
-       ar -r babel_functions.a $^\r
-\r
-dist: \r
-       cut -c0-31 MANIFEST | zip babel.zip -@\r
diff --git a/babel/babel.a b/babel/babel.a
deleted file mode 100644 (file)
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 (file)
index 5bb7213..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-/* babel.c   The babel command line program\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends upon misc.c and babel.h\r
- *\r
- * This file exports one variable: char *rv, which points to the file name\r
- * for an ifiction file.  This is used only by babel_ifiction_verify\r
- */\r
-\r
-#include "babel.h"\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <string.h>\r
-\r
-#ifdef __cplusplus\r
-extern "C" {\r
-#endif\r
-int chdir(const char *);\r
-char *getcwd(char *, int);\r
-#ifdef __cplusplus\r
-}\r
-#endif\r
-\r
-char *fn;\r
-\r
-/* checked malloc function */\r
-void *my_malloc(int, char *);\r
-\r
-/* babel performs several fundamental operations, which are specified\r
-   by command-line objects. Each of these functions corresponds to\r
-   a story function (defined in babel_story_functions.c) or an\r
-   ifiction function (defined in babel_ifiction_functions.c) or both.\r
-   These are the types of those functions.\r
-*/\r
-\r
-typedef void (*story_function)(void);\r
-typedef void (*ifiction_function)(char *);\r
-typedef void (*multi_function)(char **, char *, int);\r
-/* This structure tells babel what to do with its command line arguments.\r
-   if either of story or ifiction are NULL, babel considers this command line\r
-   option inappropriate for that type of file.\r
-*/\r
-struct function_handler {\r
-        char *function;         /* the textual command line option */\r
-        story_function story;   /* handler for story files */\r
-        ifiction_function ifiction; /* handler for ifiction files */\r
-        char *desc;             /* Textual description for help text */\r
-        };\r
-\r
-struct multi_handler {\r
-        char *function;\r
-        char *argnames;\r
-        multi_function handler;\r
-        int nargsm;\r
-        int nargsx;\r
-        char *desc;\r
-        };\r
-/* This is an array of function_handler objects which specify the legal\r
-   arguments.  It is terminated by a function_handler with a NULL function\r
- */\r
-static struct function_handler functions[] = {\r
-        { "-ifid", babel_story_ifid, babel_ifiction_ifid, "Deduce IFID"},\r
-        { "-format", babel_story_format, NULL, "Deduce story format" },\r
-        { "-ifiction", babel_story_ifiction, NULL, "Extract iFiction file" },\r
-        { "-meta", babel_story_meta, NULL, "Print story metadata" },\r
-        { "-identify", babel_story_identify, NULL, "Describe story file" },\r
-        { "-cover", babel_story_cover, NULL, "Extract cover art" },\r
-        { "-story", babel_story_story, NULL, "Extract story file (ie. from a blorb)" },\r
-        { "-verify", NULL, babel_ifiction_verify, "Verify integrity of iFiction file" },\r
-        { "-lint", NULL, babel_ifiction_lint, "Verify style of iFiction file" },\r
-        { "-fish", babel_story_fish, babel_ifiction_fish, "Extract all iFiction and cover art"},\r
-        { "-unblorb", babel_story_unblorb, NULL, "As -fish, but also extract story files"},\r
-        { NULL, NULL, NULL }\r
-        };\r
-static struct multi_handler multifuncs[] = {\r
-        { "-blorb", "<storyfile> <ifictionfile> [<cover art>]", babel_multi_blorb, 2, 3, "Bundle story file and (sparse) iFiction into blorb" },\r
-        { "-blorbs", "<storyfile> <ifictionfile> [<cover art>]", babel_multi_blorb1, 2, 3, "Bundle story file and (sparse) iFiction into sensibly-named blorb" },\r
-        { "-complete", "<storyfile> <ifictionfile>", babel_multi_complete, 2, 2, "Create complete iFiction file from sparse iFiction" },\r
-        { NULL, NULL, NULL, 0, 0, NULL }\r
-};\r
-\r
-int main(int argc, char **argv)\r
-{\r
- char *todir=".";\r
- char cwd[512];\r
- int ok=1,i, l, ll;\r
- FILE *f;\r
- char *md=NULL;\r
- /* Set the input filename.  Note that if this is invalid, babel should\r
-   abort before anyone notices\r
- */\r
- fn=argv[2];\r
-\r
- if (argc < 3) ok=0;\r
- /* Detect the presence of the "-to <directory>" argument.\r
-  */\r
- if (ok && argc >=5 && strcmp(argv[argc-2], "-to")==0)\r
- {\r
-  todir=argv[argc-1];\r
-  argc-=2;\r
- }\r
- if (ok) for(i=0;multifuncs[i].function;i++)\r
-          if (strcmp(argv[1],multifuncs[i].function)==0 &&\r
-              argc>= multifuncs[i].nargsm+2 &&\r
-              argc <= multifuncs[i].nargsx+2)\r
-          {\r
-\r
-   multifuncs[i].handler(argv+2, todir, argc-2);\r
-   exit(0);\r
-          }\r
-\r
- if (argc!=3) ok=0;\r
-\r
- /* Find the apropriate function_handler */\r
- if (ok) {\r
- for(i=0;functions[i].function && strcmp(functions[i].function,argv[1]);i++);\r
- if (!functions[i].function) ok=0;\r
- else  if (strcmp(fn,"-")) {\r
-   f=fopen(argv[2],"r");\r
-   if (!f) ok=0;\r
-  }\r
- }\r
-\r
- /* Print usage error if anything has gone wrong */\r
- if (!ok)\r
- {\r
-  printf("%s: Treaty of Babel Analysis Tool (%s, %s)\n"\r
-         "Usage:\n", argv[0],BABEL_VERSION, TREATY_COMPLIANCE);\r
-  for(i=0;functions[i].function;i++)\r
-  {\r
-   if (functions[i].story)\r
-    printf(" babel %s <storyfile>\n",functions[i].function);\r
-   if (functions[i].ifiction)\r
-    printf(" babel %s <ifictionfile>\n",functions[i].function);\r
-   printf("   %s\n",functions[i].desc);\r
-  }\r
-  for(i=0;multifuncs[i].function;i++)\r
-  {\r
-   printf("babel %s %s\n   %s\n",\r
-           multifuncs[i].function,\r
-           multifuncs[i].argnames,\r
-           multifuncs[i].desc);\r
-  }\r
-\r
-  printf ("\nFor functions which extract files, add \"-to <directory>\" to the command\n"\r
-          "to set the output directory.\n"\r
-          "The input file can be specified as \"-\" to read from standard input\n"\r
-          "(This may only work for .iFiction files)\n");\r
-  return 1;\r
- }\r
-\r
- /* For story files, we end up reading the file in twice.  This\r
-    is unfortunate, but unavoidable, since we want to be all\r
-    cross-platformy, so the first time we read it in, we\r
-    do the read in text mode, and the second time, we do it in binary\r
-    mode, and there are platforms where this makes a difference.\r
- */\r
- ll=0;\r
- if (strcmp(fn,"-"))\r
- {\r
-  fseek(f,0,SEEK_END);\r
-  l=ftell(f)+1;\r
-  fseek(f,0,SEEK_SET);\r
-  md=(char *)my_malloc(l,"Input file buffer");\r
-  fread(md,1,l-1,f);\r
-  md[l-1]=0;\r
- }\r
- else\r
-  while(!feof(stdin))\r
-  {\r
-   char *tt, mdb[1024];\r
-   int ii;\r
-   ii=fread(mdb,1,1024,stdin);\r
-   tt=(char *)my_malloc(ll+ii,"file buffer");\r
-   if (md) { memcpy(tt,md,ll); free(md); }\r
-   memcpy(tt+ll,mdb,ii);\r
-   md=tt;\r
-   ll+=ii;\r
-   if (ii<1024) break;\r
-  }\r
-\r
-\r
-  if (strstr(md,"<?xml version=") && strstr(md,"<ifindex"))\r
-  { /* appears to be an ifiction file */\r
-   char *pp;\r
-   pp=strstr(md,"</ifindex>");\r
-   if (pp) *(pp+10)=0;\r
-   getcwd(cwd,512);\r
-   chdir(todir);\r
-   l=0;\r
-   if (functions[i].ifiction)\r
-    functions[i].ifiction(md);\r
-   else\r
-    fprintf(stderr,"Error: option %s is not valid for iFiction files\n",\r
-             argv[1]);\r
-   chdir(cwd);\r
- }\r
-\r
- if (strcmp(fn,"-"))\r
- {\r
- free(md);\r
- fclose(f);\r
- }\r
- if (l)\r
- { /* Appears to be a story */\r
-   char *lt;\r
-   if (functions[i].story)\r
-   {\r
-    if (strcmp(fn,"-")) lt=babel_init(argv[2]);\r
-    else { lt=babel_init_raw(md,ll);\r
-           free(md);\r
-         }\r
-\r
-    if (lt)\r
-    {\r
-     getcwd(cwd,512);\r
-     chdir(todir);\r
-     if (!babel_get_authoritative() && strcmp(argv[1],"-format"))\r
-      printf("Warning: Story format could not be positively identified. Guessing %s\n",lt);\r
-     functions[i].story();\r
-\r
-     chdir(cwd);\r
-    }\r
-    else if (strcmp(argv[1],"-ifid")==0) /* IFID is calculable for all files */\r
-    {\r
-     babel_md5_ifid(cwd,512);\r
-     printf("IFID: %s\n",cwd);\r
-    }\r
-    else\r
-     fprintf(stderr,"Error: Did not recognize format of story file\n");\r
-    babel_release();\r
-   }\r
-   else\r
-    fprintf(stderr,"Error: option %s is not valid for story files\n",\r
-             argv[1]);\r
-  }    \r
-\r
- return 0;\r
-}\r
diff --git a/babel/babel.h b/babel/babel.h
deleted file mode 100644 (file)
index 4108e3d..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/* babel.h  declarations for babel\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends upon treaty.h, babel_ifiction_functions.c,\r
- * babel_story_functions.c, and babel_handler.c\r
- *\r
- */\r
-\r
-#define BABEL_VERSION "0.2b"\r
-\r
-#include "treaty.h"\r
-#include "babel_handler.h"\r
-#include "ifiction.h"\r
-/* Functions from babel_story_functions.c\r
- *\r
- * Each of these assumes that the story file has been loaded by babel_handler\r
- *\r
- * Each function babel_story_XXXX coresponds to the command line option -XXXX\r
- */\r
-void babel_story_ifid(void);\r
-void babel_story_cover(void);\r
-void babel_story_ifiction(void);\r
-void babel_story_meta(void);\r
-void babel_story_fish(void);\r
-void babel_story_format(void);\r
-void babel_story_identify(void);\r
-void babel_story_story(void);\r
-void babel_story_unblorb(void);\r
-/* Functions from babel_ifiction_functions.c\r
- *\r
- * as with babel_story_XXXX, but for metadata, which is handed in as the\r
- * C string parameter\r
- */\r
-void babel_ifiction_ifid(char *);\r
-void babel_ifiction_verify(char *);\r
-void babel_ifiction_fish(char *);\r
-void babel_ifiction_lint(char *);\r
-\r
-/* Functions from babel_multi_functions.c\r
- *\r
- */\r
-void babel_multi_blorb(char **, char * , int);\r
-void babel_multi_blorb1(char **, char * , int);\r
-void babel_multi_complete(char **, char *, int);\r
-\r
-/* uncomment this line on platforms which limit extensions to 3 characters */\r
-/* #define THREE_LETTER_EXTENSIONS */\r
diff --git a/babel/babel_functions.a b/babel/babel_functions.a
deleted file mode 100644 (file)
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 (file)
index f7afb78..0000000
+++ /dev/null
@@ -1,366 +0,0 @@
-/* babel_handler.c   dispatches Treaty of Babel queries to the treaty modules\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends upon register.c, misc.c, babel.h, and treaty.h\r
- * and L. Peter Deutsch's md5.c\r
- * usage:\r
- *  char *babel_init(char *filename)\r
- *      Initializes babel to use the specified file. MUST be called before\r
- *      babel_treaty.  Returns the human-readable name of the format\r
- *      or NULL if the format is not known. Do not call babel_treaty unless\r
- *      babel_init returned a nonzero value.\r
- *      The returned string will be the name of a babel format, possibly\r
- *      prefixed by "blorbed " to indicate that babel will process this file\r
- *      as a blorb.\r
- * int32 babel_treaty(int32 selector, void *output, void *output_extent)\r
- *      Dispatches the call to the treaty handler for the currently loaded\r
- *      file.\r
- *      When processing a blorb, all treaty calls will be deflected to the\r
- *      special blorb handler.  For the case of GET_STORY_FILE_IFID_SEL,\r
- *      The treaty handler for the underlying format will be called if an\r
- *      IFID is not found in the blorb resources.\r
- * void babel_release()\r
- *      Frees all resources allocated during babel_init.\r
- *      You should do this even if babel_init returned NULL.\r
- *      After this is called, do not call babel_treaty until after\r
- *      another successful call to babel_init.\r
- * char *babel_get_format()\r
- *      Returns the same value as the last call to babel_init (ie, the format name)\r
- * int32 babel_md5_ifid(char *buffer, int extent);\r
- *      Generates an MD5 IFID from the loaded story.  Returns zero if something\r
- *      went seriously wrong.\r
- *\r
- * If you wish to use babel in multiple threads, you must use the contextualized\r
- * versions of the above functions.\r
- * Each function above has a companion function whose name ends in _ctx (eg.\r
- * "babel_treaty_ctx") which takes one additional argument.  This argument is\r
- * the babel context. A new context is returned by void *ctx=get_babel_ctx(),\r
- * and should be released when finished by calling release_babel_ctx(ctx);\r
- */\r
-\r
-                      \r
-#include "treaty.h"\r
-#include <stdlib.h>\r
-#include <string.h>\r
-#include <stdio.h>\r
-#include <ctype.h>\r
-#include "md5.h"\r
-\r
-void *my_malloc(int, char *);\r
-\r
-struct babel_handler\r
-{\r
- TREATY treaty_handler;\r
- TREATY treaty_backup;\r
- void *story_file;\r
- int32 story_file_extent;\r
- void *story_file_blorbed;\r
- int32 story_file_blorbed_extent;\r
- char blorb_mode;\r
- char *format_name;\r
- char auth;\r
-};\r
-\r
-static struct babel_handler default_ctx;\r
-\r
-extern TREATY treaty_registry[];\r
-extern TREATY container_registry[];\r
-\r
-static char *deeper_babel_init(char *story_name, void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- int i;\r
- char *ext;\r
-\r
- static char buffer[TREATY_MINIMUM_EXTENT];\r
- int best_candidate;\r
- char buffert[TREATY_MINIMUM_EXTENT];\r
-\r
- if (story_name)\r
-  {\r
-   ext=strrchr(story_name,'.');\r
-   if (ext) for(i=0;ext[i];i++) ext[i]=tolower(ext[i]);\r
-  }\r
- else ext=NULL;\r
- best_candidate=-1;\r
- if (ext) /* pass 1: try best candidates */\r
-  for(i=0;container_registry[i];i++)\r
-   if (container_registry[i](GET_FILE_EXTENSIONS_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT) >=0 &&\r
-       strstr(buffer,ext) &&\r
-       container_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0)>=NO_REPLY_RV)\r
-    break;\r
-  if (!ext || !container_registry[i]) /* pass 2: try all candidates */\r
-  {\r
-  \r
-  for(i=0;container_registry[i];i++)\r
-   {int l=container_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0);\r
-    \r
-    if (l==VALID_STORY_FILE_RV)\r
-    break;\r
-    else if (l==NO_REPLY_RV && best_candidate < 0) best_candidate=i;\r
-    }\r
-}\r
- if (!container_registry[i] && best_candidate >=0) { bh->auth=0; i=best_candidate; }\r
- if (container_registry[i])\r
- {\r
-   char buffer2[TREATY_MINIMUM_EXTENT];\r
-   \r
-   bh->treaty_handler=container_registry[i];\r
-   container_registry[i](GET_FORMAT_NAME_SEL,NULL,0,buffert,TREATY_MINIMUM_EXTENT);\r
-   bh->blorb_mode=1;\r
-\r
-   bh->story_file_blorbed_extent=container_registry[i](CONTAINER_GET_STORY_EXTENT_SEL,bh->story_file,bh->story_file_extent,NULL,0);\r
-   if (bh->story_file_blorbed_extent>0) bh->story_file_blorbed=my_malloc(bh->story_file_blorbed_extent, "contained story file");\r
-   if (bh->story_file_blorbed_extent<=0 ||\r
-       container_registry[i](CONTAINER_GET_STORY_FORMAT_SEL,bh->story_file,bh->story_file_extent,buffer2,TREATY_MINIMUM_EXTENT)<0 ||\r
-       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\r
-      )\r
-    return NULL;\r
\r
-   for(i=0;treaty_registry[i];i++)\r
-    if (treaty_registry[i](GET_FORMAT_NAME_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT)>=0 &&\r
-        strcmp(buffer,buffer2)==0 &&\r
-        treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file_blorbed,bh->story_file_blorbed_extent,NULL,0)>=NO_REPLY_RV)\r
-     break;\r
-  if (!treaty_registry[i])\r
-   return NULL;\r
-  bh->treaty_backup=treaty_registry[i];\r
-  sprintf(buffer,"%sed %s",buffert,buffer2);\r
-  return buffer;\r
-  }\r
-\r
- bh->blorb_mode=0;\r
- best_candidate=-1;\r
-\r
- if (ext) /* pass 1: try best candidates */\r
-  for(i=0;treaty_registry[i];i++)\r
-   if (treaty_registry[i](GET_FILE_EXTENSIONS_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT) >=0 &&\r
-       strstr(buffer,ext) && \r
-       treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0)>=NO_REPLY_RV)\r
-    break;\r
-  if (!ext || !treaty_registry[i]) /* pass 2: try all candidates */\r
-  {\r
-  \r
-  for(i=0;treaty_registry[i];i++)\r
-   {int l;\r
-   l=treaty_registry[i](CLAIM_STORY_FILE_SEL,bh->story_file,bh->story_file_extent,NULL,0);\r
-\r
-    if (l==VALID_STORY_FILE_RV)\r
-    break;\r
-    else if (l==NO_REPLY_RV && best_candidate < 0) best_candidate=i;\r
-    }\r
-  }\r
-  if (!treaty_registry[i])\r
-   if (best_candidate>0) { i=best_candidate; bh->auth=0; }\r
-   else return NULL;\r
-  bh->treaty_handler=treaty_registry[i];\r
-\r
-  if (bh->treaty_handler(GET_FORMAT_NAME_SEL,NULL,0,buffer,TREATY_MINIMUM_EXTENT)>=0)\r
-  return buffer;\r
-  return NULL;\r
-\r
-\r
-}\r
-\r
-static char *deep_babel_init(char *story_name, void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- FILE *file;\r
-\r
- bh->treaty_handler=NULL;\r
- bh->treaty_backup=NULL;\r
- bh->story_file=NULL;\r
- bh->story_file_extent=0;\r
- bh->story_file_blorbed=NULL;\r
- bh->story_file_blorbed_extent=0;\r
- bh->format_name=NULL;\r
- file=fopen(story_name, "rb");\r
- if (!file) return NULL;\r
- fseek(file,0,SEEK_END);\r
- bh->story_file_extent=ftell(file);\r
- fseek(file,0,SEEK_SET);\r
- bh->auth=1; \r
- bh->story_file=my_malloc(bh->story_file_extent,"story file storage");\r
- fread(bh->story_file,1,bh->story_file_extent,file);\r
- fclose(file);\r
-\r
- return deeper_babel_init(story_name, bhp);\r
-}\r
-\r
-char *babel_init_ctx(char *sf, void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- char *b;\r
- b=deep_babel_init(sf,bh);\r
- if (b) bh->format_name=strdup(b);\r
- return b;\r
-}\r
-char *babel_init(char *sf)\r
-{\r
-  return babel_init_ctx(sf, &default_ctx);\r
-}\r
-\r
-char *babel_init_raw_ctx(void *sf, int32 extent, void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- char *b;\r
- bh->treaty_handler=NULL;\r
- bh->treaty_backup=NULL;\r
- bh->story_file=NULL;\r
- bh->story_file_extent=0;\r
- bh->story_file_blorbed=NULL;\r
- bh->story_file_blorbed_extent=0;\r
- bh->format_name=NULL;\r
- bh->story_file_extent=extent;\r
- bh->auth=1; \r
- bh->story_file=my_malloc(bh->story_file_extent,"story file storage");\r
- memcpy(bh->story_file,sf,extent);\r
-\r
- b=deeper_babel_init(NULL, bhp);\r
- if (b) bh->format_name=strdup(b);\r
- return b;\r
-}\r
-char *babel_init_raw(void *sf, int32 extent)\r
-{\r
-  return babel_init_raw_ctx(sf, extent, &default_ctx);\r
-}\r
-\r
-void babel_release_ctx(void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- if (bh->story_file) free(bh->story_file);\r
- bh->story_file=NULL;\r
- if (bh->story_file_blorbed) free(bh->story_file_blorbed);\r
- bh->story_file_blorbed=NULL;\r
- if (bh->format_name) free(bh->format_name);\r
- bh->format_name=NULL;\r
-}\r
-void babel_release()\r
-{\r
- babel_release_ctx(&default_ctx);\r
-}\r
-int32 babel_md5_ifid_ctx(char *buffer, int32 extent, void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- md5_state_t md5;\r
- int i;\r
- unsigned char ob[16];\r
- if (extent <33 || bh->story_file==NULL)\r
-  return 0;\r
- md5_init(&md5);\r
- md5_append(&md5,bh->story_file,bh->story_file_extent);\r
- md5_finish(&md5,ob);\r
- for(i=0;i<16;i++)\r
-  sprintf(buffer+(2*i),"%02X",ob[i]);\r
- buffer[32]=0;\r
- return 1;\r
-\r
-}\r
-int32 babel_md5_ifid(char *buffer, int32 extent)\r
-{\r
- return babel_md5_ifid_ctx(buffer, extent,\r
-                &default_ctx);\r
-}\r
-\r
-int32 babel_treaty_ctx(int32 sel, void *output, int32 output_extent,void *bhp)\r
-{\r
- int32 rv;\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- if (!(sel & TREATY_SELECTOR_INPUT) && bh->blorb_mode)\r
-  rv=bh->treaty_backup(sel,bh->story_file_blorbed,bh->story_file_blorbed_extent,output, output_extent);\r
- else\r
- {\r
-  rv=bh->treaty_handler(sel,bh->story_file,bh->story_file_extent,output,output_extent);\r
-  if ((!rv|| rv==UNAVAILABLE_RV) && bh->blorb_mode)\r
-   rv=bh->treaty_backup(sel,bh->story_file_blorbed,bh->story_file_blorbed_extent,output, output_extent);\r
-  }\r
- if (!rv && sel==GET_STORY_FILE_IFID_SEL)\r
-  return babel_md5_ifid_ctx(output,output_extent, bh);\r
- if (rv==INCOMPLETE_REPLY_RV && sel==GET_STORY_FILE_IFID_SEL)\r
-  return babel_md5_ifid_ctx((void *)((char *) output+strlen((char *)output)),\r
-                            output_extent-strlen((char *)output),\r
-                            bh);\r
-\r
- return rv;\r
-}\r
-int32 babel_treaty(int32 sel, void *output, int32 output_extent)\r
-{\r
- return babel_treaty_ctx(sel, output, output_extent, &default_ctx);\r
-}\r
-char *babel_get_format_ctx(void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- return bh->format_name;\r
-}\r
-char *babel_get_format()\r
-{\r
- return babel_get_format_ctx(&default_ctx);\r
-}\r
-void *get_babel_ctx()\r
-{\r
- return my_malloc(sizeof(struct babel_handler), "babel handler context");\r
-}\r
-void release_babel_ctx(void *b)\r
-{\r
- free(b);\r
-}\r
-\r
-int32 babel_get_length_ctx(void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- return bh->story_file_extent;\r
-}\r
-int32 babel_get_length()\r
-{\r
- return babel_get_length_ctx(&default_ctx);\r
-}\r
-\r
-int32 babel_get_authoritative_ctx(void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- return bh->auth;\r
-}\r
-int32 babel_get_authoritative()\r
-{\r
-  return babel_get_authoritative_ctx(&default_ctx);\r
-}\r
-void *babel_get_file_ctx(void *bhp)\r
-{\r
- struct babel_handler *bh=(struct babel_handler *) bhp;\r
- return bh->story_file;\r
-}\r
-void *babel_get_file()\r
-{\r
- return babel_get_file_ctx(&default_ctx);\r
-}\r
-\r
-int32 babel_get_story_length_ctx(void *ctx)\r
-{\r
-  struct babel_handler *bh=(struct babel_handler *) ctx;\r
-  if (bh->blorb_mode) return bh->story_file_blorbed_extent;\r
-  return bh->story_file_extent;\r
-}\r
-int32 babel_get_story_length()\r
-{\r
-\r
- return babel_get_story_length_ctx(&default_ctx);\r
-}\r
-void *babel_get_story_file_ctx(void *ctx)\r
-{\r
-  struct babel_handler *bh=(struct babel_handler *) ctx;\r
-  if (bh->blorb_mode) return bh->story_file_blorbed;\r
-  return bh->story_file;\r
-}\r
-void *babel_get_story_file()\r
-{\r
- return babel_get_story_file_ctx(&default_ctx);\r
-}\r
diff --git a/babel/babel_handler.h b/babel/babel_handler.h
deleted file mode 100644 (file)
index a01194a..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/* babel_handler.h  declarations for the babel handler API\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- */\r
-\r
-#ifndef BABEL_HANDLER_H\r
-#define BABEL_HANDLER_H\r
-\r
-#include "treaty.h"\r
-\r
-/* Functions from babel_handler.c */\r
-char *babel_init(char *filename);\r
- /* initialize the babel handler */\r
-char *babel_init_raw(void *sf, int32 extent);\r
- /* Initialize from loaded data */\r
-int32 babel_treaty(int32 selector, void *output, int32 output_extent);\r
- /* Dispatch treaty calls */\r
-void babel_release(void);\r
- /* Release babel_handler resources */\r
-char *babel_get_format(void);\r
- /* return the format of the loaded file */\r
-int32 babel_md5_ifid(char *buffer, int32 extent);\r
- /* IFID generator of last resort */\r
-int32 babel_get_length(void);\r
- /* Fetch file length */\r
-int32 babel_get_story_length(void);\r
- /* Fetch file length */\r
-int32 babel_get_authoritative(void);\r
- /* Determine if babel handler has a good grasp on the format */\r
-void *babel_get_file(void);\r
- /* Get loaded story file */\r
-void *babel_get_story_file(void);\r
- /* Get loaded story file */\r
-\r
-/* threadsafe versions of above */\r
-char *babel_init_ctx(char *filename, void *);\r
- /* initialize the babel handler */\r
-int32 babel_treaty_ctx(int32 selector, void *output, int32 output_extent, void *);\r
- /* Dispatch treaty calls */\r
-void babel_release_ctx(void *);\r
- /* Release babel_handler resources */\r
-char *babel_get_format_ctx(void *);\r
- /* return the format of the loaded file */\r
-int32 babel_md5_ifid_ctx(char *buffer, int extent, void *);\r
- /* IFID generator of last resort */\r
-int32 babel_get_length_ctx(void *);\r
-int32 babel_get_story_length_ctx(void *);\r
-void *babel_get_file_ctx(void *bhp);\r
-void *babel_get_story_ctx(void *bhp);\r
-int32 babel_get_authoritative_ctx(void *bhp);\r
-char *babel_init_raw_ctx(void *sf, int32 extent, void *bhp);\r
-void *get_babel_ctx(void);\r
-void release_babel_ctx(void *);\r
- /* get and release babel contexts */\r
-\r
-#endif\r
diff --git a/babel/babel_ifiction_functions.c b/babel/babel_ifiction_functions.c
deleted file mode 100644 (file)
index e59adf6..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-/* babel_ifiction_functions.c babel top-level operations for ifiction\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends upon babel.c (for rv), babel.h, misc.c and ifiction.c\r
- */\r
-\r
-#include "babel.h"\r
-#include <string.h>\r
-#include <stdlib.h>\r
-#include <stdio.h>\r
-#include <ctype.h>\r
-\r
-#ifndef THREE_LETTER_EXTENSIONS\r
-#define IFICTION_EXT ".iFiction"\r
-#else\r
-#define IFICTION_EXT ".ifi"\r
-#endif\r
-\r
-void *my_malloc(int, char *);\r
-\r
-struct IFiction_Info\r
-{\r
- char ifid[256];\r
- int wmode;\r
-};\r
-\r
-static void write_story_to_disk(struct XMLTag *xtg, void *ctx)\r
-{\r
- char *b, *ep;\r
- char *begin, *end;\r
- char buffer[TREATY_MINIMUM_EXTENT];\r
- int32 l, j;\r
- if (ctx) { }\r
-\r
- if (strcmp(xtg->tag,"story")==0)\r
- {\r
- begin=xtg->begin;\r
- end=xtg->end;\r
- l=end-begin+1;\r
- b=(char *)my_malloc(l,"XML buffer");\r
- memcpy(b,begin,l-1);\r
- b[l]=0;\r
- j=ifiction_get_IFID(b,buffer,TREATY_MINIMUM_EXTENT);\r
- if (!j)\r
- {\r
-  fprintf(stderr,"No IFID found for this story\n");\r
-  free(b);\r
-  return;\r
- }\r
- ep=strtok(buffer,",");\r
- while(ep)\r
- {\r
-  char buf2[256];\r
-  FILE *f;\r
-  sprintf(buf2,"%s%s",ep,IFICTION_EXT);\r
-  f=fopen(buf2,"w");\r
-\r
-  if (!f ||\r
-  fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\r
-        "<!-- Metadata extracted by Babel -->"\r
-        "<ifindex version=\"1.0\" xmlns=\"http://babel.ifarchive.org/protocol/iFiction/\">\n"\r
-        " <story>",\r
-        f)==EOF ||\r
-  fputs(b,f)==EOF ||\r
-  fputs("/<story>\n</ifindex>\n",f)==EOF\r
-  )\r
-  {\r
-   fprintf(stderr,"Error writing to file %s\n",buf2);\r
-  } else\r
-   printf("Extracted %s\n",buf2);\r
-  if (f) fclose(f);\r
-\r
-  ep=strtok(NULL,",");\r
- }\r
-\r
- free(b);\r
- }\r
-}\r
-\r
-void babel_ifiction_ifid(char *md)\r
-{\r
- char output[TREATY_MINIMUM_EXTENT];\r
- int i;\r
- char *ep;\r
- i=ifiction_get_IFID(md,output,TREATY_MINIMUM_EXTENT);\r
- if (!i)\r
-\r
- {\r
-  fprintf(stderr,"Error: No IFIDs found in iFiction file\n");\r
-  return;\r
- }\r
- ep=strtok(output,",");\r
- while(ep)\r
- {\r
-  printf("IFID: %s\n",ep);\r
-  ep=strtok(NULL,",");\r
- }\r
-\r
-}\r
-\r
-static char isok;\r
-\r
-static void examine_tag(struct XMLTag *xtg, void *ctx)\r
-{\r
- struct IFiction_Info *xti=(struct IFiction_Info *)ctx;\r
-\r
- if (strcmp("ifid",xtg->tag)==0 && strcmp(xti->ifid,"UNKNOWN")==0)\r
- {\r
- memcpy(xti->ifid,xtg->begin,xtg->end-xtg->begin);\r
- xti->ifid[xtg->end-xtg->begin]=0;\r
- }\r
-\r
-}\r
-static void verify_eh(char *e, void *ctx)\r
-{\r
- if (*((int *)ctx) < 0) return;\r
- if (*((int *)ctx) || strncmp(e,"Warning",7))\r
-  { isok=0;\r
-   fprintf(stderr, "%s\n",e);\r
-  }\r
-}\r
-\r
-\r
-\r
-void babel_ifiction_fish(char *md)\r
-{\r
- int i=-1;\r
- ifiction_parse(md,write_story_to_disk,NULL,verify_eh,&i);\r
-}\r
-\r
-void deep_ifiction_verify(char *md, int f)\r
-{\r
- struct IFiction_Info ii;\r
- int i=0;\r
- ii.wmode=0;\r
- isok=1;\r
- strcpy(ii.ifid,"UNKNOWN");\r
- ifiction_parse(md,examine_tag,&ii,verify_eh,&i);\r
- if (f&& isok) printf("Verified %s\n",ii.ifid);\r
-}\r
-void babel_ifiction_verify(char *md)\r
-{\r
- deep_ifiction_verify(md,1);\r
-\r
-}\r
-\r
-\r
-void babel_ifiction_lint(char *md)\r
-{\r
- struct IFiction_Info ii;\r
- int i=1;\r
- ii.wmode=1;\r
- isok=1;\r
- strcpy(ii.ifid,"UNKNOWN");\r
- ifiction_parse(md,examine_tag,&ii,verify_eh,&i);\r
- if (isok) printf("%s conforms to iFiction style guidelines\n",ii.ifid);\r
-}\r
-\r
-\r
diff --git a/babel/babel_multi_functions.c b/babel/babel_multi_functions.c
deleted file mode 100644 (file)
index cd867c6..0000000
+++ /dev/null
@@ -1,312 +0,0 @@
-#include "babel.h"\r
-\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <string.h>\r
-#include <ctype.h>\r
-\r
-#ifdef __cplusplus\r
-extern "C" {\r
-#endif\r
-int chdir(const char *);\r
-char *getcwd(char *, int);\r
-#ifdef __cplusplus\r
-}\r
-#endif\r
-\r
-void deep_ifiction_verify(char *md, int f);\r
-void * my_malloc(int32, char *);\r
-char *blorb_chunk_for_name(char *name);\r
-#ifndef THREE_LETTER_EXTENSIONS\r
-static char *ext_table[] = { "zcode", ".zblorb",\r
-                             "glulx", ".gblorb",\r
-                             NULL, NULL\r
-                             };\r
-#else\r
-static char *ext_table[] = { "zcode", ".zlb",\r
-                             "glulx", ".glb",\r
-                             NULL, NULL\r
-                             };\r
-\r
-#endif\r
-char *blorb_ext_for_name(char *fmt)\r
-{\r
- int i;\r
- for(i=0;ext_table[i];i+=2)\r
-  if (strcmp(ext_table[i],fmt)==0) return ext_table[i+1];\r
-#ifndef THREE_LETTER_EXTENSIONS\r
- return ".blorb";\r
-#else\r
- return ".blb";\r
-#endif\r
-}\r
-\r
-char *deep_complete_ifiction(char *fn, char *ifid, char *format)\r
-{\r
- FILE *f;\r
- int32 i;\r
- char *md;\r
- char *id, *idp;\r
- char *idb;\r
- f=fopen(fn,"r");\r
- if (!f) { fprintf(stderr,"Error: Can not open file %s\n",fn);\r
-          return NULL;\r
-          }\r
- fseek(f,0,SEEK_END);\r
- i=ftell(f);\r
- fseek(f,0,SEEK_SET);\r
- md=(char *) my_malloc(i+1,"Metadata buffer");\r
- fread(md,1,i,f);\r
- md[i]=0;\r
- id=strstr(md,"</ifindex>");\r
- if (id) *(id+10)=0;\r
- fclose(f);\r
- id=strdup(ifid);\r
- idp=strtok(id,",");\r
- /* Find the identification chunk */\r
- {\r
-   char *bp, *ep;\r
-   bp=strstr(md,"<identification>");\r
-   if (!bp)\r
-   {\r
-    idb=(char *)my_malloc(TREATY_MINIMUM_EXTENT+128,"ident buffer");\r
-    sprintf(idb,"<format>%s</format>\n", format);\r
-   }\r
-   else\r
-   {\r
-    int ii;\r
-    ep=strstr(bp,"</identification>");\r
-    idb=(char *)my_malloc(TREATY_MINIMUM_EXTENT+128+(ep-bp),"ident buffer");\r
-    for(ii=16;bp+ii<ep;ii++)\r
-    idb[ii-16]=bp[ii];\r
-    idb[ii]=0;\r
-    for(ep+=18;*ep;ep++)\r
-     *bp++=*ep;\r
-    *bp=0;\r
-    bp=strstr(idb,"<format>");\r
-    if (bp)\r
-     if (memcmp(bp+8,format,strlen(format)))\r
-      fprintf(stderr,"Error: Format in sparse .iFiction does not match story\n");\r
-\r
-   }\r
-\r
- }\r
- /* Insert the new ifids */\r
- while(idp)\r
- {\r
-  char bfr[TREATY_MINIMUM_EXTENT];\r
-  sprintf(bfr,"<ifid>%s</ifid>",idp);\r
-  if (!strstr(idb,bfr)) { strcat(idb,bfr); strcat(idb,"\n"); }\r
-  idp=strtok(NULL,",");\r
-\r
- }\r
- free(id);\r
- idp=(char *) my_malloc(strlen(md)+strlen(idb)+64, "Output metadata");\r
-/* printf("%d bytes for metadata\n",strlen(md)+strlen(idb)+64);*/\r
- id=strstr(md,"<story>");\r
- id[0]=0;\r
- id+=7;\r
- strcpy(idp,md);\r
- strcat(idp,"<story>\n <identification>\n");\r
- strcat(idp,idb);\r
- free(idb);\r
- strcat(idp," </identification>\n");\r
- strcat(idp,id);\r
- free(md);\r
- md=idp;\r
- deep_ifiction_verify(md, 0);\r
- return md;\r
-}\r
-\r
-void write_int(int32 i, FILE *f)\r
-{\r
- char bf[4];\r
- bf[0]=(((unsigned) i) >> 24) & 0xFF;\r
- bf[1]=(((unsigned) i) >> 16) & 0xFF;\r
- bf[2]=(((unsigned) i) >> 8) & 0xFF;\r
- bf[3]=(((unsigned) i)) & 0xFF;\r
- fwrite(bf,1,4,f);\r
-}\r
-static void _babel_multi_blorb(char *outfile, char **args, char *todir , int argc)\r
-{\r
- int32 total, storyl, coverl, i;\r
- char buffer[TREATY_MINIMUM_EXTENT+10];\r
- char b2[TREATY_MINIMUM_EXTENT];\r
-\r
- char cwd[512];\r
- char *cover, *md, *cvrf, *ep;\r
-\r
- FILE *f, *c;\r
- if (argc!=2 && argc !=3)\r
- {\r
-  fprintf(stderr,"Invalid usage\n");\r
-  return;\r
- }\r
- if (!babel_init(args[0]))\r
- {\r
-  fprintf(stderr,"Error: Could not determine the format of file %s\n",args[0]);\r
-  return;\r
- }\r
- if (babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT)<=0 ||\r
-     babel_treaty(GET_FORMAT_NAME_SEL,b2,TREATY_MINIMUM_EXTENT)<0\r
-    )\r
- {\r
-  fprintf(stderr,"Error: Could not deduce an IFID for file %s\n",args[0]);\r
-  return;\r
- }\r
- if (babel_get_length() != babel_get_story_length())\r
- {\r
-  fprintf(stderr,"Warning: Story file will be extacted from container before blorbing\n");\r
- }\r
-/* printf("Completing ifiction\n");*/\r
- md=deep_complete_ifiction(args[1],buffer,b2);\r
-/* printf("Ifiction is %d bytes long\n",strlen(md));*/\r
- ep=strchr(buffer,',');\r
- if (ep) *ep=0;\r
- if (outfile)\r
-  strcpy(buffer,outfile);\r
- strcat(buffer,blorb_ext_for_name(b2));\r
- getcwd(cwd,512);\r
- chdir(todir);\r
- f=fopen(buffer,"wb");\r
- chdir(cwd);\r
- if (!f)\r
- {\r
-  fprintf(stderr,"Error: Error writing to file %s\n",buffer);\r
-  return;\r
- }\r
- storyl=babel_get_story_length();\r
- total=storyl + (storyl%2) + 36;\r
- if (md) total+=8+strlen(md)+strlen(md)%2;\r
- if (argc==3)\r
- {\r
-  c=fopen(args[2],"rb");\r
-  if (c)\r
-  {\r
-   fseek(c,0,SEEK_END);\r
-   coverl=ftell(c);\r
-   if (coverl > 5){\r
-\r
-   cover=(char *) my_malloc(coverl+2,"Cover art buffer");\r
-   fseek(c,0,SEEK_SET);\r
-   fread(cover,1,coverl,c);\r
-   if (memcmp(cover+1,"PNG",3)==0) cvrf="PNG ";\r
-   else cvrf="JPEG";\r
-   total += 32+coverl + (coverl%2);\r
-   }\r
-   else argc=2;\r
-   fclose(c);\r
-  }\r
-  else argc=2;\r
- }\r
-/* printf("Writing header\n;");*/\r
- fwrite("FORM",1,4,f);\r
- write_int(total,f);\r
-/* printf("Writing index\n;");*/\r
- fwrite("IFRSRIdx",1,8,f);\r
- write_int(argc==3 ? 28:16,f);\r
- write_int(argc==3 ? 2:1,f);\r
-/* printf("Writing story\n;");*/\r
- fwrite("Exec", 1,4,f);\r
- write_int(0,f);\r
- write_int(argc==3 ? 48:36,f);\r
- if (argc==3)\r
- {\r
-/* printf("Writing image\n;"); */\r
- fwrite("Pict", 1,4,f);\r
- write_int(1,f);\r
- write_int(56+storyl+(storyl%2),f);\r
- }\r
-/* printf("Invoking chunk for name %s\n",b2); */\r
- fwrite(blorb_chunk_for_name(b2),1,4,f);\r
- write_int(storyl,f);\r
-/* printf("Writing story data\n"); */\r
- fwrite(babel_get_story_file(),1,storyl,f);\r
- if (storyl%2) fwrite("\0",1,1,f);\r
- if (argc==3)\r
- {\r
-/*  printf("Writing cover data header %s\n",cvrf); */\r
-  fwrite(cvrf,1,4,f);\r
-/*  printf("Writing cover data size %d\n",coverl); */\r
-  write_int(coverl,f);\r
-/*  printf("Writing cover data\n"); */\r
-  fwrite(cover,1,coverl,f);\r
-  if (coverl%2) fwrite("\0",1,1,f);\r
-/*  printf("Done with cover\n");*/\r
-/*  free(cover);*/\r
-/*  printf("Writing frontispiece\n;");*/\r
-  fwrite("Fspc\0\0\0\004\0\0\0\001",1,12,f);\r
- }\r
-\r
- if (md) {\r
-/*  printf("Writing metadata\n;");*/\r
- fwrite("IFmd",1,4,f);\r
- write_int(strlen(md),f);\r
- fwrite(md,1,strlen(md),f);\r
- if (strlen(md)%2)\r
-  fwrite("\0",1,1,f);\r
- free(md);\r
- }\r
-\r
- fclose(f);\r
- printf("Created %s\n",buffer);\r
\r
-}\r
-void babel_multi_complete(char **args, char *todir, int argc)\r
-{\r
- char buffer[TREATY_MINIMUM_EXTENT+10];\r
-  char b2[TREATY_MINIMUM_EXTENT];\r
- char cwd[512];\r
- char *ep, *md;\r
- FILE *f;\r
- if (argc!=2)\r
- {\r
-  fprintf(stderr,"Invalid usage\n");\r
-  return;\r
- }\r
- if (!babel_init(args[0]))\r
- {\r
-  fprintf(stderr,"Error: Could not determine the format of file %s\n",args[0]);\r
-  return;\r
- }\r
- if (babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT)<=0\r
-    ||  babel_treaty(GET_FORMAT_NAME_SEL,b2,TREATY_MINIMUM_EXTENT)<0)\r
- {\r
-  fprintf(stderr,"Error: Could not deduce an IFID for file %s\n",args[0]);\r
-  return;\r
- }\r
- md=deep_complete_ifiction(args[1],buffer, b2);\r
- if (!md) return;\r
- ep=strchr(buffer,',');\r
- if (ep) *ep=0;\r
- strcat(buffer,".iFiction");\r
- getcwd(cwd,512);\r
- chdir(todir);\r
- f=fopen(buffer,"w");\r
- chdir(cwd);\r
- if (!f || !fputs(md,f))\r
- {\r
-  fprintf(stderr,"Error: Error writing to file %s\n",buffer);\r
-  return;\r
- }\r
- fclose(f);\r
- free(md);\r
- printf("Created %s\n",buffer);\r
-}\r
-void babel_multi_blorb(char **args, char *todir , int argc)\r
-{\r
- _babel_multi_blorb(NULL,args,todir,argc);\r
-}\r
-void babel_multi_blorb1(char **args, char *todir , int argc)\r
-{\r
- char *buf;\r
- char *bb;\r
- buf=(char *)my_malloc(strlen(args[0])+1,"blorb name buffer");\r
- strcpy(buf,args[0]);\r
- bb=strrchr(buf,'.');\r
- if (bb) *bb=0;\r
- _babel_multi_blorb(buf,args,todir,argc);\r
- free(buf);\r
\r
-\r
-}\r
diff --git a/babel/babel_story_functions.c b/babel/babel_story_functions.c
deleted file mode 100644 (file)
index b1cf0e9..0000000
+++ /dev/null
@@ -1,411 +0,0 @@
-/* babel_story_functions.c babel top-level operations for story files\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends upon babel_handler.c, babel.h, and misc.c\r
- */\r
-\r
-#include "babel.h"\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <string.h>\r
-\r
-#ifndef THREE_LETTER_EXTENSIONS\r
-#define IFICTION_EXT ".iFiction"\r
-#else\r
-#define IFICTION_EXT ".ifi"\r
-#endif\r
-void *my_malloc(int32, char *);\r
-\r
-void babel_story_ifid()\r
-{\r
-  char buffer[TREATY_MINIMUM_EXTENT];\r
-  char *ep;\r
-  int i;\r
-  i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT);\r
-  ep=strtok(buffer, ",");\r
-  while(ep)\r
-  {\r
-   printf("IFID: %s\n",ep);\r
-   ep=strtok(NULL,",");\r
-  }\r
-  if (!i)\r
-    fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n");\r
-\r
-}\r
-\r
-\r
-void babel_story_format()\r
-{\r
-  char *b;\r
-  b=babel_get_format();\r
-  if (!b) b="unknown";\r
-  if (!babel_get_authoritative())\r
-   printf("Format: %s (non-authoritative)\n",b);   \r
-  else printf("Format: %s\n",b);\r
-}\r
-\r
-static void deep_babel_ifiction(char stopped)\r
-{\r
-  char buffer[TREATY_MINIMUM_EXTENT];\r
-  char *md;\r
-  char *ep;\r
-  int32 i;\r
-  FILE *f;\r
-\r
-  if (stopped!=2)\r
-  {\r
-  i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT);\r
-  if (i==0 && !babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT))\r
-  {\r
-   fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n");\r
-   return;\r
-  }\r
-\r
-\r
-  ep=strtok(buffer, ",");\r
-  }\r
-  else ep="-";\r
-  i=babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL,NULL,0);\r
-  if (i<=0)\r
-  {\r
-   if (stopped) printf("No iFiction record for %s\n",buffer);\r
-   return;\r
-  }\r
-  md=(char *)my_malloc(i,"Metadata buffer");\r
-  if (babel_treaty(GET_STORY_FILE_METADATA_SEL,md,i)<0)\r
-  {\r
-    fprintf(stderr,"A serious error occurred while retrieving metadata.\n");\r
-    free(md);\r
-    return;\r
-  }\r
-  while(ep)\r
-  {\r
-   char epb[TREATY_MINIMUM_EXTENT+9];\r
-   if (stopped!=2)\r
-   {\r
-   strcpy(epb,ep);\r
-   strcat(epb, IFICTION_EXT);\r
-\r
-   f=fopen(epb,"w");\r
-   }\r
-   else f=stdout;\r
-\r
-   if (!f || fputs(md,f)==EOF)\r
-    fprintf(stderr,"A serious error occurred writing to disk.\n");\r
-   else if (stopped!=2) printf("Extracted %s\n",epb);\r
-   if (f) fclose(f);\r
-   if (stopped) break;\r
-   ep=strtok(NULL,",");\r
-  }\r
-  free(md);\r
-}\r
-\r
-void babel_story_ifiction()\r
-{\r
- deep_babel_ifiction(1);\r
-}\r
-static char *get_jpeg_dim(void *img, int32 extent)\r
-{\r
-  unsigned char *dp=(unsigned char *) img;\r
-  unsigned char *ep=dp+extent;\r
-  static char buffer[256];\r
-  unsigned int t1, t2, w, h;\r
-\r
-\r
-  t1=*(dp++);\r
-  t2=*(dp++);\r
-  if (t1!=0xff || t2!=0xD8 )\r
-  {\r
-   return "(invalid)";\r
-  }\r
-\r
-  while(1)\r
-  {\r
-   if (dp>ep) return "(invalid)";\r
-   for(t1=*(dp++);t1!=0xff;t1=*(dp++)) if (dp>ep) return "(invalid)";\r
-   do { t1=*(dp++); if (dp>ep) return "(invalid 4)";} while (t1 == 0xff);\r
-\r
-   if ((t1 & 0xF0) == 0xC0 && !(t1==0xC4 || t1==0xC8 || t1==0xCC))\r
-   {\r
-    dp+=3;\r
-    if (dp>ep) return "(invalid)";\r
-    h=*(dp++) << 8;\r
-    if (dp>ep) return "(invalid)";\r
-    h|=*(dp++);\r
-    if (dp>ep) return "(invalid)";\r
-    w=*(dp++) << 8;\r
-    if (dp>ep) return "(invalid)";\r
-    w|=*(dp);\r
-    sprintf(buffer, "(%dx%d)",w,h);\r
-    return buffer;\r
-   }\r
-   else if (t1==0xD8 || t1==0xD9)\r
-    break;\r
-   else\r
-   { int l;\r
-\r
-    if (dp>ep) return "(invalid)";\r
-     l=*(dp++) << 8;\r
-    if (dp>ep) return "(invalid)";\r
-     l|= *(dp++);\r
-     l-=2;\r
-     dp+=l;\r
-     if (dp>ep) return "(invalid)";\r
-    }\r
-   }\r
-  return "(invalid)";\r
-}\r
-\r
-static int32 read_int(unsigned char  *mem)\r
-{\r
-  int32 i4 = mem[0],\r
-                    i3 = mem[1],\r
-                    i2 = mem[2],\r
-                    i1 = mem[3];\r
-  return i1 | (i2<<8) | (i3<<16) | (i4<<24);\r
-}\r
-\r
-\r
-static char *get_png_dim(void *img, int32 extent)\r
-{\r
- unsigned char *dp=(unsigned char *)img;\r
- static char buffer[256];\r
- int32 w, h;\r
- if (extent<33 ||\r
- !(dp[0]==137 && dp[1]==80 && dp[2]==78 && dp[3]==71 &&\r
-        dp[4]==13 && dp[5] == 10 && dp[6] == 26 && dp[7]==10)||\r
- !(dp[12]=='I' && dp[13]=='H' && dp[14]=='D' && dp[15]=='R'))\r
- return "(invalid)";\r
- w=read_int(dp+16);\r
- h=read_int(dp+20);\r
- sprintf(buffer,"(%dx%d)",w,h);\r
- return buffer;\r
-}\r
-static char *get_image_dim(void *img, int32 extent, int fmt)\r
-{\r
- if (fmt==JPEG_COVER_FORMAT) return get_jpeg_dim(img,extent);\r
- else if (fmt==PNG_COVER_FORMAT) return get_png_dim(img, extent);\r
- return "(unknown)";\r
-\r
-}\r
-static void deep_babel_cover(char stopped)\r
-{\r
-  char buffer[TREATY_MINIMUM_EXTENT];\r
-  void *md;\r
-  char *ep;\r
-  char *ext;\r
-  char *dim;\r
-  int32 i,j;\r
-  FILE *f;\r
-  i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT);\r
-  if (i==0)\r
-   if (babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT))\r
-    printf("IFID: %s\n",buffer);\r
-   else\r
-    {\r
-     fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n");\r
-     return;\r
-    }\r
-  else \r
-\r
-  ep=strtok(buffer, ",");\r
-  i=babel_treaty(GET_STORY_FILE_COVER_EXTENT_SEL,NULL,0);\r
-  j=babel_treaty(GET_STORY_FILE_COVER_FORMAT_SEL,NULL,0);\r
-\r
-  if (i<=0 || j<=0)\r
-  {\r
-   if (stopped) printf("No cover art for %s\n",buffer);\r
-   return;\r
-  }\r
-  if (j==PNG_COVER_FORMAT) ext=".png";\r
-  else if (j==JPEG_COVER_FORMAT) ext=".jpg";\r
-  md=my_malloc(i,"Image buffer");\r
-  if (babel_treaty(GET_STORY_FILE_COVER_SEL,md,i)<0)\r
-  {\r
-    fprintf(stderr,"A serious error occurred while retrieving cover art.\n");\r
-    free(md);\r
-    return;\r
-  }\r
-  dim=get_image_dim(md,i,j);\r
-  while(ep)\r
-  {\r
-   char epb[TREATY_MINIMUM_EXTENT+9];\r
-   strcpy(epb,ep);\r
-   strcat(epb, ext);\r
-\r
-   f=fopen(epb,"wb");\r
-   if (!f || fwrite(md,1,i,f)==EOF)\r
-    fprintf(stderr,"A serious error occurred writing to disk.\n");\r
-   else printf("Extracted %s %s\n",epb, dim);\r
-   if (f) fclose(f);\r
-   if (stopped) break;\r
-   ep=strtok(NULL,",");\r
-  }\r
-  free(md);\r
-}\r
-\r
-void babel_story_cover()\r
-{\r
- deep_babel_cover(1);\r
-}\r
-\r
-void babel_story_fish()\r
-{\r
- deep_babel_ifiction(0);\r
- deep_babel_cover(0);\r
-}\r
-\r
-static char *get_biblio(void)\r
-{\r
- int32 i;\r
- char *md;\r
- char *bib="No bibliographic data";\r
- char *bibb; char *bibe; \r
- char *t;\r
- static char buffer[TREATY_MINIMUM_EXTENT];\r
-\r
- i=babel_treaty(GET_STORY_FILE_METADATA_EXTENT_SEL,NULL,0);\r
- if (i<=0) return bib;\r
-\r
- md=(char *) my_malloc(i,"Metadata buffer");\r
- if (babel_treaty(GET_STORY_FILE_METADATA_SEL,md,i)<0) return bib;\r
\r
- bibb=strstr(md,"<bibliographic>");\r
- if (!bibb) { free(md); return bib; }\r
- bibe=strstr(bibb,"</bibliographic>");\r
- if (bibe) *bibe=0;\r
- t=strstr(bibb,"<title>");\r
- if (t)\r
- {\r
-  t+=7;\r
-  bibe=strstr(t,"</title>");\r
-  if (bibe)\r
-  {\r
-    *bibe=0;\r
-    bib=buffer;\r
-    for(i=0;t[i];i++) if (t[i]<0x20 || t[i]>0x7e) t[i]='_';\r
-    sprintf(buffer, "\"%s\" ",t);\r
-    *bibe='<';\r
-  }\r
-  else strcpy(buffer,"<no title found> ");\r
- }\r
- t=strstr(bibb,"<author>");\r
- if (t)\r
- {\r
-  t+=8;\r
-  bibe=strstr(t,"</author>");\r
-  if (bibe)\r
-  {\r
-    bib=buffer;\r
-    *bibe=0;\r
-    for(i=0;t[i];i++) if (t[i]<0x20 || t[i]>0x7e) t[i]='_';\r
-    strcat(buffer, "by ");\r
-    strcat(buffer, t);\r
-    *bibe='<';\r
-  }\r
-  else strcat(buffer, "<no author found>");\r
- }\r
- free(md);\r
- return bib;\r
-\r
-}\r
-void babel_story_identify()\r
-{\r
- int32 i, j, l;\r
- char *b, *cf, *dim;\r
- char buffer[TREATY_MINIMUM_EXTENT];\r
-\r
- printf("%s\n",get_biblio());\r
- babel_story_ifid();\r
- b=babel_get_format();\r
- if (!b) b="unknown";\r
- l=babel_get_length() / 1024;\r
\r
-\r
- i=babel_treaty(GET_STORY_FILE_COVER_EXTENT_SEL,NULL,0);\r
- j=babel_treaty(GET_STORY_FILE_COVER_FORMAT_SEL,NULL,0);\r
-\r
- if (i<=0 || j<=0)\r
- {\r
-  cf="no cover"; \r
- }\r
- else\r
- {\r
-  char *md=my_malloc(i,"Image buffer");\r
-  if (babel_treaty(GET_STORY_FILE_COVER_SEL,md,i)<0)\r
-  {\r
-   cf="no cover";\r
-  }\r
-  else\r
-  {\r
-   dim=get_image_dim(md,i,j)+1;\r
-   dim[strlen(dim)-1]=0;\r
-   if (j==JPEG_COVER_FORMAT) cf="jpeg";\r
-   else if (j==PNG_COVER_FORMAT) cf="png";\r
-   else cf="unknown format";\r
-   sprintf(buffer,"cover %s %s",dim,cf);\r
-   cf=buffer;\r
-  }\r
- }\r
- printf("%s, %dk, %s\n",b, l,cf);\r
-}\r
-\r
-void babel_story_meta()\r
-{\r
- deep_babel_ifiction(2);\r
-}\r
-\r
-void babel_story_story()\r
-{\r
-  int32 j,i;\r
-  void *p;\r
-  FILE *f;\r
-  char *ep;\r
-  char buffer[TREATY_MINIMUM_EXTENT+20];\r
-  j=babel_get_story_length();\r
-  p=babel_get_story_file();\r
-  if (!j || !p)\r
-  {\r
-    fprintf(stderr,"A serious error occurred while retrieving the story file.\n");\r
-    return;\r
-  }\r
-\r
-  i=babel_treaty(GET_STORY_FILE_IFID_SEL,buffer,TREATY_MINIMUM_EXTENT);\r
-  if (i==0 && !babel_md5_ifid(buffer, TREATY_MINIMUM_EXTENT))\r
-  {\r
-   fprintf(stderr,"Unable to create an IFID (A serious problem occurred while loading the file).\n");\r
-   return;\r
-  }\r
-  ep=strchr(buffer, ',');\r
-  if (!ep) ep=buffer+strlen(buffer);\r
-  *ep=0;\r
-  babel_treaty(GET_STORY_FILE_EXTENSION_SEL,ep,19);\r
-  f=fopen(buffer,"wb");\r
-  if (!f || !fwrite(p,1,j,f))\r
-    {\r
-     fprintf(stderr,"A serious error occurred writing to disk.\n");\r
-     return;\r
-    }\r
- fclose(f);\r
- printf("Extracted %s\n",buffer);\r
-\r
-  \r
-\r
-}\r
-\r
-void babel_story_unblorb()\r
-{\r
- deep_babel_ifiction(1);\r
- deep_babel_cover(1);\r
- babel_story_story();\r
-\r
-}\r
diff --git a/babel/blorb.c b/babel/blorb.c
deleted file mode 100644 (file)
index ebb84e4..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/* blorb.c      Babel interface to blorb files\r
- * Copyright 2006 by L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends upon treaty_builder.h, misc.c and ifiction.c\r
- *\r
- * Header note: to add support for new executable chunk types, see\r
- * TranslateExec.\r
- *\r
- * This file defines a Treaty of Babel compatable module for handling blorb\r
- * files.  However, blorb files are not themselves a babel format. This module\r
- * is used internally by the babel program to handle blorbs.\r
- *\r
- * As a result, if GET_STORY_FILE_IFID_SEL returns NO_REPLY_RV,\r
- * you should check the story file against the babel registry before resorting\r
- * to the default IFID calculation.\r
- *\r
- */\r
-#define FORMAT blorb\r
-#define HOME_PAGE "http://eblong.com/zarf/blorb"\r
-#define FORMAT_EXT ".blorb,.blb,.zblorb,.zlb,.gblorb,.glb"\r
-#define CONTAINER_FORMAT\r
-#include "treaty_builder.h"\r
-#include <stdlib.h>\r
-#include <ctype.h>\r
-\r
-extern TREATY treaty_registry[];\r
-/* The following is the translation table of Blorb chunk types to\r
-   babel formats.  it is NULL-terminated. */\r
-static char *TranslateExec[] = { "ZCOD", "zcode",\r
-                                 "GLUL", "glulx",\r
-                                 "TAD2", "tads2",\r
-                                 "TAD3", "tads3",\r
-                                 NULL, NULL };\r
-\r
-void *my_malloc(int32, char *);\r
-int32 ifiction_get_IFID(char *, char *, int32);\r
-\r
-static int32 read_int(void *inp)\r
-{\r
-  unsigned char *mem=(unsigned char *)inp;\r
-  int32 i4 = mem[0],\r
-                    i3 = mem[1],\r
-                    i2 = mem[2],\r
-                    i1 = mem[3];\r
-  return i1 | (i2<<8) | (i3<<16) | (i4<<24);\r
-}\r
-\r
-\r
-static int32 blorb_get_chunk(void *blorb_file, int32 extent, char *id, int32 *begin, int32 *output_extent)\r
-{\r
- int32 i=12, j;\r
- while(i<extent-8)\r
- {\r
-  if (memcmp(((char *)blorb_file)+i,id,4)==0)\r
-  {\r
-   *output_extent=read_int((char *)blorb_file+i+4);\r
-   if (*output_extent > extent) return NO_REPLY_RV;\r
-   *begin=i+8;\r
-   return 1;\r
-  }\r
-\r
-  j=read_int((char *)blorb_file+i+4);\r
-  if (j%2) j++;\r
-  i+=j+8;\r
-\r
- }\r
- return NO_REPLY_RV;\r
-}\r
-static int32 blorb_get_resource(void *blorb_file, int32 extent, char *rid, int32 number, int32 *begin, int32 *output_extent)\r
-{\r
- int32 ridx_len;\r
- int32 i,j;\r
- void *ridx;\r
- if (blorb_get_chunk(blorb_file, extent,"RIdx",&i,&ridx_len)==NO_REPLY_RV)\r
-  return NO_REPLY_RV;\r
-\r
- ridx=(char *)blorb_file+i+4;\r
- ridx_len=read_int((char *)blorb_file+i);\r
- for(i=0;i<ridx_len;i++)\r
- { \r
-  if(memcmp((char *)ridx+(i*12),rid,4)==0 && read_int((char *)ridx+(i*12)+4)==number)\r
-  {\r
-   j=i;\r
-   i=read_int((char *)ridx+(j*12)+8);\r
-   *begin=i+8;\r
-   *output_extent=read_int((char *)blorb_file+i+4);\r
-   return 1;\r
-  }\r
- }\r
- return NO_REPLY_RV;\r
-}\r
-static int32 blorb_get_story_file(void *blorb_file, int32 extent, int32 *begin, int32 *output_extent)\r
-{\r
- return blorb_get_resource(blorb_file, extent, "Exec", 0, begin, output_extent);\r
-\r
-}\r
-\r
-static int32 get_story_extent(void *blorb_file, int32 extent)\r
-{\r
- int32 i,j;\r
- if (blorb_get_resource(blorb_file, extent, "Exec", 0, &i, &j))\r
- {\r
-  return j;\r
- }\r
- return NO_REPLY_RV;\r
-\r
-}\r
-static int32 get_story_file(void *blorb_file, int32 extent, void *output, int32 output_extent)\r
-{\r
- int32 i,j;\r
- if (blorb_get_resource(blorb_file, extent, "Exec", 0, &i, &j))\r
- {\r
-  ASSERT_OUTPUT_SIZE(j);\r
-  memcpy(output,(char *)blorb_file+i,j);\r
-  return j;\r
- }\r
- return NO_REPLY_RV;\r
-\r
-}\r
-\r
-char *blorb_chunk_for_name(char *name)\r
-{\r
- static char buffer[5];\r
- int j;\r
- for(j=0;TranslateExec[j];j+=2)\r
-  if (strcmp(name,TranslateExec[j+1])==0) return TranslateExec[j];\r
- for(j=0;j<4 && name[j];j++) buffer[j]=toupper(buffer[j]);\r
- while(j<4) buffer[j++]=' ';\r
- buffer[4]=0;\r
- return buffer;\r
-\r
-}\r
-static char *blorb_get_story_format(void *blorb_file, int32 extent)\r
-{\r
- int32 i, j;\r
-\r
- for(j=0;treaty_registry[j];j++)\r
- {\r
-  static char fn[512];\r
-  treaty_registry[j](GET_FORMAT_NAME_SEL,NULL,0,fn,512);\r
-  if (blorb_get_chunk(blorb_file,extent,blorb_chunk_for_name(fn),&i, &i)) return fn;\r
- }\r
- return NULL;\r
-}\r
-\r
-static int32 get_story_format(void *blorb_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- char *o;\r
- o=blorb_get_story_format(blorb_file, extent);\r
- if (!o) return NO_REPLY_RV;\r
- ASSERT_OUTPUT_SIZE((signed) strlen(o)+1);\r
- strcpy(output,o);\r
- return strlen(o)+1;\r
-}\r
-static int32 get_story_file_metadata_extent(void *blorb_file, int32 extent)\r
-{\r
- int32 i,j;\r
- if (blorb_get_chunk(blorb_file,extent,"IFmd",&i,&j)) return j+1;\r
- return NO_REPLY_RV;\r
-}\r
-static int32 blorb_get_cover(void *blorb_file, int32 extent, int32 *begin, int32 *output_extent)\r
-{\r
- int i,j;\r
- if (blorb_get_chunk(blorb_file,extent,"Fspc",&i,&j))\r
- {\r
-  if (j<4) return NO_REPLY_RV;\r
-  i=read_int((char *)blorb_file+i);\r
-  if (!blorb_get_resource(blorb_file,extent,"Pict",i,&i,&j)) return NO_REPLY_RV;\r
-  *begin=i;\r
-  *output_extent=j;\r
-  if (memcmp((char *)blorb_file+i-8,"PNG ",4)==0) return PNG_COVER_FORMAT;\r
-  else if (memcmp((char *)blorb_file+i-8,"JPEG",4)==0) return JPEG_COVER_FORMAT;\r
- }\r
- return NO_REPLY_RV;\r
-\r
-}\r
-\r
-static int32 get_story_file_cover_extent(void *blorb_file, int32 extent)\r
-{\r
- int32 i,j;\r
- if (blorb_get_cover(blorb_file,extent,&i,&j)) return j;\r
- return NO_REPLY_RV;\r
-}\r
-\r
-static int32 get_story_file_cover_format(void *blorb_file, int32 extent)\r
-{\r
- int32 i,j;\r
- return blorb_get_cover(blorb_file, extent, &i,&j);\r
-}\r
-\r
-static int32 get_story_file_IFID(void *b, int32 e, char *output, int32 output_extent)\r
-{\r
- int32 j;\r
- char *md;\r
- j=get_story_file_metadata_extent(b,e);\r
- if (j<=0) return NO_REPLY_RV;\r
- md=(char *)my_malloc(j, "Metadata buffer");\r
- j=get_story_file_metadata(b,e,md,j);\r
- if (j<=0) return NO_REPLY_RV;\r
-\r
- j=ifiction_get_IFID(md,output,output_extent);\r
- free(md);\r
- return j;\r
-}\r
-\r
-static int32 get_story_file_metadata(void *blorb_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- int32 i,j;\r
- if (!blorb_get_chunk(blorb_file, extent,"IFmd",&i,&j)) return NO_REPLY_RV;\r
- ASSERT_OUTPUT_SIZE(j+1);\r
- memcpy(output,(char *)blorb_file+i,j);\r
- output[j]=0;\r
- return j+1;\r
-}\r
-static int32 get_story_file_cover(void *blorb_file, int32 extent, void *output, int32 output_extent)\r
-{\r
- int32 i,j;\r
- if (!blorb_get_cover(blorb_file, extent,&i,&j)) return NO_REPLY_RV;\r
- ASSERT_OUTPUT_SIZE(j);\r
- memcpy(output,(char *)blorb_file+i,j);\r
- return j;\r
-}\r
-\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- int i;\r
-\r
- if (extent<16 ||\r
-     memcmp(story_file,"FORM",4) ||\r
-     memcmp((char *)story_file+8,"IFRS",4) \r
-    ) i= INVALID_STORY_FILE_RV;\r
- else i= NO_REPLY_RV;\r
\r
- return i;\r
-\r
-}\r
diff --git a/babel/executable.c b/babel/executable.c
deleted file mode 100644 (file)
index ac0c403..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/* executable.c  Treaty of Babel module for Z-code files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT executable\r
-#define HOME_PAGE "http://http://en.wikipedia.org/wiki/Executable"\r
-#define FORMAT_EXT ".exe"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-static char elfmagic[] = { 0x7f, 0x45, 0x4c, 0x46, 0 };\r
-static char javamagic[] = { 0xCA, 0xFE, 0xBA, 0xBE, 0 };\r
-static char amigamagic[] = { 0, 0, 3, 0xe7, 0 };\r
-static char machomagic[] = { 0xFE, 0xED, 0xFA, 0xCE, 0};\r
-struct exetype\r
-{\r
- char *magic;\r
- char *name;\r
- int len;\r
-};\r
-static struct exetype magic[]= { { "MZ", "MZ", 2 },\r
-                                 { elfmagic, "ELF", 4 },\r
-                                 { javamagic, "JAVA", 4 },\r
-                                 { amigamagic, "AMIGA", 4 },\r
-                                 { "#! ", "SCRIPT", 3 },\r
-                                 { machomagic, "MACHO",4 },\r
-                                 { "APPL", "MAC",4 },\r
-                                 { NULL, NULL, 0 } };\r
-\r
-static char *deduce_magic(void *sf, int32 extent)\r
-{\r
- int i;\r
- for(i=0;magic[i].magic;i++)\r
- if (extent >= magic[i].len && memcmp(magic[i].magic,sf,magic[i].len)==0)\r
-  return magic[i].name;\r
- return NULL;\r
-}\r
-                                 \r
-static int32 claim_story_file(void *sf, int32 extent)\r
-{\r
- if (deduce_magic(sf,extent)) return VALID_STORY_FILE_RV;\r
- return NO_REPLY_RV;\r
-}\r
-static int32 get_story_file_IFID(void *sf, int32 extent, char *output, int32 output_extent)\r
-{\r
- char *o;\r
- o=deduce_magic(sf,extent);\r
- if (!o) return 0;\r
- ASSERT_OUTPUT_SIZE((signed) strlen(o)+2);\r
- strcpy(output,o);\r
- strcat(output,"-");\r
- return INCOMPLETE_REPLY_RV;\r
-}\r
diff --git a/babel/glulx.c b/babel/glulx.c
deleted file mode 100644 (file)
index ea1b3c4..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/* glulx.c  Treaty of Babel module for Glulx files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT glulx\r
-#define HOME_PAGE "http://eblong.com/zarf/glulx"\r
-#define FORMAT_EXT ".ulx"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-static int32 read_int(unsigned char  *mem)\r
-{\r
-  int32 i4 = mem[0],\r
-                    i3 = mem[1],\r
-                    i2 = mem[2],\r
-                    i1 = mem[3];\r
-  return i1 | (i2<<8) | (i3<<16) | (i4<<24);\r
-}\r
-\r
-\r
-\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- int32 i,j, k;\r
- char ser[7];\r
- char buffer[32];\r
-\r
-\r
- if (extent<256) return INVALID_STORY_FILE_RV;\r
- for(i=0;i<extent;i++) if (memcmp((char *)story_file+i,"UUID://",7)==0) break;\r
- if (i<extent) /* Found explicit IFID */\r
-  {\r
-   for(j=i+7;j<extent && ((char *)story_file)[j]!='/';j++);\r
-   if (j<extent)\r
-   {\r
-    i+=7;\r
-    ASSERT_OUTPUT_SIZE(j-i);\r
-    memcpy(output,(char *)story_file+i,j-i);\r
-    output[j-i]=0;\r
-    return 1;\r
-   }\r
-  }\r
-\r
- /* Did not find intact IFID.  Build one */\r
-\r
- j=read_int((unsigned char *)story_file+32);\r
- k=read_int((unsigned char *)story_file+12);\r
- if (memcmp((char *)story_file+36,"Info",4)==0)\r
- { /* Inform generated */\r
-  char *bb=(char *)story_file+52;\r
-  k= (int) bb[0]<<8 | (int) bb[1];\r
-  memcpy(ser,bb+2,6);\r
-  ser[6]=0;\r
-  for(i=0;i<6;i++) if (!isalnum(ser[i])) ser[i]='-';\r
-  sprintf(buffer,"GLULX-%u-%s-%04X",k,ser,j);\r
- }\r
- else\r
-  sprintf(buffer,"GLULX-%08X-%08X",k,j);\r
-\r
- ASSERT_OUTPUT_SIZE((signed) strlen(buffer)+1);\r
- strcpy((char *)output,buffer);\r
- return 1;\r
-\r
-}\r
-\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- if (extent<256 ||\r
-     memcmp(story_file,"Glul",4)\r
-    ) return INVALID_STORY_FILE_RV;\r
- return VALID_STORY_FILE_RV;\r
-}\r
diff --git a/babel/hugo.c b/babel/hugo.c
deleted file mode 100644 (file)
index 42ad4f3..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/* hugo.c  Treaty of Babel module for hugo files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT hugo\r
-#define HOME_PAGE "http://www.generalcoffee.com"\r
-#define FORMAT_EXT ".hex"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-static int32 get_story_file_IFID(void *s_file, int32 extent, char *output, int32 output_extent)\r
-{\r
-\r
- int32 i,j;\r
- char ser[9];\r
- char buffer[32];\r
- char *story_file = (char *) s_file;\r
-\r
-\r
- if (extent<0x0B) return INVALID_STORY_FILE_RV;\r
-\r
- for(i=0;i<extent;i++) if (memcmp((char *)story_file+i,"UUID://",7)==0) break;\r
- if (i<extent) /* Found explicit IFID */\r
-  {\r
-   for(j=i+7;j<extent && ((char *)story_file)[j]!='/';j++);\r
-   if (j<extent)\r
-   {\r
-    i+=7;\r
-    ASSERT_OUTPUT_SIZE(j-i);\r
-    memcpy(output,(char *)story_file+i,j-i);\r
-    output[j-i]=0;\r
-    return 1;\r
-   }\r
-  }\r
\r
- memcpy(ser, (char *) story_file+0x03, 8);\r
- ser[8]=0;\r
-\r
- for(j=0;j<8;j++)\r
-  if (!isalnum(ser[j])) ser[j]='-';\r
-\r
-\r
- sprintf(buffer,"HUGO-%d-%02X-%02X-%s",story_file[0],story_file[1], story_file[2],ser);\r
-\r
- ASSERT_OUTPUT_SIZE((signed) strlen(buffer)+1);\r
- strcpy((char *)output,buffer);\r
- return 1;\r
-}\r
-\r
-static int32 read_hugo_addx(unsigned char *from)\r
-{\r
- return ((unsigned int) from[0])| ((unsigned int)from[1] << 8);\r
-}\r
-\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- unsigned char *sf=(unsigned char *)story_file;\r
- int32 i;\r
- int32 scale;\r
-\r
- if (!story_file || extent < 0x28) return  INVALID_STORY_FILE_RV;\r
-\r
- if (sf[0]<34) scale=4;\r
- else scale=16;\r
- for(i=3;i<0x0B;i++) if (sf[i]<0x20 || sf[i]>0x7e) return INVALID_STORY_FILE_RV;\r
- for(i=0x0b;i<0x18;i+=2)\r
- if (read_hugo_addx(sf+i) * scale > extent) return INVALID_STORY_FILE_RV;\r
-\r
- return VALID_STORY_FILE_RV;\r
-}\r
diff --git a/babel/ifiction.a b/babel/ifiction.a
deleted file mode 100644 (file)
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 (file)
index cb620e7..0000000
+++ /dev/null
@@ -1,534 +0,0 @@
-/* ifiction.c  common babel interface for processing ifiction metadata\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends on treaty.h\r
- *\r
- * This file contains common routines for handling ifiction metadata strings\r
- *\r
- * int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent)\r
- * does what the babel treaty function GET_STORY_FILE_IFID_SEL would do for ifiction\r
- *\r
- * void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx,\r
- *                     IFErrorHandler error_handler, void *error_ctx)\r
- * parses the given iFiction metadata.  close_tag(struct XMLtag xtg, close_ctx)\r
- * is called for each tag as it is closed, error_handler(char *error, error_ctx)\r
- * is called each time a structural or logical error is found in the iFiction\r
- * This is a very simple XML parser, and probably not as good as any "real"\r
- * XML parser.  Its only two benefits are that (1) it's really small, and (2)\r
- * it strictly checks the ifiction record against the Treaty of Babel\r
- * requirements\r
- *\r
- */\r
-\r
-#include "ifiction.h"\r
-#include <string.h>\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <ctype.h>\r
-\r
-void *my_malloc(int, char *);\r
-extern char *format_registry[];\r
-\r
-\r
-static int32 llp;\r
-static char *lnlst;\r
-\r
-static char utfeol[3] = { 0xe2, 0x80, 0xa8 };\r
-static int32 getln(char *endp)\r
-{\r
- for(;lnlst<endp;lnlst++) if (*lnlst=='\n' || memcmp(lnlst,utfeol,3)==0) llp++;\r
- return llp;\r
-}\r
-\r
-\r
-static int32 ifiction_get_first_IFID(char *metadata, char *output, int32 output_extent)\r
-{\r
- char *ifid_begin, *ifid_end;\r
\r
- ifid_begin=strstr(metadata,"<ifid>");\r
- if (!ifid_begin) return NO_REPLY_RV;\r
- ifid_begin+=6;\r
-\r
- ifid_end=strstr(ifid_begin,"</ifid>");\r
- if (!ifid_end) return NO_REPLY_RV;\r
- if (output_extent<=(ifid_end-ifid_begin)) return INVALID_USAGE_RV;\r
-\r
- memcpy(output,ifid_begin,ifid_end-ifid_begin);\r
-\r
- output[ifid_end-ifid_begin]=0;\r
-\r
- return ifid_end-metadata+7;\r
-}\r
-\r
-\r
-int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent)\r
-{\r
- int32 j=0, k;\r
-\r
- while(*metadata)\r
- {\r
- if ((k=ifiction_get_first_IFID(metadata,output,output_extent)) <= 0) break;\r
- j++;\r
- metadata+=k;\r
- output_extent-=strlen(output)+1;\r
- output+=strlen(output);\r
- *output=',';\r
- output++;\r
- }\r
- if (*(output-1)==',') *(output-1)=0;\r
- return j;\r
-}\r
-\r
-\r
-static char *leaf_tags[] = { "ifid",\r
-                             "format",\r
-                             "bafn",\r
-                             "title",\r
-                             "author",\r
-                             "headline",\r
-                             "firstpublished",\r
-                             "genre",\r
-                             "group",\r
-                             "description",\r
-                             "leafname",\r
-                             "url",\r
-                             "authoremail",\r
-                             "height",\r
-                             "width",\r
-\r
-                             NULL\r
-                             };\r
-static char *one_per[] = { "identification",\r
-                           "bibliographic",\r
-                           "format",\r
-                           "title",\r
-                           "author",\r
-                           "headline",\r
-                           "firstpublished",\r
-                           "genre",\r
-                           "group",\r
-                           "description",\r
-                           "leafname",\r
-                           "height",\r
-                           "width",\r
-                           "forgiveness",\r
-                           "colophon",\r
-                           NULL\r
-                         };\r
-\r
-static char *required[] = {\r
-                "cover", "height",\r
-                "cover", "width",\r
-                "cover", "format",\r
-                "resources", "auxiliary",\r
-                "auxiliary", "leafname",\r
-                "auxiliary", "description",\r
-                "ifiction", "story",\r
-                "story", "identification",\r
-                "story", "bibliographic",\r
-                "identification", "ifid",\r
-                "identification", "format",\r
-                "bibliographic", "title",\r
-                "bibliographic", "author",\r
-                "colophon", "generator",\r
-                "colophon", "originated",\r
-                NULL, NULL\r
-                };\r
-static char *zarfian[] = {\r
-        "Merciful",\r
-        "Polite",\r
-        "Tough",\r
-        "Nasty",\r
-        "Cruel",\r
-        NULL\r
-        };\r
-\r
-struct ifiction_info {\r
-        int32 width;\r
-        int32 height;\r
-        int format;\r
-        };\r
-static void ifiction_validate_tag(struct XMLTag *xtg, struct ifiction_info *xti, IFErrorHandler err_h, void *ectx)\r
-{\r
- int i;\r
- char ebuf[512];\r
- struct XMLTag *parent=xtg->next;\r
- if (parent)\r
- {\r
- for(i=0;leaf_tags[i];i++)\r
-  if (strcmp(parent->tag,leaf_tags[i])==0)\r
-   {\r
-    sprintf(ebuf, "Error: (line %d) Tag <%s> is not permitted within tag <%s>",\r
-        xtg->beginl,xtg->tag,parent->tag);\r
-    err_h(ebuf,ectx);\r
-    }\r
- for(i=0;required[i];i+=2)\r
- if (strcmp(required[i],parent->tag)==0 && strcmp(required[i+1],xtg->tag)==0)\r
-  parent->rocurrences[i]=1;\r
- for(i=0;one_per[i];i++)\r
- if (strcmp(one_per[i],xtg->tag)==0)\r
-  if (parent->occurences[i]) { \r
-                               sprintf(ebuf,"Error: (line %d) Found more than one <%s> within <%s>",xtg->beginl,xtg->tag,\r
-                                        parent->tag);\r
-                               err_h(ebuf,ectx);\r
-                             }\r
-   else parent->occurences[i]=1;\r
- }\r
- for(i=0;required[i];i+=2)\r
- if (strcmp(required[i],xtg->tag)==0 && !xtg->rocurrences[i])\r
- {\r
-  sprintf(ebuf,"Error: (line %d) Tag <%s> is required within <%s>",xtg->beginl, required[i+1],xtg->tag);\r
-  err_h(ebuf,ectx);\r
- }\r
- if (parent && strcmp(parent->tag,"identification")==0)\r
- {\r
-  if (strcmp(xtg->tag,"format")==0)\r
-  {\r
-   int i;\r
-   for(i=0;format_registry[i];i++) if (memcmp(xtg->begin,format_registry[i],strlen(format_registry[i]))==0) break;\r
-   if (format_registry[i]) xti->format=i;\r
-   else\r
-   {\r
-    char bf[256];\r
-    memcpy(bf,xtg->begin,xtg->end-xtg->begin);\r
-    bf[xtg->end-xtg->begin]=0;\r
-    xti->format=-1;\r
-    sprintf(ebuf,"Warning: (line %d) Unknown format %s.",xtg->beginl,bf);\r
-    err_h(ebuf,ectx);\r
-   }\r
-  }\r
- }\r
- if (parent && strcmp(parent->tag,"cover")==0)\r
- {\r
- if (strcmp(xtg->tag,"width")==0)\r
- {\r
-  int i;\r
-  sscanf(xtg->begin,"%d",&i);\r
-  if (i<120)\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Cover art width should not be less than 120.",xtg->beginl);\r
-  err_h(ebuf,ectx);\r
-  }\r
-  if (i>1200)\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Cover art width should not exceed 1200.",xtg->beginl);\r
-  err_h(ebuf,ectx);\r
-  }\r
-  if (!xti->width) xti->width=i;\r
-  if (xti->height && (xti->width> 2 * xti->height || xti->height > 2 * xti->width))\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl);\r
-  err_h(ebuf,ectx);\r
-  }\r
-\r
- }\r
- if (strcmp(xtg->tag,"height")==0)\r
- {\r
-  int i;\r
-  sscanf(xtg->begin,"%d",&i);\r
-  if (i<120)\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Cover art height should not be less than 120.",xtg->beginl);\r
-  err_h(ebuf,ectx);\r
-  }\r
-  if (i>1200)\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Cover art height should not exceed 1200.",xtg->beginl);\r
-  err_h(ebuf,ectx);\r
-  }\r
-  if (!xti->height) xti->height=i;\r
-  if (xti->width && (xti->width> 2 * xti->height || xti->height > 2 * xti->width))\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl);\r
-  err_h(ebuf,ectx);\r
-  }\r
-\r
- }\r
- if (strcmp(xtg->tag,"format")==0 && memcmp(xtg->begin,"jpg",3) && memcmp(xtg->begin,"png",3))\r
- {\r
-  sprintf(ebuf,"Warning: (line %d) <format> should be one of: png, jpg.",xtg->beginl);\r
-  err_h(ebuf,ectx);\r
- }\r
- }\r
- if (parent && strcmp(parent->tag,"bibliographic")==0)\r
- {\r
-  char *p;\r
-  if (isspace(*xtg->begin)|| isspace(*(xtg->end-1)))\r
-   {\r
-    sprintf(ebuf,"Warning: (line %d) Extraneous spaces at beginning or end of tag <%s>.",xtg->beginl,xtg->tag);\r
-    err_h(ebuf,ectx);\r
-   }\r
-  for(p=xtg->begin;p<xtg->end-1;p++)\r
-/* Obsoleted by Revision 6\r
-  if (isspace(*p) && isspace(*(p+1)))\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Extraneous spaces found in tag <%s>.",xtg->beginl, xtg->tag);\r
-  err_h(ebuf,ectx);\r
-  }\r
-  else if (isspace(*p) && *p!=' ')\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Improper whitespace character found in tag <%s>.",xtg->beginl, xtg->tag);\r
-  err_h(ebuf,ectx);\r
-\r
-  }\r
-*/\r
- if (strcmp(xtg->tag, "description") && xtg->end-xtg->begin > 240)\r
- { \r
-  sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag);\r
-  err_h(ebuf,ectx);\r
- }\r
- if (strcmp(xtg->tag, "description")==0 && xtg->end-xtg->begin > 2400)\r
- {\r
-  sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag);\r
-  err_h(ebuf,ectx);\r
- }\r
- if (strcmp(xtg->tag,"firstpublished")==0)\r
- {\r
-  int l=xtg->end-xtg->begin;\r
-  if ((l!=4 && l!=10) ||\r
-      (!isdigit(xtg->begin[0]) ||\r
-       !isdigit(xtg->begin[1]) ||\r
-       !isdigit(xtg->begin[2]) ||\r
-       !isdigit(xtg->begin[3])) ||\r
-      (l==10 && ( xtg->begin[4]!='-' ||\r
-                  xtg->begin[7]!='-' ||\r
-                  !isdigit(xtg->begin[5]) ||\r
-                  !isdigit(xtg->begin[6]) ||\r
-                  !(xtg->begin[5]=='0' || xtg->begin[5]=='1') ||\r
-                  !(xtg->begin[5]=='0' || xtg->begin[6]<='2') ||\r
-                  !isdigit(xtg->begin[8]) ||\r
-                  !isdigit(xtg->begin[9]))))\r
-  {\r
-   sprintf(ebuf,"Warning: (line %d) Tag <%s> should be format YYYY or YYYY-MM-DD",xtg->beginl, xtg->tag);\r
-   err_h(ebuf,ectx);\r
-  }\r
- }\r
- if (strcmp(xtg->tag,"seriesnumber")==0)\r
- {\r
-  char *l;\r
-  if (*xtg->begin=='0' && xtg->end!=xtg->begin+1)\r
-  {\r
-   sprintf(ebuf,"Warning: (line %d) Tag <%s> should not use leading zeroes",xtg->beginl, xtg->tag);\r
-   err_h(ebuf,ectx);\r
-  }\r
-\r
-  for(l=xtg->begin;l<xtg->end;l++) if (!isdigit(*l))\r
-  {\r
-   sprintf(ebuf,"Warning: (line %d) Tag <%s> should be a positive number",xtg->beginl, xtg->tag);\r
-   err_h(ebuf,ectx);\r
-  }\r
- }\r
- if (strcmp(xtg->tag,"forgiveness")==0)\r
- {\r
-  int l;\r
-  for(l=0;zarfian[l];l++) if (memcmp(xtg->begin,zarfian[l],strlen(zarfian[l]))==0) break;\r
-  if (!zarfian[l])\r
-  {\r
-   sprintf(ebuf,"Warning: (line %d) <forgiveness> should be one of: Merciful, Polite, Tough, Cruel",xtg->beginl);\r
-   err_h(ebuf,ectx);\r
-  }\r
- }\r
- }\r
- if (xti->format>0)\r
- { \r
-  for(i=0;format_registry[i];i++) if (strcmp(xtg->tag,format_registry[i])==0) break;\r
-  if (format_registry[i] && xti->format !=i)\r
-  {\r
-  sprintf(ebuf,"Warning: (line %d) Found <%s> tag, but story is identified as %s.",xtg->beginl, xtg->tag, format_registry[xti->format]);\r
-  err_h(ebuf,ectx);\r
-  }\r
- }\r
- if (strcmp(xtg->tag,"story")==0)\r
- {\r
-  xti->format=-1;\r
-  xti->width=0;\r
-  xti->height=0;\r
- }\r
-\r
-}\r
-\r
-\r
-\r
-void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, IFErrorHandler error_handler, void *error_ctx)\r
-{\r
-char *xml, buffer[2400], *aep, *mda=md, ebuffer[512];\r
-struct XMLTag *parse=NULL, *xtg;\r
-struct ifiction_info xti;\r
-char BOM[3]={ 0xEF, 0xBB, 0xBF};\r
-xti.width=0;\r
-xti.height=0;\r
-xti.format=-1;\r
-llp=1;\r
-lnlst=md;\r
-\r
-while(*mda && isspace(*mda)) mda++;\r
-if (memcmp(mda,BOM,3)==0)\r
-{ mda+=3;\r
-  while(*mda && isspace(*mda)) mda++;\r
-}\r
-\r
-\r
-if (strncmp("<?xml version=\"1.0\" encoding=\"UTF-8\"?>",mda,\r
-        strlen("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"))\r
-    &&\r
-    strncmp("<?xml version=\"1.0\" encoding=\"utf-8\"?>",mda,\r
-        strlen("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"))\r
-   )\r
-{\r
- error_handler("Error: XML header not found.",error_ctx);\r
- return;\r
-}\r
-\r
-xml=strstr(md,"<ifindex");\r
-if (!xml) {\r
- error_handler("Error: <ifindex> not found",error_ctx);\r
- return;\r
- }\r
-while(xml && *xml)\r
-{\r
- char *bp, *ep, *tp;\r
- while(*xml&&*xml!='<') xml++;\r
- if (!*xml) break;\r
- bp=xml;\r
- tp=strchr(bp+1,'<');\r
- ep=strchr(bp+1,'>');\r
- if (!ep) break;\r
- if (tp && tp < ep)\r
-  { xml=tp; continue; }\r
- if (!tp) tp=ep+1; \r
- if (bp[1]=='/') /* end tag */\r
- {\r
-   strncpy(buffer,bp+2,(ep-bp)-2);\r
-   buffer[(ep-bp)-2]=0;\r
-   if (parse && strcmp(buffer,parse->tag)==0)\r
-   { /* copasetic. Close the tag */\r
-    xtg=parse;\r
-    parse=xtg->next;\r
-    xtg->end=ep-strlen(buffer)-2;\r
-    ifiction_validate_tag(xtg,&xti,error_handler, error_ctx);\r
-    close_tag(xtg,close_ctx);\r
-    free(xtg);\r
-   }\r
-   else\r
-   {\r
-    for(xtg=parse;xtg && strcmp(buffer,xtg->tag);xtg=xtg->next);\r
-    if (xtg) /* Intervening unclosed tags */\r
-    { for(xtg=parse;xtg && strcmp(buffer,parse->tag);xtg=parse)\r
-     {\r
-      xtg->end=xml-1;\r
-      parse=xtg->next;\r
-      sprintf(ebuffer,"Error: (line %d) unclosed <%s> tag",xtg->beginl,xtg->tag);\r
-      error_handler(ebuffer,error_ctx);\r
-      ifiction_validate_tag(xtg,&xti,error_handler, error_ctx);\r
-      close_tag(xtg,close_ctx);\r
-      free(xtg);\r
-     }\r
-     xtg=parse;\r
-     if (xtg)\r
-     {\r
-      xtg->end=xml-1;\r
-      parse=xtg->next;\r
-      ifiction_validate_tag(xtg,&xti, error_handler, error_ctx);\r
-      close_tag(xtg,close_ctx);\r
-      free(xtg);\r
-     }\r
-    }\r
-    else\r
-    { \r
-      sprintf(ebuffer,"Error: (line %d) saw </%s> without <%s>",getln(xml), buffer,buffer);\r
-      error_handler(ebuffer,error_ctx);\r
-    }\r
-   }\r
-\r
- }\r
- else if(*(ep-1)=='/' || bp[1]=='!') /* unterminated tag */\r
- {\r
-  /* Do nothing */\r
- }\r
- else /* Terminated tag beginning */\r
- {\r
-  int i;\r
-  xtg=(struct XMLTag *)my_malloc(sizeof(struct XMLTag),"XML Tag");\r
-  xtg->next=parse;\r
-  xtg->beginl=getln(bp);\r
-  for(i=0;bp[i+1]=='_' || bp[i+1]=='-' || isalnum(bp[i+1]);i++)\r
-   xtg->tag[i]=bp[i+1];\r
-  if (i==0)\r
-  { xml=tp;\r
-    free(xtg);\r
-    continue;\r
-  }\r
-  parse=xtg;\r
-  parse->tag[i]=0;\r
-  strncpy(parse->fulltag,bp+1,ep-bp-1);\r
-  parse->fulltag[ep-bp-1]=0;\r
-  parse->begin=ep+1;\r
- }\r
- xml=tp;\r
-}\r
- while (parse)\r
- {\r
-      xtg=parse;\r
-      xtg->end=aep-1;\r
-      parse=xtg->next;\r
-      sprintf(ebuffer,"Error: (line %d) Unclosed tag <%s>",xtg->beginl,xtg->tag);\r
-      ifiction_validate_tag(xtg,&xti,error_handler, error_ctx);\r
-      close_tag(xtg,close_ctx);\r
-      free(xtg);\r
- }\r
-}\r
-\r
-struct get_tag\r
-{\r
- char *tag;\r
- char *parent;\r
- char *output;\r
- char *target;\r
-};\r
-\r
-static void ifiction_null_eh(char *e, void *c)\r
-{\r
- if (e || c) { }\r
-\r
-}\r
-\r
-static void ifiction_find_value(struct XMLTag *xtg, void *xti)\r
-{\r
- struct get_tag *gt=(struct get_tag *)xti;\r
-\r
- if (gt->output && !gt->target) return;\r
- if (gt->target && gt->output && strcmp(gt->output,gt->target)==0) { gt->target=NULL; free(gt->output); gt->output=NULL; }\r
- if (((!xtg->next && !gt->parent) || (xtg->next && gt->parent && strcmp(xtg->next->tag,gt->parent)==0)) &&\r
-      strcmp(xtg->tag,gt->tag)==0)\r
- {\r
-  int32 l = xtg->end-xtg->begin;\r
-\r
-  if (gt->output) free(gt->output);\r
-  gt->output=(char *)my_malloc(l+1, "ifiction tag buffer");\r
-  memcpy(gt->output, xtg->begin, l);\r
-  gt->output[l]=0;\r
-\r
- }\r
-}\r
-\r
-\r
-char *ifiction_get_tag(char *md, char *p, char *t, char *from)\r
-{\r
- struct get_tag gt;\r
- gt.output=NULL;\r
- gt.parent=p;\r
- gt.tag=t;\r
- gt.target=from;\r
- ifiction_parse(md,ifiction_find_value,&gt,ifiction_null_eh,NULL);\r
- if (gt.target){ if (gt.output) free(gt.output); return NULL; }\r
- return gt.output;\r
-}\r
diff --git a/babel/ifiction.h b/babel/ifiction.h
deleted file mode 100644 (file)
index 75ae946..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/* ifiction.h  declarations for the babel ifiction API\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- */\r
-\r
-#ifndef IFICTION_H\r
-#define IFICTION_H\r
-\r
-#include "treaty.h"\r
-\r
-/* Babel's notion of an XML tag */\r
-struct XMLTag\r
-{\r
- int32 beginl;                  /* Beginning line number */\r
- char tag[256];                 /* name of the tag */\r
- char fulltag[256];             /* Full text of the opening tag */\r
- char *begin;                   /* Points to the beginning of the tag's content */\r
- char *end;                     /* Points to the end of the tag's content.\r
-                                   setting *end=0 will turn begin into a string\r
-                                   containing the tag's content (But if you do this, you\r
-                                   should restore the original value of *end before\r
-                                   allowing control to return to the ifiction parser) */\r
- char occurences[256];          /* Tables used internally to find missing required tags */\r
- char rocurrences[256];\r
- struct XMLTag *next;           /* The tag's parent */\r
-\r
-};\r
-\r
-typedef void (*IFCloseTag)(struct XMLTag *, void *);\r
-typedef void (*IFErrorHandler)(char *, void *);\r
-\r
-\r
-void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, IFErrorHandler error_handler, void *error_ctx);\r
-int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent);\r
-char *ifiction_get_tag(char *md, char *p, char *t, char *from);\r
-#endif\r
diff --git a/babel/level9.c b/babel/level9.c
deleted file mode 100644 (file)
index de855b0..0000000
+++ /dev/null
@@ -1,495 +0,0 @@
-/* level9.c  Treaty of Babel module for Level 9 files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * Note that this module will handle both bare Level 9 A-Code and\r
- * Spectrum .SNA snapshots.  It will not handle compressed .Z80 images.\r
- *\r
- * The Level 9 identification algorithm is based in part on the algorithm\r
- * used by Paul David Doherty's l9cut program.\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT level9\r
-#define HOME_PAGE "http://www.if-legends.org/~l9memorial/html/home.html"\r
-#define FORMAT_EXT ".l9,.sna"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-#include <string.h>\r
-\r
-struct l9rec {\r
-                int32 length;\r
-                unsigned char chk;\r
-                char *ifid;\r
-};\r
-\r
-\r
-static struct l9rec l9_registry[] = {\r
-      { 0x3a31, 0xe5, "LEVEL9-001-1" },\r
-      { 0x8333, 0xb7, "LEVEL9-001-1" },\r
-      { 0x7c6f, 0x0f, "LEVEL9-001-1" },\r
-      { 0x72fa, 0x8b, "LEVEL9-001-1" },\r
-      { 0x38dd, 0x31, "LEVEL9-001-A" },\r
-      { 0x39c0, 0x44, "LEVEL9-001-B" },\r
-      { 0x3a12, 0x8f, "LEVEL9-001-C" },\r
-      { 0x37f1, 0x77, "LEVEL9-001-2" },\r
-      { 0x844d, 0x50, "LEVEL9-001-2" },\r
-      { 0x738e, 0x5b, "LEVEL9-001-2" },\r
-      { 0x3900, 0x1c, "LEVEL9-001-3" },\r
-      { 0x8251, 0x5f, "LEVEL9-001-3" },\r
-      { 0x7375, 0xe5, "LEVEL9-001-3" },\r
-      { 0x3910, 0xac, "LEVEL9-001-4" },\r
-      { 0x7a78, 0x5e, "LEVEL9-001-4" },\r
-      { 0x78d5, 0xe3, "LEVEL9-001-4" },\r
-      { 0x3ad6, 0xa7, "LEVEL9-001-5" },\r
-      { 0x38a5, 0x0f, "LEVEL9-001-6" },\r
-      { 0x361e, 0x7e, "LEVEL9-001-7" },\r
-      { 0x3934, 0x75, "LEVEL9-001-8" },\r
-      { 0x3511, 0xcc, "LEVEL9-001-9" },\r
-      { 0x593a, 0xaf, "LEVEL9-002-1" },\r
-      { 0x7931, 0xb9, "LEVEL9-002-1" },\r
-      { 0x6841, 0x4a, "LEVEL9-002-1" },\r
-      { 0x57e6, 0x8a, "LEVEL9-002-2" },\r
-      { 0x7cdf, 0xa5, "LEVEL9-002-2" },\r
-      { 0x6bc0, 0x62, "LEVEL9-002-2" },\r
-      { 0x5819, 0xcd, "LEVEL9-002-3" },\r
-      { 0x7a0c, 0x97, "LEVEL9-002-3" },\r
-      { 0x692c, 0x21, "LEVEL9-002-3" },\r
-      { 0x579b, 0xad, "LEVEL9-002-4" },\r
-      { 0x7883, 0xe2, "LEVEL9-002-4" },\r
-      { 0x670a, 0x94, "LEVEL9-002-4" },\r
-      { 0x5323, 0xb7, "LEVEL9-003" },\r
-      { 0x6e60, 0x83, "LEVEL9-003" },\r
-      { 0x5b58, 0x50, "LEVEL9-003" },\r
-      { 0x63b6, 0x2e, "LEVEL9-003" },\r
-      { 0x6968, 0x32, "LEVEL9-003" },\r
-      { 0x5b50, 0x66, "LEVEL9-003" },\r
-      { 0x6970, 0xd6, "LEVEL9-003" },\r
-      { 0x5ace, 0x11, "LEVEL9-003" },\r
-      { 0x6e5c, 0xf6, "LEVEL9-003" },\r
-      { 0x1929, 0x00, "LEVEL9-004-DEMO" },\r
-      { 0x40e0, 0x02, "LEVEL9-004-DEMO" },\r
-      { 0x3ebb, 0x00, "LEVEL9-004-en" },\r
-      { 0x3e4f, 0x00, "LEVEL9-004-en" },\r
-      { 0x3e8f, 0x00, "LEVEL9-004-en" },\r
-      { 0x0fd8, 0x00, "LEVEL9-004-en" },\r
-      { 0x14a3, 0x00, "LEVEL9-004-en" },\r
-      { 0x110f, 0x00, "LEVEL9-004-fr" },\r
-      { 0x4872, 0x00, "LEVEL9-004-de" },\r
-      { 0x4846, 0x00, "LEVEL9-004-de" },\r
-      { 0x11f5, 0x00, "LEVEL9-004-de" },\r
-      { 0x11f5, 0x00, "LEVEL9-004-de" },\r
-      { 0x76f4, 0x5e, "LEVEL9-005" },\r
-      { 0x5b16, 0x3b, "LEVEL9-005" },\r
-      { 0x6c8e, 0xb6, "LEVEL9-005" },\r
-      { 0x6f4d, 0xcb, "LEVEL9-005" },\r
-      { 0x6f6a, 0xa5, "LEVEL9-005" },\r
-      { 0x5e31, 0x7c, "LEVEL9-005" },\r
-      { 0x6f70, 0x40, "LEVEL9-005" },\r
-      { 0x6f6e, 0x78, "LEVEL9-005" },\r
-      { 0x5a8e, 0xf2, "LEVEL9-005" },\r
-      { 0x76f4, 0x5a, "LEVEL9-005" },\r
-      { 0x630e, 0x8d, "LEVEL9-006" },\r
-      { 0x630e, 0xbe, "LEVEL9-006" },\r
-      { 0x6f0c, 0x95, "LEVEL9-006" },\r
-      { 0x593a, 0x80, "LEVEL9-006" },\r
-      { 0x6bd2, 0x65, "LEVEL9-006" },\r
-      { 0x6dc0, 0x63, "LEVEL9-006" },\r
-      { 0x58a6, 0x24, "LEVEL9-006" },\r
-      { 0x6de8, 0x4c, "LEVEL9-006" },\r
-      { 0x58a3, 0x38, "LEVEL9-006" },\r
-      { 0x63be, 0xd6, "LEVEL9-007" },\r
-      { 0x378c, 0x8d, "LEVEL9-007" },\r
-      { 0x63be, 0x0a, "LEVEL9-007" },\r
-      { 0x34b3, 0x20, "LEVEL9-008" },\r
-      { 0x34b3, 0xc7, "LEVEL9-008" },\r
-      { 0x34b3, 0x53, "LEVEL9-008" },\r
-      { 0xb1a9, 0x80, "LEVEL9-009-1" },\r
-      { 0x908e, 0x0d, "LEVEL9-009-1" },\r
-      { 0xad41, 0xa8, "LEVEL9-009-1" },\r
-      { 0xb1aa, 0xad, "LEVEL9-009-1" },\r
-      { 0x8aab, 0xc0, "LEVEL9-009-1" },\r
-      { 0xb0ec, 0xc2, "LEVEL9-009-1" },\r
-      { 0xb19e, 0x92, "LEVEL9-009-1" },\r
-      { 0x5ff0, 0xf8, "LEVEL9-009-1" },\r
-      { 0x52aa, 0xdf, "LEVEL9-009-1" },\r
-      { 0xab9d, 0x31, "LEVEL9-009-2" },\r
-      { 0x8f6f, 0x0a, "LEVEL9-009-2" },\r
-      { 0xa735, 0xf7, "LEVEL9-009-2" },\r
-      { 0xab8b, 0xbf, "LEVEL9-009-2" },\r
-      { 0x8ac8, 0x9a, "LEVEL9-009-2" },\r
-      { 0xaf82, 0x83, "LEVEL9-009-2" },\r
-      { 0x6024, 0x01, "LEVEL9-009-2" },\r
-      { 0x6ffa, 0xdb, "LEVEL9-009-2" },\r
-      { 0xae28, 0x87, "LEVEL9-009-3" },\r
-      { 0x9060, 0xbb, "LEVEL9-009-3" },\r
-      { 0xa9c0, 0x9e, "LEVEL9-009-3" },\r
-      { 0xae16, 0x81, "LEVEL9-009-3" },\r
-      { 0x8a93, 0x4f, "LEVEL9-009-3" },\r
-      { 0xb3e6, 0xab, "LEVEL9-009-3" },\r
-      { 0x6036, 0x3d, "LEVEL9-009-3" },\r
-      { 0x723a, 0x69, "LEVEL9-009-3" },\r
-      { 0xd188, 0x13, "LEVEL9-010-1" },\r
-      { 0x9089, 0xce, "LEVEL9-010-1" },\r
-      { 0xb770, 0x03, "LEVEL9-010-1" },\r
-      { 0xd19b, 0xad, "LEVEL9-010-1" },\r
-      { 0x8ab7, 0x68, "LEVEL9-010-1" },\r
-      { 0xd183, 0x83, "LEVEL9-010-1" },\r
-      { 0x5a38, 0xf7, "LEVEL9-010-1" },\r
-      { 0x76a0, 0x3a, "LEVEL9-010-1" },\r
-      { 0xc594, 0x03, "LEVEL9-010-2" },\r
-      { 0x908d, 0x80, "LEVEL9-010-2" },\r
-      { 0xb741, 0xb6, "LEVEL9-010-2" },\r
-      { 0xc5a5, 0xfe, "LEVEL9-010-2" },\r
-      { 0x8b1e, 0x84, "LEVEL9-010-2" },\r
-      { 0xc58f, 0x65, "LEVEL9-010-2" },\r
-      { 0x531a, 0xed, "LEVEL9-010-2" },\r
-      { 0x7674, 0x0b, "LEVEL9-010-2" },\r
-      { 0xd79f, 0xb5, "LEVEL9-010-3" },\r
-      { 0x909e, 0x9f, "LEVEL9-010-3" },\r
-      { 0xb791, 0xa1, "LEVEL9-010-3" },\r
-      { 0xd7ae, 0x9e, "LEVEL9-010-3" },\r
-      { 0x8b1c, 0xa8, "LEVEL9-010-3" },\r
-      { 0xd79a, 0x57, "LEVEL9-010-3" },\r
-      { 0x57e4, 0x19, "LEVEL9-010-3" },\r
-      { 0x765e, 0xba, "LEVEL9-010-3" },\r
-      { 0xbb93, 0x36, "LEVEL9-011-1" },\r
-      { 0x898a, 0x43, "LEVEL9-011-1" },\r
-      { 0x8970, 0x6b, "LEVEL9-011-1" },\r
-      { 0xbb6e, 0xa6, "LEVEL9-011-1" },\r
-      { 0x86d0, 0xb7, "LEVEL9-011-1" },\r
-      { 0xbb6e, 0xad, "LEVEL9-011-1" },\r
-      { 0x46ec, 0x64, "LEVEL9-011-1" },\r
-      { 0x74e0, 0x92, "LEVEL9-011-1" },\r
-      { 0xc58e, 0x4a, "LEVEL9-011-2" },\r
-      { 0x8b9f, 0x61, "LEVEL9-011-2" },\r
-      { 0x8b90, 0x4e, "LEVEL9-011-2" },\r
-      { 0xc58e, 0x43, "LEVEL9-011-2" },\r
-      { 0x8885, 0x22, "LEVEL9-011-2" },\r
-      { 0x6140, 0x18, "LEVEL9-011-2" },\r
-      { 0x6dbc, 0x97, "LEVEL9-011-2" },\r
-      { 0xcb9a, 0x0f, "LEVEL9-011-3" },\r
-      { 0x8af9, 0x61, "LEVEL9-011-3" },\r
-      { 0x8aea, 0x4e, "LEVEL9-011-3" },\r
-      { 0xcb9a, 0x08, "LEVEL9-011-3" },\r
-      { 0x87e5, 0x0e, "LEVEL9-011-3" },\r
-      { 0x640e, 0xc1, "LEVEL9-011-3" },\r
-      { 0x7402, 0x07, "LEVEL9-011-3" },\r
-      { 0xbba4, 0x94, "LEVEL9-012-1" },\r
-      { 0xc0cf, 0x4e, "LEVEL9-012-1" },\r
-      { 0x8afc, 0x07, "LEVEL9-012-1" },\r
-      { 0x8feb, 0xba, "LEVEL9-012-1" },\r
-      { 0xb4c9, 0x94, "LEVEL9-012-1" },\r
-      { 0xc0bd, 0x57, "LEVEL9-012-1" },\r
-      { 0x8ade, 0xf2, "LEVEL9-012-1" },\r
-      { 0x4fd2, 0x9d, "LEVEL9-012-1" },\r
-      { 0x5c7a, 0x44, "LEVEL9-012-1" },\r
-      { 0x768c, 0xe8, "LEVEL9-012-1" },\r
-      { 0xd0c0, 0x56, "LEVEL9-012-2" },\r
-      { 0xd5e9, 0x6a, "LEVEL9-012-2" },\r
-      { 0x8aec, 0x13, "LEVEL9-012-2" },\r
-      { 0x8f6b, 0xfa, "LEVEL9-012-2" },\r
-      { 0xb729, 0x51, "LEVEL9-012-2" },\r
-      { 0xd5d7, 0x99, "LEVEL9-012-2" },\r
-      { 0x8b0e, 0xfb, "LEVEL9-012-2" },\r
-      { 0x4dac, 0xa8, "LEVEL9-012-2" },\r
-      { 0x53a2, 0x1e, "LEVEL9-012-2" },\r
-      { 0x76b0, 0x1d, "LEVEL9-012-2" },\r
-      { 0xb6ac, 0xc6, "LEVEL9-012-3" },\r
-      { 0xbb8f, 0x1a, "LEVEL9-012-3" },\r
-      { 0x8aba, 0x0d, "LEVEL9-012-3" },\r
-      { 0x8f71, 0x2f, "LEVEL9-012-3" },\r
-      { 0xb702, 0xe4, "LEVEL9-012-3" },\r
-      { 0xbb7d, 0x17, "LEVEL9-012-3" },\r
-      { 0x8ab3, 0xc1, "LEVEL9-012-3" },\r
-      { 0x4f96, 0x22, "LEVEL9-012-3" },\r
-      { 0x5914, 0x22, "LEVEL9-012-3" },\r
-      { 0x765e, 0x4f, "LEVEL9-012-3" },\r
-      { 0x5eb9, 0x30, "LEVEL9-013" },\r
-      { 0x5eb9, 0x5d, "LEVEL9-013" },\r
-      { 0x5eb9, 0x6e, "LEVEL9-013" },\r
-      { 0xb257, 0xf8, "LEVEL9-013" },\r
-      { 0xb576, 0x2a, "LEVEL9-013" },\r
-      { 0x8d78, 0x3a, "LEVEL9-013" },\r
-      { 0x9070, 0x43, "LEVEL9-013" },\r
-      { 0xb38c, 0x37, "LEVEL9-013" },\r
-      { 0xb563, 0x6a, "LEVEL9-013" },\r
-      { 0xb57c, 0x44, "LEVEL9-013" },\r
-      { 0xb260, 0xe5, "LEVEL9-013" },\r
-      { 0x8950, 0xa1, "LEVEL9-013" },\r
-      { 0xb579, 0x89, "LEVEL9-013" },\r
-      { 0x579e, 0x97, "LEVEL9-013" },\r
-      { 0x69fe, 0x56, "LEVEL9-013" },\r
-      { 0x6f1e, 0xda, "LEVEL9-013" },\r
-      { 0x5671, 0xbc, "LEVEL9-014" },\r
-      { 0x6fc6, 0x14, "LEVEL9-014" },\r
-      { 0x5aa4, 0xc1, "LEVEL9-014" },\r
-      { 0x7410, 0x5e, "LEVEL9-014" },\r
-      { 0x5aa4, 0xc1, "LEVEL9-014" },\r
-      { 0x5aa4, 0xc1, "LEVEL9-014" },\r
-      { 0xb797, 0x1f, "LEVEL9-014" },\r
-      { 0xbaca, 0x3a, "LEVEL9-014" },\r
-      { 0x8c46, 0xf0, "LEVEL9-014" },\r
-      { 0x8f51, 0xb2, "LEVEL9-014" },\r
-      { 0xb451, 0xa8, "LEVEL9-014" },\r
-      { 0xbab2, 0x87, "LEVEL9-014" },\r
-      { 0xbac7, 0x7f, "LEVEL9-014" },\r
-      { 0xb7a0, 0x7e, "LEVEL9-014" },\r
-      { 0x8a60, 0x2a, "LEVEL9-014" },\r
-      { 0xbac4, 0x80, "LEVEL9-014" },\r
-      { 0x579a, 0x2a, "LEVEL9-014" },\r
-      { 0x5a50, 0xa9, "LEVEL9-014" },\r
-      { 0x6108, 0xdd, "LEVEL9-014" },\r
-      { 0x506c, 0xf0, "LEVEL9-015" },\r
-      { 0x505d, 0x32, "LEVEL9-015" },\r
-      { 0xa398, 0x82, "LEVEL9-015" },\r
-      { 0xa692, 0xd1, "LEVEL9-015" },\r
-      { 0x8d56, 0xd3, "LEVEL9-015" },\r
-      { 0x903f, 0x6b, "LEVEL9-015" },\r
-      { 0xa4e2, 0xa6, "LEVEL9-015" },\r
-      { 0xa67c, 0xb8, "LEVEL9-015" },\r
-      { 0xa69e, 0x6c, "LEVEL9-015" },\r
-      { 0xa3a4, 0xdf, "LEVEL9-015" },\r
-      { 0x8813, 0x11, "LEVEL9-015" },\r
-      { 0xa698, 0x41, "LEVEL9-015" },\r
-      { 0x5500, 0x50, "LEVEL9-015" },\r
-      { 0x6888, 0x8d, "LEVEL9-015" },\r
-      { 0x6da0, 0xb8, "LEVEL9-015" },\r
-      { 0x6064, 0xbd, "LEVEL9-016" },\r
-      { 0x6064, 0x01, "LEVEL9-016" },\r
-      { 0x6047, 0x6c, "LEVEL9-016" },\r
-      { 0x6064, 0xda, "LEVEL9-016" },\r
-      { 0x6064, 0x95, "LEVEL9-016" },\r
-      { 0x60c4, 0x28, "LEVEL9-016" },\r
-      { 0x5cb7, 0xfe, "LEVEL9-016" },\r
-      { 0x5ca1, 0x33, "LEVEL9-016" },\r
-      { 0x5cb7, 0x64, "LEVEL9-016" },\r
-      { 0x7d16, 0xe6, "LEVEL9-016" },\r
-      { 0x639c, 0x8b, "LEVEL9-016" },\r
-      { 0x60f7, 0x68, "LEVEL9-016" },\r
-      { 0x772f, 0xca, "LEVEL9-016" },\r
-      { 0x7cff, 0xf8, "LEVEL9-016" },\r
-      { 0x7cf8, 0x24, "LEVEL9-016" },\r
-      { 0x7d14, 0xe8, "LEVEL9-016" },\r
-      { 0x7c55, 0x18, "LEVEL9-016" },\r
-      { 0x5f43, 0xca, "LEVEL9-016" },\r
-      { 0xc132, 0x14, "LEVEL9-017-1" },\r
-      { 0xbeab, 0x2d, "LEVEL9-017-1" },\r
-      { 0x9058, 0xcf, "LEVEL9-017-1" },\r
-      { 0xbe94, 0xcc, "LEVEL9-017-1" },\r
-      { 0x8a21, 0xf4, "LEVEL9-017-1" },\r
-      { 0x55ce, 0xa1, "LEVEL9-017-1" },\r
-      { 0x5cbc, 0xa5, "LEVEL9-017-1" },\r
-      { 0x762e, 0x82, "LEVEL9-017-1" },\r
-      { 0x99bd, 0x65, "LEVEL9-017-2" },\r
-      { 0x8f43, 0xc9, "LEVEL9-017-2" },\r
-      { 0x8a12, 0xe3, "LEVEL9-017-2" },\r
-      { 0x54a6, 0xa9, "LEVEL9-017-2" },\r
-      { 0x5932, 0x4e, "LEVEL9-017-2" },\r
-      { 0x5bd6, 0x35, "LEVEL9-017-2" },\r
-      { 0xbcb6, 0x7a, "LEVEL9-017-3 (Amiga/PC/ST)" },\r
-      { 0x90ac, 0x68, "LEVEL9-017-3" },\r
-      { 0x8a16, 0xcc, "LEVEL9-017-3" },\r
-      { 0x51bc, 0xe3, "LEVEL9-017-3" },\r
-      { 0x5860, 0x95, "LEVEL9-017-3" },\r
-      { 0x6fa8, 0xa4, "LEVEL9-017-3" },\r
-      { 0x5fab, 0x5c, "LEVEL9-018" },\r
-      { 0x5fab, 0x2f, "LEVEL9-018" },\r
-      { 0x7b31, 0x6e, "LEVEL9-018" },\r
-      { 0x67a3, 0x9d, "LEVEL9-018" },\r
-      { 0x6bf8, 0x3f, "LEVEL9-018" },\r
-      { 0x7363, 0x65, "LEVEL9-018" },\r
-      { 0x7b2f, 0x70, "LEVEL9-018" },\r
-      { 0x7b2f, 0x70, "LEVEL9-018" },\r
-      { 0x6541, 0x02, "LEVEL9-018" },\r
-      { 0x5834, 0x42, "LEVEL9-019-1" },\r
-      { 0x765d, 0xcd, "LEVEL9-019-1" },\r
-      { 0x6ce5, 0x58, "LEVEL9-019-1" },\r
-      { 0x56dd, 0x51, "LEVEL9-019-2" },\r
-      { 0x6e58, 0x07, "LEVEL9-019-2" },\r
-      { 0x68da, 0xc1, "LEVEL9-019-2" },\r
-      { 0x5801, 0x53, "LEVEL9-019-3" },\r
-      { 0x7e98, 0x6a, "LEVEL9-019-3" },\r
-      { 0x6c67, 0x9a, "LEVEL9-019-3" },\r
-      { 0x54a4, 0x01, "LEVEL9-019-4" },\r
-      { 0x81e2, 0xd5, "LEVEL9-019-4" },\r
-      { 0x6d91, 0xb9, "LEVEL9-019-4" },\r
-      { 0x5828, 0xbd, "LEVEL9-020" },\r
-      { 0x6d84, 0xf9, "LEVEL9-020" },\r
-      { 0x6d84, 0xc8, "LEVEL9-020" },\r
-      { 0x6030, 0x47, "LEVEL9-020" },\r
-      { 0x772b, 0xcd, "LEVEL9-020" },\r
-      { 0x546c, 0xb7, "LEVEL9-020" },\r
-      { 0x7cd9, 0x0c, "LEVEL9-020" },\r
-      { 0x60dd, 0xf2, "LEVEL9-020" },\r
-      { 0x6161, 0xf3, "LEVEL9-020" },\r
-      { 0x788d, 0x72, "LEVEL9-020" },\r
-      { 0x7cd7, 0x0e, "LEVEL9-020" },\r
-      { 0x5ebb, 0xf1, "LEVEL9-020" },\r
-\r
-      { 0, 0, NULL }\r
-};\r
-\r
-\r
-\r
-static int32 read_l9_int(unsigned char *sf)\r
-{\r
- return ((int32) sf[1]) << 8 | sf[0];\r
-\r
-}\r
-static int v2_recognition (unsigned char *sf, int32 extent, int32 *l, unsigned char *c)\r
-{\r
-  int32 i, j;\r
-  for (i=0;i<extent-20;i++)\r
-    if ((read_l9_int(sf+i+4) == 0x0020) &&\r
-        (read_l9_int(sf+i+0x0a) == 0x8000) &&\r
-        (read_l9_int(sf+i+0x14) == read_l9_int(sf+i+0x16)))\r
-    {\r
-      *l=read_l9_int(sf+i+0x1c);\r
-      if (*l && *l+i <=extent)\r
-       {\r
-         *c=0;\r
-         for(j=0;j<=*l;j++)\r
-          *c+=sf[i+j];\r
-         return 2;\r
-       }\r
-    }\r
- return 0;\r
-}\r
-static int v1_recognition(unsigned char *sf, int32 extent, char **ifid)\r
-{\r
-  int32 i;\r
-  unsigned char a = 0xff, b = 0xff;\r
-\r
-  for (i=0;i<extent-20;i++)\r
-   if (memcmp(sf+i,"ATTAC",5)==0 && sf[i+5]==0xcb)\r
-   {   \r
-    a = sf[i+6];\r
-    break;\r
-   }\r
-  for (;i<(extent-20);i++)\r
-   if (memcmp(sf+i,"BUNC",4)==0 && sf[i+4]==0xc8)\r
-   {\r
-    b = sf[i + 5];\r
-    break;\r
-   }\r
-  if (a == 0xff && b == 0xff)\r
-   return 0;\r
-  if (a == 0x14 && b == 0xff) *ifid="LEVEL9-006";\r
-  else if (a == 0x15 && b == 0x5d) *ifid="LEVEL9-013";\r
-  else if (a == 0x1a && b == 0x24) *ifid="LEVEL9-005";\r
-  else if (a == 0x20 && b == 0x3b) *ifid="LEVEL9-003";\r
-  else *ifid=NULL;\r
-  return 1;\r
-}\r
-static int v3_recognition_phase (int phase,unsigned char *sf, int32 extent, int32 *l, unsigned char *c)\r
-{\r
-  int32 end, i, j, ll;\r
-  ll=0;\r
-  for (i=0;i<extent-20;i++)\r
-  {\r
-    if (ll) break;\r
-    *l = read_l9_int(sf+i);\r
-    end=*l+i;\r
-    if (phase!=3)\r
-    {\r
-    if (end <= (extent - 2) &&\r
-       (\r
-        ((phase == 2) ||\r
-        (((sf[end-1] == 0) &&\r
-         (sf[end-2] == 0)) ||\r
-        ((sf[end+1] == 0) &&\r
-         (sf[end+2] == 0))))\r
-        && (*l>0x4000) && (*l<=0xdb00)))\r
-      if ((*l!=0) && (sf[i+0x0d] == 0))\r
-       for (j=i;j<i+16;j+=2)\r
-        if (((read_l9_int(sf+j)+read_l9_int(sf+j+2))==read_l9_int(sf+j+4))\r
-            && ((read_l9_int(sf+j)+read_l9_int(sf+j+2))))\r
-        ll++;\r
-     }\r
-     else\r
-     {\r
-      if ((extent>0x0fd0) && (end <= (extent - 2)) &&\r
-         (((read_l9_int(sf+i+2) + read_l9_int(sf+i+4))==read_l9_int(sf+i+6))\r
-                    && (read_l9_int(sf+i+2) != 0) && (read_l9_int(sf+i+4)) != 0) &&\r
-         (((read_l9_int(sf+i+6) + read_l9_int(sf+i+8)) == read_l9_int(sf+i+10))\r
-          && ((sf[i + 18] == 0x2a) || (sf[i + 18] == 0x2c))\r
-          && (sf[i + 19] == 0) && (sf[i + 20] == 0) && (sf[i + 21] == 0)))\r
-        ll = 2;\r
-     }\r
-    if (ll>1)\r
-    {\r
-     *c=0;\r
-     if (phase==3) ll=1;\r
-     else\r
-     { char checksum=0;\r
-      *c = sf[end];\r
-      for (j=i;j<=end;j++)\r
-       checksum += sf[j];\r
-      if (!checksum) ll=1;\r
-      else ll=0;\r
-     }\r
-     } else ll=0;\r
-   }\r
-\r
-  if (ll) return *l < 0x8500 ? 3:4;\r
-  return 0;\r
-}\r
-static char *get_l9_ifid(int32 length, unsigned char chk)\r
-{\r
- int i;\r
- for(i=0;l9_registry[i].length;i++)\r
-  if (length==l9_registry[i].length && chk==l9_registry[i].chk) return l9_registry[i].ifid;\r
- return NULL;\r
-}\r
-static int get_l9_version(unsigned char *sf, int32 extent, char **ifid)\r
-{\r
- int i;\r
- int32 l;\r
- unsigned char c;\r
- if (v2_recognition(sf,extent, &l, &c)) { *ifid=get_l9_ifid(l,c); return 2; }\r
- l=0; c=0;\r
- i=v3_recognition_phase(1,sf,extent, &l, &c);\r
- if (i) { *ifid=get_l9_ifid(l,c); return i; }\r
- if (v1_recognition(sf,extent, ifid)) return 1;\r
- l=0; c=0;\r
- i=v3_recognition_phase(2,sf,extent, &l, &c);\r
- if (i) { *ifid=get_l9_ifid(l,c); return i; }\r
- i=v3_recognition_phase(3,sf,extent, &l, &c);\r
- *ifid=NULL;\r
- return i;\r
-}\r
-\r
-static int32 claim_story_file(void *story, int32 extent)\r
-{\r
- char *ifid=NULL;\r
- if (get_l9_version((unsigned char *) story,extent, &ifid))\r
-  if (ifid) return VALID_STORY_FILE_RV;\r
-  else return NO_REPLY_RV;\r
- return INVALID_STORY_FILE_RV; \r
-}\r
-\r
-\r
-\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- char *ifid=NULL;\r
- int i=get_l9_version((unsigned char *)story_file, extent, &ifid);\r
- if (!i) return INVALID_STORY_FILE_RV;\r
- if (ifid)\r
- {\r
-   ASSERT_OUTPUT_SIZE((signed) strlen(ifid)+1);\r
-   strcpy(output,ifid);\r
-   return 1;\r
- }\r
- ASSERT_OUTPUT_SIZE(10);\r
- sprintf(output,"LEVEL9-%d-",i);\r
- return INCOMPLETE_REPLY_RV;\r
-}\r
diff --git a/babel/magscrolls.c b/babel/magscrolls.c
deleted file mode 100644 (file)
index c21c922..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-/* magscrolls.c  Treaty of Babel module for Z-code files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT magscrolls\r
-#define HOME_PAGE "http://www.if-legends.org/~msmemorial/memorial.htm"\r
-#define FORMAT_EXT ".mag"\r
-#define NO_COVER\r
-#define NO_METADATA\r
-\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-struct maginfo\r
-{\r
-  int gv;\r
-  char header[21];\r
-  char *title;\r
-  int bafn;\r
-  int year;\r
-  char *ifid;\r
-  char *author;\r
-  char *meta;\r
-};\r
-\r
-\r
-static struct maginfo manifest[] = {\r
-        { 0, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",\r
-          "The Pawn",\r
-          0,\r
-          1985,\r
-          "MAGNETIC-1",\r
-          "Rob Steggles",\r
-        },\r
-        { 1, "\000\004\000\001\007\370\000\000\340\000\000\000\041\064\000\000\040\160\000\000",\r
-          "Guild of Thieves",\r
-          0,\r
-          1987,\r
-          "MAGNETIC-2",\r
-          "Rob Steggles",\r
-        },\r
-        { 2, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",\r
-          "Jinxter",\r
-          0,\r
-          1987,\r
-          "MAGNETIC-3",\r
-          "Georgina Sinclair and Michael Bywater",\r
-        },\r
-        { 4, "\000\004\000\001\045\140\000\001\000\000\000\000\161\017\000\000\035\210\000\001",\r
-          "Corruption",\r
-          0,\r
-          1988,\r
-          "MAGNETIC-4",\r
-          "Rob Steggles and Hugh Steers",\r
-        },\r
-        { 4, "\000\004\000\001\044\304\000\001\000\000\000\000\134\137\000\000\040\230\000\001",\r
-          "Fish!",\r
-          0,\r
-          1988,\r
-          "MAGNETIC-5",\r
-          "John Molloy, Pete Kemp, Phil South, Rob Steggles",\r
-        },\r
-        { 4, "\000\003\000\000\377\000\000\000\340\000\000\000\221\000\000\000\036\000\000\001",\r
-          "Corruption",\r
-          0,\r
-          1988,\r
-          "MAGNETIC-4",\r
-          "Rob Steggles and Hugh Steers",\r
-        },\r
-        { 4, "\000\003\000\001\000\000\000\000\340\000\000\000\175\000\000\000\037\000\000\001",\r
-          "Fish!",\r
-          0,\r
-          1988,\r
-          "MAGNETIC-5",\r
-          "John Molloy, Pete Kemp, Phil South, Rob Steggles",\r
-        },\r
-        { 4, "\000\003\000\000\335\000\000\000\140\000\000\000\064\000\000\000\023\000\000\000",\r
-          "Myth",\r
-          0,\r
-          1989,\r
-          "MAGNETIC-6",\r
-          "Paul Findley",\r
-        },\r
-        { 4, "\000\004\000\001\122\074\000\001\000\000\000\000\114\146\000\000\057\240\000\001",\r
-          "Wonderland",\r
-          0,\r
-          1990,\r
-          "MAGNETIC-7",\r
-          "David Bishop",\r
-        },\r
-        { 0, "0", NULL, 0, 0, NULL, NULL }\r
-        };\r
-\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- int i;\r
- unsigned char *sf=(unsigned char *)story_file;\r
- if (extent < 42) return INVALID_STORY_FILE_RV;\r
-\r
- for(i=0;manifest[i].title;i++)\r
-  if ((sf[13]<3 && manifest[i].gv==sf[13]) || memcmp(manifest[i].header,sf+12,20)==0)\r
-   {\r
-    ASSERT_OUTPUT_SIZE(((int32) strlen(manifest[i].ifid)+1));\r
-    strcpy(output,manifest[i].ifid);\r
-    return 1;\r
-   }\r
- strcpy(output,"MAGNETIC-");\r
- return INCOMPLETE_REPLY_RV;\r
-}\r
-\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- if (extent<42 ||\r
-     memcmp(story_file,"MaSc",4)\r
-    ) return INVALID_STORY_FILE_RV;\r
- return VALID_STORY_FILE_RV;\r
-}\r
-\r
diff --git a/babel/md5.c b/babel/md5.c
deleted file mode 100644 (file)
index c35d96c..0000000
+++ /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
-  <ghost@aladdin.com>.  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 <string.h>
-       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 <stdio.h> 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 <string.h>
-
-#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 (file)
index 698c995..0000000
+++ /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
-  <ghost@aladdin.com>.  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 <purschke@bnl.gov>.
-  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 (file)
index 982927f..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-/* misc.h : miscellany for babel\r
- * This file is public domain\r
- * 2006 by L. Ross Raszewski\r
- */\r
-\r
-#include <stdlib.h>\r
-#include <stdio.h>\r
-\r
-void *my_malloc(int size, char *rs)\r
-{\r
- void *buf=calloc(size,1);\r
- if (size && !buf)\r
-  {\r
-        fprintf(stderr,"Error: Memory exceeded (%d for %s)!\n",size,rs);\r
-        exit(2);\r
-   }\r
-   return buf;\r
-}\r
-\r
diff --git a/babel/modules.h b/babel/modules.h
deleted file mode 100644 (file)
index cc799ee..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/* modules.h  Declaration of treaty modules for the babel program\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends upon treaty.h and all the references treaty modules\r
- *\r
- * Persons wishing to add support for a new module to babel need only\r
- * add a line in the form below.  New modules should be positioned according\r
- * to their popularity.  If this file is being used in tandem with register.c\r
- * (as it is in babel), then being dishonest about the popularity of an added\r
- * system will make the program non-compliant with the treaty of Babel\r
- *\r
- * REGISTER_NAME is used as a placeholder for formats which are specified\r
- * as existing by the treaty but for which no handler yet exists.\r
- * remove the REGISTER_NAME for any format which has a registered treaty.\r
- */\r
-\r
-\r
-#include "treaty.h"\r
-#undef REGISTER_TREATY\r
-#undef REGISTER_CONTAINER\r
-#undef REGISTER_NAME\r
-#ifdef TREATY_REGISTER\r
-#ifdef CONTAINER_REGISTER\r
-#ifdef FORMAT_REGISTER\r
-#define REGISTER_TREATY(x)        #x,\r
-#define REGISTER_NAME(x)          #x,\r
-#define REGISTER_CONTAINER(x)\r
-#else\r
-#define REGISTER_TREATY(x)\r
-#define REGISTER_CONTAINER(x)     x##_treaty,\r
-#define REGISTER_NAME(x)\r
-#endif\r
-#else\r
-#define REGISTER_TREATY(x)        x##_treaty,\r
-#define REGISTER_CONTAINER(x)\r
-#define REGISTER_NAME(x)\r
-#endif\r
-#else\r
-#define REGISTER_TREATY(x)        int32 x##_treaty(int32, void *, int32, void *, int32);\r
-#define REGISTER_CONTAINER(x)        int32 x##_treaty(int32, void *, int32, void *, int32);\r
-#define REGISTER_NAME(x)\r
-#endif\r
-\r
-\r
-REGISTER_CONTAINER(blorb)\r
-REGISTER_TREATY(zcode)\r
-REGISTER_TREATY(glulx)\r
-REGISTER_TREATY(tads2)\r
-REGISTER_TREATY(tads3)\r
-REGISTER_TREATY(hugo)\r
-REGISTER_TREATY(alan)\r
-REGISTER_TREATY(adrift)\r
-REGISTER_TREATY(level9)\r
-REGISTER_TREATY(agt)\r
-REGISTER_TREATY(magscrolls)\r
-REGISTER_TREATY(advsys)\r
-REGISTER_TREATY(executable)\r
-\r
-\r
-\r
-\r
diff --git a/babel/modules.h.gch b/babel/modules.h.gch
deleted file mode 100644 (file)
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 (file)
index 22bbed5..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/* register.c    Register modules for the babel handler api\r
- *\r
- * 2006 by L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends on modules.h\r
- *\r
- * The purpose of this file is to create the treaty_registry array.\r
- * This array is a null-terminated list of the known treaty modules.\r
- */\r
-\r
-#include <stdlib.h>\r
-#include "modules.h"\r
-\r
-\r
-TREATY treaty_registry[] = {\r
-        #define TREATY_REGISTER\r
-        #include "modules.h"\r
-        NULL\r
-        };\r
-\r
-TREATY container_registry[] = {\r
-        #define CONTAINER_REGISTER\r
-        #include "modules.h"\r
-        NULL\r
-\r
-};\r
-\r
diff --git a/babel/register_ifiction.c b/babel/register_ifiction.c
deleted file mode 100644 (file)
index 55fe9e2..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/* register_ifiction.c    Register modules for babel's ifiction API\r
- *\r
- * 2006 by L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends on modules.h\r
- *\r
- * This version of register.c is stripped down to include only the\r
- * needed functionality for the ifiction api\r
- */\r
-\r
-#include <stdlib.h>\r
-#include "treaty.h"\r
-\r
-char *format_registry[] = {\r
-        #define TREATY_REGISTER\r
-        #define CONTAINER_REGISTER\r
-        #define FORMAT_REGISTER\r
-        #include "modules.h"\r
-        NULL\r
-};\r
diff --git a/babel/tads.c b/babel/tads.c
deleted file mode 100644 (file)
index bde1e5a..0000000
+++ /dev/null
@@ -1,1827 +0,0 @@
-/* \r
- *   tads.c - Treaty of Babel common functions for tads2 and tads3 modules\r
- *   \r
- *   This file depends on treaty_builder.h\r
- *   \r
- *   This file is public domain, but note that any changes to this file may\r
- *   render it noncompliant with the Treaty of Babel\r
- *   \r
- *   Modified\r
- *.   04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions\r
- *.   04/08/2006 MJRoberts  - initial implementation\r
- */\r
-\r
-\r
-#include "treaty.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-#include <string.h>\r
-#include <stdlib.h>\r
-#include "tads.h"\r
-#include "md5.h"\r
-\r
-#define ASSERT_OUTPUT_SIZE(x) \\r
-    do { if (output_extent < (x)) return INVALID_USAGE_RV; } while (0)\r
-\r
-#define T2_SIGNATURE "TADS2 bin\012\015\032"\r
-#define T3_SIGNATURE "T3-image\015\012\032"\r
-\r
-#ifndef FALSE\r
-#define FALSE 0\r
-#endif\r
-#ifndef TRUE\r
-#define TRUE 1\r
-#endif\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   private structures \r
- */\r
-\r
-/*\r
- *   resource information structure - this encapsulates the location and size\r
- *   of a binary resource object embedded in a story file \r
- */\r
-typedef struct resinfo resinfo;\r
-struct resinfo\r
-{\r
-    /* pointer and length of the data in the story file buffer */\r
-    const char *ptr;\r
-    int32 len;\r
-\r
-    /* tads major version (currently, 2 or 3) */\r
-    int tads_version;\r
-};\r
-\r
-/*\r
- *   Name/value pair list entry \r
- */\r
-typedef struct valinfo valinfo;\r
-struct valinfo\r
-{\r
-    const char *name;\r
-    size_t name_len;\r
-\r
-    /* value string */\r
-    char *val;\r
-    size_t val_len;\r
-\r
-    /* next entry in the list */\r
-    valinfo *nxt;\r
-};\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   forward declarations \r
- */\r
-static valinfo *parse_game_info(const void *story_file, int32 story_len,\r
-                                int *version);\r
-static int find_resource(const void *story_file, int32 story_len,\r
-                         const char *resname, resinfo *info);\r
-static int find_cover_art(const void *story_file, int32 story_len,\r
-                          resinfo *resp, int32 *image_format,\r
-                          int32 *width, int32 *height);\r
-static int t2_find_res(const void *story_file, int32 story_len,\r
-                       const char *resname, resinfo *info);\r
-static int t3_find_res(const void *story_file, int32 story_len,\r
-                       const char *resname, resinfo *info);\r
-static valinfo *find_by_key(valinfo *list_head, const char *key);\r
-static void delete_valinfo_list(valinfo *head);\r
-static int32 generate_md5_ifid(void *story_file, int32 extent,\r
-                               char *output, int32 output_extent);\r
-static int32 synth_ifiction(valinfo *vals, int tads_version,\r
-                            char *buf, int32 bufsize,\r
-                            void *story_file, int32 extent);\r
-static int get_png_dim(const void *img, int32 extent,\r
-                       int32 *xout, int32 *yout);\r
-static int get_jpeg_dim(const void *img, int32 extent,\r
-                        int32 *xout, int32 *yout);\r
-\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Get the IFID for a given story file.  \r
- */\r
-int32 tads_get_story_file_IFID(void *story_file, int32 extent,\r
-                               char *output, int32 output_extent)\r
-{\r
-    valinfo *vals;\r
-    \r
-    /* if we have GameInfo, try looking for an IFID there */\r
-    if ((vals = parse_game_info(story_file, extent, 0)) != 0)\r
-    {\r
-        valinfo *val;\r
-        int found = 0;\r
-        \r
-        /* find the "IFID" key */\r
-        if ((val = find_by_key(vals, "IFID")) != 0)\r
-        {\r
-            char *p;\r
-            \r
-            /* copy the output as a null-terminated string */\r
-            ASSERT_OUTPUT_SIZE((int32)val->val_len + 1);\r
-            memcpy(output, val->val, val->val_len);\r
-            output[val->val_len] = '\0';\r
-\r
-            /* \r
-             *   count up the IFIDs in the buffer - there might be more than\r
-             *   one, separated by commas \r
-             */\r
-            for (found = 1, p = output ; *p != '\0' ; ++p)\r
-            {\r
-                /* if this is a comma, it delimits a new IFID */\r
-                if (*p == ',')\r
-                    ++found;\r
-            }\r
-        }\r
-\r
-        /* delete the GameInfo list */\r
-        delete_valinfo_list(vals);\r
-\r
-        /* if we found an IFID, indicate how many results we found */\r
-        if (found != 0)\r
-            return found;\r
-    }\r
-\r
-    /* \r
-     *   we didn't find an IFID in the GameInfo, so generate a default IFID\r
-     *   using the MD5 method \r
-     */\r
-    return generate_md5_ifid(story_file, extent, output, output_extent);\r
-}\r
-\r
-/*\r
- *   Get the size of the ifiction metadata for the game \r
- */\r
-int32 tads_get_story_file_metadata_extent(void *story_file, int32 extent)\r
-{\r
-    valinfo *vals;\r
-    int32 ret;\r
-    int ver;\r
-    \r
-    /*\r
-     *   First, make sure we have a GameInfo record.  If we don't, simply\r
-     *   indicate that there's no metadata to fetch.  \r
-     */\r
-    if ((vals = parse_game_info(story_file, extent, &ver)) == 0)\r
-        return NO_REPLY_RV;\r
-\r
-    /*\r
-     *   Run the ifiction synthesizer with no output buffer, to calculate the\r
-     *   size we need. \r
-     */\r
-    ret = synth_ifiction(vals, ver, 0, 0, story_file, extent);\r
-\r
-    /* delete the value list */\r
-    delete_valinfo_list(vals);\r
-\r
-    /* return the required size */\r
-    return ret;\r
-}\r
-\r
-/*\r
- *   Get the ifiction metadata for the game\r
- */\r
-int32 tads_get_story_file_metadata(void *story_file, int32 extent,\r
-                                   char *buf, int32 bufsize)\r
-{\r
-    valinfo *vals;\r
-    int32 ret;\r
-    int ver;\r
-\r
-    /* make sure we have metadata to fetch */\r
-    if ((vals = parse_game_info(story_file, extent, &ver)) == 0)\r
-        return NO_REPLY_RV;\r
-\r
-    /* synthesize the ifiction data into the output buffer */\r
-    ret = synth_ifiction(vals, ver, buf, bufsize, story_file, extent);\r
-\r
-    /* if that required more space than we had available, return an error */\r
-    if (ret > bufsize)\r
-        ret = INVALID_USAGE_RV;\r
-\r
-    /* delete the value list */\r
-    delete_valinfo_list(vals);\r
-\r
-    /* return the result */\r
-    return ret;\r
-}\r
-\r
-/*\r
- *   Get the size of the cover art \r
- */\r
-int32 tads_get_story_file_cover_extent(void *story_file, int32 story_len)\r
-{\r
-    resinfo res;\r
-    \r
-    /* look for the cover art resource */\r
-    if (find_cover_art(story_file, story_len, &res, 0, 0, 0))\r
-        return res.len;\r
-    else\r
-        return NO_REPLY_RV;\r
-}\r
-\r
-/*\r
- *   Get the format of the cover art \r
- */\r
-int32 tads_get_story_file_cover_format(void *story_file, int32 story_len)\r
-{\r
-    int32 typ;\r
-\r
-    /* look for CoverArt.jpg */\r
-    if (find_cover_art(story_file, story_len, 0, &typ, 0, 0))\r
-        return typ;\r
-    else\r
-        return NO_REPLY_RV;\r
-}\r
-\r
-/*\r
- *   Get the cover art data \r
- */\r
-int32 tads_get_story_file_cover(void *story_file, int32 story_len,\r
-                                void *outbuf, int32 output_extent)\r
-{\r
-    resinfo res;\r
-\r
-    /* look for CoverArt.jpg, then for CoverArt.png */\r
-    if (find_cover_art(story_file, story_len, &res, 0, 0, 0))\r
-    {\r
-        /* got it - copy the data to the buffer */\r
-        ASSERT_OUTPUT_SIZE(res.len);\r
-        memcpy(outbuf, res.ptr, res.len);\r
-\r
-        /* success */\r
-        return res.len;\r
-    }\r
-\r
-    /* otherwise, we didn't find it */\r
-    return NO_REPLY_RV;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Generate a default IFID using the MD5 hash method \r
- */\r
-static int32 generate_md5_ifid(void *story_file, int32 extent,\r
-                               char *output, int32 output_extent)\r
-{\r
-    md5_state_t md5;\r
-    unsigned char md5_buf[16];\r
-    char *p;\r
-    int i;\r
-\r
-    /* calculate the MD5 hash of the story file */\r
-    md5_init(&md5);\r
-    md5_append(&md5, story_file, extent);\r
-    md5_finish(&md5, md5_buf);\r
-\r
-    /* make sure we have room to store the result */\r
-    ASSERT_OUTPUT_SIZE(39);\r
-\r
-    /* the prefix is "TADS2-" or "TADS3-", depending on the format */\r
-    if (tads_match_sig(story_file, extent, T2_SIGNATURE))\r
-        strcpy(output, "TADS2-");\r
-    else\r
-        strcpy(output, "TADS3-");\r
-\r
-    /* the rest is the MD5 hash of the file, as hex digits */\r
-    for (i = 0, p = output + strlen(output) ; i < 16 ; p += 2, ++i)\r
-        sprintf(p, "%02X", md5_buf[i]);\r
-\r
-    /* indicate that we found one result */\r
-    return 1;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Some UTF-8 utility functions and macros.  We use our own rather than the\r
- *   ctype.h macros because we're parsing UTF-8 text.  \r
- */\r
-\r
-/* is c a space? */\r
-#define u_isspace(c) ((unsigned char)(c) < 128 && isspace(c))\r
-\r
-/* is c a horizontal space? */\r
-#define u_ishspace(c) (u_isspace(c) && (c) != '\n' && (c) != '\r')\r
-\r
-/* is-newline - matches \n, \r, and \u2028 */\r
-static int u_isnl(const char *p, int32 len)\r
-{\r
-    return (*p == '\n' \r
-            || *p == '\r'\r
-            || (len >= 3\r
-                && *(unsigned char *)p == 0xe2\r
-                && *(unsigned char *)(p+1) == 0x80\r
-                && *(unsigned char *)(p+2) == 0xa8));\r
-}\r
-\r
-/* skip to the next utf-8 character */\r
-static void nextc(const char **p, int32 *len)\r
-{\r
-    /* skip the first byte */\r
-    if (*len != 0)\r
-        ++*p, --*len;\r
-\r
-    /* skip continuation bytes */\r
-    while (*len != 0 && (**p & 0xC0) == 0x80)\r
-        ++*p, --*len;\r
-}\r
-\r
-/* skip to the previous utf-8 character */\r
-static void prevc(const char **p, int32 *len)\r
-{\r
-    /* move back one byte */\r
-    --*p, ++*len;\r
-\r
-    /* keep skipping as long as we're looking at continuation characters */\r
-    while ((**p & 0xC0) == 0x80)\r
-        --*p, ++*len;\r
-}\r
-\r
-/*\r
- *   Skip a newline sequence.  Skips all common conventions, including \n,\r
- *   \r, \n\r, \r\n, and \u2028.  \r
- */\r
-static void skip_newline(const char **p, int32 *rem)\r
-{\r
-    /* make sure we have something to skip */\r
-    if (*rem == 0)\r
-        return;\r
-\r
-    /* check what we have */\r
-    switch (**(const unsigned char **)p)\r
-    {\r
-    case '\n':\r
-        /* skip \n or \n\r */\r
-        nextc(p, rem);\r
-        if (**p == '\r')\r
-            nextc(p, rem);\r
-        break;\r
-\r
-    case '\r':\r
-        /* skip \r or \r\n */\r
-        nextc(p, rem);\r
-        if (**p == '\n')\r
-            nextc(p, rem);\r
-        break;\r
-\r
-    case 0xe2:\r
-        /* \u2028 (unicode line separator) - just skip the one character */\r
-        nextc(p, rem);\r
-        break;\r
-    }\r
-}\r
-\r
-/*\r
- *   Skip to the next line \r
- */\r
-static void skip_to_next_line(const char **p, int32 *rem)\r
-{\r
-    /* look for the next newline */\r
-    for ( ; *rem != 0 ; nextc(p, rem))\r
-    {\r
-        /* if this is a newline of some kind, we're at the end of the line */\r
-        if (u_isnl(*p, *rem))\r
-        {\r
-            /* skip the newline, and we're done */\r
-            skip_newline(p, rem);\r
-            break;\r
-        }\r
-    }\r
-}\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   ifiction synthesizer output context \r
- */\r
-typedef struct synthctx synthctx;\r
-struct synthctx\r
-{\r
-    /* the current output pointer */\r
-    char *buf;\r
-\r
-    /* the number of bytes remaining in the output buffer */\r
-    int32 buf_size;\r
-\r
-    /* \r
-     *   the total number of bytes needed for the output (this might be more\r
-     *   than we've actually written, since we count up the bytes required\r
-     *   even if we need more space than the buffer provides) \r
-     */\r
-    int32 total_size;\r
-\r
-    /* the head of the name/value pair list from the parsed GameInfo */\r
-    valinfo *vals;\r
-};\r
-\r
-/* initialize a synthesizer context */\r
-static void init_synthctx(synthctx *ctx, char *buf, int32 bufsize,\r
-                          valinfo *vals)\r
-{\r
-    /* set up at the beginning of the output buffer */\r
-    ctx->buf = buf;\r
-    ctx->buf_size = bufsize;\r
-\r
-    /* we haven't written anything to the output buffer yet */\r
-    ctx->total_size = 0;\r
-\r
-    /* remember the name/value pair list */\r
-    ctx->vals = vals;\r
-}\r
-\r
-/* \r
- *   Write out a chunk to a synthesized ifiction record, updating pointers\r
- *   and counters.  We won't copy past the end of the buffer, but we'll\r
- *   continue counting the output length needed in any case.  \r
- */\r
-static void write_ifiction(synthctx *ctx, const char *src, size_t srclen)\r
-{\r
-    int32 copy_len;\r
-\r
-    /* copy as much as we can, up to the remaining buffer size */\r
-    copy_len = srclen;\r
-    if (copy_len > ctx->buf_size)\r
-        copy_len = ctx->buf_size;\r
-\r
-    /* do the copying, if any */\r
-    if (copy_len != 0)\r
-    {\r
-        /* copy the bytes */\r
-        memcpy(ctx->buf, src, (size_t)copy_len);\r
-\r
-        /* adjust the buffer pointer and output buffer size remaining */\r
-        ctx->buf += copy_len;\r
-        ctx->buf_size -= copy_len;\r
-    }\r
-\r
-    /* count this source data in the total size */\r
-    ctx->total_size += srclen;\r
-}\r
-\r
-/* write a null-terminated chunk to the synthesized ifiction record */\r
-static void write_ifiction_z(synthctx *ctx, const char *src)\r
-{\r
-    write_ifiction(ctx, src, strlen(src));\r
-}\r
-\r
-/*\r
- *   Write a PCDATA string to the synthesized ifiction record.  In\r
- *   particular, we rewrite '<', '>', and '&' as '&lt;', '&gt;', and '&amp;',\r
- *   respectively; we trim off leading and trailing spaces; and we compress\r
- *   each run of whitespace down to a single \u0020 (' ') character.\r
- */\r
-static void write_ifiction_pcdata(synthctx *ctx, const char *p, size_t len)\r
-{\r
-    /* first, skip any leading whitespace */\r
-    for ( ; len != 0 && u_ishspace(*p) ; ++p, --len) ;\r
-\r
-    /* keep going until we run out of string */\r
-    for (;;)\r
-    {\r
-        const char *start;\r
-        \r
-        /* scan to the next whitespace or markup-significant character */\r
-        for (start = p ;\r
-             len != 0 && !u_ishspace(*p)\r
-             && *p != '<' && *p != '>' && *p != '&' ; ++p, --len) ;\r
-\r
-        /* write the part up to here */\r
-        if (p != start)\r
-            write_ifiction(ctx, start, p - start);\r
-\r
-        /* if we've reached the end of the string, we can stop */\r
-        if (len == 0)\r
-            break;\r
-\r
-        /* check what stopped us */\r
-        switch (*p)\r
-        {\r
-        case '<':\r
-            write_ifiction_z(ctx, "&lt;");\r
-            ++p, --len;\r
-            break;\r
-\r
-        case '>':\r
-            write_ifiction_z(ctx, "&gt;");\r
-            ++p, --len;\r
-            break;\r
-\r
-        case '&':\r
-            write_ifiction_z(ctx, "&amp;");\r
-            ++p, --len;\r
-            break;\r
-\r
-        default:\r
-            /* \r
-             *   The only other thing that could have stopped us is\r
-             *   whitespace.  Skip all consecutive whitespace. \r
-             */\r
-            for ( ; len != 0 && u_ishspace(*p) ; ++p, --len);\r
-\r
-            /* \r
-             *   if that's not the end of the string, replace the run of\r
-             *   whitespace with a single space character in the output; if\r
-             *   we've reached the end of the string, we don't even want to\r
-             *   do that, since we want to trim off trailing spaces \r
-             */\r
-            if (len != 0)\r
-                write_ifiction_z(ctx, " ");\r
-            break;\r
-        }\r
-    }\r
-}\r
-\r
-/*\r
- *   Translate a GameInfo keyed value to the corresponding ifiction tagged\r
- *   value.  We find the GameInfo value keyed by 'gameinfo_key', and write\r
- *   out the same string under the ifiction XML tag 'ifiction_tag'.  We write\r
- *   a complete XML container sequence - <tag>value</tag>.\r
- *   \r
- *   If the given GameInfo key doesn't exist, we use the default value string\r
- *   'dflt', if given.  If the GameInfo key doesn't exist and 'dflt' is null,\r
- *   we don't write anything - we don't even write the open/close tags.\r
- *   \r
- *   If 'html' is true, we assume the value is in html format, and we write\r
- *   it untranslated.  Otherwise, we write it as PCDATA, translating markup\r
- *   characters into '&' entities and compressing whitespace.  \r
- */\r
-static void write_ifiction_xlat_base(synthctx *ctx, int indent,\r
-                                     const char *gameinfo_key,\r
-                                     const char *ifiction_tag,\r
-                                     const char *dflt, int html)\r
-{\r
-    valinfo *val;\r
-    const char *valstr;\r
-    size_t vallen;\r
-    \r
-    /* look up the GameInfo key */\r
-    if ((val = find_by_key(ctx->vals, gameinfo_key)) != 0)\r
-    {\r
-        /* we found the GameInfo value - use it */\r
-        valstr = val->val;\r
-        vallen = val->val_len;\r
-    }\r
-    else if (dflt != 0)\r
-    {\r
-        /* the GameInfo value doesn't exist, but we have a default - use it */\r
-        valstr = dflt;\r
-        vallen = strlen(dflt);\r
-    }\r
-    else\r
-    {\r
-        /* there's no GameInfo value and no default, so write nothing */\r
-        return;\r
-    }\r
-\r
-    /* write the indentation */\r
-    while (indent != 0)\r
-    {\r
-        static const char spaces[] = "          ";\r
-        size_t cur;\r
-\r
-        /* figure how much we can write on this round */\r
-        cur = indent;\r
-        if (cur > sizeof(spaces) - 1)\r
-            cur = sizeof(spaces) - 1;\r
-\r
-        /* write it */\r
-        write_ifiction(ctx, spaces, cur);\r
-\r
-        /* deduct it from the amount remaining */\r
-        indent -= cur;\r
-    }\r
-\r
-    /* write the open tag */\r
-    write_ifiction_z(ctx, "<");\r
-    write_ifiction_z(ctx, ifiction_tag);\r
-    write_ifiction_z(ctx, ">");\r
-\r
-    /* write the value, applying pcdata translations */\r
-    if (html)\r
-        write_ifiction(ctx, valstr, vallen);\r
-    else\r
-        write_ifiction_pcdata(ctx, valstr, vallen);\r
-\r
-    /* write the close tag */\r
-    write_ifiction_z(ctx, "</");\r
-    write_ifiction_z(ctx, ifiction_tag);\r
-    write_ifiction_z(ctx, ">\n");\r
-}\r
-\r
-#define write_ifiction_xlat(ctx, indent, gikey, iftag, dflt) \\r
-    write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, FALSE)\r
-\r
-#define write_ifiction_xlat_html(ctx, indent, gikey, iftag, dflt) \\r
-    write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, TRUE)\r
-\r
-\r
-/*\r
- *   Retrieve the next author name from the GameInfo "Author" format.  The\r
- *   format is as follows:\r
- *   \r
- *   name <email> <email>... ; ...\r
- *   \r
- *   That is, each author is listed with a name followed by one or more email\r
- *   addresses in angle brackets, and multiple authors are separated by\r
- *   semicolons.  \r
- */\r
-static int scan_author_name(const char **p, size_t *len,\r
-                            const char **start, const char **end)\r
-{\r
-    /* keep going until we find a non-empty author name */\r
-    for (;;)\r
-    {\r
-        /* skip leading spaces */\r
-        for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ;\r
-\r
-        /* if we ran out of string, there's definitely no author name */\r
-        if (*len == 0)\r
-            return FALSE;\r
-\r
-        /* \r
-         *   Find the end of this author name.  The author name ends at the\r
-         *   next semicolon or angle bracket.  \r
-         */\r
-        for (*start = *p ; *len != 0 && **p != ';' && **p != '<' ;\r
-             ++*p, --*len) ;\r
-\r
-        /* trim off any trailing spaces */\r
-        for (*end = *p ; *end > *start && u_ishspace(*(*end - 1)) ; --*end) ;\r
-\r
-        /* now skip any email addresses */\r
-        while (*len != 0 && **p == '<')\r
-        {\r
-            /* skip to the closing bracket */\r
-            for (++*p, --*len ; *len != 0 && **p != '>' ; ++*p, --*len) ;\r
-\r
-            /* skip the bracket */\r
-            if (*len != 0)\r
-                ++*p, --*len;\r
-\r
-            /* skip whitespace */\r
-            for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ;\r
-\r
-            /* \r
-             *   if we're not at a semicolon, another angle bracket, or the\r
-             *   end of the string, it's a syntax error \r
-             */\r
-            if (*len != 0 && **p != '<' && **p != ';')\r
-            {\r
-                *len = 0;\r
-                return FALSE;\r
-            }\r
-        }\r
-\r
-        /* if we're at a semicolon, skip it */\r
-        if (*len != 0 && **p == ';')\r
-            ++*p, --*len;\r
-\r
-        /* \r
-         *   if we found a non-empty name, return it; otherwise, continue on\r
-         *   to the next semicolon section \r
-         */\r
-        if (*end != *start)\r
-            return TRUE;\r
-    }\r
-}\r
-\r
-\r
-/*\r
- *   Synthesize an ifiction record for the given GameInfo name/value pair\r
- *   list.  Returns the number of bytes required for the result, including\r
- *   null termination.  We'll copy as much as we can to the output buffer, up\r
- *   to bufsize; if the buffer size is insufficient to hold the result, we'll\r
- *   still indicate the length needed for the full result, but we're careful\r
- *   not to actually copy anything past the end of the buffer.  \r
- */\r
-static int32 synth_ifiction(valinfo *vals, int tads_version,\r
-                            char *buf, int32 bufsize,\r
-                            void *story_file, int32 extent)\r
-{\r
-    char default_ifid[TREATY_MINIMUM_EXTENT];\r
-    valinfo *ifid = find_by_key(vals, "IFID");\r
-    const char *ifid_val;\r
-    size_t ifid_len;\r
-    valinfo *author = find_by_key(vals, "AuthorEmail");\r
-    valinfo *url = find_by_key(vals, "Url");\r
-    synthctx ctx;\r
-    const char *p;\r
-    size_t rem;\r
-    int32 art_fmt;\r
-    int32 art_wid, art_ht;\r
-\r
-    /* initialize the output content */\r
-    init_synthctx(&ctx, buf, bufsize, vals);\r
-\r
-    /* make sure the tads version is one we know how to handle */\r
-    if (tads_version != 2 && tads_version != 3)\r
-        return NO_REPLY_RV;\r
-\r
-    /* \r
-     *   The IFID is mandatory.  If there's not an IFID specifically listed\r
-     *   in the GameInfo, we need to generate the default IFID based on the\r
-     *   MD5 hash of the game file. \r
-     */\r
-    if (ifid != 0)\r
-    {\r
-        /* use the explicit IFID(s) listed in the GameInfo */\r
-        ifid_val = ifid->val;\r
-        ifid_len = ifid->val_len;\r
-    }\r
-    else\r
-    {\r
-        /* generate the default IFID */\r
-        generate_md5_ifid(story_file, extent,\r
-                          default_ifid, TREATY_MINIMUM_EXTENT);\r
-\r
-        /* use this as the IFID */\r
-        ifid_val = default_ifid;\r
-        ifid_len = strlen(default_ifid);\r
-    }\r
-\r
-    /* write the header, and start the <identification> section */\r
-    write_ifiction_z(\r
-        &ctx,\r
-        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\r
-        "<ifindex version=\"1.0\" "\r
-        "xmlns=\"http://babel.ifarchive.org/protocol/iFiction/\">\n"\r
-        "  <!-- Bibliographic data translated from TADS GameInfo -->\n"\r
-        "  <story>\n"\r
-        "    <colophon>\n"\r
-        "     <generator>Babel</generator>\n"\r
-        "     <generatorversion>" TREATY_VERSION "</generatorversion>\n"\r
-        "      <originated>2006-04-14</originated>\n"\r
-        "     </colophon>\n"\r
-        "    <identification>\n");\r
-\r
-    /* write each IFID (there might be several) */\r
-    for (p = ifid_val, rem = ifid_len ; rem != 0 ; )\r
-    {\r
-        const char *start;\r
-        const char *end;\r
-\r
-        /* skip leading spaces */\r
-        for ( ; rem != 0 && u_ishspace(*p) ; ++p, --rem) ;\r
-        \r
-        /* find the end of this IFID */\r
-        for (start = p ; rem != 0 && *p != ',' ; ++p, --rem) ;\r
-\r
-        /* remove trailing spaces */\r
-        for (end = p ; end > start && u_ishspace(*(end-1)) ; --end) ;\r
-\r
-        /* if we found one, write it out */\r
-        if (end != start)\r
-        {\r
-            write_ifiction_z(&ctx, "      <ifid>");\r
-            write_ifiction(&ctx, start, end - start);\r
-            write_ifiction_z(&ctx, "</ifid>\n");\r
-        }\r
-\r
-        /* skip the comma */\r
-        if (rem != 0 && *p == ',')\r
-            ++p, --rem;\r
-    }\r
-\r
-    /* add the format information */\r
-    write_ifiction_z(&ctx,\r
-                     tads_version == 2\r
-                     ? "      <format>tads2</format>\n"\r
-                     : "      <format>tads3</format>\n");\r
-\r
-    /* close the <identification> section and start the <bibliographic> */\r
-    write_ifiction_z(&ctx,\r
-                     "    </identification>\n"\r
-                     "    <bibliographic>\n");\r
-\r
-    /* write the various bibliographic data */\r
-    write_ifiction_xlat(&ctx, 6, "Name", "title", "An Interactive Fiction");\r
-    write_ifiction_xlat(&ctx, 6, "Headline", "headline", 0);\r
-    write_ifiction_xlat(&ctx, 6, "Desc", "description", 0);\r
-    write_ifiction_xlat(&ctx, 6, "Genre", "genre", 0);\r
-    write_ifiction_xlat(&ctx, 6, "Forgiveness", "forgiveness", 0);\r
-    write_ifiction_xlat(&ctx, 6, "Series", "series", 0);\r
-    write_ifiction_xlat(&ctx, 6, "SeriesNumber", "seriesnumber", 0);\r
-    write_ifiction_xlat(&ctx, 6, "Language", "language", 0);\r
-    write_ifiction_xlat(&ctx, 6, "FirstPublished", "firstpublished", 0);\r
-\r
-    /* if there's an author, write the list of author names */\r
-    if (author != 0)\r
-    {\r
-        int cnt;\r
-        int i;\r
-        const char *start;\r
-        const char *end;\r
-\r
-        /* start the <author> tag */\r
-        write_ifiction_z(&ctx, "      <author>");\r
-        \r
-        /* \r
-         *   first, count up the number of authors - authors are separated by\r
-         *   semicolons, so there's one more author than there are semicolons\r
-         */\r
-        for (p = author->val, rem = author->val_len, cnt = 1 ;\r
-             scan_author_name(&p, &rem, &start, &end) ; ) ;\r
-\r
-        /* \r
-         *   Now generate the list of authors.  If there are multiple\r
-         *   authors, use commas to separate them. \r
-         */\r
-        for (p = author->val, rem = author->val_len, i = 0 ; ; ++i)\r
-        {\r
-            /* scan this author's name */\r
-            if (!scan_author_name(&p, &rem, &start, &end))\r
-                break;\r
-            \r
-            /* write out this author name */\r
-            write_ifiction_pcdata(&ctx, start, end - start);\r
-\r
-            /* if there's another name to come, write a separator */\r
-            if (i + 1 < cnt)\r
-            {\r
-                /* \r
-                 *   write just "and" to separate two items; write ","\r
-                 *   between items in lists of more than two, with ",and"\r
-                 *   between the last two items \r
-                 */\r
-                write_ifiction_z(&ctx,\r
-                                 cnt == 2 ? " and " :\r
-                                 i + 2 < cnt ? ", " : ", and ");\r
-            }\r
-        }\r
-\r
-        /* end the <author> tag */\r
-        write_ifiction_z(&ctx, "</author>\n");\r
-    }\r
-\r
-    /* end the biblio section */\r
-    write_ifiction_z(&ctx, "    </bibliographic>\n");\r
-\r
-    /* if there's cover art, add its information */\r
-    if (find_cover_art(story_file, extent, 0, &art_fmt, &art_wid, &art_ht)\r
-        && (art_fmt == PNG_COVER_FORMAT || art_fmt == JPEG_COVER_FORMAT))\r
-    {\r
-        char buf[200];\r
-        \r
-        sprintf(buf,\r
-                "    <cover>\n"\r
-                "        <format>%s</format>\n"\r
-                "        <height>%lu</height>\n"\r
-                "        <width>%lu</width>\n"\r
-                "    </cover>\n",\r
-                art_fmt == PNG_COVER_FORMAT ? "png" : "jpg",\r
-                (long)art_ht, (long)art_wid);\r
-\r
-        write_ifiction_z(&ctx, buf);\r
-    }\r
-\r
-    /* if there's an author email, include it */\r
-    if (author != 0 || url != 0)\r
-    {\r
-        const char *p;\r
-        size_t rem;\r
-        int i;\r
-        \r
-        /* open the section */\r
-        write_ifiction_z(&ctx, "    <contacts>\n");\r
-\r
-        /* add the author email, if provided */\r
-        if (author != 0)\r
-        {\r
-            /* write the email list */\r
-            for (i = 0, p = author->val, rem = author->val_len ; ; ++i)\r
-            {\r
-                const char *start;\r
-                \r
-                /* skip to the next email address */\r
-                for ( ; rem != 0 && *p != '<' ; ++p, --rem) ;\r
-                \r
-                /* if we didn't find an email address, we're done */\r
-                if (rem == 0)\r
-                    break;\r
-                \r
-                /* find the matching '>' */\r
-                for (++p, --rem, start = p ; rem != 0 && *p != '>' ;\r
-                     ++p, --rem) ;\r
-\r
-                /* \r
-                 *   if this is the first one, open the section; otherwise,\r
-                 *   add a comma \r
-                 */\r
-                if (i == 0)\r
-                    write_ifiction_z(&ctx, "      <authoremail>");\r
-                else\r
-                    write_ifiction_z(&ctx, ",");\r
-                \r
-                /* write this address */\r
-                write_ifiction(&ctx, start, p - start);\r
-                \r
-                /* \r
-                 *   skip the closing bracket, if there is one; if we're out\r
-                 *   of string, we're done \r
-                 */\r
-                if (rem != 0)\r
-                    ++p, --rem;\r
-                else\r
-                    break;\r
-            }\r
-\r
-            /* if we found any emails to write, end the section */\r
-            if (i != 0)\r
-                write_ifiction_z(&ctx, "</authoremail>\n");\r
-        }\r
-\r
-        /* if there's a URL, add it */\r
-        if (url != 0)\r
-        {\r
-            write_ifiction_z(&ctx, "      <url>");\r
-            write_ifiction(&ctx, url->val, url->val_len);\r
-            write_ifiction_z(&ctx, "</url>\n");\r
-        }\r
-\r
-        /* close the section */\r
-        write_ifiction_z(&ctx, "    </contacts>\n");\r
-    }\r
-\r
-    /* add the tads-specific section */\r
-    write_ifiction_z(&ctx, "    <tads>\n");\r
-    \r
-    write_ifiction_xlat(&ctx, 6, "Version", "version", 0);\r
-    write_ifiction_xlat(&ctx, 6, "ReleaseDate", "releasedate", 0);\r
-    write_ifiction_xlat(&ctx, 6, "PresentationProfile",\r
-                        "presentationprofile", 0);\r
-    write_ifiction_xlat(&ctx, 6, "Byline", "byline", 0);\r
-\r
-    write_ifiction_z(&ctx, "    </tads>\n");\r
-\r
-    /* close the story section and the main body */\r
-    write_ifiction_z(&ctx, "  </story>\n</ifindex>\n");\r
-    \r
-    /* add the null terminator */\r
-    write_ifiction(&ctx, "", 1);\r
-\r
-    /* return the total output size */\r
-    return ctx.total_size;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Check a data block to see if it starts with the given signature. \r
- */\r
-int tads_match_sig(const void *buf, int32 len, const char *sig)\r
-{\r
-    /* note the length of the signature string */\r
-    size_t sig_len = strlen(sig);\r
-    \r
-    /* if matches if the buffer starts with the signature string */\r
-    return (len >= (int32)sig_len && memcmp(buf, sig, sig_len) == 0);\r
-}\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   portable-to-native format conversions \r
- */\r
-#define osbyte(p, ofs) \\r
-    (*(((unsigned char *)(p)) + (ofs)))\r
-\r
-#define osrp1(p) \\r
-    ((unsigned int)osbyte(p, 0))\r
-\r
-#define osrp2(p) \\r
-    ((unsigned int)osbyte(p, 0) \\r
-    + ((unsigned int)osbyte(p, 1) << 8))\r
-\r
-#define osrp4(p) \\r
-    (((unsigned long)osbyte(p, 0)) \\r
-    + (((unsigned long)osbyte(p, 1)) << 8) \\r
-    + (((unsigned long)osbyte(p, 2)) << 16) \\r
-    + (((unsigned long)osbyte(p, 3)) << 24))\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Parse a game file and retrieve the GameInfo data.  Returns the head of a\r
- *   linked list of valinfo entries.\r
- */\r
-static valinfo *parse_game_info(const void *story_file, int32 story_len,\r
-                                int *tads_version)\r
-{\r
-    resinfo res;\r
-    const char *p;\r
-    int32 rem;\r
-    valinfo *val_head = 0;\r
-\r
-    /* \r
-     *   first, find the GameInfo resource - if it's not there, there's no\r
-     *   game information to parse \r
-     */\r
-    if (!find_resource(story_file, story_len, "GameInfo.txt", &res))\r
-        return 0;\r
-\r
-    /* if the caller wants the TADS version number, hand it back */\r
-    if (tads_version != 0)\r
-        *tads_version = res.tads_version;\r
-\r
-    /* parse the data */\r
-    for (p = res.ptr, rem = res.len ; rem != 0 ; )\r
-    {\r
-        const char *name_start;\r
-        size_t name_len;\r
-        const char *val_start;\r
-        valinfo *val;\r
-        const char *inp;\r
-        int32 inlen;\r
-        char *outp;\r
-\r
-        /* skip any leading whitespace */\r
-        while (rem != 0 && u_isspace(*p))\r
-            ++p, --rem;\r
-\r
-        /* if the line starts with '#', it's a comment, so skip it */\r
-        if (rem != 0 && *p == '#')\r
-        {\r
-            skip_to_next_line(&p, &rem);\r
-            continue;\r
-        }\r
-\r
-        /* we must have the start of a name - note it */\r
-        name_start = p;\r
-\r
-        /* skip ahead to a space or colon */\r
-        while (rem != 0 && *p != ':' && !u_ishspace(*p))\r
-            nextc(&p, &rem);\r
-\r
-        /* note the length of the name */\r
-        name_len = p - name_start;\r
-\r
-        /* skip any whitespace before the presumed colon */\r
-        while (rem != 0 && u_ishspace(*p))\r
-            nextc(&p, &rem);\r
-\r
-        /* if we're not at a colon, the line is ill-formed, so skip it */\r
-        if (rem == 0 || *p != ':')\r
-        {\r
-            /* skip the entire line, and go back for the next one */\r
-            skip_to_next_line(&p, &rem);\r
-            continue;\r
-        }\r
-\r
-        /* skip the colon and any whitespace immediately after it */\r
-        for (nextc(&p, &rem) ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ;\r
-\r
-        /* note where the value starts */\r
-        val_start = p;\r
-\r
-        /*\r
-         *   Scan the value to get its length.  The value runs from here to\r
-         *   the next newline that's not followed immediately by a space. \r
-         */\r
-        while (rem != 0)\r
-        {\r
-            const char *nl;\r
-            int32 nlrem;\r
-            \r
-            /* skip to the next line */\r
-            skip_to_next_line(&p, &rem);\r
-\r
-            /* if we're at eof, we can stop now */\r
-            if (rem == 0)\r
-                break;\r
-\r
-            /* note where this line starts */\r
-            nl = p;\r
-            nlrem = rem;\r
-\r
-            /* \r
-             *   if we're at a non-whitespace character, it's definitely not\r
-             *   a continuation line \r
-             */\r
-            if (!u_ishspace(*p))\r
-                break;\r
-\r
-            /* \r
-             *   check for spaces followed by a non-space character - this\r
-             *   would signify a continuation line\r
-             */\r
-            for ( ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ;\r
-            if (rem == 0 || u_isnl(p, rem))\r
-            {\r
-                /* \r
-                 *   we're at end of file, we found a line with nothing but\r
-                 *   whitespace, so this isn't a continuation line; go back\r
-                 *   to the start of this line and end the value here \r
-                 */\r
-                p = nl;\r
-                rem = nlrem;\r
-                break;\r
-            }\r
-\r
-            /* \r
-             *   We found whitespace followed by non-whitespace, so this is a\r
-             *   continuation line.  Keep going for now.\r
-             */\r
-        }\r
-\r
-        /* remove any trailing newlines */\r
-        while (p > val_start)\r
-        {\r
-            /* move back one character */\r
-            prevc(&p, &rem);\r
-\r
-            /* \r
-             *   if it's a newline, keep going; otherwise, keep this\r
-             *   character and stop trimming \r
-             */\r
-            if (!u_isnl(p, rem))\r
-            {\r
-                nextc(&p, &rem);\r
-                break;\r
-            }\r
-        }\r
-\r
-        /* \r
-         *   Allocate a new value entry.  Make room for the entry itself plus\r
-         *   a copy of the value.  We don't need to make a copy of the name,\r
-         *   since we can just use the original copy from the story file\r
-         *   buffer.  We do need a copy of the value because we might need to\r
-         *   transform it slightly, to remove newlines and leading spaces on\r
-         *   continuation lines. \r
-         */\r
-        val = (valinfo *)malloc(sizeof(valinfo) + (p - val_start));\r
-\r
-        /* link it into our list */\r
-        val->nxt = val_head;\r
-        val_head = val;\r
-\r
-        /* point the name directly to the name in the buffer */\r
-        val->name = name_start;\r
-        val->name_len = name_len;\r
-\r
-        /* point the value to the space allocated along with the valinfo */\r
-        val->val = (char *)(val + 1);\r
-\r
-        /* store the name, removing newlines and continuation-line spaces */\r
-        for (outp = val->val, inp = val_start, inlen = p - val_start ;\r
-             inlen != 0 ; )\r
-        {\r
-            const char *l;\r
-            \r
-            /* find the next newline */\r
-            for (l = inp ; inlen != 0 && !u_isnl(inp, inlen) ;\r
-                 nextc(&inp, &inlen)) ;\r
-\r
-            /* copy this line to the output */\r
-            memcpy(outp, l, inp - l);\r
-            outp += inp - l;\r
-\r
-            /* if we're out of input, we're done */\r
-            if (inlen == 0)\r
-                break;\r
-\r
-            /* we're at a newline: replace it with a space in the output */\r
-            *outp++ = ' ';\r
-\r
-            /* skip the newline and subsequent whitespace in the input */\r
-            for (skip_newline(&inp, &inlen) ;\r
-                 inlen != 0 && u_ishspace(*inp) ; nextc(&inp, &inlen)) ;\r
-        }\r
-\r
-        /* set the length of the parsed value */\r
-        val->val_len = outp - val->val;\r
-\r
-        /* skip to the next line and continue parsing */\r
-        skip_to_next_line(&p, &rem);\r
-    }\r
-\r
-    /* return the head of the linked list of value entries */\r
-    return val_head;\r
-}\r
-static int my_memicmp(const void *aa, const void *bb, int l)\r
-{\r
- int s=0,i;\r
- char *a=(char *) aa;\r
- char *b=(char *) bb;\r
- for(i=0;i<l && !s;i++)\r
-  s=tolower(a[i])-tolower(b[i]);\r
- return s;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Given a valinfo list obtained from parse_game_info(), find the value for\r
- *   the given key \r
- */\r
-static valinfo *find_by_key(valinfo *list_head, const char *key)\r
-{\r
-    valinfo *p;\r
-    size_t key_len = strlen(key);\r
-    \r
-    /* scan the list for the given key */\r
-    for (p = list_head ; p != 0 ; p = p->nxt)\r
-    {\r
-        /* if this one matches the key we're looking for, return it */\r
-        if (p->name_len == key_len && my_memicmp(p->name, key, key_len) == 0)\r
-            return p;\r
-    }\r
-\r
-    /* no luck */\r
-    return 0;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Delete a valinfo list obtained from parse_game_info() \r
- */\r
-static void delete_valinfo_list(valinfo *head)\r
-{\r
-    /* keep going until we run out of entries */\r
-    while (head != 0)\r
-    {\r
-        /* remember the next entry, before we delete this one */\r
-        valinfo *nxt = head->nxt;\r
-\r
-        /* delete this one */\r
-        free(head);\r
-\r
-        /* move on to the next one */\r
-        head = nxt;\r
-    }\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Find the cover art resource.  We'll look for CoverArt.jpg and\r
- *   CoverArt.png, in that order. \r
- */\r
-static int find_cover_art(const void *story_file, int32 story_len,\r
-                          resinfo *resp, int32 *image_format,\r
-                          int32 *width, int32 *height)\r
-{\r
-    resinfo res;\r
-    int32 x, y;\r
-\r
-    /* if they didn't want the resource info, provide a placeholder */\r
-    if (resp == 0)\r
-        resp = &res;\r
-\r
-    /* look for CoverArt.jpg first */\r
-    if (find_resource(story_file, story_len, "CoverArt.jpg", resp))\r
-    {\r
-        /* get the width and height */\r
-        if (!get_jpeg_dim(resp->ptr, resp->len, &x, &y))\r
-            return FALSE;\r
-\r
-        /* hand back the width and height if it was requested */\r
-        if (width != 0)\r
-            *width = x;\r
-        if (height != 0)\r
-            *height = y;\r
-\r
-        /* tell them it's a JPEG image */\r
-        if (image_format != 0)\r
-            *image_format = JPEG_COVER_FORMAT;\r
-\r
-        /* indicate success */\r
-        return TRUE;\r
-    }\r
-\r
-    /* look for CoverArt.png second */\r
-    if (find_resource(story_file, story_len, "CoverArt.png", resp))\r
-    {\r
-        /* get the width and height */\r
-        if (!get_png_dim(resp->ptr, resp->len, &x, &y))\r
-            return FALSE;\r
-\r
-        /* hand back the width and height if it was requested */\r
-        if (width != 0)\r
-            *width = x;\r
-        if (height != 0)\r
-            *height = y;\r
-\r
-        /* tell them it's a PNG image */\r
-        if (image_format != 0)\r
-            *image_format = PNG_COVER_FORMAT;\r
-\r
-        /* indicate success */\r
-        return TRUE;\r
-    }\r
-\r
-    /* didn't find it */\r
-    return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Find a resource in a TADS 2 or 3 story file that's been loaded into\r
- *   memory.  On success, fills in the offset and size of the resource and\r
- *   returns TRUE; if the resource isn't found, returns FALSE.\r
- */\r
-static int find_resource(const void *story_file, int32 story_len,\r
-                         const char *resname, resinfo *info)\r
-{\r
-    /* if there's no file, there's no resource */\r
-    if (story_file == 0)\r
-        return FALSE;\r
-\r
-    /* check for tads 2 */\r
-    if (tads_match_sig(story_file, story_len, T2_SIGNATURE))\r
-    {\r
-        info->tads_version = 2;\r
-        return t2_find_res(story_file, story_len, resname, info);\r
-    }\r
-\r
-    /* check for tads 3 */\r
-    if (tads_match_sig(story_file, story_len, T3_SIGNATURE))\r
-    {\r
-        info->tads_version = 3;\r
-        return t3_find_res(story_file, story_len, resname, info);\r
-    }\r
-\r
-    /* it's not one of ours */\r
-    return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Find a resource in a tads 2 game file \r
- */\r
-static int t2_find_res(const void *story_file, int32 story_len,\r
-                       const char *resname, resinfo *info)\r
-{\r
-    const char *basep = (const char *)story_file;\r
-    const char *endp = basep + story_len;\r
-    const char *p;\r
-    size_t resname_len;\r
-\r
-    /* note the length of the name we're seeking */\r
-    resname_len = strlen(resname);\r
-\r
-    /* \r
-     *   skip past the tads 2 file header (13 bytes for the signature, 7\r
-     *   bytes for the version header, 2 bytes for the flags, 26 bytes for\r
-     *   the timestamp) \r
-     */\r
-    p = basep + 13 + 7 + 2 + 26;\r
-\r
-    /* \r
-     *   scan the sections in the file; stop on $EOF, and skip everything\r
-     *   else but HTMLRES, which is the section type that \r
-     */\r
-    while (p < endp)\r
-    {\r
-        unsigned long endofs;\r
-\r
-        /*\r
-         *   We're pointing to a section block header, which looks like this:\r
-         *   \r
-         *.    <byte> type-length\r
-         *.    <byte * type-length> type-name\r
-         *.    <uint32> next-section-address\r
-         */\r
-\r
-        /* read the ending offset */\r
-        endofs = osrp4(p + 1 + osrp1(p));\r
-\r
-        /* check the type */\r
-        if (p[0] == 7 && memcmp(p + 1, "HTMLRES", 7) == 0)\r
-        {\r
-            unsigned long found_ofs;\r
-            int found;\r
-            unsigned long entry_cnt;\r
-\r
-            /* we haven't found the resource yet */\r
-            found = FALSE;\r
-\r
-            /* \r
-             *   It's a multimedia resource block.  Skip the section block\r
-             *   header and look at the index table - the index table\r
-             *   consists of a uint32 giving the number of entries, followed\r
-             *   by a reserved uint32, followed by the entries.  \r
-             */\r
-            p += 12;\r
-            entry_cnt = osrp4(p);\r
-\r
-            /* skip to the first index entry */\r
-            p += 8;\r
-\r
-            /* scan the index entries */\r
-            for ( ; entry_cnt != 0 ; --entry_cnt)\r
-            {\r
-                unsigned long res_ofs;\r
-                unsigned long res_siz;\r
-                size_t name_len;\r
-\r
-                /*\r
-                 *   We're at the next index entry, which looks like this:\r
-                 *\r
-                 *.    <uint32>  resource-address (bytes from end of index)\r
-                 *.    <uint32>  resource-length (in bytes)\r
-                 *.    <uint2> name-length\r
-                 *.    <byte * name-length> name\r
-                 */\r
-                res_ofs = osrp4(p);\r
-                res_siz = osrp4(p + 4);\r
-                name_len = osrp2(p + 8);\r
-                p += 10;\r
-\r
-                /* check for a match to the name we're looking for */\r
-                if (name_len == resname_len\r
-                    && my_memicmp(resname, p, name_len) == 0)\r
-                {\r
-                    /* \r
-                     *   it's the one we want - note its resource location\r
-                     *   and size, but keep scanning for now, since we need\r
-                     *   to find the end of the index before we'll know where\r
-                     *   the actual resources begin \r
-                     */\r
-                    found = TRUE;\r
-                    found_ofs = res_ofs;\r
-                    info->len = res_siz;\r
-                }\r
-\r
-                /* skip this one's name */\r
-                p += name_len;\r
-            }\r
-\r
-            /* \r
-             *   if we found our resource, the current seek position is the\r
-             *   base of the offset we found in the directory; so we can\r
-             *   finally fix up the offset to give the actual file location\r
-             *   and return the result \r
-             */\r
-            if (found)\r
-            {\r
-                /* fix up the offset with the actual file location */\r
-                info->ptr = p + found_ofs;\r
-\r
-                /* tell the caller we found it */\r
-                return TRUE;\r
-            }\r
-        }\r
-        else if (p[0] == 4 && memcmp(p + 1, "$EOF", 4) == 0)\r
-        {\r
-            /* \r
-             *   that's the end of the file - we've finished without finding\r
-             *   the resource, so return failure \r
-             */\r
-            return FALSE;\r
-        }\r
-\r
-        /* move to the next section */\r
-        p = basep + endofs;\r
-    }\r
-\r
-    /* \r
-     *   reached EOF without an $EOF marker - file must be corrupted; return\r
-     *   'not found' \r
-     */\r
-    return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Find a resource in a T3 image file \r
- */\r
-static int t3_find_res(const void *story_file, int32 story_len,\r
-                       const char *resname, resinfo *info)\r
-{\r
-    const char *basep = (const char *)story_file;\r
-    const char *endp = basep + story_len;\r
-    const char *p;\r
-    size_t resname_len;\r
-\r
-    /* note the length of the name we're seeking */\r
-    resname_len = strlen(resname);\r
-\r
-    /* \r
-     *   skip the file header - 11 bytes for the signature, 2 bytes for the\r
-     *   format version, 32 reserved bytes, and 24 bytes for the timestamp \r
-     */\r
-    p = basep + 11 + 2 + 32 + 24;\r
-\r
-    /* scan the data blocks */\r
-    while (p < endp)\r
-    {\r
-        unsigned long siz;\r
-\r
-        /*\r
-         *   We're at the next block header, which looks like this:\r
-         *\r
-         *.    <byte * 4> type-name\r
-         *.    <uint32> block-size\r
-         *.    <uint16> flags\r
-         */\r
-\r
-        /* get the block size */\r
-        siz = osrp4(p + 4);\r
-\r
-        /* check the type */\r
-        if (memcmp(p, "MRES", 4) == 0)\r
-        {\r
-            unsigned int entry_cnt;\r
-            unsigned int i;\r
-            const char *blockp;\r
-\r
-            /* skip the header */\r
-            p += 10;\r
-\r
-            /* \r
-             *   remember the location of the base of the block - the data\r
-             *   seek location for each index entry is given as an offset\r
-             *   from this location \r
-             */\r
-            blockp = p;\r
-\r
-            /* the first thing in the table is the number of entries */\r
-            entry_cnt = osrp2(p);\r
-            p += 2;\r
-\r
-            /* read the entries */\r
-            for (i = 0 ; i < entry_cnt ; ++i)\r
-            {\r
-                unsigned long entry_ofs;\r
-                unsigned long entry_siz;\r
-                size_t entry_name_len;\r
-                char namebuf[256];\r
-                char *xp;\r
-                size_t xi;\r
-\r
-                /* \r
-                 *   Parse this index entry:\r
-                 *   \r
-                 *.    <uint32> address (as offset from the block base)\r
-                 *.    <uint32> size (in bytes)\r
-                 *.    <uint8> name-length\r
-                 *.    <byte * name-length> name (all bytes XORed with 0xFF)\r
-                 */\r
-                entry_ofs = osrp4(p);\r
-                entry_siz = osrp4(p + 4);\r
-                entry_name_len = (unsigned char)p[8];\r
-\r
-                /* unmask the name */\r
-                memcpy(namebuf, p + 9, resname_len);\r
-                for (xi = resname_len, xp = namebuf ; xi != 0 ; --xi)\r
-                    *xp++ ^= 0xFF;\r
-\r
-                /* if this is the one we're looking for, return it */\r
-                if (entry_name_len == resname_len\r
-                    && my_memicmp(resname, namebuf, resname_len) == 0)\r
-                {\r
-                    /* \r
-                     *   fill in the return information - note that the entry\r
-                     *   offset given in the header is an offset from data\r
-                     *   block's starting location, so fix this up to an\r
-                     *   absolute seek location for the return value \r
-                     */\r
-                    info->ptr = blockp + entry_ofs;\r
-                    info->len = entry_siz;\r
-\r
-                    /* return success */\r
-                    return TRUE;\r
-                }\r
-\r
-                /* skip this entry (header + name length) */\r
-                p += 9 + entry_name_len;\r
-            }\r
-\r
-            /* \r
-             *   if we got this far, we didn't find the name; so skip past\r
-             *   the MRES section by adding the section length to the base\r
-             *   pointer, and resume the main file scan \r
-             */\r
-            p = blockp + siz;\r
-        }\r
-        else if (memcmp(p, "EOF ", 4) == 0)\r
-        {\r
-            /* \r
-             *   end of file - we've finished without finding the resource,\r
-             *   so return failure \r
-             */\r
-            return FALSE;\r
-        }\r
-        else\r
-        {\r
-            /* \r
-             *   we don't care about anything else - just skip this block and\r
-             *   keep going; to skip the block, simply seek ahead past the\r
-             *   block header and then past the block's contents, using the\r
-             *   size given the in block header \r
-             */\r
-            p += siz + 10;\r
-        }\r
-    }\r
-\r
-    /* \r
-     *   reached EOF without an EOF marker - file must be corrupted; return\r
-     *   'not found' \r
-     */\r
-    return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   JPEG and PNG information extraction (based on the versions in\r
- *   babel_story_functions.c) \r
- */\r
-static int get_jpeg_dim(const void *img, int32 extent,\r
-                        int32 *xout, int32 *yout)\r
-{\r
-    const unsigned char *dp=(const unsigned char *) img;\r
-    const unsigned char *ep=dp+extent;\r
-    unsigned int t1, t2, w, h;\r
-\r
-    t1 = *dp++;\r
-    t2 = *dp++;\r
-    if (t1 != 0xff || t2 != 0xD8)\r
-        return FALSE;\r
-\r
-    while(1)\r
-    {\r
-        if (dp>ep) return FALSE;\r
-        for(t1=*(dp++);t1!=0xff;t1=*(dp++)) if (dp>ep) return FALSE;\r
-        do { t1=*(dp++); if (dp>ep) return FALSE;} while (t1 == 0xff);\r
-\r
-        if ((t1 & 0xF0) == 0xC0 && !(t1==0xC4 || t1==0xC8 || t1==0xCC))\r
-        {\r
-            dp+=3;\r
-            if (dp>ep) return FALSE;\r
-            h=*(dp++) << 8;\r
-            if (dp>ep) return FALSE;\r
-            h|=*(dp++);\r
-            if (dp>ep) return FALSE;\r
-            w=*(dp++) << 8;\r
-            if (dp>ep) return FALSE;\r
-            w|=*(dp);\r
-\r
-            *xout = w;\r
-            *yout = h;\r
-            return TRUE;\r
-        }\r
-        else if (t1==0xD8 || t1==0xD9)\r
-            break;\r
-        else\r
-        {\r
-            int l;\r
-\r
-            if (dp>ep) return FALSE;\r
-            l=*(dp++) << 8;\r
-            if (dp>ep) return FALSE;\r
-            l|= *(dp++);\r
-            l-=2;\r
-            dp+=l;\r
-            if (dp>ep) return FALSE;\r
-        }\r
-    }\r
-    return FALSE;\r
-}\r
-\r
-static int32 png_read_int(const unsigned char *mem)\r
-{\r
-    int32 i4 = mem[0],\r
-    i3 = mem[1],\r
-    i2 = mem[2],\r
-    i1 = mem[3];\r
-    return i1 | (i2<<8) | (i3<<16) | (i4<<24);\r
-}\r
-\r
-\r
-static int get_png_dim(const void *img, int32 extent,\r
-                       int32 *xout, int32 *yout)\r
-{\r
-    const unsigned char *dp=(const unsigned char *)img;\r
-\r
-    if (extent<33 ||\r
-        !(dp[0]==137 && dp[1]==80 && dp[2]==78 && dp[3]==71 &&\r
-          dp[4]==13 && dp[5] == 10 && dp[6] == 26 && dp[7]==10)||\r
-        !(dp[12]=='I' && dp[13]=='H' && dp[14]=='D' && dp[15]=='R'))\r
-        return FALSE;\r
-\r
-    *xout = png_read_int(dp+16);\r
-    *yout = png_read_int(dp+20);\r
-    return TRUE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- *   Testing main() - this implements a set of unit tests on the tads\r
- *   version.  \r
- */\r
-\r
-#ifdef TADS_TEST\r
-\r
-#include "babel_handler.h"\r
-\r
-void main(int argc, char **argv)\r
-{\r
-    FILE *fp;\r
-    int32 siz;\r
-    void *buf;\r
-    valinfo *head;\r
-    int32 rv;\r
-    int tadsver;\r
-    char outbuf[TREATY_MINIMUM_EXTENT];\r
-\r
-    /* check arguments */\r
-    if (argc != 2)\r
-    {\r
-        printf("usage: tads <game-file>\n");\r
-        exit(1);\r
-    }\r
-\r
-    /* initialize the babel subsystems */\r
-    babel_init(argv[1]);\r
-\r
-    /* open the story file */\r
-    if ((fp = fopen(argv[1], "rb")) == 0)\r
-    {\r
-        printf("error opening input file\n");\r
-        exit(2);\r
-    }\r
-\r
-    /* check the file size */\r
-    fseek(fp, 0, SEEK_END);\r
-    siz = ftell(fp);\r
-    fseek(fp, 0, SEEK_SET);\r
-\r
-    /* allocate space for it */\r
-    if ((buf = malloc(siz)) == 0)\r
-    {\r
-        printf("error allocating space to load file\n");\r
-        exit(2);\r
-    }\r
-\r
-    /* load it */\r
-    if ((int32)fread(buf, 1, siz, fp) != siz)\r
-    {\r
-        printf("error reading file\n");\r
-        exit(2);\r
-    }\r
-\r
-    /* done with the file */\r
-    fclose(fp);\r
-\r
-\r
-\r
-    /* ===== test 1 - basic parse_game_info() test ===== */\r
-\r
-    /* parse the gameinfo record and print the results */\r
-    if ((head = parse_game_info(buf, siz, &tadsver)) != 0)\r
-    {\r
-        valinfo *val;\r
-\r
-        printf("found GameInfo - tads major version = %d\n", tadsver);\r
-        for (val = head ; val != 0 ; val = val->nxt)\r
-        {\r
-            printf("%.*s=[%.*s]\n",\r
-                   (int)val->name_len, val->name,\r
-                   (int)val->val_len, val->val);\r
-        }\r
-        printf("\n");\r
-    }\r
-    else\r
-        printf("no GameInfo found\n\n");\r
-\r
-\r
-\r
-    /* ===== test 2 - test the get_story_file_IFID generator ===== */\r
-    rv = tads_get_story_file_IFID(buf, siz, outbuf, TREATY_MINIMUM_EXTENT);\r
-    if (rv == 1)\r
-        printf("IFID = [%s]\n\n", outbuf);\r
-    else\r
-        printf("IFID return code = %ld\n", rv);\r
-\r
-\r
-\r
-    /* ===== test 3 - test the ifiction synthesizer ===== */\r
-    if ((rv = tads_get_story_file_metadata_extent(buf, siz)) > 0)\r
-    {\r
-        char *ifbuf;\r
-\r
-        /* try allocating the space */\r
-        if ((ifbuf = malloc((size_t)rv)) != 0)\r
-        {\r
-            /* synthesize the story file */\r
-            rv = tads_get_story_file_metadata(buf, siz, ifbuf, rv);\r
-            if (rv > 0)\r
-                printf("ifiction metadata:\n=====\n%.*s\n=====\n\n",\r
-                       (int)rv, ifbuf);\r
-            else\r
-                printf("tads_get_story_file_metadata result = %ld\n", rv);\r
-        }\r
-        else\r
-            printf("unable to allocate %ld bytes for metadata record\n", rv);\r
-    }\r
-    else\r
-        printf("tads_get_story_file_metadata_extent result code = %ld\n", rv);\r
-    \r
-\r
-    /* free the loaded story file buffer */\r
-    free(buf);\r
-}\r
-\r
-\r
-#endif TADS_TEST\r
-\r
diff --git a/babel/tads.h b/babel/tads.h
deleted file mode 100644 (file)
index 296b91d..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*\r
- *   tads.h - Treaty of Babel common declarations for tads2 and tads3 modules\r
- *   \r
- *   This file depends on treaty_builder.h\r
- *   \r
- *   This file is public domain, but note that any changes to this file may\r
- *   render it noncompliant with the Treaty of Babel\r
- *   \r
- *   Modified\r
- *.   04/18/2006 MJRoberts  - creation\r
- */\r
-\r
-#ifndef TADS_H\r
-#define TADS_H\r
-\r
-/* match a TADS file signature */\r
-int tads_match_sig(const void *buf, int32 len, const char *sig);\r
-\r
-/* get the IFID for a tads story file */\r
-int32 tads_get_story_file_IFID(void *story_file, int32 extent,\r
-                               char *output, int32 output_extent);\r
-\r
-/* get the synthesized iFiction record from a tads story file */\r
-int32 tads_get_story_file_metadata(void *story_file, int32 extent,\r
-                                   char *buf, int32 bufsize);\r
-\r
-/* get the size of the synthesized iFiction record for a tads story file */\r
-int32 tads_get_story_file_metadata_extent(void *story_file, int32 extent);\r
-\r
-/* get the cover art from a tads story file */\r
-int32 tads_get_story_file_cover(void *story_file, int32 extent,\r
-                                void *buf, int32 bufsize);\r
-\r
-/* get the size of the cover art from a tads story file */\r
-int32 tads_get_story_file_cover_extent(void *story_file, int32 extent);\r
-\r
-/* get the image format (jpeg, png) of the covert art in a tads story file */\r
-int32 tads_get_story_file_cover_format(void *story_file, int32 extent);\r
-\r
-#endif /* TADS_H */\r
diff --git a/babel/tads2.c b/babel/tads2.c
deleted file mode 100644 (file)
index 87c1fcc..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/* \r
- *   tads2.c - Treaty of Babel module for Tads 2 files\r
- *   \r
- *   This file depends on treaty_builder.h\r
- *   \r
- *   This file is public domain, but note that any changes to this file may\r
- *   render it noncompliant with the Treaty of Babel\r
- *   \r
- *   Modified\r
- *.   04/15/2006 LRRaszewski - Separated tads2.c and tads3.c \r
- *.   04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions\r
- *.   04/08/2006 MJRoberts  - initial implementation\r
- */\r
-\r
-#define FORMAT tads2\r
-#define HOME_PAGE "http://www.tads.org"\r
-#define FORMAT_EXT ".gam"\r
-\r
-\r
-#include "treaty_builder.h"\r
-#include "tads.h"\r
-\r
-#define T2_SIGNATURE "TADS2 bin\012\015\032"\r
-\r
-#ifndef FALSE\r
-#define FALSE 0\r
-#endif\r
-#ifndef TRUE\r
-#define TRUE 1\r
-#endif\r
-\r
-/*\r
- *   get a story file's IFID\r
- */\r
-static int32 get_story_file_IFID(void *story_file, int32 extent,\r
-                                 char *output, int32 output_extent)\r
-{\r
-    /* use the common tads IFID extractor/generator */\r
-    return tads_get_story_file_IFID(story_file, extent,\r
-                                    output, output_extent);\r
-}\r
-\r
-/*\r
- *   determine if a given story file is one of ours \r
- */\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
-    /* check our signature */\r
-    if (tads_match_sig(story_file, extent, T2_SIGNATURE))\r
-        return VALID_STORY_FILE_RV;\r
-\r
-    /* not one of ours */\r
-    return INVALID_STORY_FILE_RV;\r
-}\r
-\r
-/*\r
- *   Get the size of the iFiction metadata for the game \r
- */\r
-static int32 get_story_file_metadata_extent(void *story_file, int32 extent)\r
-{\r
-    /* use the common tads iFiction synthesizer */\r
-    return tads_get_story_file_metadata_extent(story_file, extent);\r
-}\r
-\r
-/*\r
- *   Get the iFiction metadata for the game\r
- */\r
-static int32 get_story_file_metadata(void *story_file, int32 extent,\r
-                                     char *buf, int32 bufsize)\r
-{\r
-    /* use the common tads iFiction synthesizer */\r
-    return tads_get_story_file_metadata(story_file, extent, buf,  bufsize);\r
-}\r
-\r
-static int32 get_story_file_cover_extent(void *story_file, int32 story_len)\r
-{\r
-    /* use the common tads cover file extractor */\r
-    return tads_get_story_file_cover_extent(story_file, story_len);\r
-}\r
-\r
-/*\r
- *   Get the format of the cover art \r
- */\r
-static int32 get_story_file_cover_format(void *story_file, int32 story_len)\r
-{\r
-    /* use the common tads cover file extractor */\r
-    return tads_get_story_file_cover_format(story_file, story_len);\r
-}\r
-\r
-/*\r
- *   Get the cover art data \r
- */\r
-static int32 get_story_file_cover(void *story_file, int32 story_len,\r
-                                  void *outbuf, int32 output_extent)\r
-{\r
-    /* use the common tads cover file extractor */\r
-    return tads_get_story_file_cover(story_file, story_len,\r
-                                     outbuf, output_extent);\r
-}\r
-\r
diff --git a/babel/tads3.c b/babel/tads3.c
deleted file mode 100644 (file)
index 23d8fb5..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/* \r
- *   tads3.c - Treaty of Babel module for Tads 3 files\r
- *   \r
- *   This file depends on treaty_builder.h\r
- *   \r
- *   This file is public domain, but note that any changes to this file may\r
- *   render it noncompliant with the Treaty of Babel\r
- *   \r
- *   Modified\r
- *.   04/15/2006 LRRaszewski - Separated tads2.c and tads3.c \r
- *.   04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions\r
- *.   04/08/2006 MJRoberts  - initial implementation\r
- */\r
-\r
-#define FORMAT tads3\r
-#define HOME_PAGE "http://www.tads.org"\r
-#define FORMAT_EXT ".t3"\r
-\r
-\r
-#include "treaty_builder.h"\r
-#include "tads.h"\r
-\r
-#define T3_SIGNATURE "T3-image\015\012\032"\r
-\r
-#ifndef FALSE\r
-#define FALSE 0\r
-#endif\r
-#ifndef TRUE\r
-#define TRUE 1\r
-#endif\r
-\r
-/*\r
- *   get a story file's IFID \r
- */\r
-static int32 get_story_file_IFID(void *story_file, int32 extent,\r
-                                 char *output, int32 output_extent)\r
-{\r
-    /* use the common tads IFID extractor/generator */\r
-    return tads_get_story_file_IFID(story_file, extent,\r
-                                    output, output_extent);\r
-}\r
-\r
-/*\r
- *   determine if a given story file is one of ours \r
- */\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
-    /* check our signature */\r
-    if (tads_match_sig(story_file, extent, T3_SIGNATURE))\r
-        return VALID_STORY_FILE_RV;\r
-\r
-    /* not one of ours */\r
-    return INVALID_STORY_FILE_RV;\r
-}\r
-\r
-/*\r
- *   Get the size of the iFiction metadata for the game \r
- */\r
-static int32 get_story_file_metadata_extent(void *story_file, int32 extent)\r
-{\r
-    /* use the common tads iFiction synthesizer */\r
-    return tads_get_story_file_metadata_extent(story_file, extent);\r
-}\r
-\r
-/*\r
- *   Get the iFiction metadata for the game\r
- */\r
-static int32 get_story_file_metadata(void *story_file, int32 extent,\r
-                                     char *buf, int32 bufsize)\r
-{\r
-    /* use the common tads iFiction synthesizer */\r
-    return tads_get_story_file_metadata(story_file, extent, buf,  bufsize);\r
-}\r
-\r
-static int32 get_story_file_cover_extent(void *story_file, int32 story_len)\r
-{\r
-    /* use the common tads cover file extractor */\r
-    return tads_get_story_file_cover_extent(story_file, story_len);\r
-}\r
-\r
-/*\r
- *   Get the format of the cover art \r
- */\r
-static int32 get_story_file_cover_format(void *story_file, int32 story_len)\r
-{\r
-    /* use the common tads cover file extractor */\r
-    return tads_get_story_file_cover_format(story_file, story_len);\r
-}\r
-\r
-/*\r
- *   Get the cover art data \r
- */\r
-static int32 get_story_file_cover(void *story_file, int32 story_len,\r
-                                  void *outbuf, int32 output_extent)\r
-{\r
-    /* use the common tads cover file extractor */\r
-    return tads_get_story_file_cover(story_file, story_len,\r
-                                     outbuf, output_extent);\r
-}\r
-\r
diff --git a/babel/treaty.h b/babel/treaty.h
deleted file mode 100644 (file)
index 7155553..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*  treaty.h    Header file for Treaty of Babel compliant format modules\r
- *  By L. Ross Raszewski\r
- *  Version 3b\r
- *\r
- *  This file is public domain, but please note that derived versions\r
- *  may not not be compliant with the Treaty of Babel.\r
- *\r
- *  It would be wise for derived works to change the value of\r
- *  TREATY_COMPLIANCE to reflect deviations\r
- */\r
-\r
-#ifndef TREATY_H\r
-\r
-#define TREATY_H\r
-\r
-#define TREATY_COMPLIANCE "Treaty of Babel revision 7"\r
-#define TREATY_VERSION "r7"\r
-\r
-/* return codes */\r
-#define NO_REPLY_RV                    0\r
-#define INVALID_STORY_FILE_RV          -1\r
-#define UNAVAILABLE_RV                 -2\r
-#define INVALID_USAGE_RV               -3\r
-#define INCOMPLETE_REPLY_RV            -4\r
-#define VALID_STORY_FILE_RV            1\r
-\r
-#define PNG_COVER_FORMAT                1\r
-#define JPEG_COVER_FORMAT               2\r
-\r
-/* Treaty bitmasks.  These are not required by the treaty, but are here\r
-   as a convenience.\r
-*/\r
-#define TREATY_SELECTOR_INPUT           0x100\r
-#define TREATY_SELECTOR_OUTPUT          0x200\r
-#define TREATY_SELECTOR_NUMBER          0xFF\r
-\r
-#define TREATY_CONTAINER_SELECTOR       0x400\r
-\r
-/* Treaty selectors */\r
-#define GET_HOME_PAGE_SEL                       0x201\r
-#define GET_FORMAT_NAME_SEL                     0x202\r
-#define GET_FILE_EXTENSIONS_SEL                 0x203\r
-#define CLAIM_STORY_FILE_SEL                    0x104\r
-#define GET_STORY_FILE_METADATA_EXTENT_SEL      0x105\r
-#define GET_STORY_FILE_COVER_EXTENT_SEL         0x106\r
-#define GET_STORY_FILE_COVER_FORMAT_SEL         0x107\r
-#define GET_STORY_FILE_IFID_SEL                 0x308\r
-#define GET_STORY_FILE_METADATA_SEL             0x309\r
-#define GET_STORY_FILE_COVER_SEL                0x30A\r
-#define GET_STORY_FILE_EXTENSION_SEL            0x30B\r
-\r
-/* Container selectors */\r
-#define CONTAINER_GET_STORY_FORMAT_SEL                0x710\r
-#define CONTAINER_GET_STORY_EXTENT_SEL          0x511\r
-#define CONTAINER_GET_STORY_FILE_SEL            0x711\r
-\r
-\r
-\r
-\r
-/* Other magic size limits */\r
-#define TREATY_MINIMUM_EXTENT           512\r
-\r
-\r
-#include <limits.h>\r
-\r
-/* 32-bit integer types */\r
-#ifndef VAX\r
-#if   SCHAR_MAX >= 0x7FFFFFFFL && SCHAR_MIN <= -0x7FFFFFFFL\r
-      typedef signed char       int32;\r
-#elif SHRT_MAX >= 0x7FFFFFFFL  && SHRT_MIN <= -0x7FFFFFFFL\r
-      typedef signed short int  int32;\r
-#elif INT_MAX >= 0x7FFFFFFFL   && INT_MIN <= -0x7FFFFFFFL\r
-      typedef signed int        int32;\r
-#elif LONG_MAX >= 0x7FFFFFFFL  && LONG_MIN <= -0x7FFFFFFFL\r
-      typedef signed long int   int32;\r
-#else\r
-#error No type large enough to support 32-bit integers.\r
-#endif\r
-#else\r
-      /*  VAX C does not provide these limit constants, contrary to ANSI  */\r
-      typedef int int32;\r
-#endif\r
-\r
-\r
-\r
-/* Pointer to treaty function.  Treaty functions must follow this prototype */\r
-\r
-typedef int32 (*TREATY)(int32 selector, void *, int32, void *, int32);\r
-\r
-#endif\r
-\r
diff --git a/babel/treaty_builder.h b/babel/treaty_builder.h
deleted file mode 100644 (file)
index d52ae3e..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-/* treaty_builder.h common macros to build a treaty module\r
- *\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file is public domain, but be aware that any changes to it may\r
- * cause it to cease to be compliant with the Treaty of Babel.\r
- *\r
- * This file depends on treaty.h\r
- *\r
- * The purpose of this file is to simplify the building of a treaty\r
- * module.  It automatically generates a generic treaty function.\r
- *\r
- * Usage:\r
- *\r
- * #define the following values:\r
- *  FORMAT              The treaty name of the format\r
- *  HOME_PAGE           A string containing the URL of the format home page\r
- *  FORMAT_EXT          A string containing a comma separated list of common\r
- *                       extensions for game files in this format\r
- *  NO_METADATA         If the format does not support metadata\r
- *  NO_COVER            If the format does not support cover art\r
- *  CUSTOM_EXTENSION    If game files should not always use the first listed extension\r
- *\r
- * (Note: Formats which support metadata and cover art via a container should\r
- * define NO_METADATA and NO_COVER as container support is handled separately)\r
- *\r
- * #include "treaty_builder.h"\r
- * Define the following functions:\r
- *   static int32 get_story_file_IFID(void *, int32, char *, int32);\r
- *   static int32 claim_story_file(void *, int32);\r
- * Define the following functions if NO_METADATA is not defined:\r
- *   static int32 get_story_file_metadata_extent(void *, int32);\r
- *   static int32 get_story_file_metadata(void *, int32, char *, int32);\r
- * Define the following functions if NO_COVER is not defined\r
- *   static int32 get_story_file_cover_extent(void *, int32);\r
- *   static int32 get_story_file_cover_format(void *, int32);\r
- *   static int32 get_story_file_cover(void *, int32, void *, int32);\r
- * Define the following if CUSTOM_EXTENSION is defined\r
- *   static int32 get_story_file_extension(void *, int32, char *, int32);\r
- *\r
- * The two-parameter functions take the story file and story file extent\r
- * as parameters.  The four-parameter ones also take the output\r
- * buffer and its extent.  They perform the corresponding task to the\r
- * similarly-named selector.\r
- *\r
- * This file also defines the macro ASSERT_OUTPUT_SIZE(x) which\r
- * returns INVALID_USAGE_RV if output_extent is less than x.\r
- *\r
- * #define CONTAINER_FORMAT  before inclusion to generate a container\r
- * module.  A container module should define three additional functions:\r
- *    static int32 get_story_format(void *, int32, char *, int32);\r
- *    static int32 get_story_extent(void *, int32);\r
- *    static int32 get_story_file(void *, int32, void *, int32);\r
- *\r
- */\r
-\r
-#ifndef TREATY_BUILDER\r
-#define TREATY_BUILDER\r
-\r
-#include "treaty.h"\r
-#include <string.h>\r
-\r
-#define ASSERT_OUTPUT_SIZE(x) do { if (output_extent < (x)) return INVALID_USAGE_RV; } while (0)\r
-\r
-#ifndef NO_METADATA\r
-static int32 get_story_file_metadata_extent(void *, int32);\r
-static int32 get_story_file_metadata(void *, int32, char *, int32);\r
-#endif\r
-#ifndef NO_COVER\r
-static int32 get_story_file_cover_extent(void *, int32);\r
-static int32 get_story_file_cover_format(void *, int32);\r
-static int32 get_story_file_cover(void *, int32, void *, int32);\r
-#endif\r
-static int32 get_story_file_IFID(void *, int32, char *, int32);\r
-static int32 claim_story_file(void *, int32);\r
-#ifdef CONTAINER_FORMAT\r
-static int32 get_story_file(void *, int32, void *, int32);\r
-static int32 get_story_format(void *, int32, char *, int32);\r
-static int32 get_story_extent(void *, int32);\r
-#endif\r
-#ifdef CUSTOM_EXTENSION\r
-static int32 get_story_file_extension(void *, int32, char *, int32);\r
-#else\r
-#include <stdio.h>\r
-static int32 get_story_file_extension(void *sf, int32 extent, char *out, int32 output_extent)\r
-{\r
- int i;\r
-\r
- if (!sf || !extent) return INVALID_STORY_FILE_RV;\r
-\r
- for(i=0;FORMAT_EXT[i] && FORMAT_EXT[i]!=',';i++);\r
- ASSERT_OUTPUT_SIZE(i+1);\r
- memcpy(out,FORMAT_EXT,i);\r
- out[i]=0;\r
- return strlen(out);\r
-}\r
-\r
-#endif\r
-\r
-#define TREATY_FUNCTION(X)  DEEP_TREATY_FUNCTION(X)\r
-#define DEEP_TREATY_FUNCTION(X) X ## _treaty\r
-#define dSTRFRY(X) #X\r
-#define STRFRY(X) dSTRFRY(X)\r
-\r
-int32 TREATY_FUNCTION(FORMAT)(int32 selector,\r
-                   void *story_file, int32 extent,\r
-                   void *output, int32 output_extent)\r
-{\r
- int32 ll, csf;\r
- if ((TREATY_SELECTOR_INPUT & selector) &&\r
-     (csf=claim_story_file(story_file, extent))<NO_REPLY_RV)\r
-         return INVALID_STORY_FILE_RV;\r
\r
-\r
- if ((TREATY_SELECTOR_OUTPUT & selector) &&\r
-     (output_extent ==0 ||\r
-     output==NULL))\r
-        return INVALID_USAGE_RV;\r
- switch(selector)\r
- {\r
-  case GET_HOME_PAGE_SEL:\r
-                ASSERT_OUTPUT_SIZE((signed) strlen(HOME_PAGE)+1);\r
-                strcpy((char *) output,HOME_PAGE);\r
-                return NO_REPLY_RV;\r
-  case GET_FORMAT_NAME_SEL:\r
-                ASSERT_OUTPUT_SIZE(512);\r
-                strncpy((char *) output,STRFRY(FORMAT),output_extent-1);\r
-                return NO_REPLY_RV;\r
-  case GET_FILE_EXTENSIONS_SEL:\r
-                ll=(strlen(FORMAT_EXT)+1) < 512 ? (strlen(FORMAT_EXT)+1):512;\r
-                ASSERT_OUTPUT_SIZE(ll);\r
-                strncpy((char *) output, FORMAT_EXT, output_extent);\r
-                return NO_REPLY_RV;\r
-  case CLAIM_STORY_FILE_SEL:\r
-                return csf;\r
-  case GET_STORY_FILE_METADATA_EXTENT_SEL:\r
-#ifndef NO_METADATA\r
-                return get_story_file_metadata_extent(story_file, extent);\r
-#endif\r
-  case GET_STORY_FILE_METADATA_SEL:\r
-#ifndef NO_METADATA\r
-                return get_story_file_metadata(story_file, extent, (char *)output, output_extent);\r
-#else\r
-                return NO_REPLY_RV;\r
-#endif\r
-  case GET_STORY_FILE_COVER_EXTENT_SEL:\r
-#ifndef NO_COVER\r
-                return get_story_file_cover_extent(story_file, extent);\r
-#endif\r
-  case GET_STORY_FILE_COVER_FORMAT_SEL:\r
-#ifndef NO_COVER\r
-                return get_story_file_cover_format(story_file, extent);\r
-#endif\r
-  case GET_STORY_FILE_COVER_SEL:\r
-#ifndef NO_COVER\r
-                return get_story_file_cover(story_file, extent, output, output_extent);\r
-#else\r
-                return NO_REPLY_RV;\r
-#endif\r
-  case GET_STORY_FILE_IFID_SEL:\r
-                return get_story_file_IFID(story_file, extent, (char *)output, output_extent);\r
-  case GET_STORY_FILE_EXTENSION_SEL:\r
-                return get_story_file_extension(story_file, extent, (char *)output, output_extent);\r
-#ifdef CONTAINER_FORMAT\r
-  case CONTAINER_GET_STORY_FORMAT_SEL:\r
-                return get_story_format(story_file, extent, (char *)output, output_extent);\r
-  case CONTAINER_GET_STORY_EXTENT_SEL:\r
-                return get_story_extent(story_file, extent);\r
-  case CONTAINER_GET_STORY_FILE_SEL:\r
-                return get_story_file(story_file, extent, output, output_extent);\r
-#endif\r
-\r
- }\r
- return UNAVAILABLE_RV;\r
-}\r
-\r
-\r
-#else\r
-#error "treaty_builder should be used as most once in any source file";\r
-#endif\r
diff --git a/babel/zcode.c b/babel/zcode.c
deleted file mode 100644 (file)
index fb1d6ba..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/* zcode.c  Treaty of Babel module for Z-code files\r
- * 2006 By L. Ross Raszewski\r
- *\r
- * This file depends on treaty_builder.h\r
- *\r
- * This file is public domain, but note that any changes to this file\r
- * may render it noncompliant with the Treaty of Babel\r
- */\r
-\r
-#define FORMAT zcode\r
-#define HOME_PAGE "http://www.inform-fiction.org"\r
-#define FORMAT_EXT ".z3,.z4,.z5,.z6,.z7,.z8"\r
-#define NO_METADATA\r
-#define NO_COVER\r
-#define CUSTOM_EXTENSION\r
-#include "treaty_builder.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-\r
-static int32 get_story_file_IFID(void *story_file, int32 extent, char *output, int32 output_extent)\r
-{\r
- int32 i,j;\r
- char ser[7];\r
- char buffer[32];\r
-\r
-\r
- if (extent<0x1D) return INVALID_STORY_FILE_RV;\r
- memcpy(ser, (char *) story_file+0x12, 6);\r
- ser[6]=0;\r
- /* Detect vintage story files */\r
- if (!(ser[0]=='8' || ser[0]=='9' ||\r
-     (ser[0]=='0' && ser[1]>='0' && ser[1]<='5')))\r
- {\r
-  for(i=0;i<extent;i++) if (memcmp((char *)story_file+i,"UUID://",7)==0) break;\r
-  if (i<extent) /* Found explicit IFID */\r
-  {\r
-   for(j=i+7;j<extent && ((char *)story_file)[j]!='/';j++);\r
-   if (j<extent)\r
-   {\r
-    i+=7;\r
-    ASSERT_OUTPUT_SIZE(j-i);\r
-    memcpy(output,(char *)story_file+i,j-i);\r
-    output[j-i]=0;\r
-    return 1;\r
-   }\r
-  }\r
- }\r
- /* Did not find intact IFID.  Build one */\r
- i=((unsigned char *)story_file)[2] << 8 |((unsigned char *)story_file)[3];\r
- for(j=0;j<6;j++)\r
-  if (!isalnum(ser[j])) ser[j]='-';\r
-\r
- j=((unsigned char *)story_file)[0x1C] << 8 |((unsigned char *)story_file)[0x1D];\r
-\r
- if (strcmp(ser,"000000") && isdigit(ser[0]) && ser[0]!='8')\r
-  sprintf(buffer,"ZCODE-%d-%s-%04X",i,ser,j);\r
- else\r
-  sprintf(buffer,"ZCODE-%d-%s",i,ser);\r
-\r
- ASSERT_OUTPUT_SIZE((signed) strlen(buffer)+1);\r
- strcpy((char *)output,buffer);\r
- return 1;\r
-\r
-}\r
-\r
-static int32 read_zint(unsigned char *sf)\r
-{\r
- return ((int32)sf[0] << 8) | ((int32) sf[1]);\r
-\r
-}\r
-static int32 claim_story_file(void *story_file, int32 extent)\r
-{\r
- unsigned char *sf=(unsigned char *)story_file;\r
- int32 i,j;\r
- if (extent<0x3c ||\r
-     sf[0] < 1 ||\r
-     sf[0] > 8\r
-    ) return INVALID_STORY_FILE_RV;\r
- for(i=4;i<=14;i+=2)\r
- {\r
-  j=read_zint(sf+i);\r
-  if (j>extent || j < 0x40) return INVALID_STORY_FILE_RV;\r
- }\r
-\r
- return VALID_STORY_FILE_RV;\r
-}\r
-static int32 get_story_file_extension(void *sf, int32 extent, char *out, int32 output_extent)\r
-{\r
- int v;\r
- if (!extent) return INVALID_STORY_FILE_RV;\r
- v= ((char *) sf)[0];\r
- if (v>9) ASSERT_OUTPUT_SIZE(5);\r
- else ASSERT_OUTPUT_SIZE(4);\r
- sprintf(out,".z%d",v);\r
- return 3+(v>9);\r
-\r
-}\r
diff --git a/bundle/Info-chimara.plist b/bundle/Info-chimara.plist
new file mode 100644 (file)
index 0000000..7f3a3f6
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>CFBundleDevelopmentRegion</key>
+    <string>English</string>
+    <key>CFBundleExecutable</key>
+    <string>chimara</string>
+    <key>CFBundleGetInfoString</key>
+    <string>0.9 (C) 2009-2012 Philip Chimento and Marijn van Vliet http://www.chimara-if.org</string>
+    <key>CFBundleIconFile</key>
+    <string>chimara.icns</string>
+    <key>CFBundleIdentifier</key>
+    <string>org.chimara.chimara</string>
+    <key>CFBundleInfoDictionaryVersion</key>
+    <string>6.0</string>
+    <key>CFBundlePackageType</key>
+    <string>APPL</string>
+    <key>CFBundleShortVersionString</key>
+    <string>0.9</string>
+    <key>CFBundleSignature</key>
+    <string>????</string>
+    <key>CFBundleVersion</key>
+    <string>0.9</string>
+    <key>NSHumanReadableCopyright</key>
+    <string>Copyright 2009 - 2012 Philip Chimento and Marijn van Vliet, BSD License.</string>
+    <key>LSMinimumSystemVersion</key>
+    <string>10.4</string>
+</dict>
+</plist>
diff --git a/bundle/chimara.bundle b/bundle/chimara.bundle
new file mode 100644 (file)
index 0000000..39329de
--- /dev/null
@@ -0,0 +1,150 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<app-bundle>
+
+  <meta>
+    <!-- Where to pick up the GTK+ installation, icon themes,
+         etc. Note that "${env:JHBUILD_PREFIX}" is evaluated to the
+         value of the environment variable JHBUILD_PREFIX. You can
+         define additional prefixes and refer to them in paths
+         throughout this file on the form "${prefix:name}". This is
+         useful for installing certain libraries or even the
+         application itself separately. Note that JHBUILD_PREFIX is
+         defined by jhbuild, so it you are not using jhbuild you can
+         either define your own or just hardcode the path here.
+    -->
+    <prefix name="default">${env:JHBUILD_PREFIX}</prefix>
+
+    <!-- The project directory is the default location of the created
+         app. If you leave out the path, the current directory is
+         used. Note the usage of an environment variable here again.
+    -->
+    <destination overwrite="yes">${env:HOME}/projects/chimara</destination>
+
+    <image>
+      <!-- Not implemented yet (DMG image). -->
+    </image>
+
+    <!-- Comment this out to keep the install names in binaries -->
+    <run-install-name-tool/>
+    <!-- Optionally specify a launcher script to use. If the
+         application sets up everything needed itself, like
+         environment variable, linker paths, etc, a launcher script is
+         not needed. If the source path is left out, the default
+         script will be used.
+    -->
+    <launcher-script>${project}/launcher.sh</launcher-script >
+
+    <!-- Not implemented: Optional runtime, could be python or mono
+         for example.
+    -->
+    <!-- runtime copy="yes">/usr/bin/python</runtime -->
+    <!-- Indicate the active gtk version to use. This is needed only
+         for gtk+-3.0 projects. -->
+    <gtk>gtk+-3.0</gtk>
+  </meta>
+
+  <!-- The special macro "${project}" refers to the directory where
+       this bundle file is located. The application name and bundle
+       identifier are taken from the plist file.
+  -->
+  <plist>${project}/Info-chimara.plist</plist>
+
+  <main-binary>${prefix}/bin/chimara</main-binary>
+
+  <!-- Copy in GTK+ modules.  Note the ${gtkdir} macro, which expands
+       to the correct library subdirectory for the specified gtk
+       version.
+  -->
+  <binary>
+    ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/immodules/*.so
+  </binary>
+
+  <binary>
+     ${prefix}/lib/chimara/*.so
+  </binary>
+
+  <!-- Copy in GTK+ theme engines and print backends. Note the use of the
+       "${pkg:module:variable}" macro, which evaluates to a pkg-config
+       variable in the specified module. Note that any libraries that
+       binaries link to are also copied in automatically.  Note also
+       the included ${gtk} macro, which gets the correct package name
+       to get. -->
+  <!--
+  <binary>
+    ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/engines/*.so
+  </binary>
+  -->
+  <binary>
+    ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/printbackends/*.so
+  </binary>
+
+<!-- Starting with 2.24, gdk-pixbuf installs into its own directory. -->
+  <binary>
+    ${prefix}/lib/gdk-pixbuf-2.0/${pkg:gdk-pixbuf-2.0:gdk_pixbuf_binary_version}/loaders/*.so
+  </binary>
+
+  <!-- Translation filenames, one for each program or library that you
+       want to copy in to the bundle. The "dest" attribute is
+       optional, as usual. Bundler will find all translations of that
+       library/program under the indicated directory and copy them.-->
+  <translations name="gtk30">
+    ${prefix}/share/locale
+  </translations>
+
+
+  <!-- Data to copy in, usually Glade/UI files, images, sounds files
+       etc. The destination inside the bundle can be specified if the
+       files should end up at a different location, by using the
+       "dest" property. The destination must then start with the macro
+       "${bundle}", which refers to the bundle root directory.
+  -->
+  <!-- data>
+    ${prefix}/share/gtk-demo
+  </data -->
+
+  <!-- Copy in the themes data. You may want to trim this to save space
+       in your bundle. -->
+  <data>
+    ${prefix}/share/themes
+  </data>
+
+  <data>
+    ${prefix}/share/glib-2.0/schemas
+  </data>
+
+  <!-- Copy icons. Note that the .icns file is an Apple format which
+       contains up to 4 sizes of icon. You can use
+       /Developer/Applications/Utilities/Icon Composer.app to import
+       artwork and create the file. >
+  <data dest="${bundle}/Contents/Resources">
+    ${project}/Giggle.icns
+  </data -->
+
+  <!-- This is where theme commands go. You can copy them in from your
+       theme of choice if they provide and example, or you can just
+       change the source path. -->
+
+  <data dest="${bundle}/Contents/Resources/etc/${gtkdir}/gtkrc">
+    ${project}/gtkrc
+  </data>
+
+  <!-- Icon themes to copy. The "icons" property can be either of
+       "auto", "all", or "none". All or none should be
+       self-explanatory, while auto means that the script will try to
+       figure out which icons are needed. This is done by getting all
+       the strings from all copied binaries, and matching them against
+       icon names. To be safe, you should use "all". "none" is useful
+       if you want just the index.theme file but no icons, mostly
+       needed for the "hicolor" base theme.
+  >
+  <icon-theme icons="auto">
+    Tango
+  </icon-theme -->
+
+  <!-- Pango stuff -->
+  <binary>
+     ${prefix}/lib/pango/${pkg:pango:pango_module_version}/modules/*.so
+  </binary>
+
+</app-bundle>
diff --git a/bundle/chimara_icon_512.png b/bundle/chimara_icon_512.png
new file mode 100644 (file)
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 (file)
index 0000000..e69de29
diff --git a/bundle/launcher.sh b/bundle/launcher.sh
new file mode 100755 (executable)
index 0000000..7408de4
--- /dev/null
@@ -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
index bb97b7025f0bc0ba266ed0c9230a32de746afa48..54b947f25e9ddbf4a68d3992fd15afff55322b93 100644 (file)
@@ -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 (file)
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 (executable)
index 18a5f7c..0000000
+++ /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 (executable)
index 79a8829..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Typing 100
diff --git a/iliad/manifest.xml b/iliad/manifest.xml
deleted file mode 100755 (executable)
index 64da7e7..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<package>
-  <metadata>
-    <dc-metadata>
-      <Title>Chimara</Title>
-      <Description>Interactive Fiction Player</Description>
-         <Date>2009-11-17T15:00:00</Date>
-      <Identifier/>
-      <Language/>
-      <Type/>
-    </dc-metadata>
-    <y-metadata>
-      <startpage>run.sh</startpage>
-      <image>chimara.png</image> 
-      <version>000</version>
-         <ItemSize>2457600</ItemSize>
-    </y-metadata>
-  </metadata>
-</package>
diff --git a/iliad/run.sh b/iliad/run.sh
deleted file mode 100755 (executable)
index fe44afa..0000000
+++ /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 (file)
index 7f0d1fc..0000000
+++ /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;
-}
index 0b1cb2e11122dd9bc68e7b9ccc42d5333c5db126..cd8e930fdbc0e1bf02914c6d46d32cf4c13b36bb 100644 (file)
@@ -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 (file)
index 0000000..30411e8
--- /dev/null
@@ -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.<glk_library_name> 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/<youros>.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 (file)
index 0000000..a3f6b12
--- /dev/null
@@ -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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+       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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
index 0000000..94a9ed0
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <http://www.gnu.org/licenses/>.
+
+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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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
+<http://www.gnu.org/licenses/>.
+
+  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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/interpreters/bocfel/Makefile.am b/interpreters/bocfel/Makefile.am
new file mode 100644 (file)
index 0000000..ecb779e
--- /dev/null
@@ -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 (file)
index 0000000..1d22cd3
--- /dev/null
@@ -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 <cspiegel@gmail.com>
+http://bocfel.googlecode.com/
diff --git a/interpreters/bocfel/blorb.c b/interpreters/bocfel/blorb.c
new file mode 100644 (file)
index 0000000..456e976
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..654becc
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef ZTERP_BLORB_H
+#define ZTERP_BLORB_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#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 (file)
index 0000000..2af3d29
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..709860c
--- /dev/null
@@ -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 (file)
index 0000000..f184ddb
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#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 (file)
index 0000000..e1c8c02
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef DICTIONARY_H
+#define DICTIONARY_H
+
+#include <stdint.h>
+
+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 (file)
index 0000000..89cfb27
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <glk.h>
+#include <libchimara/garglk.h>
+
+/* Even on Win32, Gargoyle provides a glkunix startup. */
+#if defined(ZTERP_UNIX) || defined(GARGLK)
+#include <string.h>
+
+#include <glkstart.h>
+
+#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 <windows.h>
+
+#include <WinGlk.h>
+
+#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 (file)
index 0000000..e7fe4e1
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#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 (file)
index 0000000..fd63d26
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef ZTERP_IFF_H
+#define ZTERP_IFF_H
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..d51e65a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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 (file)
index 0000000..c3b9945
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef ZTERP_IO_H
+#define ZTERP_IO_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+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 (file)
index 0000000..925391d
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..94c427a
--- /dev/null
@@ -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 (file)
index 0000000..e234b88
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..a53dee3
--- /dev/null
@@ -0,0 +1,55 @@
+#ifndef ZTERP_MEMORY_H
+#define ZTERP_MEMORY_H
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..ae68839
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..eb532ab
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef OBJECTS_H
+#define OBJECTS_H
+
+#include <stdint.h>
+
+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 (file)
index 0000000..454c1b2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef ZTERP_UNIX
+#define _XOPEN_SOURCE  600
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+
+#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 <sys/stat.h>
+
+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 <unistd.h>
+#include <sys/ioctl.h>
+#include <curses.h>
+#include <term.h>
+#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 (file)
index 0000000..de8f062
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef ZTERP_OSDEP_H
+#define ZTERP_OSDEP_H
+
+#include <stdio.h>
+#include <stddef.h>
+
+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 (file)
index 0000000..385aee3
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <setjmp.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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 (file)
index 0000000..e98b2c8
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef ZTERP_PROCESS_h
+#define ZTERP_PROCESS_H
+
+#include <stdint.h>
+
+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 (file)
index 0000000..2d15347
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <limits.h>
+#include <time.h>
+
+#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 (file)
index 0000000..5968432
--- /dev/null
@@ -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 (file)
index 0000000..35feb56
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#include <libchimara/garglk.h>
+#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 (file)
index 0000000..4e11101
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef ZTERP_SCREEN_H
+#define ZTERP_SCREEN_H
+
+#include <stdint.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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 (file)
index 0000000..493dd36
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <setjmp.h>
+
+#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 (file)
index 0000000..ab87413
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef ZTERP_STACK_H
+#define ZTERP_STACK_H
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..1de5e9e
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..0855541
--- /dev/null
@@ -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 (file)
index 0000000..78f610d
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..b421fef
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef ZTERP_TABLES_H
+#define ZTERP_TABLES_H
+
+#include <stdint.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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 (file)
index 0000000..6cd3d9a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "util.h"
+#include "screen.h"
+#include "unicode.h"
+#include "zterp.h"
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#include <libchimara/garglk.h>
+#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 (file)
index 0000000..a3047a7
--- /dev/null
@@ -0,0 +1,64 @@
+#ifndef ZTERP_UTIL_H
+#define ZTERP_UTIL_H
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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 (file)
index 0000000..e805762
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <time.h>
+
+#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 (file)
index 0000000..9611b83
--- /dev/null
@@ -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 (file)
index 0000000..49b84a0
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <signal.h>
+#include <limits.h>
+
+#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 <glk.h>
+#include <libchimara/garglk.h>
+#ifdef GARGLK
+#include <glkstart.h>
+#include <gi_blorb.h>
+
+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 (file)
index 0000000..2280ef0
--- /dev/null
@@ -0,0 +1,117 @@
+#ifndef ZTERP_H
+#define ZTERP_H
+
+#include <stdint.h>
+
+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
index 16c91a62c72675c548a96f37870c940fed41fad1..fa0f0b1394b3eb64374a8c2161a3eecc20af037f 100644 (file)
@@ -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
index bff9572ffd8c677aac651ceaef53751ebd624c8a..f454f726acf7f2db471c6af5a4de78c226a784a0 100644 (file)
@@ -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"
index ffb5168aea400d58e1b200824b1499655e460a81..81f2f14b2db1f736c9b7c804602b8803188054b8 100644 (file)
@@ -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"
index cb4a20e6d2ba5ff6bb8b2953098254918499cd52..2e99d8dafcc8062341ca95966cf7544d8c264928 100644 (file)
@@ -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
  */
 
 /*
index f40739593b664e8f6ddb7f097a4f4981d1e79d1a..5804d7f4758e936a5747b9693627e49714fe9732 100644 (file)
@@ -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"
index c5598180b354ea091ba2fb82e20377f798ca7538..85b13c9d2e17693ec93b168682a21f9f26b68017 100644 (file)
@@ -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)
index 079373bed1162d9b13cbd4a5bfd2e097196e900f..2d72bfdd11ae8f66b690df268ec0b04d26821bfd 100644 (file)
@@ -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);
 
index f17ec66a80f1f5e7e15aebdfcd5febbe036a10e6..ced562f2b76e6c3e5a9849156d8160c500e0ee79 100644 (file)
@@ -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"
index 4fbbd1e0036590bdc31ff5e23892df384b9dc3ce..671184b54f863e27512088d5f88cf708cba5c64b 100644 (file)
@@ -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
  */
 
 /*
index 5ff5163230bd4680e55b438dc6168b2c37eb2312..944710f044b7f435874dfb338b3169b40f6f369e 100644 (file)
@@ -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"
index a1e86e4570da57861f3f0fe5a4ec4001ba9349ea..ed2108788d8ba830010337a4f5dffdb0f698db74 100644 (file)
@@ -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"
index 58c86078b0c10547c6180833451ce6babf17833b..4d9687abc997656975e47281e0a4b77e37c95e99 100644 (file)
@@ -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"
index e41a57b577a759238a23ce2fb75bb2ca77661d90..d48a9734041f994818309d35c016b97e9cc8529b 100644 (file)
@@ -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"
index 6ea14d89c999abd0648a8b043c5eb29a18c850fe..7eed57a3c370d4e0e52cc9739949fbbd95c5c40f 100644 (file)
@@ -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"
index 239e9c43dfa29f378e586364f20c573029c5ae42..6b0703c8928b6b3704c6dcf8d57635da2d1175b6 100644 (file)
@@ -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"
index 9c4d63ad5d14a427c795e07ea6ad4948badf8172..0a29d0b5b55372af73eb09b8424791bcb4b563b8 100644 (file)
@@ -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"
index 54cef78c60a9f6ad078b31ced9e61e94e6d35593..69e67644db0a465ae09d7a869f53d09c4b45b51d 100644 (file)
@@ -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"
index eb3a16366d2d75dca7e7c42c47a6f2f91d15023f..9ec990c48acbdc806f7908cd86a02fb28ae57086 100644 (file)
@@ -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"
index 9cccc2c1cb0be5813b07872fa80095f8eabbb50a..fd07b2c14da48cfb1378c48e8cec2ec1f8952c71 100644 (file)
@@ -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"
index 3e5c6e0c0980cfb6e39d133093f1a0a057257f43..98356f5d1b44f0410592da1b5a906536d97552c1 100644 (file)
@@ -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"
index bbed8c42cbe4ee7c7b2f58d5b11d2c25ad72603d..f025600644845224b72869f21d35875083566ae7 100644 (file)
@@ -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
index 2462bcf28ee94272e465de7468b7de6dd0a3393d..4161fdd488b6cfcdda821b540f44e134e4681c3b 100644 (file)
@@ -1,6 +1,6 @@
 Git is an interpreter for the Glulx virtual machine. Its homepage is here:\r
 \r
-  http://diden.net/if/git\r
+http://ifarchive.org/indexes/if-archiveXprogrammingXglulxXinterpretersXgit.html\r
 \r
 Git's main goal in life is to be fast. It's about five times faster than Glulxe,\r
 and about twice as fast as Frotz (using the same Inform source compiled for the\r
@@ -14,9 +14,12 @@ between each prompt.
 \r
 Have fun, and let me know what you think!\r
 \r
-  Iain Merrick\r
+  Iain Merrick (Original author)\r
   iain@diden.net\r
 \r
+  David Kinder (Current maintainer)\r
+  davidk.kinder@virgin.net\r
+\r
 --------------------------------------------------------------------------------\r
 \r
 * Building and installing Git\r
@@ -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,\r
 look here:\r
 \r
-  http://eblong.com/zarf/glk\r
+http://eblong.com/zarf/glk/\r
+http://ifarchive.org/indexes/if-archiveXprogrammingXglkXimplementations.html\r
 \r
 Exactly how you build and link everything depends on what platform you're on and\r
 which Glk library you're using. The supplied Makefile should work on any Unix\r
@@ -46,8 +50,8 @@ respectively, but I can't guarantee that they're fully up-to-date.
 \r
 It should be possible to build Git with any C compiler, but it works best with\r
 GCC, because that has a non-standard extension that Git can use for a big speed\r
-boost. GCC 2.95 actually generates faster code than GCC 3, so if you have a\r
-choice, use the former. (On OS X, this means compiling with 'gcc2'.)\r
+boost. GCC 2.95 actually generates faster code than later versions, so if you\r
+have a choice, use the former. (On OS X, this means compiling with 'gcc2'.)\r
 \r
 --------------------------------------------------------------------------------\r
 \r
@@ -116,12 +120,9 @@ KB. 256KB is usually enough to store dozens of moves.
 \r
 GCC 3 has bigger problems than I thought. On PowerPC, the direct threading\r
 option results in much slower code; and on x86, terp.c crashes GCC itself if\r
-direct threading is used. Therefore, I recommend that you use GCC 2.95 if\r
-possible. If you only have GCC 3, don't define USE_DIRECT_THREADING, at least\r
-until the compiler bug is fixed.\r
-\r
-Since the previous update, GCC 4 has been released, but I haven't evaluated it\r
-yet. If you want to give it a try, let me know how you get on!\r
+direct threading is used. GCC 4 seems to work, given some very limited testing,\r
+but still results in slow code. Therefore, I recommend that you use GCC 2.95 if\r
+possible. If you only have GCC 3, don't define USE_DIRECT_THREADING.\r
 \r
 Some Glk libraries, such as xglk, can't deal with memory-mapped files. You can\r
 tell that this is happening if Git can open .ulx files, but complains that .blb\r
@@ -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\r
 USE_MMAP isn't defined.\r
 \r
-1-byte and 2-byte local variables are not implemented yet. This means git can't\r
-currently play games created with the Superglus system. This will be fixed at\r
-some point.\r
+1-byte and 2-byte local variables are not implemented. This means git can't\r
+play games created with old versions of the Superglus system. As these small\r
+local variables now deprecated, it is unlikely that this will be fixed.\r
 \r
 In the search opcodes, direct keys don't work unless they're exactly 4 bytes\r
 long.\r
@@ -191,6 +192,12 @@ also to Eliuk Blau for tracking down bugs in the memory management opcodes.
 \r
 * Version History\r
 \r
+1.2.9 2011-08-28  Fixed a bug in glkop.c dispatching, to do with optional\r
+                  array arguments, following a similar fix in Glulxe.\r
+                  Glk array and string operations are now checked for memory\r
+                  overflows (though not for ROM writing), following a similar\r
+                  fix in Glulxe.\r
+\r
 1.2.8 2010-08-25  Fixed a problem with 'undo' when compiled as 64 bit,\r
                   contributed by Ben Cressey.\r
                   Fixed a sign problem for the @fceil opcode, following a\r
@@ -267,3 +274,4 @@ also to Eliuk Blau for tracking down bugs in the memory management opcodes.
                   Added gitWithStream() as a workaround for xglk\r
 \r
 1.0   2003-10-18  First public release\r
+\r
index cd1f90e09c4ce2e8a2ac156b4ae7fefaadf91e97..cfb0a9407003d8c3fa6d535411a7b487e80b0947 100644 (file)
@@ -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
 
index f8c7ff08f696b734075ce8aeb67d2ee6f0d450bb..fa5b0299888a93a246e2656e7890da97c98b59c0 100644 (file)
@@ -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)
 {
index 870b4f11fef8d9cde3683bde5bf1a3c5db533ae6..d157826ce309290fcbcc9e026e17dcbf7f4f28d2 100644 (file)
@@ -38,29 +38,29 @@ Opcode* gOpcodeTable;
 
 int floatCompare(git_sint32 L1, git_sint32 L2, git_sint32 L3)
 {
-  git_float F1, F2;\r
-\r
-  if (((L3 & 0x7F800000) == 0x7F800000) && ((L3 & 0x007FFFFF) != 0))\r
-    return 0;\r
-  if ((L1 == 0x7F800000 || L1 == 0xFF800000) && (L2 == 0x7F800000 || L2 == 0xFF800000))\r
-    return (L1 == L2);\r
-\r
-  F1 = DECODE_FLOAT(L2) - DECODE_FLOAT(L1);\r
-  F2 = fabs(DECODE_FLOAT(L3));\r
-  return ((F1 <= F2) && (F1 >= -F2));\r
+  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\r
-float git_powf(float x, float y)\r
-{\r
-  if (x == 1.0f)\r
-    return 1.0f;\r
-  else if ((y == 0.0f) || (y == -0.0f))\r
-    return 1.0f;\r
-  else if ((x == -1.0f) && isinf(y))\r
-    return 1.0f;\r
-  return powf(x,y);\r
-}\r
+#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));\r
+    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:\r
-        F1 = (git_float) L1;\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_ftonumz:\r
-        F1 = DECODE_FLOAT(L1);\r
-        if (!signbit(F1)) {\r
-          if (isnan(F1) || isinf(F1) || (F1 > 2147483647.0))\r
-            S1 = 0x7FFFFFFF;\r
-          else\r
-            S1 = (git_sint32) truncf(F1);\r
-        } else {\r
-          if (isnan(F1) || isinf(F1) || (F1 < -2147483647.0))\r
-            S1 = 0x80000000;\r
-          else\r
-            S1 = (git_sint32) truncf(F1);\r
-        }\r
-        NEXT;\r
-\r
-    do_ftonumn:\r
-        F1 = DECODE_FLOAT(L1);\r
-        if (!signbit(F1)) {\r
-          if (isnan(F1) || isinf(F1) || (F1 > 2147483647.0))\r
-            S1 = 0x7FFFFFFF;\r
-          else\r
-            S1 = (git_sint32) roundf(F1);\r
-        } else {\r
-          if (isnan(F1) || isinf(F1) || (F1 < -2147483647.0))\r
-            S1 = 0x80000000;\r
-          else\r
-            S1 = (git_sint32) roundf(F1);\r
-        }\r
-        NEXT;\r
-\r
-    do_ceil:\r
-        F1 = ceilf(DECODE_FLOAT(L1));\r
-        L2 = ENCODE_FLOAT(F1);\r
-        if ((L2 == 0x0) || (L2 == 0x80000000))\r
-          L2 = L1 & 0x80000000;\r
-        S1 = L2;\r
-        NEXT;\r
-\r
-    do_floor:\r
-        F1 = floorf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_sqrt:\r
-        F1 = sqrtf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_exp:\r
-        F1 = expf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_log:\r
-        F1 = logf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_pow:\r
-#ifdef USE_OWN_POWF\r
-        F1 = git_powf(DECODE_FLOAT(L1), DECODE_FLOAT(L2));\r
-#else\r
-        F1 = powf(DECODE_FLOAT(L1), DECODE_FLOAT(L2));\r
-#endif\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_atan2:\r
-        F1 = atan2f(DECODE_FLOAT(L1), DECODE_FLOAT(L2));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_fmod:\r
-        F1 = DECODE_FLOAT(L1);\r
-        F2 = DECODE_FLOAT(L2);\r
-        F3 = fmodf(F1, F2);\r
-        F4 = (F1 - F3) / F2;\r
-        L4 = ENCODE_FLOAT(F4);\r
-        if ((L4 == 0) || (L4 == 0x80000000))\r
-          L4 = (L1 ^ L2) & 0x80000000;\r
-        S1 = ENCODE_FLOAT(F3);\r
-        S2 = L4;\r
-        NEXT;\r
-\r
-    do_sin:\r
-        F1 = sinf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_cos:\r
-        F1 = cosf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_tan:\r
-        F1 = tanf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_asin:\r
-        F1 = asinf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_acos:\r
-        F1 = acosf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
-\r
-    do_atan:\r
-        F1 = atanf(DECODE_FLOAT(L1));\r
-        S1 = ENCODE_FLOAT(F1);\r
-        NEXT;\r
+    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
     
index ed0e7aba6278e921e7423a9be42aa8a2267dc43c..184ed51d876ce44d592475aa1c7666ef56115238 100644 (file)
@@ -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
index 38779a2d788443d4e8eb1bed9c4cfbad0ea201d5..f4fa2057990fb35aa8eb5fe237d21e70ea990026 100644 (file)
@@ -3,22 +3,14 @@
     http://eblong.com/zarf/glulx/index.html
 */
 
-#include <string.h>
 #include "glk.h"
 #include "glulxe.h"
 #include "glkstart.h" /* This comes with the Glk library. */
+#include <string.h>
 
-/* 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; ix<data->argc; ix++) {
-
-#if VM_PROFILING
-    if (!strcmp(data->argv[ix], "--profile")) {
-      ix++;
-      if (ix<data->argc) {
-        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. */
index a54d42a2df2bd788d1ad30df833871a7d5a3dd4f..1e721b1f90bb48ec11887b364b287c46569151e9 100644 (file)
@@ -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
 
index b2647b901050487f1414155b1f90740cc430106d..bb5c83210901312cb6a940f8efb4939b4646e2b0 100644 (file)
@@ -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
 */
index 2163a762c784441ea7437ad703186abb7008e16f..ea980eef588340e8f67337ed3b6523402f3a5b2c 100644 (file)
@@ -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
 */
index 7f31eacdf3ce183b00a632fae00451f4d7791770..2c9eb3bf6975d3d85bc98654c4e9b84ef94e226d 100644 (file)
@@ -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
 */
index a49332e48f7cfb4fa9b83294723a4859f7417534..d69fefacdd00784707dc0af0abca7bc9a62ba3a7 100644 (file)
@@ -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
 */
index fa85d2bb755dd69abd173d851448193e6783b692..3e332210d304c04ac09030f6a822655a89d52b1d 100644 (file)
@@ -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
 */
index 4fcfe41547abaf4acd10864299b16109f22fc6ae..dc0ba817eb6b5bced08e448cf9b4930af8f2143f 100644 (file)
@@ -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
 */
index 47b208d9664ce5550970aa11c6b38d2a465495aa..f31da359deca4d8be1a5f3c9bfa31d5401efb2b8 100644 (file)
@@ -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
 */
index 6196d408b0e1978c52e5df74a5ab2f1720047942..23b94ac21c5381fd9fee9b454ab192ac916b72ca 100644 (file)
@@ -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
 */
index ee297990d92fe0317514dbce72c0bfd11fde64da..41d62f335a32a2e580cb6678dfd7d9e069ed4a14 100644 (file)
@@ -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
 */
index df65116218995d53e74ac05599aaefd1db47abaf..6ba1aa816a9af377094d7ff0a5efd1d109dece5f 100644 (file)
@@ -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
 */
index ce0ceacc949528b71c544b761238486f31363cf0..79e42bd0273ad0b8b69748649f7962c50b087113 100644 (file)
@@ -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
 */
index 319a5360c0787652354f0f9418fc5172f9c0ef1d..1b753cb5d6947679ffd70680a90c44e56ff2d62f 100644 (file)
@@ -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
 */
index 185a7810500c431e27ce5d4d75ee283cdfbadddf..1ae964b8161978d031aa629dbd9e20d509cbffcb 100644 (file)
@@ -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
 */
index ebedf4f452f7eb432dfb84a51b66c2131eb6774a..d6db12dcdd8978063df7f8e1cb5a78779c0cc07b 100644 (file)
@@ -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
 */
index 9da59de4a85ff7504a636f3896b35506fcdc1bb1..4a36996760a1bfa968cb377d758e7cae62ad7943 100644 (file)
@@ -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
 */
index 923bcde0d887a8a092037cdb1a84c94bc9ed371a..b2fb4d23da155a1750b5c296da116c1b2e3abde2 100644 (file)
@@ -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
 */
index 1ccef3274af3e10f87b0ddcffc390d7d91551c42..573334d6a9fb52d0b40d64e6262225e0bcdee314 100644 (file)
@@ -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
 */
index 9c2e54ccbb9041853914bf81610b855daf097da0..aa0d722401d645b10ffdc5dde829fc0250f5f0f3 100644 (file)
@@ -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
 */
index b641c3af6485db5da9ffcca2410c796acd27edb7..fbecba834e8b2659e3ed282454ac978e3056ac08 100644 (file)
@@ -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
 */
index 454e02adc6f9c8ab05b4bb13da59726d9921bfb0..4bc567600c419d7dccf3e1e77fd294b3c320dde5 100644 (file)
@@ -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
 */
index 6f60a644270b437fa54fc64553cafa7eef329307..0a5f2557efe9701b91b07d89313a1d184510c668 100644 (file)
@@ -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
 
index 9c5c552da5aebd18128f92463fb923f30b660f0c..9a444b6b57fa273e51f8c899ee5e79b014b5d65f 100644 (file)
@@ -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
 */
index 0ddbf3630041b689dfb835e245514c2f6841c90b..7fd27dadaa7e8037bfcee2f624aec7e7a4290b4a 100644 (file)
@@ -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
 */
index 33a3e30799856c8528b5a9cbdac4ad5dc252fbd3..97999067dda627a74c63292c4e2b9a75e8d58fb0 100644 (file)
@@ -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
 */
index 763afcdd72c449dd0fc2819c104896af5de48d77..4d79cbfa4d6043efe2090f14a52a9d5df7465003 100644 (file)
@@ -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
 */
index 08195c2094739e0b9c70b87d301efb74c19dad8e..3b9320997cec700d77910653d8ec4819b99cbe42 100644 (file)
@@ -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
 */
index c0a58cfd5dda1bda3d817f2444e5d44995803bc1..6108b471d81bb52f61b47e20e84ac94ac010d7e4 100644 (file)
@@ -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
 */
index 7db9db658db18629cf2d41b04b0cf0b405b95469..e32387b9e4e6045d36a76e62ca0af5769f6e60cc 100644 (file)
@@ -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
 */
index 3f2cb225a92069f1ef6ca8b723ce386685407d31..5a98da95540fe99c41f9a55b7a08078b394c69a1 100644 (file)
@@ -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
 */
index a06fb5afb7e0c97f68b32717a219cc0397480fea..f7bcbbf3546e99c60990370c63e9f4d70f205998 100644 (file)
@@ -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
 */
index f7c12d49f5f295963bb36e4eee463d5f54aab7ae..0beb9bf349dc8d337dc0b00f120b4a3fdcf4be40 100644 (file)
@@ -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
 */
index 96d861d7e6ad7909527dba071c3fe32519d64f98..efb023dde1e030c7588c5903daa76043876f3f66 100644 (file)
@@ -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
 
index 0469b8da33ac313bacaf97e64c697630ed2f3fc7..01b4a256ddfdc731d7e0fd8ee8a99545a30dbb75 100644 (file)
@@ -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 */
index 6a4c0d3e07a402aec0ee11ce1132ca1a3912dffa..aea0f690dce1ed9ddb50871388a089a30c59eace 100644 (file)
 #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
index 95f8b93dca03082d9fe9fd69d4dca18a4da6f279..0554d93fc3530a6ea190034e972de3cdafcbfbf3 100644 (file)
  */
 
 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)
 {
index 72d64610346542555c9b68407b1d04f39cb833ea..f8ec757dfd3948676bd15db0d2e84959698f794d 100644 (file)
@@ -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;
index d4f2043dc454fc322c627844d22f168060b47e61..5838996ba840bad83952d612e88fba5cd00a3a59 100644 (file)
@@ -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:
index 1ab5a359b68ba304bf3d0a1c5ec90d357bc13ff0..543ad43563b147a6ecd5ed0f3e0e9cdbe3f443d0 100644 (file)
@@ -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;
+}
index fe39694c3591205161e38d150168d7bf0a1c395d..5ca8c609e72b93b182e07877f9779776417b809c 100644 (file)
@@ -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
index 7f9b693cf9c8bc7738a7482789d84387f23d7024..fd5aef4e5189836034199637e2e4c16951956e04 100644 (file)
@@ -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 */
index 194a21dc64edb9e46cccba7cd3e9b3ea4237cccd..c36edc79cdde8a9ad02501a1c029dc036fd06783 100644 (file)
@@ -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
                }
        }
 }
index 59188732dc9afdec306acd620619d0c389949ef7..4e7bbf0cfa1b9ec1d1d7f9189d45ab6609db635e 100644 (file)
@@ -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
index 9c40723057393c32f885365cff9dab2baa4dc6a1..ba47dd41dff43137c9c319c99b7e60c2c367a48a 100644 (file)
@@ -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)
index a7325416e5ee1efb90327fe41d4d361fa31c9789..6f5b70d3bda6ea063548debd7dd8a7c7ed455081 100644 (file)
@@ -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
index 617adc19eb90b46736ecf817468beadf7db6c80b..c92e7fc1e7e252b6a1fbd9f57b205096ee912bd1 100644 (file)
@@ -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++;
index dc0d4bb942acb13d4e73ddbeeca3bac7131e1f4f..406a8848969f6d4cce70a9533694bac5401b374e 100644 (file)
@@ -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; i<style_NUMSTYLES; i++) {
@@ -389,9 +379,6 @@ scan_css_file(GScanner *scanner, ChimaraGlk *glk)
        }
 
        g_scanner_destroy(scanner);
-
-       /* Update the pager prompt to the new style */
-       style_update(glk);
 }
 
 /* Internal function: parses a token */
@@ -1199,16 +1186,6 @@ text_tag_to_attr_list(GtkTextTag *tag, PangoAttrList *list)
        }
 }
 
-/* Update pager tag */
-void
-style_update(ChimaraGlk *glk)
-{
-       CHIMARA_GLK_USE_PRIVATE(glk, priv);
-
-       GtkTextTag *pager_tag = GTK_TEXT_TAG( g_hash_table_lookup(priv->styles->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)
index ba9939a1f3218eb22bd5aa6949e1a1fdddbbd488..ee456d88c8ab3dab868009f3f8570b647a708021 100644 (file)
@@ -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
 
index ef85e3f2651f4de44bd99798c490b40d36f2926c..c9132939237d5548f9632ff85a903f0e70148088 100644 (file)
@@ -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;
index c67654a15b3f6a3fcbaad2442de719412a9272c8..aa3250274a5e5e3609e12252457b587f7a61a0cf 100644 (file)
@@ -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
index e7aee92c58fcc308e32ba9e1812839e97c0169c0..6ef3b76f67f004c9e07d3eae2b44ea227a2b5715 100644 (file)
@@ -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
 
index a4cc173593c8a1185ecf272519fad2d4e71c390f..94b9e5b3aaa0d8f547e2fe60dc78c7f01673a668 100644 (file)
@@ -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;
index 1d4261d6b24ed8817dba8ab3f6247cbae5fa496f..260ac8d212cf11ab1c68e8ca22bf958b05be7ca7 100644 (file)
@@ -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 (file)
index abb2ad1..0000000
+++ /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 <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <glib.h>
-#include <glib/gi18n.h>
-#include <gtk/gtk.h>
-
-#include "error.h"
-#include <libchimara/chimara-glk.h>
-#include <libchimara/chimara-if.h>
-
-/* Iliad includes */
-#include <liberdm/erdm.h>
-#include <liberipc/eripcviewer.h>
-#include <liberipc/eripctoolbar.h>
-#include <liberipc/eripcbusyd.h>
-/*#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;
-}
index d76ef56b6d3884338878c63b8b0f9f93aa1c60c4..b919203cae0aaf2dca1768a2faf203ab5b2b07f4 100644 (file)
@@ -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)
index 82a619546b3c91ccb600ae5d4c27bd2a7e0200f7..c153c688545f34c98c259ac40d0e4a5359417440 100644 (file)
@@ -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(
index 9154f4e290a4299e221fe4211688e2d76cdba244..a9f5f43b661634c56fc116e67c20c04ac9908d9b 100644 (file)
@@ -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
index 87f5026de5193a6acaa950dfaa5bc35190901036..37cc4346b5621763df8550365057f5b39c6ce015 100644 (file)
@@ -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 (file)
index e69de29..0000000
diff --git a/player/xepdmgrclient.h b/player/xepdmgrclient.h
deleted file mode 100644 (file)
index e69de29..0000000
index 1fed1058916e7db17e65045192487de13d5b9a5c..aacfc6e4a5fb694e97fdfa53b60c9da451e2d531 100644 (file)
@@ -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 (file)
index 2e8827e..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-#include "babel/babel_handler.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <glib/gprintf.h>
-#include <libgda/libgda.h>
-#include <libgda/sql-parser/gda-sql-parser.h>
-#include <libsoup/soup.h>
-
-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 <story file>\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 (file)
index 0000000..28cbabe
--- /dev/null
@@ -0,0 +1,105 @@
+#include <gtk/gtk.h>
+#include <libchimara/chimara-glk.h>
+
+/* 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
index 54113a4305afab31db8659a81032b480cef8fa6b..fc9a9b1c93d49f7021f71f2bf74f632d90ca24c7 100644 (file)
@@ -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);