diff options
Diffstat (limited to 'backends/ps2/fileio.cpp')
-rw-r--r-- | backends/ps2/fileio.cpp | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/backends/ps2/fileio.cpp b/backends/ps2/fileio.cpp new file mode 100644 index 0000000000..797eda7595 --- /dev/null +++ b/backends/ps2/fileio.cpp @@ -0,0 +1,718 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2005 The ScummVM project + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + * + */ + +#include "backends/ps2/fileio.h" + +#include <tamtypes.h> +#include <kernel.h> +#include <fileio.h> +#include <assert.h> +#include <string.h> +//#include <fileXio_rpc.h> +#include <cdvd_rpc.h> +#include "backends/ps2/asyncfio.h" +#include "base/engine.h" +#include "common/file.h" + +#define CACHE_BUF_SIZE (2048 * 16) +#define MAX_CACHED_FILES 8 + +//#define DEFAULT_MODE (FIO_S_IRUSR | FIO_S_IWUSR | FIO_S_IRGRP | FIO_S_IWGRP | FIO_S_IROTH | FIO_S_IWOTH) + +extern void sioprintf(const char *zFormat, ...); + +AsyncFio fio; + +AccessFio::AccessFio(void) { + _handle = -1; +} + +AccessFio::~AccessFio(void) { + if (_handle >= 0) + fio.close(_handle); +} + +int32 AccessFio::sync(void) { + return fio.sync(_handle); +} + +bool AccessFio::poll(void) { + return fio.poll(_handle); +} + +bool AccessFio::fioAvail(void) { + return fio.fioAvail(); +} + +bool AccessFio::open(const char *name, int mode) { + _handle = fio.open(name, mode); + return (_handle >= 0); +} + +void AccessFio::read(void *dest, uint32 size) { + fio.read(_handle, dest, size); +} + +void AccessFio::write(const void *src, uint32 size) { + fio.write(_handle, src, size); +} + +int AccessFio::seek(int32 offset, int whence) { + return fio.seek(_handle, offset, whence); +} + +/*class AccessFioX : public AccessFio{ +public: + AccessFioX(void); + virtual ~AccessFioX(void); + virtual bool open(const char *name, int mode); + virtual int read(void *dest, uint32 size); + virtual int write(const void *src, uint32 size); + virtual int seek(int32 offset, int whence); + virtual void sync(int32 *res); +}; + +AccessFioX::AccessFioX(void) { + _handle = -1; +} + +AccessFioX::~AccessFioX(void) { + if (_handle >= 0) + fileXioClose(_handle); +} + +void AccessFioX::sync(int32 *res) { + assert(false); +} + +bool AccessFioX::open(const char *name, int mode) { + _handle = fileXioOpen(name, mode, DEFAULT_MODE); + return (_handle >= 0); +} + +int AccessFioX::read(void *dest, uint32 size) { + return fileXioRead(_handle, (unsigned char*)dest, size); +} + +int AccessFioX::write(const void *src, uint32 size) { + return fileXioWrite(_handle, (unsigned char*)src, size); +} + +int AccessFioX::seek(int32 offset, int whence) { + return fileXioLseek(_handle, offset, whence); +}*/ + +struct TocNode { + char name[64]; + TocNode *next, *sub; + bool isDir; + uint8 nameLen; +}; + +class TocManager { +public: + TocManager(void); + ~TocManager(void); + void readEntries(const char *root); + int64 fileExists(const char *name); + bool haveEntries(void); +private: + void readDir(const char *path, TocNode **node, int level); + TocNode *_rootNode; + char _root[256]; + uint8 _rootLen; +}; + +class Ps2File { +public: + Ps2File(int64 cacheId); + ~Ps2File(void); + bool open(const char *name, int ioMode); + bool isOpen(void); + uint32 read(void *dest, uint32 size); + uint32 write(const void *src, uint32 size); + uint32 tell(void); + uint32 size(void); + int seek(int32 offset, int origin); + bool eof(void); + AccessFio *giveHandle(void); + int64 _cacheId; + void setSeekReset(void); +private: + void checkCache(void); + void syncCache(void); + bool _cacheOp; + bool _seekReset; + + bool _forWriting; + AccessFio *_handle; + uint8 *_cacheBuf, *_cachePos; + uint32 _filePos; + uint32 _cacheBytesLeft; + uint32 _fSize; + uint32 _readBytesBlock; +}; + +Ps2File::Ps2File(int64 cacheId) { + _handle = NULL; + _cachePos = _cacheBuf = (uint8*)malloc(CACHE_BUF_SIZE); + _fSize = _filePos = _cacheBytesLeft = 0; + _forWriting = false; + _readBytesBlock = 0; + _cacheOp = false; + _cacheId = cacheId; + _seekReset = false; +} + +Ps2File::~Ps2File(void) { + if (_handle != NULL) { + syncCache(); + if (_forWriting && (_cachePos != _cacheBuf)) { + _handle->write(_cacheBuf, _cachePos - _cacheBuf); + int res = _handle->sync(); + if (res != (_cachePos - _cacheBuf)) { + // Fixme: writing operation failed and we noticed + // too late to return an error to the engine. + printf("ERROR: flushing the cache on fclose() failed!\n"); + } + } + delete _handle; + } + free(_cacheBuf); +} + +bool Ps2File::open(const char *name, int ioMode) { + //if (strncmp(name, "pfs0", 4) == 0) + // _handle = new AccessFioX(); + //else + _handle = new AccessFio(); + + if (_handle->open(name, ioMode)) { + if (ioMode == O_RDONLY) { + _fSize = _handle->seek(0, SEEK_END); + _handle->seek(0, SEEK_SET); + } else { + _cacheBytesLeft = CACHE_BUF_SIZE; + _forWriting = true; + } + return true; + } else { + delete _handle; + _handle = NULL; + return false; + } +} + +void Ps2File::setSeekReset(void) { + _seekReset = true; +} + +bool Ps2File::isOpen(void) { + return (_handle != NULL); +} + +int Ps2File::seek(int32 offset, int origin) { + assert(!_forWriting); + syncCache(); + uint32 seekDest; + switch (origin) { + case SEEK_SET: + seekDest = offset; + break; + case SEEK_CUR: + if (_seekReset) + seekDest = offset; + else + seekDest = _filePos + offset; + break; + case SEEK_END: + seekDest = _fSize + offset; + break; + default: + return -1; + } + _seekReset = false; + if (seekDest <= _fSize) { + if ((seekDest >= _filePos) && (seekDest < _filePos + _cacheBytesLeft)) { + uint32 cacheOffset = (seekDest - _filePos); + _cacheBytesLeft -= cacheOffset; + _cachePos += cacheOffset; + } else { + _handle->seek(seekDest, SEEK_SET); + _cacheBytesLeft = 0; + } + _filePos = seekDest; + _readBytesBlock = 0; + return 0; + } else + return -1; +} + +bool Ps2File::eof(void) { + if ((!_forWriting) && (_filePos == _fSize)) + return true; + else + return false; +} + +void Ps2File::checkCache(void) { + if (!_forWriting) { + if (_readBytesBlock > 32768) { + if (_cacheBytesLeft <= (CACHE_BUF_SIZE / 4)) { + if (_cacheBytesLeft && (_cachePos != _cacheBuf)) { + memmove(_cacheBuf, _cachePos, _cacheBytesLeft); + } + _cachePos = _cacheBuf; + _handle->read(_cacheBuf + _cacheBytesLeft, CACHE_BUF_SIZE - _cacheBytesLeft); + _cacheOp = true; + } + } + } +} + +void Ps2File::syncCache(void) { + if ((!_forWriting) && _cacheOp) { + int cacheRes = _handle->sync(); + assert(cacheRes >= 0); + _cacheBytesLeft += cacheRes; + _cacheOp = false; + } +} + +uint32 Ps2File::read(void *dest, uint32 size) { + assert(!_forWriting); + syncCache(); + if (_seekReset) + seek(0, SEEK_SET); + _readBytesBlock += size; + + uint8 *destBuf = (uint8*)dest; + while (size) { + if (_cacheBytesLeft != 0) { + uint32 doCopy = (size >= _cacheBytesLeft) ? _cacheBytesLeft : size; + memcpy(destBuf, _cachePos, doCopy); + size -= doCopy; + _cacheBytesLeft -= doCopy; + _cachePos += doCopy; + destBuf += doCopy; + _filePos += doCopy; + } + if (size > 0) { + assert(_cacheBytesLeft == 0); + if (size >= CACHE_BUF_SIZE) { + int readRes; + do { + _handle->read(destBuf, size); + readRes = _handle->sync(); + _filePos += readRes; + destBuf += readRes; + size -= readRes; + } while (size && readRes); + if (size) + printf("read operation failed, %d bytes left to read\n", size); + return destBuf - (uint8*)dest; + } else { + uint32 doRead = size; + if ((doRead < 2048) && (_cacheId >= 0)) + doRead = 2048; + memset(_cacheBuf, 'A', 0x20); + _handle->read(_cacheBuf, doRead); + _cacheBytesLeft = _handle->sync(); + _cachePos = _cacheBuf; + if (_cacheBytesLeft == 0) { + return destBuf - (uint8*)dest; + } + } + } + } + checkCache(); + return destBuf - (uint8*)dest; +} + +uint32 Ps2File::write(const void *src, uint32 size) { + assert(_forWriting && (!_seekReset)); + const uint8 *srcBuf = (const uint8*)src; + + while (size) { + uint32 doWrite = (size > _cacheBytesLeft) ? _cacheBytesLeft : size; + memcpy(_cachePos, srcBuf, doWrite); + _cachePos += doWrite; + _cacheBytesLeft -= doWrite; + size -= doWrite; + srcBuf += doWrite; + _filePos += doWrite; + + if (_cacheBytesLeft == 0) { + _handle->write(_cacheBuf, CACHE_BUF_SIZE); + int res = _handle->sync(); + if (res == CACHE_BUF_SIZE) { + _cachePos = _cacheBuf; + _cacheBytesLeft = CACHE_BUF_SIZE; + } else { + printf("cache write operation failed, only %d bytes written\n", res); + return 0; + } + + if (size >= CACHE_BUF_SIZE) { + int writeRes; + do { + _handle->write(srcBuf, size); + writeRes = _handle->sync(); + _filePos += writeRes; + srcBuf += writeRes; + size -= writeRes; + } while (size && writeRes); + if (size) + printf("write operation failed, %d bytes left to write\n", size); + return srcBuf - (uint8*)src; + } + } + } + return srcBuf - (uint8*)src; +} + +uint32 Ps2File::tell(void) { + if (_seekReset) + seek(0, SEEK_SET); + return _filePos; +} + +uint32 Ps2File::size(void) { + assert(!_forWriting); + return _fSize; +} + +AccessFio *Ps2File::giveHandle(void) { + assert(_handle); + return _handle; +} + +TocManager tocManager; + +struct FioHandleCache { + Ps2File *file; + FioHandleCache *next, *prev; +}; + +static FioHandleCache *cacheListStart = NULL; +static FioHandleCache *cacheListEnd = NULL; +static int cacheListLen = 0; +static int cacheListSema = -1; +static bool wantHandleCaching = true; + +extern void ps2_disableHandleCaching(void) { + wantHandleCaching = false; +} + +Ps2File *findInCache(int64 id); + +FILE *ps2_fopen(const char *fname, const char *mode) { + if (cacheListSema == -1) { + ee_sema_t newSema; + newSema.init_count = 1; + newSema.max_count = 1; + cacheListSema = CreateSema(&newSema); + assert(cacheListSema >= 0); + } + if (!tocManager.haveEntries() && g_engine) // read the TOC the first time the engine opens a file + tocManager.readEntries(g_engine->getGameDataPath()); + + int fioMode = 0; + switch(*mode) { + case 'r': + fioMode = O_RDONLY; + break; + case 'w': + fioMode = O_WRONLY | O_CREAT | O_TRUNC; + break; + default: + printf("unsupported mode \"%s\" for file \"%s\"\n", mode, fname); + return NULL; + } + int64 cacheId = -1; + if ((fioMode == O_RDONLY) && tocManager.haveEntries()) + cacheId = tocManager.fileExists(fname); + + if (cacheId != 0) { + Ps2File *file = findInCache(cacheId); + if (file) + return (FILE*)file; + + if ((mode[1] != 'b') && (mode[1] != '\0')) { + printf("unsupported mode \"%s\" for file \"%s\"\n", mode, fname); + return NULL; + } + file = new Ps2File(cacheId); + if (file->open(fname, fioMode)) + return (FILE*)file; + else + delete file; + } + return NULL; +} + +void checkCacheListLen(void) { + while (cacheListLen > MAX_CACHED_FILES) { + assert(cacheListEnd && cacheListStart); + delete cacheListEnd->file; + cacheListEnd->prev->next = NULL; + FioHandleCache *temp = cacheListEnd; + cacheListEnd = cacheListEnd->prev; + delete temp; + cacheListLen--; + } +} + +int ps2_fclose(FILE *stream) { + Ps2File *file = (Ps2File*)stream; + if ((file->_cacheId > 0) && wantHandleCaching) { // this is a file on the CD, could be smart to cache it + FioHandleCache *newHandle = new FioHandleCache; + newHandle->file = file; + file->setSeekReset(); + + WaitSema(cacheListSema); + if (!cacheListEnd) { + newHandle->prev = newHandle->next = NULL; + cacheListEnd = cacheListStart = newHandle; + } else { + newHandle->prev = NULL; + newHandle->next = cacheListStart; + cacheListStart->prev = newHandle; + cacheListStart = newHandle; + } + cacheListLen++; + checkCacheListLen(); + SignalSema(cacheListSema); + } else + delete file; + return 0; +} + +Ps2File *findInCache(int64 id) { + if ((id <= 0) || !wantHandleCaching) + return NULL; + WaitSema(cacheListSema); + FioHandleCache *node = cacheListStart; + while (node) { + if (node->file->_cacheId == id) { + if (node == cacheListStart) + cacheListStart = node->next; + if (node == cacheListEnd) + cacheListEnd = node->prev; + if (node->prev) + node->prev->next = node->next; + if (node->next) + node->next->prev = node->prev; + Ps2File *ret = node->file; + delete node; + cacheListLen--; + SignalSema(cacheListSema); + return ret; + } else + node = node->next; + } + SignalSema(cacheListSema); + return NULL; +} + +int ps2_fseek(FILE *stream, long offset, int origin) { + return ((Ps2File*)stream)->seek(offset, origin); +} + +long ps2_ftell(FILE *stream) { + return ((Ps2File*)stream)->tell(); +} + +long ps2_fsize(FILE *stream) { + return ((Ps2File*)stream)->size(); +} + +int ps2_feof(FILE *stream) { + return ((Ps2File*)stream)->eof(); +} + +size_t ps2_fread(void *buf, size_t r, size_t n, FILE *stream) { + assert(r != 0); + return ((Ps2File*)stream)->read(buf, r * n) / r; +} + +int ps2_fgetc(FILE *stream) { + uint8 temp; + if (((Ps2File*)stream)->read(&temp, 1)) + return temp; + else + return EOF; +} + +char *ps2_fgets(char *buf, int n, FILE *stream) { + char *retVal = buf; + while (n--) { + if (n == 0) + *buf = '\0'; + else { + char c = ps2_fgetc(stream); + if (c == EOF) + return NULL; + if ((c == '\r') || (c == '\n')) { + *buf++ = '\0'; + return retVal; + } + *buf++ = c; + } + } + return retVal; +} + +int ps2_fprintf(FILE *pOut, const char *zFormat, ...) { + va_list ap; + char resStr[2048]; + + va_start(ap,zFormat); + int res = vsnprintf(resStr, 2048, zFormat, ap); + va_end(ap); + if ((pOut == stderr) || (pOut == stdout)) { + printf("%s", resStr); + sioprintf("%s", resStr); + } else + res = ps2_fwrite(resStr, 1, res, pOut); + return res; +} + +size_t ps2_fwrite(const void *buf, size_t r, size_t n, FILE *stream) { + assert(r != 0); + return ((Ps2File*)stream)->write(buf, r * n) / r; +} + +int ps2_fputc(int c, FILE *stream) { + if (((Ps2File*)stream)->write(&c, 1) == 1) + return c; + else + return -1; +} + +int ps2_fputs(const char *s, FILE *stream) { + int len = strlen(s); + if (ps2_fwrite(s, 1, len, stream) == len) + return len; + else + return EOF; +} + +int ps2_fflush(FILE *stream) { + printf("fflush not implemented\n"); + return 0; +} + +TocManager::TocManager(void) { + _rootNode = NULL; +} + +TocManager::~TocManager(void) { + // todo: write this... +} + +bool TocManager::haveEntries(void) { + return _rootNode != NULL; +} + +void TocManager::readEntries(const char *root) { + _rootLen = strlen(root); + strcpy(_root, root); + if (_root[_rootLen - 1] == '/') { + _rootLen--; + _root[_rootLen] = '\0'; + } + readDir(_root, &_rootNode, 0); +} + +#define MAX_DIR_ENTRIES 512 + +void TocManager::readDir(const char *path, TocNode **node, int level) { + if (level <= 2) { // we don't scan deeper than that + struct TocEntry tocEntries[MAX_DIR_ENTRIES]; + int files = CDVD_GetDir(path + 5, NULL, CDVD_GET_FILES_AND_DIRS, tocEntries, MAX_DIR_ENTRIES, NULL); + + for (int cnt = 0; cnt < files; cnt++) { + if (tocEntries[cnt].filename[0] != '.') { // skip '.' and '..' + *node = new TocNode; + (*node)->sub = (*node)->next = NULL; + + (*node)->nameLen = strlen(tocEntries[cnt].filename); + memcpy((*node)->name, tocEntries[cnt].filename, (*node)->nameLen + 1); + + if (tocEntries[cnt].fileProperties & 2) { // directory + (*node)->isDir = true; + char nextPath[256]; + sprintf(nextPath, "%s/%s", path, tocEntries[cnt].filename); + readDir(nextPath, &((*node)->sub), level + 1); + } else + (*node)->isDir = false; + node = &((*node)->next); + } + } + } +} + +int64 TocManager::fileExists(const char *name) { + const char *tmpName = name; + + if (((name[_rootLen] != '/') && (name[_rootLen] != '\0')) || (strnicmp(name, _root, _rootLen) != 0)) { + for (int i = 0; i < 8; i++) + if (name[i] == ':') // we don't know the content of other drives, + return -1; // assume file exists + else if ((name[i] == '/') || (name[i] == '\\')) + return 0; // does not exists (this is probably ScummVM trying the 'current directory'.) + return 0; + } + + uint8 nameLen = strlen(name); + + name += _rootLen + 1; + nameLen -= _rootLen + 1; + + TocNode *node = _rootNode; + int64 retId = 1; + while (node) { + if (((name[node->nameLen] == '/') || (name[node->nameLen] == '\0')) && (strnicmp(name, node->name, node->nameLen) == 0)) { + name += node->nameLen; + nameLen -= node->nameLen; + if (node->isDir) { + if (nameLen) { + name++; // skip '/' + nameLen--; + node = node->sub; + retId <<= 10; + } else + return 0; // can't open a directory with fopen() + } else { + if (nameLen == 0) + return retId; // ok, found + else + return 0; // here's a file, but there's something left in the path + } + } else { + node = node->next; + retId++; + } + } + return 0; // does not exist +} + |