diff options
Diffstat (limited to 'engines/glk/agt/util.cpp')
-rw-r--r-- | engines/glk/agt/util.cpp | 1489 |
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 |