mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-10 11:51:52 +00:00
884c106c9a
neccessary -> necessary succesfull -> successfull
1476 lines
40 KiB
C++
1476 lines
40 KiB
C++
/* 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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);
|
|
Common::vsprintf_s(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 nullptr;
|
|
p = malloc((size_t)size);
|
|
if (p == nullptr && 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 nullptr;
|
|
}
|
|
if (rm_acct && old == nullptr) ralloc_cnt++;
|
|
p = realloc(old, (size_t)size);
|
|
if (p == nullptr && rm_trap && size > 0) {
|
|
error("Memory reallocation error: Out of memory.");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
char *rstrdup(const char *s) {
|
|
if (s == nullptr) return nullptr;
|
|
|
|
char *t = scumm_strdup(s);
|
|
if (t == nullptr && rm_trap) {
|
|
error("Memory duplication error: Out of memory.");
|
|
}
|
|
if (rm_acct) ralloc_cnt++;
|
|
|
|
return t;
|
|
}
|
|
|
|
void r_free(void *p) {
|
|
int tmp;
|
|
|
|
if (p == nullptr) 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 successful.
|
|
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 != nullptr) len1 = strlen(s1);
|
|
if (s2 != nullptr) len2 = strlen(s2);
|
|
|
|
s = (char *)rmalloc(sizeof(char) * (len1 + len2 + 2));
|
|
if (s1 != nullptr)
|
|
memcpy(s, s1, len1);
|
|
memcpy(s + len1, " ", 1);
|
|
if (s2 != nullptr)
|
|
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 */
|
|
size_t ln = strlen(err) + strlen(fname) + 2;
|
|
estring = (char *)rmalloc(ln);
|
|
Common::sprintf_s(estring, ln, 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);
|
|
size_t bytesRead = rs->read(ptr, size * nmemb);
|
|
return bytesRead / size;
|
|
}
|
|
|
|
size_t fwrite(const void *ptr, size_t size, size_t nmemb, genfile stream) {
|
|
Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(stream);
|
|
assert(ws);
|
|
size_t bytesWritten = ws->write(ptr, size * nmemb);
|
|
return bytesWritten / size;
|
|
}
|
|
|
|
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 != nullptr && err != nullptr)
|
|
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 != nullptr && err != nullptr) {
|
|
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 == nullptr) {
|
|
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 nullptr;
|
|
}
|
|
|
|
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 = nullptr;
|
|
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 == nullptr); /* If not, it means these routines have been
|
|
called by someone else who isn't done yet */
|
|
|
|
bfile = readopen(fc, ext, &errstr);
|
|
if (errstr != nullptr) {
|
|
if (rectype == nullptr) {
|
|
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) {
|
|
Common::sprintf_s(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:%ld 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 */
|
|
Common::sprintf_s(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 == nullptr);
|
|
|
|
bfile = writeopen(fc, ext, &bw_fileid, &errstr);
|
|
if (errstr != nullptr) fatal(errstr);
|
|
bw_last = 0;
|
|
buffsize = 0;
|
|
buffer = nullptr;
|
|
#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 necessary */
|
|
if (buffer != nullptr) {
|
|
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);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* 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 = nullptr;
|
|
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 != nullptr)
|
|
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 != nullptr)
|
|
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 necessary 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(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 == nullptr || (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 = nullptr;
|
|
}
|
|
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(int32) = fixu16(filedata[0], filedata[1]);
|
|
break;
|
|
case FT_CMDPTR: /* cmd ptr */
|
|
case FT_INT32:
|
|
*p(int32) = 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(uint32) = 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("Unrecognized 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(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("Unrecognized 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 nullptr;
|
|
|
|
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 == nullptr)
|
|
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 != nullptr)
|
|
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 != nullptr) {
|
|
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, nullptr, 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 == nullptr) 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->eos() ? EOF : 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) {
|
|
Common::sprintf_s(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
|