aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/agt/util.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/glk/agt/util.cpp')
-rw-r--r--engines/glk/agt/util.cpp1489
1 files changed, 1489 insertions, 0 deletions
diff --git a/engines/glk/agt/util.cpp b/engines/glk/agt/util.cpp
new file mode 100644
index 0000000000..0bf96351f1
--- /dev/null
+++ b/engines/glk/agt/util.cpp
@@ -0,0 +1,1489 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/agt/agility.h"
+#include "glk/quetzal.h"
+#include "common/textconsole.h"
+
+namespace Glk {
+namespace AGT {
+
+/* This includes wrappers for malloc, realloc, strdup, and free
+ that exit gracefully if we run out of memory. */
+/* There are also various utilities:
+ concdup: Creates a new string that is concatation of two others.
+ strcasecmp: case insensitive comparison of strings
+ strncasecmp: case insensitive compare of first n characters of strings
+ fixsign16, fixsign32: routines to assemble signed ints out of
+ individual bytes in an endian-free way. */
+/* Also buffered file i/o routines and some misc. file utilites. */
+
+#ifdef force16
+#undef int
+#endif
+
+#ifdef force16
+#define int short
+#endif
+
+long rangefix(long n) {
+ if (n > 0) return n;
+ return 0;
+}
+
+/*-------------------------------------------------------------------*/
+/* Sign fixing routines, to build a signed 16- and 32-bit quantities */
+/* out of their component bytes. */
+/*-------------------------------------------------------------------*/
+
+#ifndef FAST_FIXSIGN
+short fixsign16(uchar n1, uchar n2) {
+ rbool sflag;
+ short n;
+
+ if (n2 > 0x80) {
+ n2 &= 0x7F;
+ sflag = 1;
+ } else sflag = 0;
+
+ n = n1 + (n2 << 8);
+ if (sflag) n = n - 0x7fff - 1;
+ return n;
+}
+
+long fixsign32(uchar n1, uchar n2, uchar n3, uchar n4) {
+ rbool sflag;
+ long n;
+
+ if (n4 > 0x80) {
+ n4 &= 0x7F;
+ sflag = 1;
+ } else sflag = 0;
+
+ n = n1 + (((long)n2) << 8) + (((long)n3) << 16) + (((long)n4) << 24);
+ if (sflag) n = n - 0x7fffffffL - 1L;
+ return n;
+}
+#endif
+
+
+
+/*----------------------------------------------------------------------*/
+/* rprintf(), uses writestr for output */
+/* This function is used mainly for diagnostic information */
+/* There should be no newlines in the format string or in any of the */
+/* arguments as those could confuse writestr, except for the last */
+/* character in the string which can be a newline. */
+/*----------------------------------------------------------------------*/
+
+void rprintf(const char *fmt, ...) {
+ int i;
+ char s[100];
+ va_list args;
+
+ va_start(args, fmt);
+ vsprintf(s, fmt, args);
+ va_end(args);
+ i = strlen(s) - 1;
+ if (i >= 0 && s[i] == '\n') {
+ s[i] = 0;
+ writeln(s);
+ } else writestr(s);
+}
+
+
+/*----------------------------------------------------------------------*/
+/* Memory allocation wrappers: All memory allocation should run through */
+/* these routines, which trap error conditions and do accounting to */
+/* help track down memory leaks. */
+/*----------------------------------------------------------------------*/
+
+rbool rm_trap = 1;
+
+long get_rm_size(void)
+/* Return the amount of space being used by dynamically allocated things */
+{
+#ifdef MEM_INFO
+ struct mstats memdata;
+
+ memdata = mstats();
+ return memdata.bytes_used;
+#endif
+ return 0;
+}
+
+long get_rm_freesize(void)
+/* Return estimate of amount of space left */
+{
+#ifdef MEM_INFO
+ struct mstats memdata;
+
+ memdata = mstats();
+ return memdata.bytes_free;
+#endif
+ return 0;
+}
+
+
+void *rmalloc(long size) {
+ void *p;
+
+ if (size > MAXSTRUC) {
+ error("Memory allocation error: Over-sized structure requested.");
+ }
+ assert(size >= 0);
+ if (size == 0) return NULL;
+ p = malloc((size_t)size);
+ if (p == NULL && rm_trap && size > 0) {
+ error("Memory allocation error: Out of memory.");
+ }
+ if (rm_acct) ralloc_cnt++;
+ return p;
+}
+
+void *rrealloc(void *old, long size) {
+ void *p;
+
+ if (size > MAXSTRUC) {
+ error("Memory reallocation error: Oversized structure requested.");
+ }
+ assert(size >= 0);
+ if (size == 0) {
+ r_free(old);
+ return NULL;
+ }
+ if (rm_acct && old == NULL) ralloc_cnt++;
+ p = realloc(old, (size_t)size);
+ if (p == NULL && rm_trap && size > 0) {
+ error("Memory reallocation error: Out of memory.");
+ }
+ return p;
+}
+
+char *rstrdup(const char *s) {
+ char *t;
+#ifndef HAVE_STRDUP
+ int i;
+#endif
+
+ if (s == NULL) return NULL;
+#ifdef HAVE_STRDUP
+ t = strdup(s);
+#else
+ t = (char *)malloc((strlen(s) + 1) * sizeof(char));
+#endif
+ if (t == NULL && rm_trap) {
+ error("Memory duplication error: Out of memory.");
+ }
+ if (rm_acct) ralloc_cnt++;
+#ifndef HAVE_STRDUP
+ for (i = 0; s[i] != 0; i++)
+ t[i] = s[i];
+ t[i] = 0;
+#endif
+ return t;
+}
+
+void r_free(void *p) {
+ int tmp;
+
+ if (p == NULL) return;
+
+ tmp = get_rm_size(); /* Take worst case in all cases */
+ if (tmp > rm_size) rm_size = tmp;
+ tmp = get_rm_freesize();
+ if (tmp < rm_freesize) rm_freesize = tmp;
+
+ if (rm_acct) rfree_cnt++;
+ free(p);
+}
+
+
+
+/*----------------------------------------------------------------------*/
+/* String utilities: These are utilities to manipulate strings. */
+/*----------------------------------------------------------------------*/
+
+/* rnstrncpy copies src to dest, copying at most (max-1) characters.
+ Unlike ANSI strncpy, it doesn't fill extra space will nulls and
+ it always puts a terminating null. */
+char *rstrncpy(char *dest, const char *src, int max) {
+ int i;
+ for (i = 0; i < max - 1 && src[i]; i++)
+ dest[i] = src[i];
+ dest[i] = 0;
+ return dest;
+}
+
+/* This does a case-insensitive match of the beginning of *pstr to match */
+/* <match> must be all upper case */
+/* *pstr is updated to point after the match, if it is succesful.
+ Otherwise *pstr is left alone. */
+rbool match_str(const char **pstr, const char *match) {
+ int i;
+ const char *s;
+
+ s = *pstr;
+ for (i = 0; match[i] != 0 && s[i] != 0; i++)
+ if (toupper(s[i]) != match[i]) return 0;
+ if (match[i] != 0) return 0;
+ *pstr += i;
+ return 1;
+}
+
+
+
+
+/* Utility to concacate two strings with a space inserted */
+
+char *concdup(const char *s1, const char *s2) {
+ int len1, len2;
+ char *s;
+
+ len1 = len2 = 0;
+ if (s1 != NULL) len1 = strlen(s1);
+ if (s2 != NULL) len2 = strlen(s2);
+
+ s = (char *)rmalloc(sizeof(char) * (len1 + len2 + 2));
+ if (s1 != NULL)
+ memcpy(s, s1, len1);
+ memcpy(s + len1, " ", 1);
+ if (s2 != NULL)
+ memcpy(s + len1 + 1, s2, len2);
+ s[len1 + len2 + 1] = 0;
+ return s;
+}
+
+
+/* Misc. C utility functions that may be supported locally.
+ If they are, use the local functions since they'll probably be faster
+ and more efficiant. */
+
+#ifdef NEED_STR_CMP
+int strcasecmp(const char *s1, const char *s2)
+/* Compare strings s1 and s2, case insensitive; */
+/* If equal, return 0. Otherwise return nonzero. */
+{
+ int i;
+
+ for (i = 0; tolower(s1[i]) == tolower(s2[i]) && s1[i] != 0; i++);
+ if (tolower(s1[i]) == tolower(s2[i])) return 0;
+ if (s1[i] == 0) return -1;
+ if (s2[i] == 0) return 1;
+ if (tolower(s1[i]) < tolower(s2[i])) return -1;
+ return 1;
+}
+#endif /* NEED_STR_CMP */
+
+#ifdef NEED_STRN_CMP
+int strncasecmp(const char *s1, const char *s2, size_t n)
+/* Compare first n letters of strings s1 and s2, case insensitive; */
+/* If equal, return 0. Otherwise return nonzero. */
+{
+ size_t i;
+
+ for (i = 0; i < n && tolower(s1[i]) == tolower(s2[i]) && s1[i] != 0; i++);
+ if (i == n || tolower(s1[i]) == tolower(s2[i])) return 0;
+ if (s1[i] == 0) return -1;
+ if (s2[i] == 0) return 1;
+ if (tolower(s1[i]) < tolower(s2[i])) return -1;
+ return 1;
+}
+#endif /* NEED_STRN_CMP */
+
+/*----------------------------------------------------------------------*/
+/* Character utilities: Do character translation */
+/*----------------------------------------------------------------------*/
+
+void build_trans_ascii(void) {
+ int i;
+
+ for (i = 0; i < 256; i++)
+ trans_ascii[i] = (!fix_ascii_flag || i < 0x80) ? i : trans_ibm[i & 0x7f];
+ trans_ascii[0xFF] = 0xFF; /* Preserve format character */
+}
+
+
+/*----------------------------------------------------------------------*/
+/* File utilities: Utilities to manipulate files. */
+/*----------------------------------------------------------------------*/
+
+void print_error(const char *fname, filetype ext, const char *err, rbool ferr) {
+ char *estring; /* Hold error string */
+ estring = (char *)rmalloc(strlen(err) + strlen(fname) + 2);
+ sprintf(estring, err, fname);
+ if (ferr) fatal(estring);
+ else writeln(estring);
+ rfree(estring);
+}
+
+/* Routine to open files with extensions and handle basic error conditions */
+
+genfile fopen(const char *name, const char *how) {
+ if (!strcmp(how, "r") || !strcmp(how, "rb")) {
+ Common::File *f = new Common::File();
+ if (!f->open(name)) {
+ delete f;
+ f = nullptr;
+ }
+
+ return f;
+ } else if (!strcmp(how, "w") || !strcmp(how, "wb")) {
+ Common::DumpFile *f = new Common::DumpFile();
+ if (!f->open(name)) {
+ delete f;
+ f = nullptr;
+ }
+
+ return f;
+ } else {
+ error("Unknown file open how");
+ }
+}
+
+int fseek(genfile stream, long int offset, int whence) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(stream);
+ assert(rs);
+ return rs->seek(offset, whence);
+}
+
+size_t fread(void *ptr, size_t size, size_t nmemb, genfile stream) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(stream);
+ assert(rs);
+ return rs->read(ptr, size * nmemb);
+}
+
+size_t fwrite(const void *ptr, size_t size, size_t nmemb, genfile stream) {
+ Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(stream);
+ assert(ws);
+ return ws->write(ptr, size * nmemb);
+}
+
+size_t ftell(genfile f) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
+ assert(rs);
+ return rs->pos();
+}
+
+genfile openfile(fc_type fc, filetype ext, const char *err, rbool ferr)
+/* Opens the file fname+ext, printing out err if something goes wrong.
+ (unless err==NULL, in which case nothing will be printed) */
+/* err can have one %s paramater in it, which will have the file name
+ plugged in to it. */
+/* If ferr is true, then on failure the routine will abort with a fatal
+ error. */
+{
+ genfile tfile; /* Actually, this may not be a text file anymore */
+ const char *errstr;
+
+ tfile = readopen(fc, ext, &errstr);
+ if (errstr != NULL && err != NULL)
+ print_error("", ext, err, ferr);
+
+ return tfile;
+}
+
+
+genfile openbin(fc_type fc, filetype ext, const char *err, rbool ferr)
+/* Opens the file fname+ext, printing out err if something goes wrong.
+ (unless err==NULL, in which case nothing will be printed) */
+/* err can have one %s paramater in it, which will have the file name
+ plugged in to it. */
+/* If ferr is true, then on failure the routine will abort with a fatal
+ error. */
+{
+ genfile f; /* Actually, this may not be a text file anymore */
+ const char *errstr;
+ char *fname;
+
+ f = readopen(fc, ext, &errstr);
+ if (errstr != NULL && err != NULL) {
+ fname = formal_name(fc, ext);
+ print_error(fname, ext, err, ferr);
+ rfree(fname);
+ }
+
+ return f;
+}
+
+
+
+/* This routine reads in a line from a 'text' file; it's designed to work
+ regardless of the EOL conventions of the platform, at least up to a point.
+ It should work with files that have \n, \r, or \r\n termined lines. */
+
+#define READLN_GRAIN 64 /* Granularity of readln() rrealloc requests
+ this needs to be at least the size of a tab
+ character */
+#define DOS_EOF 26 /* Ctrl-Z is the DOS end-of-file marker */
+
+char *readln(genfile f, char *buff, int n)
+/* Reads first n characters of line, eliminates any newline,
+and truncates the rest of the line. 'n' does *not* include terminating
+null. */
+/* If we pass it BUFF=NULL, then it will reallocate buff as needed and
+pass it back as its return value. n is ignored in this case */
+/* If it reaches EOF, it will return NULL */
+/* This routine recognizes lines terminated by \n, \r, or \r\n */
+{
+ int c;
+ int i, j, csize;
+ int buffsize; /* Current size of buff, if we are allocating it dynamically */
+
+ if (buff == NULL) {
+ buff = (char *)rrealloc(buff, READLN_GRAIN * sizeof(char));
+ buffsize = READLN_GRAIN;
+ n = buffsize - 1;
+ } else buffsize = -1; /* So we know that we are using a fixed-size buffer */
+
+ i = 0;
+ for (;;) {
+ c = textgetc(f);
+
+ if (c == '\n' || c == '\r' || c == EOF || c == DOS_EOF) break;
+
+ csize = (c == '\t') ? 5 : 1; /* Tabs are translated into five spaces */
+
+ if (i + csize >= n && buffsize >= 0) {
+ buffsize += READLN_GRAIN;
+ n = buffsize - 1;
+ buff = (char *)rrealloc(buff, buffsize * sizeof(char));
+ }
+
+ if (c == 0) c = FORMAT_CODE;
+ else if (c != '\t') {
+ if (i < n) buff[i++] = c;
+ } else for (j = 0; j < 5 && i < n; j++) buff[i++] = ' ';
+
+ /* We can't exit the loop if i>n since we still need to discard
+ the rest of the line */
+ }
+
+ buff[i] = 0;
+
+ if (c == '\r') { /* Check for \r\n DOS-style newline */
+ char newc;
+ newc = textgetc(f);
+ if (newc != '\n') textungetc(f, newc);
+ /* Replace the character we just read. */
+ } else if (c == DOS_EOF) /* Ctrl-Z is the DOS EOF marker */
+ textungetc(f, c); /* So it will be the first character we see next time */
+
+ if (i == 0 && (c == EOF || c == DOS_EOF)) { /* We've hit the end of the file */
+ if (buffsize >= 0) rfree(buff);
+ return NULL;
+ }
+
+ if (buffsize >= 0) { /* Shrink buffer to appropriate size */
+ buffsize = i + 1;
+ buff = (char *)rrealloc(buff, buffsize);
+ }
+
+ return buff;
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Buffered file Input: Routines to do buffered file I/O for files organized */
+/* into records. These routines are highly non-reentrant: they use a */
+/* global buffer and a global file id, so only they can only access one */
+/* file at a time. */
+/* buffopen() should not be called on a new file until buffclose has been */
+/* called on the old one. */
+/*-------------------------------------------------------------------------*/
+
+genfile bfile;
+
+static uchar *buffer = NULL;
+static long buffsize; /* How big the buffer is */
+static long record_size; /* Size of a record in the file */
+static long buff_frame; /* The file index corrosponding to buffer[0] */
+static long buff_fcnt; /* Number of records that can be held in the buffer */
+static long real_buff_fcnt; /* Number of records actually held in buffer */
+static long buff_rsize; /* Minimum amount that must be read. */
+
+static long block_size; /* Size of the current block
+ (for non-AGX files, this is just the filesize) */
+static long block_offset; /* Offset of current block in file (this should
+ be zero for non-AGX files) */
+
+
+static void buff_setrecsize(long recsize) {
+ const char *errstr;
+
+ record_size = recsize;
+ real_buff_fcnt = buff_fcnt = buffsize / record_size;
+ buff_frame = 0;
+
+ /* Note that real_buff_cnt==buff_fcnt in this case because
+ the buffer will have already been resized to be <=
+ the block size-- so we don't need to worry about the
+ buffer being larger than the data we're reading in. */
+
+ binseek(bfile, block_offset);
+ if (!binread(bfile, buffer, record_size, real_buff_fcnt, &errstr))
+ fatal(errstr);
+}
+
+
+
+long buffopen(fc_type fc, filetype ext, long minbuff, const char *rectype, long recnum)
+/* Returns record size; print out error and halt on failure */
+/* (if agx_file, it returns the filesize instead) */
+/* rectype="noun","room", etc. recnum=number of records expected */
+/* If rectype==NULL, buffopen() will return 0 on failure instead of
+halting */
+/* For AGX files, recsize should be set to minbuff... but
+buffreopen will be called before any major file activity
+(in particular, recnum should be 1) */
+{
+ long filesize;
+ long recsize;
+ char ebuff[200];
+ const char *errstr;
+
+ assert(buffer == NULL); /* If not, it means these routines have been
+ called by someone else who isn't done yet */
+
+ bfile = readopen(fc, ext, &errstr);
+ if (errstr != NULL) {
+ if (rectype == NULL) {
+ return 0;
+ } else
+ fatal(errstr);
+ }
+
+ filesize = binsize(bfile);
+
+ block_size = filesize;
+ block_offset = 0;
+ if (agx_file) block_size = minbuff; /* Just for the beginning */
+
+ if (block_size % recnum != 0) {
+ sprintf(ebuff, "Fractional record count in %s file.", rectype);
+ agtwarn(ebuff, 0);
+ }
+ buff_rsize = recsize = block_size / recnum;
+ if (buff_rsize > minbuff) buff_rsize = minbuff;
+
+ /* No point in having a buffer bigger than the block size */
+ buffsize = BUFF_SIZE;
+ if (block_size < buffsize) buffsize = block_size;
+
+ /* ... but it needs to be big enough: */
+ if (buffsize < minbuff) buffsize = minbuff;
+ if (buffsize < recsize) buffsize = recsize;
+
+ buffer = (uchar *)rmalloc(buffsize); /* Might want to make this adaptive eventually */
+
+ buff_setrecsize(recsize);
+ if (!agx_file && DIAG) {
+ char *s;
+ s = formal_name(fc, ext);
+ rprintf("Reading %s file %s (size:%ld)\n", rectype, s, filesize);
+ rfree(s);
+ rprintf(" Record size= Formal:%d File:%ld", minbuff, recsize);
+ }
+ if (agx_file) return (long) filesize;
+ else return (long) recsize;
+}
+
+
+/* Compute the game signature: a checksum of relevant parts of the file */
+
+static void compute_sig(uchar *buff) {
+ long bp;
+ for (bp = 0; bp < buff_rsize; bp++)
+ game_sig = (game_sig + buff[bp]) & 0xFFFF;
+}
+
+uchar *buffread(long index) {
+ uchar *bptr;
+ const char *errstr;
+
+ assert(buff_rsize <= record_size);
+ if (index >= buff_frame && index < buff_frame + real_buff_fcnt)
+ bptr = buffer + (index - buff_frame) * record_size;
+ else {
+ binseek(bfile, block_offset + index * record_size);
+ real_buff_fcnt = block_size / record_size - index; /* How many records
+ could we read in? */
+ if (real_buff_fcnt > buff_fcnt)
+ real_buff_fcnt = buff_fcnt; /* Don't overflow buffer */
+ if (!binread(bfile, buffer, record_size, real_buff_fcnt, &errstr))
+ fatal(errstr);
+ buff_frame = index;
+ bptr = buffer;
+ }
+ if (!agx_file) compute_sig(bptr);
+ return bptr;
+}
+
+
+void buffclose(void) {
+ readclose(bfile);
+ rfree(buffer);
+}
+
+
+/* This changes the record size and offset settings of the buffered
+ file so we can read files that consist of multiple sections with
+ different structures */
+static void buffreopen(long f_ofs, long file_recsize, long recnum,
+ long bl_size, const char *rectype) {
+ char ebuff[200];
+ long recsize;
+
+ /* Compute basic statistics */
+ block_offset = f_ofs; /* Offset of this block */
+ block_size = bl_size; /* Size of the entire block (all records) */
+ if (block_size % recnum != 0) {
+ /* Check that the number of records divides the block size evenly */
+ sprintf(ebuff, "Fractional record count in %s block.", rectype);
+ agtwarn(ebuff, 0);
+ }
+ buff_rsize = recsize = block_size / recnum;
+ if (buff_rsize > file_recsize) buff_rsize = file_recsize;
+ /* recsize is the size of each record in the file.
+ buff_rsize is the internal size of each record (the part
+ we actually look at, which may be smaller than recsize) */
+
+ /* No point in having a buffer bigger than the block size */
+ buffsize = BUFF_SIZE;
+ if (block_size < buffsize) buffsize = block_size;
+
+ /* The buffer needs to be at least as big as one block, so
+ we have space to both read it in and so we can look at the
+ block without having to worry about how big it really is */
+ if (buffsize < file_recsize) buffsize = file_recsize;
+ if (buffsize < recsize) buffsize = recsize;
+
+ rfree(buffer);
+ buffer = (uchar *)rmalloc(buffsize); /* Resize the buffer */
+
+ buff_setrecsize(recsize); /* Set up remaining stats */
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Buffered file output: Routines to buffer output for files organized */
+/* into records. These routines are highly non-reentrant: they use a */
+/* global buffer and a global file id, so only they can only access one */
+/* file at a time. */
+/* This routine uses the same buffer and data structures as the reading */
+/* routines above, so both sets of routines should not be used */
+/* concurrently */
+/*-------------------------------------------------------------------------*/
+
+/* #define DEBUG_SEEK*/ /* Debug seek beyond EOF problem */
+
+static long bw_first, bw_last; /* First and last record in buffer written to.
+ This is relative to the beginning of the
+ buffer bw_last points just beyond the last
+ one written to */
+#ifdef DEBUG_SEEK
+static long bw_fileleng; /* Current file length */
+#endif /* DEBUG_SEEK */
+file_id_type bw_fileid;
+
+/* Unlike is reading counterpart, this doesn't actually allocate
+ a buffer; that's done by bw_setblock() which should be called before
+ any I/O */
+void bw_open(fc_type fc, filetype ext) {
+ const char *errstr;
+
+ assert(buffer == NULL);
+
+ bfile = writeopen(fc, ext, &bw_fileid, &errstr);
+ if (errstr != NULL) fatal(errstr);
+ bw_last = 0;
+ buffsize = 0;
+ buffer = NULL;
+#ifdef DEBUG_SEEK
+ bw_fileleng = 0;
+#endif
+}
+
+static void bw_seek(long offset) {
+#ifdef DEBUG_SEEK
+ assert(offset <= bw_fileleng);
+#endif
+ binseek(bfile, offset);
+}
+
+
+static void bw_flush(void) {
+ if (bw_first == bw_last) return; /* Nothing to do */
+ bw_first += buff_frame;
+ bw_last += buff_frame;
+ bw_seek(block_offset + bw_first * record_size);
+ binwrite(bfile, buffer, record_size, bw_last - bw_first, 1);
+#ifdef DEBUG_SEEK
+ if (block_offset + bw_last * record_size > bw_fileleng)
+ bw_fileleng = block_offset + bw_last * record_size;
+#endif
+ bw_first = bw_last = 0;
+}
+
+
+
+static void bw_setblock(long fofs, long recnum, long rsize)
+/* Set parameters for current block */
+{
+ /* First, flush old block if neccessary */
+ if (buffer != NULL) {
+ bw_flush();
+ rfree(buffer);
+ }
+ block_size = rsize * recnum;
+ block_offset = fofs;
+ record_size = rsize;
+ buff_frame = 0;
+ bw_first = bw_last = 0;
+ buffsize = BUFF_SIZE;
+ if (buffsize > block_size) buffsize = block_size;
+ if (buffsize < rsize) buffsize = rsize;
+ buff_fcnt = buffsize / rsize;
+ buffsize = buff_fcnt * rsize;
+ buffer = (uchar *)rmalloc(buffsize);
+}
+
+/* This routine returns a buffer of the current recsize and with
+ the specified index into the file */
+/* The buffer will be written to disk after the next call to
+ bw_getbuff() or bw_closebuff() */
+static uchar *bw_getbuff(long index) {
+ index -= buff_frame;
+ if (index < bw_first || index > bw_last || index >= buff_fcnt) {
+ bw_flush();
+ bw_first = bw_last = 0;
+ buff_frame = buff_frame + index;
+ index = 0;
+ }
+ if (index == bw_last) bw_last++;
+ return buffer + record_size * index;
+}
+
+
+/* This flushes all buffers to disk and closes all files */
+void bw_close(void) {
+ bw_flush();
+ rfree(buffer);
+ writeclose(bfile, bw_fileid);
+}
+
+void bw_abort(void) {
+ binremove(bfile, bw_fileid);
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Block reading and writing code and support for internal buffers */
+/*-------------------------------------------------------------------------*/
+
+
+/* If the internal buffer is not NULL, it is used instead of a file */
+/* (This is used by RESTART, etc. to save state to memory rather than
+ to a file) */
+static uchar *int_buff = NULL;
+static long ibuff_ofs, ibuff_rsize;
+
+void set_internal_buffer(void *buff) {
+ int_buff = (uchar *)buff;
+}
+
+static void set_ibuff(long offset, long rsize) {
+ ibuff_ofs = offset;
+ record_size = ibuff_rsize = rsize;
+}
+
+static uchar *get_ibuff(long index) {
+ return int_buff + ibuff_ofs + index * ibuff_rsize;
+}
+
+/* This does a block write to the currently buffered file.
+ At the moment this itself does no buffering at all; it's intended
+ for high speed reading of blocks of chars for which we've already
+ allocated the space. */
+static void buff_blockread(void *buff, long size, long offset) {
+ const char *errstr;
+
+ if (int_buff != NULL)
+ memcpy((char *)buff, int_buff + offset, size);
+ else {
+ binseek(bfile, offset);
+ if (!binread(bfile, buff, size, 1, &errstr)) fatal(errstr);
+ }
+}
+
+
+/* This writes buff to disk. */
+static void bw_blockwrite(void *buff, long size, long offset) {
+ if (int_buff != NULL)
+ memcpy(int_buff + offset, (char *)buff, size);
+ else {
+ bw_flush();
+ bw_seek(offset);
+ binwrite(bfile, buff, size, 1, 1);
+#ifdef DEBUG_SEEK
+ if (offset + size > bw_fileleng) bw_fileleng = offset + size;
+#endif
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Platform-independent record-based file I/O: Routines to read and write */
+/* files according to the file_info data structures. */
+/* These routines use the buffered I/O routines above */
+/*-------------------------------------------------------------------------*/
+
+/* Length of file datatypes */
+const size_t ft_leng[FT_COUNT] = {0, 2, 2, /* END, int16, and uint16 */
+ 4, 4, /* int32 and uint32 */
+ 1, 2, 0, /* byte, version, rbool */
+ 8, 4, /* descptr, ss_ptr */
+ 2, 26, /* slist, path[13] */
+ 4, 4, /* cmdptr, dictptr */
+ 81, /* tline */
+ 1, 1
+ }; /* char, cfg */
+
+
+long compute_recsize(file_info *recinfo) {
+ long cnt, bcnt;
+
+ cnt = 0;
+ for (; recinfo->ftype != FT_END; recinfo++)
+ if (recinfo->ftype == FT_BOOL) {
+ for (bcnt = 0; recinfo->ftype == FT_BOOL; recinfo++, bcnt++);
+ recinfo--;
+ cnt += (bcnt + 7) / 8; /* +7 is to round up */
+ } else
+ cnt += ft_leng[recinfo->ftype];
+ return cnt;
+}
+
+static const int agx_version[] = {0, 0000, 1800, 2000, 3200, 3500, 8200, 8300,
+ 5000, 5050, 5070, 10000, 10050, 15000, 15500, 16000, 20000
+ };
+
+static int agx_decode_version(int vercode) {
+ if (vercode & 1) /* Large/Soggy */
+ if (vercode == 3201) ver = 4;
+ else ver = 2;
+ else if (vercode < 10000) ver = 1;
+ else ver = 3;
+ switch (vercode & (~1)) {
+ case 0000:
+ return AGT10;
+ case 1800:
+ return AGT118;
+ case 1900:
+ return AGT12;
+ case 2000:
+ return AGT12;
+ case 3200:
+ return AGTCOS;
+ case 3500:
+ return AGT135;
+ case 5000:
+ return AGT15;
+ case 5050:
+ return AGT15F;
+ case 5070:
+ return AGT16;
+ case 8200:
+ return AGT182;
+ case 8300:
+ return AGT183;
+ case 8350:
+ return AGT183;
+ case 10000:
+ return AGTME10;
+ case 10050:
+ return AGTME10A;
+ case 15000:
+ return AGTME15;
+ case 15500:
+ return AGTME155;
+ case 16000:
+ return AGTME16;
+ case 20000:
+ return AGX00;
+ default:
+ agtwarn("Unrecognize AGT version", 0);
+ return 0;
+ }
+}
+
+/* The following reads a section of a file into variables, doing
+ the neccessary conversions. It is the foundation of all the generic
+ file reading code */
+
+#define p(t) ((t*)(rec_desc->ptr))
+#define fixu16(n1,n2) ( ((long)(n1))|( ((long)(n2))<<8 ))
+
+/* This is as large as the largest data structure we could run into */
+static const uchar zero_block[81] = {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, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0
+ };
+
+static void read_filerec(const file_info *rec_desc, const uchar *filedata) {
+ uchar mask;
+ rbool past_eob; /* Are we past the end of block? */
+ const uchar *filebase;
+
+ mask = 1;
+ past_eob = 0;
+ filebase = filedata;
+ for (; rec_desc->ftype != FT_END; rec_desc++) {
+ if (mask != 1 && rec_desc->ftype != FT_BOOL) { /* Just finished rboolean */
+ mask = 1;
+ filedata += 1;
+ }
+ if (filebase == NULL || (filedata - filebase) >= record_size) {
+ /* We're past the end of the block; read in zeros for the rest
+ of entries. */
+ past_eob = 1;
+ filedata = zero_block;
+ filebase = NULL;
+ }
+ switch (rec_desc->ftype) {
+ case FT_INT16:
+ if (rec_desc->dtype == DT_LONG)
+ *p(long) = fixsign16(filedata[0], filedata[1]);
+ else
+ *p(integer) = fixsign16(filedata[0], filedata[1]);
+ break;
+ case FT_UINT16:
+ *p(long) = fixu16(filedata[0], filedata[1]);
+ break;
+ case FT_CMDPTR: /* cmd ptr */
+ case FT_INT32:
+ *p(long) = fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ break;
+ case FT_UINT32:
+ if (filedata[3] & 0x80)
+ agtwarn("File value out of range", 0);
+ *p(long) = fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3] & 0x7F);
+ break;
+ case FT_BYTE:
+ *p(uchar) = filedata[0];
+ break;
+ case FT_CHAR:
+ *p(uchar) = trans_ascii[filedata[0]^'r'];
+ break;
+ case FT_VERSION:
+ *p(int) = agx_decode_version(fixu16(filedata[0], filedata[1]));
+ break;
+ case FT_CFG:
+ if (filedata[0] != 2 && !past_eob)
+ *p(rbool) = filedata[0];
+ break;
+ case FT_BOOL:
+ *p(rbool) = ((filedata[0] & mask) != 0);
+ if (mask == 0x80) {
+ filedata++;
+ mask = 1;
+ } else
+ mask <<= 1;
+ break;
+ case FT_DESCPTR:
+ if (skip_descr) break;
+ p(descr_ptr)->start = fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ p(descr_ptr)->size = fixsign32(filedata[4], filedata[5],
+ filedata[6], filedata[7]);
+ break;
+ case FT_STR: /* ss_string ptr */
+ *p(char *) = static_str + fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ break;
+ case FT_SLIST:
+ *p(slist) = fixsign16(filedata[0], filedata[1]);
+ break;
+ case FT_PATHARRAY: { /* integer array[13] */
+ int i;
+ for (i = 0; i < 13; i++)
+ p(integer)[i] = fixsign16(filedata[2 * i], filedata[2 * i + 1]);
+ break;
+ }
+ case FT_TLINE: { /* string of length at most 80 characters +null */
+ uchar *s;
+ int i;
+ s = (uchar *)*p(tline);
+ for (i = 0; i < 80; i++)
+ s[i] = trans_ascii[filedata[i]^'r'];
+ s[80] = 0;
+ break;
+ }
+ case FT_DICTPTR: /* ptr into dictstr */
+ *p(char *) = dictstr + fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ break;
+ default:
+ fatal("Unreconized field type");
+ }
+ filedata += ft_leng[rec_desc->ftype];
+ }
+}
+
+
+#define v(t) (*(t*)(rec_desc->ptr))
+/* Here is the corresponding routien for _writing_ to files */
+/* This copies the contents of a record into a buffer */
+
+static void write_filerec(const file_info *rec_desc, uchar *filedata) {
+ uchar mask;
+
+ mask = 1;
+ for (; rec_desc->ftype != FT_END; rec_desc++) {
+ if (mask != 1 && rec_desc->ftype != FT_BOOL) { /* Just finished rboolean */
+ mask = 1;
+ filedata += 1;
+ }
+ switch (rec_desc->ftype) {
+ case FT_INT16:
+ if (rec_desc->dtype == DT_LONG) {
+ filedata[0] = v(long) & 0xFF;
+ filedata[1] = (v(long) >> 8) & 0xFF;
+ } else {
+ filedata[0] = v(integer) & 0xFF;
+ filedata[1] = (v(integer) >> 8) & 0xFF;
+ }
+ break;
+ case FT_UINT16:
+ filedata[0] = v(long) & 0xFF;
+ filedata[1] = (v(long) >> 8) & 0xFF;
+ break;
+ case FT_CMDPTR: /* cmd ptr */
+ case FT_INT32:
+ case FT_UINT32:
+ filedata[0] = v(long) & 0xFF;
+ filedata[1] = (v(long) >> 8) & 0xFF;
+ filedata[2] = (v(long) >> 16) & 0xFF;
+ filedata[3] = (v(long) >> 24) & 0xFF;
+ break;
+ case FT_BYTE:
+ filedata[0] = v(uchar);
+ break;
+ case FT_CFG:
+ filedata[0] = v(uchar);
+ break;
+ case FT_CHAR:
+ filedata[0] = v(uchar)^'r';
+ break;
+ case FT_VERSION: {
+ int tver;
+ tver = agx_version[v(int)];
+ if (ver == 2 || ver == 4) tver += 1;
+ filedata[0] = tver & 0xFF;
+ filedata[1] = (tver >> 8) & 0xFF;
+ break;
+ }
+ case FT_BOOL:
+ if (mask == 1) filedata[0] = 0;
+ filedata[0] |= v(rbool) ? mask : 0;
+ if (mask == 0x80) {
+ filedata++;
+ mask = 1;
+ } else
+ mask <<= 1;
+ break;
+ case FT_DESCPTR: {
+ long i, n1, n2;
+ n1 = p(descr_ptr)->start;
+ n2 = p(descr_ptr)->size;
+ for (i = 0; i < 4; i++) {
+ filedata[i] = n1 & 0xFF;
+ filedata[i + 4] = n2 & 0xFF;
+ n1 >>= 8;
+ n2 >>= 8;
+ }
+ }
+ break;
+ case FT_STR: { /* ss_string ptr */
+ long delta;
+ delta = v(char *) - static_str;
+ filedata[0] = delta & 0xFF;
+ filedata[1] = (delta >> 8) & 0xFF;
+ filedata[2] = (delta >> 16) & 0xFF;
+ filedata[3] = (delta >> 24) & 0xFF;
+ break;
+ }
+ case FT_SLIST:
+ filedata[0] = v(slist) & 0xFF;
+ filedata[1] = (v(slist) >> 8) & 0xFF;
+ break;
+ case FT_PATHARRAY: { /* integer array[13] */
+ int i;
+ for (i = 0; i < 13; i++) {
+ filedata[2 * i] = *(p(integer) + i) & 0xFF;
+ filedata[2 * i + 1] = (*(p(integer) + i) >> 8) & 0xFF;
+ }
+ break;
+ }
+ case FT_TLINE: { /* string of length at most 80 characters +null */
+ uchar *s;
+ int i;
+ s = (uchar *)v(tline);
+ for (i = 0; i < 80; i++)
+ filedata[i] = s[i]^'r';
+ filedata[80] = 0;
+ break;
+ }
+ case FT_DICTPTR: { /* ptr into dictstr */
+ long delta;
+ delta = v(char *) - dictstr;
+ filedata[0] = delta & 0xFF;
+ filedata[1] = (delta >> 8) & 0xFF;
+ filedata[2] = (delta >> 16) & 0xFF;
+ filedata[3] = (delta >> 24) & 0xFF;
+ break;
+ }
+ default:
+ fatal("Unreconized field type");
+ }
+ filedata += ft_leng[rec_desc->ftype];
+ }
+}
+
+#undef v
+#undef p
+
+
+
+
+/* This reads in a structure array */
+/* base=the beginning of the array. If NULL, this is malloc'd and returned
+ eltsize = the size of each structure
+ numelts = the number of elements in the array
+ field_info = the arrangement of fields within the strucutre
+ rectype = string to print out for error messages
+ file_offset = the offset of the beginning of the array into the file
+ */
+void *read_recarray(void *base, long eltsize, long numelts,
+ file_info *field_info, const char *rectype,
+ long file_offset, long file_blocksize) {
+ long i;
+ file_info *curr;
+ uchar *file_data;
+
+ if (numelts == 0) return NULL;
+
+ if (int_buff)
+ set_ibuff(file_offset, compute_recsize(field_info));
+ else
+ buffreopen(file_offset, compute_recsize(field_info), numelts,
+ file_blocksize, rectype);
+
+ if (base == NULL)
+ base = rmalloc(eltsize * numelts);
+
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype != DT_DESCPTR && curr->dtype != DT_CMDPTR)
+ curr->ptr = ((char *)base + curr->offset);
+
+ for (i = 0; i < numelts; i++) {
+ if (!int_buff)
+ file_data = buffread(i);
+ else
+ file_data = get_ibuff(i);
+ read_filerec(field_info, file_data);
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype == DT_DESCPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(descr_ptr);
+ else if (curr->dtype == DT_CMDPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(long);
+ else
+ curr->ptr = (char *)(curr->ptr) + eltsize;
+ }
+
+ return base;
+}
+
+
+/* A NULL value means to write junk; we're just producing
+ a placeholder for systems that can't seek beyond the end-of-file */
+
+long write_recarray(void *base, long eltsize, long numelts,
+ file_info *field_info, long file_offset) {
+ long i;
+ file_info *curr;
+ uchar *file_data;
+
+ if (numelts == 0) return 0;
+
+ if (int_buff)
+ set_ibuff(file_offset, compute_recsize(field_info));
+ else
+ bw_setblock(file_offset, numelts, compute_recsize(field_info));
+
+ if (base != NULL)
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype != DT_DESCPTR && curr->dtype != DT_CMDPTR)
+ curr->ptr = ((char *)base + curr->offset);
+
+ for (i = 0; i < numelts; i++) {
+ if (int_buff)
+ file_data = get_ibuff(i);
+ else
+ file_data = bw_getbuff(i);
+ if (base != NULL) {
+ write_filerec(field_info, file_data);
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype == DT_DESCPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(descr_ptr);
+ else if (curr->dtype == DT_CMDPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(long);
+ else
+ curr->ptr = (char *)(curr->ptr) + eltsize;
+ }
+ }
+ return compute_recsize(field_info) * numelts;
+}
+
+
+void read_globalrec(file_info *global_info, const char *rectype,
+ long file_offset, long file_blocksize) {
+ uchar *file_data;
+
+ if (int_buff) {
+ set_ibuff(file_offset, compute_recsize(global_info));
+ file_data = get_ibuff(0);
+ } else {
+ buffreopen(file_offset, compute_recsize(global_info), 1, file_blocksize,
+ rectype);
+ file_data = buffread(0);
+ }
+ read_filerec(global_info, file_data);
+}
+
+
+long write_globalrec(file_info *global_info, long file_offset) {
+ uchar *file_data;
+
+ if (int_buff) {
+ set_ibuff(file_offset, compute_recsize(global_info));
+ file_data = get_ibuff(0);
+ } else {
+ bw_setblock(file_offset, 1, compute_recsize(global_info));
+ file_data = bw_getbuff(0);
+ }
+ write_filerec(global_info, file_data);
+ return compute_recsize(global_info);
+}
+
+
+
+static file_info fi_temp[] = {
+ {0, DT_DEFAULT, NULL, 0},
+ endrec
+};
+
+/* This routine reads in an array of simple data */
+
+void *read_recblock(void *base, int ftype, long numrec,
+ long offset, long bl_size) {
+ int dsize;
+
+ switch (ftype) {
+ case FT_CHAR:
+ case FT_BYTE:
+ if (base == NULL) base = rmalloc(numrec * sizeof(char));
+ buff_blockread(base, numrec, offset);
+ if (ftype == FT_CHAR) {
+ long i;
+ for (i = 0; i < numrec; i++)
+ ((uchar *)base)[i] = trans_ascii[((uchar *)base)[i]^'r' ];
+ }
+ return base;
+ case FT_SLIST:
+ dsize = sizeof(slist);
+ break;
+ case FT_INT16:
+ dsize = sizeof(integer);
+ break;
+ case FT_UINT16:
+ case FT_INT32:
+ dsize = sizeof(long);
+ break;
+ case FT_STR:
+ case FT_DICTPTR:
+ dsize = sizeof(char *);
+ break;
+ default:
+ fatal("Invalid argument to read_recblock.");
+ dsize = 0; /* Silence compiler warnings; this will never actually
+ be reached. */
+ }
+
+ fi_temp[0].ftype = ftype;
+ return read_recarray(base, dsize, numrec, fi_temp, "", offset, bl_size);
+}
+
+
+long write_recblock(void *base, int ftype, long numrec, long offset) {
+ int dsize;
+
+ if (numrec == 0) return 0;
+ switch (ftype) {
+ case FT_CHAR: {
+ int i;
+ for (i = 0; i < numrec; i++)
+ ((uchar *)base)[i] = ((uchar *)base)[i]^'r';
+ }
+ /* Fall through.... */
+ case FT_BYTE:
+ bw_blockwrite(base, numrec, offset);
+ return numrec;
+ case FT_SLIST:
+ dsize = sizeof(slist);
+ break;
+ case FT_INT16:
+ dsize = sizeof(integer);
+ break;
+ case FT_INT32:
+ dsize = sizeof(long);
+ break;
+ case FT_STR:
+ case FT_DICTPTR:
+ dsize = sizeof(char *);
+ break;
+ default:
+ fatal("Invalid argument to write_recblock.");
+ dsize = 0; /* Silence compiler warnings; this will never actually
+ be reached. */
+ }
+
+ fi_temp[0].ftype = ftype;
+ return write_recarray(base, dsize, numrec, fi_temp, offset);
+}
+
+char *textgets(genfile f, char *buf, size_t n) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(f);
+ assert(rs);
+
+ size_t count = 0;
+ char c;
+
+ while (!rs->eos() && (count < (n - 1)) && (c = rs->readByte()) != '\n') {
+ buf[count] = c;
+ ++count;
+ }
+
+ buf[count] = '\0';
+ return count ? buf : nullptr;
+}
+
+char textgetc(genfile f) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(f);
+ assert(rs);
+
+ return rs->readByte();
+}
+
+void textungetc(genfile f, char c) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
+ assert(rs);
+
+ rs->seek(-1, SEEK_SET);
+}
+
+bool texteof(genfile f) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(f);
+ assert(rs);
+
+ return rs->eos();
+}
+
+void textputs(genfile f, const char *s) {
+ Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(f);
+ assert(ws);
+
+ ws->write(s, strlen(s));
+}
+
+/* ------------------------------------------------------------------- */
+/* "Profiling" functions */
+/* Routines for timing code execution */
+/* These will only work on POSIX systems */
+
+#ifdef PROFILE_SUPPORT
+
+static struct tms start;
+clock_t start_realtime;
+static struct tms delta;
+clock_t delta_realtime;
+
+void resetwatch(void) {
+ delta.tms_utime = delta.tms_stime = delta.tms_cutime = delta.tms_cstime = 0;
+ delta_realtime = 0;
+ start_realtime = times(&start);
+}
+
+void startwatch(void) {
+ start_realtime = times(&start);
+}
+
+static char watchbuff[81];
+char *timestring(void) {
+ sprintf(watchbuff, "User:%ld.%02ld Sys:%ld.%02ld Total:%ld.%02ld"
+ " Real:%ld.%02ld",
+ delta.tms_utime / 100, delta.tms_utime % 100,
+ delta.tms_stime / 100, delta.tms_stime % 100,
+ (delta.tms_utime + delta.tms_stime) / 100,
+ (delta.tms_utime + delta.tms_stime) % 100,
+ delta_realtime / 100, delta_realtime % 100
+ );
+ return watchbuff;
+}
+
+char *stopwatch(void) {
+ struct tms curr;
+
+ delta_realtime += times(&curr) - start_realtime;
+ delta.tms_utime += (curr.tms_utime - start.tms_utime);
+ delta.tms_stime += (curr.tms_stime - start.tms_stime);
+ delta.tms_cutime += (curr.tms_cutime - start.tms_cutime);
+ delta.tms_cstime += (curr.tms_cstime - start.tms_cstime);
+ return timestring();
+}
+
+/* 5+7+9+8+4*3+4*?? = 41+?? */
+
+#endif /* PROFILE_SUPPORT */
+
+} // End of namespace AGT
+} // End of namespace Glk