scummvm/engines/glk/agt/filename.cpp
2021-12-26 18:48:43 +01:00

545 lines
14 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"
namespace Glk {
namespace AGT {
#ifdef force16
#undef int
#define int short
#endif
/*----------------------------------------------------------------------*/
/* Filetype Data */
/*----------------------------------------------------------------------*/
const char *extname[] = {
"",
DA1, DA2, DA3, DA4, DA5, DA6, DSS,
pHNT, pOPT, pTTL,
pSAV, pSCR, pLOG,
pAGX, pINS, pVOC, pCFG,
pAGT, pDAT, pMSG, pCMD, pSTD, AGTpSTD
};
#ifdef PATH_SEP
static const char *path_sep = PATH_SEP;
#else
static const char *path_sep = nullptr;
#endif
/* This returns the options to use when opening the given file type */
/* rw is true if we are writing, false if we are reading. */
const char *filetype_info(filetype ft, rbool rw) {
if (ft < fTTL) return "rb";
if (ft == fAGX) return rw ? "wb" : "rb";
if (ft == fSAV) return (rw ? "wb" : "rb");
if (ft == fTTL || ft == fINS || ft == fVOC) return "rb";
#ifdef OPEN_AS_TEXT
if (ft >= fCFG) return (open_as_binary ? "rb" : "r");
#else
if (ft >= fCFG) return "rb";
#endif
if (ft == fSCR) {
if (rw)
return (BATCH_MODE || make_test) ? "w" : "a";
else return "r";
}
if (ft == fLOG) return rw ? "w" : "r";
fatal("INTERNAL ERROR: Invalid filetype.");
return nullptr;
}
/* Returns true if ft is a possible extension in general context ft_base */
static rbool compat_ext(filetype ft, filetype ft_base) {
if (ft_base == fNONE || ft_base == fDA1 || ft_base == fAGX) { /* Game file */
return (ft >= fDA1 && ft <= fDSS)
|| ft == fOPT || ft == fTTL
|| (ft >= fAGX && ft <= fCFG);
}
if (ft_base == fSAV || ft_base == fSCR || ft_base == fLOG)
return (ft == ft_base);
if (ft_base == fAGT) { /* Source code */
return (ft >= fAGT && ft <= fCMD)
|| ft == fTTL || ft == fCFG;
}
fatal("INTERNAL ERROR: Invalid file class.");
return 0;
}
/*----------------------------------------------------------------------*/
/* Misc. utilities */
/*----------------------------------------------------------------------*/
char *assemble_filename(const char *path, const char *root,
const char *ext) {
int len1, len2, len3;
char *name;
len1 = len2 = len3 = 0;
if (path != nullptr) len1 = strlen(path);
if (root != nullptr) len2 = strlen(root);
if (ext != nullptr) len3 = strlen(ext);
name = (char *)rmalloc(len1 + len2 + len3 + 1);
if (path != nullptr) memcpy(name, path, len1);
#ifdef PREFIX_EXT
if (ext != NULL) memcpy(name + len1, ext, len3);
if (root != NULL) memcpy(name + len1 + len3, root, len2);
#else
if (root != nullptr) memcpy(name + len1, root, len2);
if (ext != nullptr) memcpy(name + len1 + len2, ext, len3);
#endif
name[len1 + len2 + len3] = 0;
return name;
}
#ifdef PATH_SEP
/* This works for binary files; we don't care about non-binary
files since this only used to search for game files. */
static rbool file_exist(const char *fname) {
return Common::File::exists(fname);
}
#endif
/* This checks to see if c matches any of the characters in matchset */
static rbool smatch(char c, const char *matchset) {
for (; *matchset != 0; matchset++)
if (*matchset == c) return 1;
return 0;
}
/*----------------------------------------------------------------------*/
/* Taking Apart the Filename */
/*----------------------------------------------------------------------*/
static int find_path_sep(const char *name) {
int i;
if (path_sep == nullptr)
return -1;
for (i = strlen(name) - 1; i >= 0; i--)
if (smatch(name[i], path_sep)) break;
return i;
}
/* Checks to see if the filename (which must be path-free)
has an extension. Returns the length of the extensions
and writes the extension type in pft */
static int search_for_ext(const char *name, filetype base_ft,
filetype *pft) {
filetype t;
int xlen, len;
*pft = fNONE;
len = strlen(name);
if (len == 0) return 0;
for (t = (filetype)(fNONE + 1); t <= fSTD; t = (filetype)((int)t + 1))
if (compat_ext(t, base_ft)) {
xlen = strlen(extname[t]);
if (xlen == 0 || xlen > len) continue;
#ifdef PREFIX_EXT
if (strncasecmp(name, extname[t], xlen) == 0)
#else
if (fnamecmp(name + len - xlen, extname[t]) == 0)
#endif
{
*pft = t;
return xlen;
}
}
return 0;
}
/* Extract root filename or extension from
pathless name, given that the extension is of length extlen. */
/* If isext is true, extract the extension. If isext is false,
then extrac the root. */
static char *extract_piece(const char *name, int extlen, rbool isext) {
char *root;
int len, xlen;
rbool first; /* If true, extract from beginning; if false, extract
from end */
len = strlen(name) - extlen;
xlen = extlen;
if (isext) {
int tmp;
tmp = len;
len = xlen;
xlen = tmp;
}
if (len == 0) return nullptr;
root = (char *)rmalloc((len + 1) * sizeof(char));
#ifdef PREFIX_EXT
first = isext ? 1 : 0;
#else
first = isext ? 0 : 1;
#endif
if (first) {
memcpy(root, name, len);
root[len] = 0;
} else {
memcpy(root, name + xlen, len);
root[len] = 0;
}
return root;
}
/* This returns true if "path" is absolute, false otherwise.
This is _very_ platform dependent. */
static rbool absolute_path(char *path) {
#ifdef pathtest
return pathtest(path);
#else
return 1;
#endif
}
/*----------------------------------------------------------------------*/
/* Basic routines for dealing with file contexts */
/*----------------------------------------------------------------------*/
#define FC(x) ((file_context_rec*)(x))
/* formal_name is used to get filenames for diagnostic messages, etc. */
char *formal_name(fc_type fc, filetype ft) {
if (FC(fc)->special) return FC(fc)->gamename;
if (ft == fNONE)
return rstrdup(FC(fc)->shortname);
if (ft == fAGT_STD)
return rstrdup(AGTpSTD);
return assemble_filename("", FC(fc)->shortname, extname[ft]);
}
#ifdef PATH_SEP
static rbool test_file(const char *path, const char *root, const char *ext) {
char *name;
rbool tmp;
name = assemble_filename(path, root, ext);
tmp = file_exist(name);
rfree(name);
return tmp;
}
/* This does a path search for the game files. */
static void fix_path(file_context_rec *fc) {
char **ppath;
if (gamepath == NULL) return;
for (ppath = gamepath; *ppath != NULL; ppath++)
if (test_file(*ppath, fc->shortname, fc->ext)
|| test_file(*ppath, fc->shortname, pAGX)
|| test_file(*ppath, fc->shortname, DA1)) {
fc->path = rstrdup(*ppath);
return;
}
}
#endif
/* This creates a new file context based on gamename. */
/* ft indicates the rough use it will be put towards:
ft=fNONE indicates it's the first pass read, before PATH has been
read in, and so the fc shouldn't be filled out until
fix_file_context() is called.
ft=pDA1 indicates that name refers to the game files.
ft=pAGX indicates the name of the AGX file to be written to.
ft=pSAV,pLOG,pSCR all indicate that name corresponds to the
related type of file. */
fc_type init_file_context(const char *name, filetype ft) {
file_context_rec *fc;
int p, x; /* Path and extension markers */
fc = (file_context_rec *)rmalloc(sizeof(file_context_rec));
fc->special = 0;
fc->gamename = rstrdup(name);
p = find_path_sep(fc->gamename);
if (p < 0)
fc->path = nullptr;
else {
fc->path = (char *)rmalloc((p + 2) * sizeof(char));
memcpy(fc->path, fc->gamename, p + 1);
fc->path[p + 1] = '\0';
}
x = search_for_ext(fc->gamename + p + 1, ft, &fc->ft);
fc->shortname = extract_piece(fc->gamename + p + 1, x, 0);
fc->ext = extract_piece(fc->gamename + p + 1, x, 1);
#ifdef PATH_SEP
if (fc->path == NULL && ft == fDA1)
fix_path(fc);
#endif
return fc;
}
void fix_file_context(fc_type fc, filetype ft) {
#ifdef PATH_SEP
if (FC(fc)->path == NULL && ft == fDA1)
fix_path(FC(fc));
#endif
}
/* This creates new file contexts from old. */
/* This is used to create save/log/script filenames from the game name,
and to create include files in the same directory as the source file. */
fc_type convert_file_context(fc_type fc, filetype ft, const char *name) {
file_context_rec *nfc;
rbool local_ftype; /* Indicates file should be in working directory,
not game directory. */
local_ftype = (ft == fSAV || ft == fSCR || ft == fLOG);
if (BATCH_MODE || make_test) local_ftype = 0;
if (name == nullptr) {
nfc = (file_context_rec *)rmalloc(sizeof(file_context_rec));
nfc->gamename = nullptr;
nfc->path = nullptr;
nfc->shortname = rstrdup(fc->shortname);
nfc->ext = nullptr;
nfc->ft = fNONE;
nfc->special = 0;
} else {
nfc = init_file_context(name, ft);
}
/* If path already defined, then combine paths. */
if (!local_ftype && nfc->path != nullptr && !absolute_path(nfc->path)) {
char *newpath;
newpath = nfc->path;
newpath = assemble_filename(fc->path, nfc->path, "");
rfree(nfc->path);
nfc->path = newpath;
}
/* scripts, save-games and logs should go in the working directory,
not the game directory, so leave nfc->path equal to NULL for them. */
if (!local_ftype && nfc->path == nullptr)
nfc->path = rstrdup(fc->path); /* Put files in game directory */
return nfc;
}
void release_file_context(fc_type *pfc) {
file_context_rec *fc;
fc = FC(*pfc);
rfree(fc->gamename);
rfree(fc->path);
rfree(fc->shortname);
rfree(fc->ext);
rfree(fc);
}
/*----------------------------------------------------------------------*/
/* Routines for Finding Files */
/*----------------------------------------------------------------------*/
static genfile try_open_file(const char *path, const char *root,
const char *ext, const char *how,
rbool nofix) {
char *name = assemble_filename(path, root, ext);
genfile f = fopen(name, how);
rfree(name);
return f;
}
static genfile findread(file_context_rec *fc, filetype ft) {
genfile f;
f = nullptr;
if (ft == fAGT_STD) {
f = try_open_file(fc->path, AGTpSTD, "", filetype_info(ft, 0), 0);
return f;
}
if (ft == fAGX || ft == fNONE) /* Try opening w/o added extension */
f = try_open_file(fc->path, fc->shortname, fc->ext, filetype_info(ft, 0), 0);
if (f == nullptr)
f = try_open_file(fc->path, fc->shortname, extname[ft], filetype_info(ft, 0), 0);
return f;
}
/*----------------------------------------------------------------------*/
/* File IO Routines */
/*----------------------------------------------------------------------*/
genfile readopen(fc_type fc, filetype ft, const char **errstr) {
genfile f;
*errstr = nullptr;
f = findread(fc, ft);
if (f == nullptr) {
*errstr = "Cannot open file";
}
return f;
}
rbool fileexist(fc_type fc, filetype ft) {
genfile f;
if (fc->special) return 0;
f = try_open_file(fc->path, fc->shortname, extname[ft], filetype_info(ft, 0), 1);
if (f != nullptr) { /* File already exists */
readclose(f);
return 1;
}
return 0;
}
genfile writeopen(fc_type fc, filetype ft,
file_id_type *pfileid, const char **errstr) {
char *name;
genfile f;
*errstr = nullptr;
name = nullptr;
{
name = assemble_filename(FC(fc)->path, FC(fc)->shortname, extname[ft]);
f = fopen(name, filetype_info(ft, 1));
}
if (f == nullptr) {
*errstr = "Cannot open file";
}
if (pfileid == nullptr)
rfree(name);
else
*pfileid = name;
return f;
}
rbool filevalid(genfile f, filetype ft) {
return (f != nullptr);
}
void binseek(genfile f, long offset) {
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
assert(rs);
rs->seek(offset);
}
/* This returns the number of bytes read, or 0 if there was an error. */
long varread(genfile f, void *buff, long recsize, long recnum, const char **errstr) {
long num;
*errstr = nullptr;
assert(f != nullptr);
num = fread(buff, recsize, recnum, f);
if (num != recnum)
*errstr = "varread";
num = num * recsize;
return num;
}
rbool binread(genfile f, void *buff, long recsize, long recnum, const char **errstr) {
long num;
num = varread(f, buff, recsize, recnum, errstr);
if (num < recsize * recnum && *errstr == nullptr)
*errstr = rstrdup("Unexpected end of file.");
return (*errstr == nullptr);
}
rbool binwrite(genfile f, void *buff, long recsize, long recnum, rbool ferr) {
assert(f != nullptr);
if (fwrite(buff, recsize, recnum, f) != (size_t)recnum) {
if (ferr) fatal("binwrite");
return 0;
}
return 1;
}
void readclose(genfile f) {
assert(f != nullptr);
fclose(f);
}
void writeclose(genfile f, file_id_type fileid) {
assert(f != nullptr);
rfree(fileid);
fclose(f);
}
long binsize(genfile f)
/* Returns the size of a binary file */
{
long pos, leng;
assert(f != nullptr);
pos = ftell(f);
fseek(f, 0, SEEK_END);
leng = ftell(f);
fseek(f, pos, SEEK_SET);
return leng;
}
rbool textrewind(genfile f) {
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
assert(rs);
rs->seek(0);
return 0;
}
genfile badfile(filetype ft) {
return nullptr;
}
} // End of namespace AGT
} // End of namespace Glk