--- /dev/null
+/*
+
+ QRun: Quill game runner for Fuzix.
+ Copyright (C) 2015 Alan Cox
+
+ Derived from
+
+ UnQuill: Disassemble games written with the "Quill" adventure game system
+ Copyright (C) 1996-2000 John Elliott
+
+ 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ - * -
+*/
+
+/* Pull Quill data from a .SNA file. *
+ * Output to stdout. */
+
+/* Format of the Quill database:
+
+ Somewhere in memory (usually 6D04h for version 'A' and 6B85h for version 'C')
+there is a colour definition table:
+
+ DEFB 10h,ink,11h,paper,12h,flash,13h,bright,14h,inverse,15h,over
+ DEFB border
+
+We use this as a magic number to find the Quill database in memory. After
+the colour table is a table of values:
+
+ DEFB no. of objects player can carry at once
+ DEFB no. of objects
+ DEFB no. of locations
+ DEFB no. of messages
+
+then a table of pointers:
+
+ DEFW rsptab ;Response table
+ DEFW protab ;Process table
+ DEFW objtop ;Top of object descriptions
+ DEFW loctop ;Top of locations.
+ DEFW msgtop ;Top of messages.
+ **
+ DEFW conntab ;Table of connections.
+ DEFW voctab ;Table of words.
+ DEFW oloctab ;Table of object inital locations.
+ DEFW free ;[Early] Start of free memory
+ ;[Late] Word/object mapping
+ DEFW freeend ;End of free memory
+ DEFS 3 ;?
+ DEFB 80h,dict ;Expansion dictionary (for compressed games).
+ ;Dictionary is stored in ASCII, high bit set on last
+ ;letter of each word.
+rsptab: ... ;response - both stored as: DB verb, noun
+protab: ... ;process DW address-of-handler
+ ; terminated by verb 0.
+ Handler format is:
+ DB conditions...0FFh
+ DB actions......0FFh
+objtab: ... ;objects - all are stored with all bytes
+objtop: DEFW objtab complemented to deter casual hackers.
+ DEFW object1
+ DEFW object2 etc.
+loctab: ... ;locations Texts are terminated with 0E0h
+loctop: DEFW loctab (ie 1Fh after decoding).
+ DEFW locno1
+ DEFW locno2 etc.
+msgtab: ... ;messages
+msgtop: DEFW msgtab
+ DEFW mess1
+ DEFW mess2 etc.
+conntab: ... ;connections - stored as DB WORD,ROOM,WORD,ROOM...
+ each entry is terminated with 0FFh.
+
+voctab: ... ;vocabulary - stored as DB 'word',number - the
+ word is complemented to deter hackers.
+ Table terminated with a word entry all
+ five bytes of which are 0 before
+ decoding.
+oloctab: ... ;initial locations of objects. 0FCh => not created
+ 0FDh => worn
+ 0FEh => carried
+ 0FFh => end of table
+
+In later games (those made with Illustrator?), there is an extra byte
+(the number of system messages) after the number of messages, and at
+the label ** the following word is inserted:
+
+ DEFW systab ;Points to word pointing to table of system
+ ;messages.
+systab: DEFW sysm0
+ DEFW sysm1 etc.
+
+The address of the user-defined graphics is stored at 23675. In "Early" games,
+the system messages are at UDGs+168.
+
+CPC differences:
+
+* I don't know where the UDGs are.
+* Strings are terminated with 0xFF (0 after decoding) rather than 0xE0
+ (0x1F).
+* No Spectrum colour codes in strings. Instead, the
+ code 0x14 means "newline" and 0x09 means "toggle reverse video".
+ There are other CPC colour codes in the range 0x01-0x0A, but I don't
+ know their meaning.
+* I have assumed that the database always starts at 0x1BD1, which it does
+ in the snapshots I have examined.
+
+Commodore 64 differences:
+
+* I don't know where the UDGs are.
+* Strings are terminated with 0xFF (0 after decoding) rather than 0xE0
+ (0x1F).
+* No Spectrum colour codes in strings.
+* I have assumed that the database always starts at 0x0804, which it does
+ in the snapshots I have examined.
+
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#define VERSION "0.1ac2"
+
+typedef uint8_t uchr; /* for brevity */
+typedef uint16_t ushrt; /* -- ditto -- */
+
+#define ZBUF_NUM 32 /* 8K */
+#define NIL 100
+#define LOC 101
+#define MSG 102
+#define OBJ 103
+#define SWAP 104
+#define PLC 105
+
+static char getch(void);
+
+static uchr zmem(ushrt);
+static ushrt zword(ushrt);
+static void clrscr(void);
+static void oneitem(ushrt, uchr);
+static void opch32(char);
+static void spc(void);
+static void nl(void);
+static void opstr32(const char *);
+static void expch(uchr, ushrt *);
+static void expdict(uchr, ushrt *);
+
+static char present(uchr);
+static uchr doproc(ushrt, uchr, uchr);
+static void listat(uchr);
+static void initgame(ushrt);
+static void playgame(ushrt);
+static void sysmess(uchr);
+static uchr ffeq(uchr, uchr);
+static uchr condtrue(ushrt);
+static uchr autobj(uchr);
+static uchr runact(ushrt, uchr);
+static uchr cplscmp(ushrt, char *);
+static uchr matchword(char **);
+static void usage(char *);
+static void dec32(ushrt);
+static char yesno(void);
+static void savegame(void);
+static void loadgame(void);
+
+/* Supported architectures */
+#define ARCH_SPECTRUM 0
+#define ARCH_CPC 1
+#define ARCH_C64 2
+
+static char arch = ARCH_SPECTRUM; /* Architecture */
+static char xpos = 0; /* Used in msg display */
+static char dbver = 0; /* Which format of database? 0 = early Spectrum
+ * 10 = later Spectrum/CPC */
+static ushrt vocab, dict; /* Spectrum address of vocabulary & dictionary */
+static char nobeep = 0; /* Peace and quiet? */
+static char running = 1; /* Actually playing the game? */
+static ushrt loctab;
+static ushrt objtab; /* Tables of locations, objects, messages */
+static ushrt msgtab;
+static ushrt sysbase; /* Base of system messages */
+static ushrt conntab; /* Connections table */
+static ushrt postab; /* Object start positions table */
+static ushrt objmap; /* Word-to-object map */
+static ushrt proctab, resptab; /* Process and Response tables */
+static char *inname; /* Input filename */
+
+static uchr fileid = 0xFF; /* Save file identity byte */
+
+struct gstate {
+ uint8_t v2;
+ uint8_t v1;
+ uint8_t fileid;
+ uchr flags[37]; /* The Quill flags, 0-36. */
+#define TURNLO state.flags[31] /* (31-36 are special */
+#define TURNHI state.flags[32] /* Meanings of the special flags */
+#define VERB state.flags[33]
+#define NOUN state.flags[34]
+#define CURLOC state.flags[35] /* state.flags[36] == 0 */
+ uchr objpos[219]; /* Positions of objects */
+ uchr xor;
+};
+
+static struct gstate state;
+static struct gstate ramsave; /* RAM save buffer */
+
+static uchr maxcar; /* Max no. of portable objects */
+static uchr maxcar1; /* As maxcar - later games can change it on the fly */
+#define NUMCAR state.flags[1] /* Number of objects currently carried */
+static uchr nobj; /* No. of objects */
+static uchr nsys; /* No. of system messages */
+static uchr alsosee = 1; /* Message "You can also see" */
+static uchr estop; /* Emergency stop flag */
+
+static int infile;
+
+static uchr found, nmsg, nloc;
+static ushrt mem_base; /* Minimum address loaded from snapshot */
+static ushrt mem_size; /* Size of memory loaded from snapshot */
+static short mem_offset; /* Load address of snapshot in physical RAM */
+static char snapid[20];
+
+static int fast, slow, miss;
+
+#define AV0 argv[0]
+
+#define AV10 argv[1][0]
+#define AV11 argv[1][1]
+
+#define isoptch(c) ( (c) == '-' )
+
+void errstr(const char *t1, const char *t2)
+{
+ write(2, t1, strlen(t1));
+ if (t2) {
+ write(2, ": ", 2);
+ write(2, t2, strlen(t2));
+ }
+ write(2, "\n", 1);
+}
+
+#define debugstr errstr
+
+void readbuf(char *p, int len)
+{
+ char *ep = p + len - 1;
+ while (p < ep && read(0, p, 1)) {
+ if (*p == '\n') {
+ p++;
+ break;
+ }
+ p++;
+ }
+ *p = 0;
+}
+
+static ushrt check_signature(ushrt n);
+
+static ushrt ucptr;
+
+int main(int argc, char *argv[])
+{
+ ushrt n, zxptr = 0;
+
+/* If command looks like a help command, print helpscreen */
+
+ if ((argc == 1) || (isoptch(AV10) && strchr("hH/?", AV11)))
+ usage(AV0);
+
+/* Parse for options. */
+
+ for (n = 2; n < argc; n++) {
+ if (isoptch(argv[n][0])) { /* Start of an option */
+ switch (argv[n][1]) {
+ case 'L':
+ case 'l':
+ dbver = 10;
+ break;
+ case 'Q':
+ case 'q':
+ nobeep = 1;
+ break;
+
+ default:
+ errstr("Invalid option", argv[n]);
+ exit(1);
+ } /* End switch */
+ } /* End For */
+ } /* End If */
+
+ /* Load the snapshot. To save space, we ignore the printer
+ buffer and the screen, which can contain nothing of value. */
+
+ inname = argv[1];
+
+ if ((infile = open(inname, O_RDONLY)) < 0) {
+ perror(inname);
+ exit(1);
+ }
+
+ /* << v0.7: Check for CPC6128 format */
+ if (read(infile, snapid, sizeof(snapid)) != sizeof(snapid)) {
+ perror(inname);
+ exit(1);
+ }
+
+ arch = ARCH_SPECTRUM;
+ mem_base = 0x5C00; /* First address loaded */
+ mem_size = 0xA400; /* Number of bytes to load from it */
+ mem_offset = 0x3FE5; /* Load address of snapshot in host system memory */
+
+ if (!memcmp(snapid, "MV - SNA", 9)) { /* CPCEMU snapshot */
+ arch = ARCH_CPC;
+
+ mem_base = 0x1B00;
+ mem_size = 0xA500;
+ mem_offset = -0x100;
+ dbver = 10; /* CPC engine is equivalent to
+ * the "later" Spectrum one. */
+
+ debugstr("CPC snapshot signature found.", NULL);
+ }
+ if (!memcmp(snapid, "VICE Snapshot File\032", 19)) { /* VICE snapshot */
+ int n;
+ unsigned char header[256];
+
+ arch = ARCH_C64;
+ memset(header, 0, sizeof(header));
+ /* [0.8.7] Do a quick, minimal parse of the VSF to find the
+ * C64MEM block. Earlier unquills assumed it would be at a
+ * fixed offset; it isn't. */
+ lseek(infile, 0L, SEEK_SET);
+ if (read(infile, header, sizeof(header)) != sizeof(header)) {
+ errstr("Warning: Failed to read C64 snapshot header",
+ NULL);
+ }
+
+ mem_base = 0x800;
+ mem_size = 0xA500;
+ mem_offset = -0x74;
+ for (n = 0; n < (int)sizeof(header) - 6; n++) {
+ if (!memcmp(header + n, "C64MEM", 6)) {
+ mem_offset = -(n + 0x1A);
+ break;
+ }
+ }
+
+ dbver = 5; /* C64 engine is between the two Spectrum
+ * ones. */
+
+ debugstr("C64 snapshot signature found.", NULL);
+ }
+ /* >> v0.7 */
+
+ /* Skip screen/printer buffer/registers and load the rest */
+
+ /* .SNA read ok. Find a Quill signature */
+
+ switch (arch) {
+ case ARCH_SPECTRUM:
+
+ /* I could _probably_ assume that the Spectrum database is
+ * always at one location for "early" and another for "late"
+ * games (0x6D04 for "early", 0x6B85 for "late". Try these
+ * first; if that fails, scan the whole file. */
+
+ if (check_signature(0x6D04)) {
+ dbver = 0;
+ found = 1;
+ zxptr = 0x6D11;
+ break;
+ }
+ if (check_signature(0x6B85)) {
+ dbver = 10;
+ found = 1;
+ zxptr = 0x6B92;
+ break;
+ }
+
+ for (n = 0x5C00; n < 0xFFF5; n++) {
+ if (check_signature(n)) {
+ debugstr("Quill signature found.", NULL);
+ found = 1;
+ zxptr = n + 13;
+ break;
+ }
+ }
+ break;
+
+ case ARCH_CPC:
+ found = 1;
+ zxptr = 0x1BD1; /* From guesswork: CPC Quill files
+ * always seem to start at 0x1BD1 */
+ break;
+ case ARCH_C64:
+ found = 1;
+ zxptr = 0x804; /* From guesswork: C64 Quill files
+ * always seem to start at 0x804 */
+ break;
+ }
+
+ if (!found) {
+ errstr("Not a valid Quill .SNA file", inname);
+ exit(1);
+ }
+
+ ucptr = zxptr;
+ maxcar1 = maxcar = zmem(zxptr); /* Player's carrying capacity */
+ nobj = zmem(zxptr + 1); /* No. of objects */
+ nloc = zmem(zxptr + 2); /* No. of locations */
+ nmsg = zmem(zxptr + 3); /* No. of messages */
+ if (dbver) {
+ ++zxptr;
+ nsys = zmem(zxptr + 3); /* No. of system messages */
+ vocab = zword(zxptr + 18); /* Words list */
+ dict = zxptr + 29; /* Expansion dictionary */
+ } else
+ vocab = zword(zxptr + 16);
+
+ resptab = zword(zxptr + 4);
+ proctab = zword(zxptr + 6);
+ objtab = zword(zxptr + 8);
+ loctab = zword(zxptr + 10);
+ msgtab = zword(zxptr + 12);
+ if (dbver)
+ sysbase = zword(zxptr + 14);
+ else
+ sysbase = zword(23675) + 168; /* Just after the UDGs */
+/* fprintf(stderr, "sysbase at %u, udg at %u\n",
+ sysbase, zword(23675));*/
+ conntab = zword(zxptr + 14 + (dbver ? 2 : 0));
+ if (dbver)
+ objmap = zword(zxptr + 22);
+ postab = zword(zxptr + 18 + (dbver ? 2 : 0));
+
+ /* Run the game */
+
+ while (running) {
+ estop = 0;
+ srand(1);
+ initgame(zxptr); /* Initialise the game */
+ playgame(zxptr); /* Play it */
+ if (estop) {
+ estop = 0; /* Emergency stop operation, game restarts */
+ continue; /* automatically */
+ }
+ sysmess(13);
+ nl();
+ if (yesno() == 'N') {
+ running = 0;
+ sysmess(14);
+ nl();
+ }
+ }
+/* printf("Fast %d Slow %d Miss %d\n", fast, slow, miss);*/
+ return 0;
+} /* End main() */
+
+static void usage(char *title)
+{
+ errstr("-L : Attempt to interpret as a 'later' type Quill file.",
+ NULL);
+ errstr("-Q : Quiet (no beeping)", NULL);
+ exit(1);
+}
+
+/* Based on an idea by Staffan Vilcans: Skip the fseek() if it isn't needed */
+static ushrt last_addr = 0;
+static uchr *last_ptr;
+
+static uchr zbuf[ZBUF_NUM][256];
+uchr zbuf_addr[ZBUF_NUM];
+uchr zbuf_pri[ZBUF_NUM]; /* 0 = unused , 1+ is use count */
+
+static uchr zbuf_alloc(void)
+{
+ uchr low = 255;
+ uchr i, lnum;
+ for (i = 0; i < ZBUF_NUM; i++) {
+ if (zbuf_pri[i] == 0)
+ return i;
+ if (zbuf_pri[i] < low) {
+ lnum = i;
+ low = zbuf_pri[i];
+ }
+ }
+ return lnum;
+}
+
+static void zbuf_sweep(void)
+{
+ uchr i;
+ for (i = 0; i < ZBUF_NUM; i++)
+ if (zbuf_pri[i] > 1)
+ zbuf_pri[i] /= 2;
+}
+
+static void zbuf_load(uchr slot, ushrt addr)
+{
+ /* FIXME: SNA files are not nicely block aligned so we might want
+ to align to their wacky offsets, or just split off the loader */
+ uchr ah = (addr >> 8);
+ zbuf_addr[slot] = ah;
+ zbuf_pri[slot] = 0x80;
+ addr &= 0xFF00;
+ if (lseek(infile, addr - mem_offset, SEEK_SET) < 0 ||
+ read(infile, zbuf[slot], 256) != 256) {
+ perror(inname);
+ exit(1);
+ }
+}
+
+static uchr zbuf_find(ushrt addr)
+{
+ uchr ah = addr >> 8;
+ uchr i;
+ for (i = 0; i < ZBUF_NUM; i++) {
+ if (zbuf_addr[i] == ah) {
+ slow++;
+ zbuf_pri[i] |= 0x80;
+ return i;
+ }
+ }
+ zbuf_sweep();
+ i = zbuf_alloc();
+ zbuf_load(i, addr);
+ miss++;
+ return i;
+}
+
+static uchr zmem(ushrt addr)
+{
+ uchr c;
+
+ /* Fast path - current buffer */
+ if (addr == last_addr + 1) {
+ if (!((last_addr ^ addr) & 0xFF00)) {
+ fast++;
+ last_addr++;
+ return *++last_ptr;
+ }
+ }
+
+ /* All Spectrum memory accesses are routed through this procedure.
+ * If TINY is defined, this accesses the .sna file directly.
+ */
+
+ if (addr < mem_base || (arch != ARCH_SPECTRUM &&
+ (addr >= (mem_base + mem_size)))) {
+/* fprintf(stderr, "addr %d mb %d, arch %d\n",
+ addr, mem_base, arch);*/
+ errstr("Invalid address requested", NULL);
+ exit(1);
+ }
+ /* Find the right buffer */
+ c = zbuf_find(addr);
+ last_addr = addr;
+ last_ptr = zbuf[c] + (addr & 0xFF);
+ return *last_ptr;
+}
+
+static ushrt zword(ushrt addr)
+{
+ return (ushrt) (zmem(addr) + (256 * zmem(addr + 1)));
+}
+
+static void dec32(ushrt v)
+{
+ char h = 0, t = 0;
+
+ while (v >= 100) {
+ h++;
+ v -= 100;
+ }
+ while (v >= 10) {
+ t++;
+ v -= 10;
+ }
+ if (h)
+ opch32(h + '0');
+ if (h || t)
+ opch32(t + '0');
+ opch32(v + '0');
+}
+
+static void opch32(char ch)
+{
+ /* Output a character, assuming 32-column screen */
+ /* FIXME: need to be smart about widths in target */
+ write(1, &ch, 1);
+ if (ch == '\n')
+ xpos = 0;
+ else if (ch == 8 && xpos)
+ xpos--;
+ else if (arch == ARCH_SPECTRUM && xpos == 31)
+ nl();
+ else if (arch != ARCH_SPECTRUM && xpos == 39)
+ nl();
+ else
+ xpos++;
+}
+
+static void nl(void)
+{
+ opch32('\n');
+}
+
+static void spc(void)
+{
+ opch32(' ');
+}
+
+static void opstr32(const char *s)
+{
+ while (*s)
+ opch32(*s++);
+}
+
+static ushrt check_signature(ushrt n)
+{
+ if ((zmem(n) == 0x10) && (zmem(n + 2) == 0x11) &&
+ (zmem(n + 4) == 0x12) && (zmem(n + 6) == 0x13) &&
+ (zmem(n + 8) == 0x14) && (zmem(n + 10) == 0x15))
+ return 1;
+ return 0;
+}
+
+static char getch(void)
+{
+ struct termios ts, ots;
+ char c;
+ int i;
+
+ tcgetattr(0, &ts); /* Switch the terminal to raw mode */
+ tcgetattr(0, &ots);
+ cfmakeraw(&ts);
+ tcsetattr(0, TCSANOW, &ts);
+
+ do {
+ i = read(0, &c, 1);
+ }
+ while (i < 0);
+
+ tcsetattr(0, TCSANOW, &ots);
+
+ return c;
+}
+
+static uchr runact(ushrt ccond, uchr noun)
+{
+/* Conditions have been met; execute the actions */
+/* WARNING: This procedure contains goto statements - take care! */
+
+ uchr cact, cdone = 0, cobj, nout, n;
+ time_t wait1;
+ int nv;
+
+ cact = zmem(ccond); /* Action byte */
+
+ while ((cact != 0xFF) && !cdone) {
+ /* Translate condition numbers for old Spectrum games */
+ if ((!dbver) && (cact > 11))
+ cact += 9; /* Translate condition numbers */
+ if ((!dbver) && (cact == 11))
+ cact = 17; /* for old-style games */
+ if ((!dbver) && (cact > 29))
+ cact++;
+
+ /* Translate numbers for C64 games */
+ if (dbver > 0 && dbver < 10 && cact >= 13)
+ cact += 4;
+
+ switch (cact) {
+ case 0: /* INVEN */
+ nout = 0;
+ nl();
+ sysmess(9);
+ nl();
+ for (n = 0; n < nobj; n++) {
+ if (state.objpos[n] == 254) {
+ oneitem(objtab, n);
+ nl();
+ nout++;
+ }
+ if (state.objpos[n] == 253) {
+ oneitem(objtab, n);
+ sysmess(10);
+ nl();
+ nout++;
+ }
+ }
+ if (nout == 0)
+ sysmess(11);
+ nl();
+ break;
+ case 1: /* DESC */
+ cdone = 2;
+ break;
+ case 2: /* QUIT */
+ sysmess(12);
+ if (yesno() == 'N')
+ cdone = 1;
+ break;
+ case 3: /* END */
+ cdone = 3;
+ break;
+ case 4: /* DONE */
+ cdone = 1;
+ break;
+ case 5: /* OK */
+ cdone = 1;
+ sysmess(15);
+ nl();
+ break;
+ case 6: /* ANYKEY */
+ sysmess(16);
+ getch();
+ nl();
+ break;
+ case 7: /* SAVE */
+ savegame();
+ cdone = 2;
+ break;
+ case 8: /* LOAD */
+ loadgame();
+ cdone = 2;
+ break;
+ case 9: /* TURNS */
+ sysmess(17);
+ dec32(TURNLO + (256 * TURNHI));
+ sysmess(18);
+ if (TURNLO + (256 * TURNHI) != 1) {
+ if (dbver > 0)
+ sysmess(19);
+ else
+ opch32('s');
+ }
+ if (dbver > 0)
+ sysmess(20);
+ else
+ opch32('.');
+ nl();
+ break;
+ case 10: /* SCORE */
+ sysmess(19 + (dbver ? 2 : 0));
+ dec32(state.flags[30]);
+ if (dbver > 0)
+ sysmess(22);
+ else
+ opch32('.');
+ nl();
+ break;
+ case 11: /* CLS */
+ clrscr();
+ break;
+ case 12: /* DROPALL (note: not a true DROP ALL) */
+ for (cobj = 0; cobj < nobj; cobj++)
+ if ((state.objpos[cobj] == 254)
+ || (state.objpos[cobj] == 253))
+ state.objpos[cobj] = CURLOC;
+ NUMCAR = 0;
+ nl();
+ break;
+ case 13: /* AUTOG Warning - these four cases contain gotos */
+ cobj = autobj(noun);
+ /* if (cobj==0xFF) cdone=1; */
+ goto get0001;
+ case 14: /* AUTOD */
+ cobj = autobj(noun);
+ /* if (cobj==0xFF) cdone=1; */
+ goto drop0001;
+ case 15: /* AUTOW */
+ if (noun < 200) {
+ sysmess(8);
+ cdone = 1;
+ break;
+ } /* v0.5 */
+ cobj = autobj(noun);
+ /* if (cobj==0xFF) cdone=1; */
+ goto wear0001;
+ case 16: /* AUTOR */
+ if (noun < 200) {
+ sysmess(8);
+ cdone = 1;
+ break;
+ } /* v0.5 */
+ cobj = autobj(noun);
+ /* if (cobj==0xFF) cdone=1; */
+ goto rem0001;
+ case 17: /* PAUSE - and extra functions */
+ n = zmem(++ccond);
+ if ((dbver > 0) && (state.flags[28] < 0x17)) {
+ uchr ybrk = 1;
+ switch (state.flags[28]) {
+ /* The following subfunctions of PAUSE cannot
+ * be implemented on this style of text-based
+ * terminal, or deliberately are no-ops.
+ */
+
+ case 1:
+ case 2:
+ case 3:
+ case 5:
+ case 6:
+ /* Sound effects... */
+ case 16:
+ case 17:
+ case 18:
+ /* Set the keyboard click */
+ case 4: /* Make the screen flicker */
+ case 7: /* Select font 1 */
+ case 8: /* Select font 2 */
+ case 19: /* Graphics on/off */
+ case 20: /* No-op */
+ break;
+
+ /* Subfunctions which can be implemented */
+
+ case 9:
+ clrscr(); /* Clear screen impressively */
+ break;
+ case 10: /* Set the "you can also see" message */
+ if (n < nsys)
+ alsosee = n;
+ break;
+ case 11: /* Set maxcar1 */
+ maxcar1 = n;
+ break;
+ case 12: /* Restart the game */
+ alsosee = 1;
+ fileid = 255;
+ maxcar1 = maxcar;
+ cdone = 3; /* End game */
+ estop = 1; /* Emergency stop */
+ break;
+ case 13: /* Reboot the Spectrum */
+ /* FIXME: via game I/O */
+ opstr32("\n\nGame terminated.\n");
+ exit(2);
+ case 14: /* Increase number of portable objects */
+ if ((255 - n) >= maxcar1)
+ maxcar1 += n;
+ else
+ maxcar1 = 255;
+ break;
+ case 15: /* Decrease number of portable objects */
+ if (maxcar1 < n)
+ maxcar1 = 0;
+ else
+ maxcar1 -= n;
+ break;
+ case 21: /* RAMsave & RAMload */
+ if (n == 50) { /* RAMload */
+ if (!ramsave.v2)
+ break;
+ memcpy(&ramsave, &state, sizeof(state));
+ } else { /* RAMsave */
+
+ ramsave.v2 = 1;
+ memcpy(&state, &ramsave, sizeof(state));
+ }
+ break;
+
+ case 22: /* Change identity byte in save file */
+ fileid = n;
+ break;
+
+ default: /* Cases 0, 23, 24 */
+ ybrk = 0;
+ break; /* Pretend it's a normal pause */
+ }
+ state.flags[28] = 0;
+ if (ybrk)
+ break;
+ }
+ /* FIXME: n = 0 means 256/50 seconds */
+ if (n > 0)
+ n--;
+ n = n / 50;
+ n++;
+ wait1 = time(NULL);
+ while (time(NULL) < (wait1 + n))
+#ifdef YES_GETCH /* In case the clock is nonexistent */
+ if (kbhit())
+ break /* (as on some CP/M boxes) */
+#endif
+ ;
+ break;
+ case 19: /* INK */
+ if (arch == ARCH_CPC)
+ ++ccond; /* On CPCs, INK has 2 parameters */
+ case 18:
+ case 20: /* PAPER, and BORDER. Ignore. */
+ ++ccond; /* (v0.5 Skip parameter byte!) */
+ break;
+ case 21: /* GOTO */
+ CURLOC = zmem(++ccond);
+ break;
+ case 22: /* MESSAGE */
+ oneitem(msgtab, zmem(++ccond));
+ nl();
+ break;
+ case 26: /* WEAR */
+ cobj = zmem(++ccond);
+ wear0001: cdone = 1;
+ if (state.objpos[cobj] != 254)
+ sysmess(25 + (dbver ? 3 : 0));
+ else {
+ state.objpos[cobj] = 253;
+ NUMCAR--;
+ cdone = 0;
+ }
+ nl();
+ break;
+ case 24: /* GET */
+ cobj = zmem(++ccond);
+ get0001: cdone = 1;
+ if (cobj == 0xFF)
+ sysmess(8);
+ else if (state.objpos[cobj] == 254)
+ sysmess(22 + (dbver ? 3 : 0));
+ else if (state.objpos[cobj] == 253)
+ sysmess(26 + (dbver ? 3 : 0));
+ else if (state.objpos[cobj] != CURLOC)
+ sysmess(23 + (dbver ? 3 : 0));
+ else if (maxcar1 == NUMCAR)
+ sysmess(21 + (dbver ? 3 : 0));
+ else {
+ cdone = 0;
+ state.objpos[cobj] = 254;
+ NUMCAR++;
+ }
+ nl();
+ break;
+ case 25: /* DROP */
+ cobj = zmem(++ccond);
+ drop0001: cdone = 1;
+ if (cobj == 0xFF)
+ sysmess(8);
+ else if ((state.objpos[cobj] != 254) && (state.objpos[cobj] != 253))
+ sysmess(25 + (dbver ? 3 : 0));
+ else {
+ state.objpos[cobj] = CURLOC;
+ NUMCAR--;
+ cdone = 0;
+ }
+ nl();
+ break;
+ case 23: /* REMOVE */
+ cobj = zmem(++ccond);
+ rem0001: cdone = 1;
+ if (state.objpos[cobj] != 253)
+ sysmess(20 + (dbver ? 3 : 0));
+ else if (maxcar1 == NUMCAR)
+ sysmess(21 + (dbver ? 3 : 0));
+ else {
+ state.objpos[cobj] = 254;
+ NUMCAR++;
+ cdone = 0;
+ }
+ nl();
+ break;
+ case 27: /* DESTROY */
+ cobj = zmem(++ccond);
+ if (state.objpos[cobj] == 254)
+ NUMCAR--;
+ state.objpos[cobj] = 252;
+ break;
+ case 28: /* CREATE */
+ cobj = zmem(++ccond);
+ state.objpos[cobj] = CURLOC;
+ break;
+ case 29: /* SWAP */
+ cobj = zmem(++ccond);
+ nout = zmem(++ccond);
+ n = state.objpos[cobj];
+ state.objpos[cobj] = state.objpos[nout];
+ state.objpos[nout] = n;
+ break;
+ case 30: /* PLACE */
+ cobj = zmem(++ccond);
+ nout = zmem(++ccond);
+ if (state.objpos[cobj] == 254)
+ NUMCAR--;
+ state.objpos[cobj] = nout;
+ break;
+ case 31: /* SET */
+ state.flags[zmem(++ccond)] = 0xFF;
+ break;
+ case 32: /* CLEAR */
+ state.flags[zmem(++ccond)] = 0;
+ break;
+ case 33: /* PLUS */
+ n = zmem(++ccond);
+ nv = state.flags[n] + zmem(++ccond);
+ if (nv < 0)
+ nv = 0;
+ else if (nv > 255)
+ nv = 255;
+ state.flags[n] = nv;
+ break;
+ case 34: /* MINUS */
+ n = zmem(++ccond);
+ nv = state.flags[n] - zmem(++ccond);
+ if (nv < 0)
+ nv = 0;
+ else if (nv > 255)
+ nv = 255;
+ state.flags[n] = nv;
+ break;
+ break;
+ case 35: /* LET */
+ n = zmem(++ccond);
+ state.flags[n] = zmem(++ccond);
+ break;
+ case 36: /* BEEP */
+ if (!nobeep) {
+ uchr b = 7;
+ /* FIXME */
+ write(1, &b, 1);
+ }
+ n = zmem(++ccond);
+ ccond++;
+/* usleep(10000L * n); FIXME */
+ printf("sleep %d\n", n);
+ if (n > 100)
+ sleep(n/100);
+ break;
+ default:
+ errstr("Invalid action", NULL);
+ }
+ cact = zmem(++ccond);
+ }
+ return (cdone);
+}
+
+static uchr condtrue(ushrt ccond)
+{
+ uchr cond, ctrue = 1;
+ uchr arg1;
+ uchr fp, op;
+
+ cond = zmem(ccond); /* Condition byte */
+
+ while ((cond != 0xFF) && ctrue) { /* If ctrue = 0, condition invalid */
+ arg1 = zmem(++ccond); /* Always at least one cond arg */
+ /* Hoist these for compilers not smart enough to do so */
+ fp = state.flags[arg1];
+ op = state.objpos[arg1];
+ switch (cond) {
+ case 0: /* AT */
+ ctrue = (arg1 == CURLOC);
+ break;
+ case 1: /* NOTAT */
+ ctrue = (arg1 != CURLOC);
+ break;
+ case 2: /* ATGT */
+ ctrue = (arg1 < CURLOC);
+ break;
+ case 3: /* ATLT */
+ ctrue = (arg1 > CURLOC);
+ break;
+ case 4: /* PRESENT */
+ ctrue = present(op);
+ break;
+ case 5: /* ABSENT */
+ ctrue = (!present(op));
+ break;
+ case 6: /* WORN */
+ ctrue = (op == 253);
+ break;
+ case 7: /* NOTWORN */
+ ctrue = (op != 253);
+ break;
+ case 8: /* CARRIED */
+ ctrue = (op == 254);
+ break;
+ case 9: /* NOTCARR */
+ ctrue = (op != 254);
+ break;
+ case 10: /* CHANCE */
+ ctrue = ((rand() % 100) < (arg1));
+ break;
+ case 11: /* ZERO */
+ ctrue = (fp == 0);
+ break;
+ case 12: /* NOTZERO */
+ ctrue = fp;
+ break;
+ case 13: /* EQ */
+ ctrue = (fp == zmem(++ccond));
+ break;
+ case 14: /* GT */
+ ctrue = (fp > zmem(++ccond));
+ break;
+ case 15: /* LT */
+ ctrue = (fp < zmem(++ccond));
+ break;
+ default:
+ errstr("Unknown condition code", NULL);
+ ctrue = 0;
+ }
+ cond = zmem(++ccond);
+ }
+ return (ctrue);
+}
+
+static void initgame(ushrt zxptr)
+{
+ uchr n;
+ ushrt obase;
+
+ alsosee = 1; /* Options possibly set by later games */
+ fileid = 255;
+ maxcar1 = maxcar;
+
+ obase = postab; /* Object initial positions table */
+ for (n = 0; n < 36; n++)
+ state.flags[n] = 0;
+
+ NUMCAR = 0;
+ for (n = 0; n < 255; n++) {
+ state.objpos[n] = zmem(obase + n);
+ if (state.objpos[n] == 254)
+ NUMCAR++;
+ }
+}
+
+static void savegame(void)
+{
+ uchr xor = fileid, l, n;
+ int savefd;
+ char filename[80];
+
+ state.v2 = 2;
+ state.v1 = 1;
+ state.fileid = fileid;
+ for (n = 0; n < 37; n++)
+ xor ^= state.flags[n];
+ for (n = 0; n < 0xDB; n++)
+ xor ^= state.objpos[n];
+ state.xor = xor;
+ nl();
+ write(1, "Save game to file>", 18);
+ readbuf(filename, sizeof(filename));
+ l = strlen(filename) - 1;
+ if ((filename[l] == '\r') || (filename[l] == '\n'))
+ filename[l] = 0;
+
+ /* FIXME: remove stdio */
+ savefd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0600);
+ if (savefd == -1) {
+ /* FIXME - via game output.. */
+ errstr("Could not create", filename);
+ return;
+ }
+ if (write(savefd, &state, sizeof(state)) != sizeof(state) ||
+ close(savefd) == -1) {
+ opstr32("Write error on ");
+ opstr32(filename);
+ nl();
+ /* Doing a double close is harmless */
+ close(savefd);
+ return;
+ }
+}
+
+/* Format of Quill save file (based on a Spectrum .TAP file):
+
+DW 0102h ;Length / magic no.
+DB 0FFh ;Block type ("fileid") - usually 0FFh, but can be changed in
+ ;later games by PAUSE, subfunction 22.
+DS 31 ;Flags 0-30
+DW xx ;Flags 31-32 = no. of turns
+DB xx ;Flag 33 = verb
+DB xx ;Flag 34 = noun
+DW xx ;Flags 35-36 = location (flag 36 is always 0)
+DS 0DBh ;Object locations table, terminated with 0FFh.
+DB xsum ;XOR of all bytes except the 0102h.
+*/
+
+static void loadgame(void)
+{
+ uchr xor = 0, l;
+ ushrt n;
+ int loadfd;
+ char filename[80];
+ struct gstate load;
+ uint8_t *p = ((uint8_t *)&load) + 2;
+
+ nl();
+ write(1, "Load game from file>", 14);
+
+ readbuf(filename, sizeof(filename));
+ l = strlen(filename) - 1;
+ if ((filename[l] == '\r') || (filename[l] == '\n'))
+ filename[l] = 0;
+
+ loadfd = open(filename, O_RDONLY);
+ if (loadfd == -1) {
+ opstr32("Could not open ");
+ opstr32(filename);
+ nl();
+ return;
+ }
+ if (read(loadfd, &load, sizeof(load)) != sizeof(load)) {
+ close(loadfd);
+ opstr32("Read error on ");
+ opstr32(filename);
+ nl();
+ return;
+ }
+ for (n = 2; n < 0x103; n++)
+ xor ^= *p++;
+ if ((xor != load.xor)
+ || (load.v2 != 2)
+ || (load.v1 != 1)
+ || (load.fileid != fileid)) {
+ opstr32(filename);
+ opstr32(" is not a suitable save file\nPress RETURN...");
+ getch();
+ return;
+ }
+ memcpy(&state, &load, sizeof(state));
+}
+
+static char present(uchr loc)
+{
+ if (loc == 254 || loc == 253 || loc == CURLOC)
+ return 1;
+ return 0;
+}
+
+static void playgame(ushrt zxptr)
+{
+ uchr desc = 1, verb, noun, pn, r;
+ char *lbstart;
+ ushrt connbase;
+ char linebuf[80];
+
+ lbstart = linebuf;
+ while (1) { /* Main loop */
+ if (desc) {
+ clrscr();
+ /* 0.7.5: Darkness */
+ if (state.flags[0] && (!present(state.objpos[0]))) {
+ sysmess(0);
+ nl();
+ } else {
+ oneitem(loctab, CURLOC);
+ nl();
+ listat(CURLOC); /* List objects present */
+ }
+ desc = 0;
+
+ /* Decrement flags depending on location descriptions */
+
+ if (state.flags[2])
+ state.flags[2]--;
+ if (state.flags[0] && state.flags[3])
+ state.flags[3]--;
+ if (state.flags[0] && (!present(0)) && state.flags[4])
+ state.flags[4]--;
+ }
+
+ /* [new in 0.7.0: Flag decrements moved to *after* the
+ * process table; this makes Bugsy work properly */
+
+ /* Process "process" conditions */
+
+ verb = 0xFF;
+ noun = 0xFF;
+
+ r = doproc(zword(zxptr + 6), verb, noun); /* The Process Table */
+ if (r == 2)
+ desc = 1; /* DESC */
+ else if (r == 3)
+ break; /* END */
+ else {
+ /* Decrement flags not depending on location descriptions */
+
+ if (state.flags[5])
+ state.flags[5]--;
+ if (state.flags[6])
+ state.flags[6]--;
+ if (state.flags[7])
+ state.flags[7]--;
+ if (state.flags[8])
+ state.flags[8]--;
+ if (state.flags[0] && state.flags[9])
+ state.flags[9]--;
+ if (state.flags[0] && (!present(0)) && state.flags[10])
+ state.flags[10]--;
+
+ /* Print the prompt */
+
+ pn = (rand() & 3);
+ sysmess(pn + 2);
+ nl();
+ if (dbver == 0)
+ sysmess(28);
+ readbuf(linebuf, sizeof(linebuf));
+
+ TURNLO++;
+ if (TURNLO == 0)
+ TURNHI++;
+
+ /* Parse the input */
+
+ lbstart = linebuf;
+ verb = matchword(&lbstart);
+ if (verb != 0xFF) {
+ VERB = verb;
+ noun = matchword(&lbstart);
+ NOUN = noun;
+// if (noun == 0xFF) noun = 0xFE;
+
+ /* v0.7.5: Moved "response" conditions to after the attempt to
+ * move the player
+ */
+
+ /* Attempt to move player */
+ r = 0;
+ /* if (verb < 20)*//* A movement word */
+/* { * This test is incorrect; Quill does not *
+ * * insist on the word number being < 20 */
+ connbase = zword(2 * CURLOC + conntab);
+ while ((!r) && (zmem(connbase) != 0xFF))
+ if (verb == zmem(connbase)) {
+ CURLOC = zmem(++connbase);
+ desc = 1;
+ r = 1;
+ } else
+ connbase += 2;
+ /* } */
+ if (r == 0) {
+ /* Process "response" conditions */
+ r = doproc(zword(zxptr + 4), verb,
+ noun);
+ if (r == 2)
+ desc = 1; /* DESC */
+ else if (r == 3)
+ break; /* END */
+ }
+ /* Print "I can't do that/go that way" */
+ if (r == 0) {
+ if (verb < 20)
+ sysmess(7);
+ else
+ sysmess(8);
+ nl();
+ }
+ } else
+ sysmess(6); /* Unknown verb */
+ }
+ }
+}
+
+static uchr cplscmp(ushrt first, char *snd)
+{
+ if (((255 - (zmem(first++))) & 0x7F) != (snd[0]))
+ return 0;
+ if (((255 - (zmem(first++))) & 0x7F) != (snd[1]))
+ return 0;
+ if (((255 - (zmem(first++))) & 0x7F) != (snd[2]))
+ return 0;
+ if (((255 - (zmem(first++))) & 0x7F) != (snd[3]))
+ return 0;
+ return 1;
+}
+
+static uchr matchword(char **wordbeg)
+{
+/* Match a word of player input with the vocabulary table */
+
+ ushrt matchp = vocab;
+ char wordbuf[5];
+ int i;
+
+ wordbuf[4] = 0;
+ while (1) {
+ for (i = 0; i < 4; i++) {
+ wordbuf[i] = (**wordbeg);
+ if (islower(wordbuf[i]))
+ wordbuf[i] = toupper(wordbuf[i]); /* (v0.4.1), was munging numbers */
+ if (wordbuf[i] == 0)
+ wordbuf[i] = ' ';
+ if (wordbuf[i] == '\n')
+ wordbuf[i] = ' ';
+ if (wordbuf[i] == '\r')
+ wordbuf[i] = ' ';
+ if (wordbuf[i] == ' ')
+ (*wordbeg)--;
+ (*wordbeg)++;
+ }
+ while ((**wordbeg)
+ && (**wordbeg != '\n')
+ && (**wordbeg != '\r')
+ && (**wordbeg != ' '))
+ (*wordbeg)++;
+ while (zmem(matchp)) {
+ if (cplscmp(matchp, wordbuf)) {
+ return zmem(matchp + 4);
+ }
+ matchp += 5;
+ }
+ matchp = vocab;
+
+ if (((**wordbeg) == 0)
+ || ((**wordbeg) == '\r')
+ || ((**wordbeg) == '\n'))
+ return 0xFF;
+
+ while ((**wordbeg) == 0x20) {
+ if ((**wordbeg) == 0)
+ return 0xFF;
+ (*wordbeg)++;
+ }
+ }
+ return 0xFF;
+
+}
+
+static void listat(uchr locno)
+{
+/* List items at location n */
+
+ uchr any = 0, n;
+
+ for (n = 0; n < nobj; n++)
+ if (state.objpos[n] == locno) {
+ if (any == 0) {
+ any = 1;
+ if (locno < 253) {
+ sysmess(alsosee);
+ nl();
+ }
+ }
+ oneitem(objtab, n);
+ nl();
+ }
+}
+
+uchr ffeq(uchr x, uchr y)
+{ /* Match x with y, 0FFh matches any */
+ return (uchr) ((x == 0xFF) || (y == 0xFF) || (x == y));
+}
+
+uchr doproc(ushrt table, uchr verb, uchr noun)
+{
+ ushrt ccond;
+ uchr done = 0; /* Done returns: 0 for fell off end of table
+ 1 for DONE
+ 2 for DESC
+ 3 for END (end game) */
+ uchr t, tverb, tnoun, td1 = 0;
+
+ while ((zmem(table)) && !done) {
+ tverb = zmem(table++);
+ tnoun = zmem(table++);
+ ccond = zword(table++);
+ table++;
+
+ if (ffeq(verb, tverb) && ffeq(noun, tnoun)) {
+ t = condtrue(ccond);
+ /* Skip over condition clauses */
+ /* FIXME: check if condskip change in unquill 0.9
+ is valid ?? */
+ while (zmem(ccond++) != 0xFF) ;
+ if (t) {
+ done = runact(ccond, noun);
+ /* Returned nonzero if should not scan */
+ td1 = 1; /* Something was run */
+ }
+ }
+ }
+ if (done == 0 && td1)
+ done = 1;
+ return done;
+}
+
+uchr autobj(uchr noun)
+{ /* Find object number for AUTOx actions */
+ uchr n;
+
+ if (dbver == 0)
+ return 0xFF;
+ if (noun > 253)
+ return 0xFF;
+ for (n = 0; n < nobj; n++)
+ if (noun == zmem(objmap + n))
+ return n;
+ return 0xFF;
+}
+
+char yesno(void)
+{
+ char n;
+
+ while (1) {
+ n = getchar();
+ if ((n == 'Y') || (n == 'y'))
+ return ('Y');
+ if ((n == 'N') || (n == 'n'))
+ return ('N');
+ }
+ return ('N');
+}
+
+static void sysmess(uchr sysno)
+{
+ uchr cch = 0;
+ ushrt msgadd;
+
+ if (dbver > 0) {
+ oneitem(sysbase, sysno);
+ return;
+ }
+ msgadd = sysbase;
+/* fprintf(stderr, "sysmsg %d - base %u\n", sysno, sysbase); */
+ while (sysno) { /* Skip (sysno) messages */
+ while (cch != 0x1F)
+ cch = 0xFF - (zmem(msgadd++));
+ sysno--;
+ cch = 0xFF - (zmem(msgadd++));
+ }
+ msgadd--;
+ while (cch != 0x1F) {
+ cch = 0xFF - (zmem(msgadd++));
+ expch(cch, &msgadd);
+ }
+}
+
+static void oneitem(ushrt table, uchr item)
+{
+ ushrt n;
+ uchr cch;
+ uchr term;
+
+ if (arch == ARCH_SPECTRUM)
+ term = 0x1F;
+ else
+ term = 0;
+ cch = ~term;
+
+ n = zword(table + 2 * item);
+// xpos=0;
+
+ while (1) {
+ cch = (0xFF - (zmem(n++)));
+ if (cch == term)
+ break;
+ expch(cch, &n);
+ }
+}
+
+static void clrscr(void)
+{
+ char n;
+
+ for (n = 0; n < 24; n++)
+ nl();
+}
+
+static void expdict(uchr cch, ushrt * n)
+{
+ ushrt d = dict;
+
+ if (dbver > 0) { /* Early games aren't compressed & have no dictionary */
+ cch -= 164;
+ while (cch)
+ if (zmem(d++) > 0x7f)
+ cch--;
+
+ /* d=address of expansion text */
+
+ while (zmem(d) < 0x80)
+ expch(zmem(d++) & 0x7F, n);
+ expch(zmem(d) & 0x7F, n);
+ }
+}
+
+static void expch_c64(uchr cch, ushrt * n)
+{
+ if (cch >= 'A' && cch <= 'Z')
+ cch = tolower(cch);
+ cch &= 0x7F;
+
+ if ((cch > 31) && (cch < 127))
+ opch32(cch);
+ else if (cch > 126)
+ opch32('?');
+ else if (cch == 8)
+ opch32(8);
+ else if (cch == 0x0D)
+ nl();
+}
+
+static void expch_cpc(uchr cch, ushrt * n)
+{
+ if ((cch > 31) && (cch < 127))
+ opch32(cch);
+ /* else if (cch > 164) expdict(cch, n); */
+ else if (cch > 126)
+ opch32('?');
+ else if (cch == 9)
+ return;
+ else if (cch == 0x0D)
+ nl();
+ else if (cch == 0x14)
+ nl();
+ else if (cch < 31)
+ opch32('?');
+}
+
+static void expch(uchr cch, ushrt * n)
+{
+ ushrt tbsp;
+
+ if (arch == ARCH_CPC) {
+ expch_cpc(cch, n);
+ return;
+ }
+ if (arch == ARCH_C64) {
+ expch_c64(cch, n);
+ return;
+ }
+
+ if ((cch > 31) && (cch < 127))
+ opch32(cch);
+ else if (cch > 164)
+ expdict(cch, n);
+ else if (cch > 126)
+ opch32('?');
+ else if (cch == 6)
+ for (spc(); (xpos % 16); spc()) ;
+ else if (cch == 8)
+ opch32(8);
+ else if (cch == 0x0D)
+ nl();
+ else if (cch == 0x17) {
+ tbsp = (255 - zmem((*n)++)) & 0x1F;
+ ++(*n);
+ if (xpos > tbsp)
+ nl();
+ for (; tbsp > 0; tbsp--)
+ spc();
+ } else if ((cch > 0x0F) && (cch < 0x16))
+ (*n)++;
+}