/* ScummVM Tools * * ScummVM Tools 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. * * Additionally this file is based on the ScummVM source code. * Copyright information for the ScummVM source code is * available in the COPYRIGHT file of the ScummVM 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 . * */ #include "file.h" #include "common/str.h" #include #include #include #include #include #include // for stat() #include #ifndef _MSC_VER #include // for unlink() #else // Add a definition for S_IFDIR for MSVC #ifndef S_ISDIR #ifndef S_IFDIR #ifndef _S_IFDIR #define S_IFDIR (-1) #else #define S_IFDIR _S_IFDIR #endif #endif #define S_ISDIR(m) (((m)&S_IFDIR)==S_IFDIR) #endif #endif namespace Common { // Filenname implementation Filename::Filename() { } Filename::Filename(const char *path) : _path(path) { postInitWithString(); } Filename::Filename(const std::string &path) : _path(path) { postInitWithString(); } Filename::Filename(const Common::String &path) : _path(path.c_str()) { postInitWithString(); } void Filename::postInitWithString() { // If this is a directory append '/' at the end if needed. if (!_path.empty() && _path[_path.size() - 1] != '/' && _path[_path.size() - 1] != '\\' && isDirectory(_path.c_str())) _path += '/'; } Filename::Filename(const Filename& filename) : _path(filename._path) { } Filename& Filename::operator=(const Filename& filename) { _path = filename._path; return *this; } void Filename::setFullPath(const std::string &path) { _path = path; // If this is a directory append '/' at the end if needed. if (!_path.empty() && _path[_path.size() - 1] != '/' && _path[_path.size() - 1] != '\\' && isDirectory(_path.c_str())) _path += '/'; } void Filename::setFullName(const std::string &newname) { _path = getPath() + newname; } void Filename::addExtension(const std::string &ext) { _path += ext; } void Filename::setExtension(const std::string &ext) { // FIXME: quick and dirty hack. But then so is this whole class... std::string path = getPath(); std::string name = getName(); _path = path + name + ext; } bool Filename::equals(const Filename &other) const { #ifdef _WIN32 // On Windows paths are case-insensitive return scumm_stricmp(_path.c_str(), other._path.c_str()) == 0; #else return _path == other._path; #endif } bool Filename::empty() const { return _path.empty(); } bool Filename::directory() const { return getFullName().size() == 0; } bool Filename::exists() const { // This fails if we don't have permission to read the file // but in most cases, that's the same thing for us. FILE *f = fopen(_path.c_str(), "r"); if (!f) { std::string fixedPath = fixPathCase(_path); f = fopen(_path.c_str(), "r"); } if (f) { fclose(f); return true; } return false; } bool Filename::hasExtension(std::string ext) const { size_t dot = _path.rfind('.'); if (dot == std::string::npos) return false; // Check that dot position is less than /, since some // directories contain ., like /home/.data/file size_t slash = _path.rfind('/'); if (slash != std::string::npos) if (slash > dot) return false; slash = _path.rfind('\\'); if (slash != std::string::npos) if (slash > dot) return false; // We compare extensions, skip any dots if (_path[dot] == '.') dot++; if (ext[0] == '.') ext = ext.substr(1); std::string tmp = _path.substr(dot); // On Windows paths are case-insensitive return scumm_stricmp(tmp.c_str(), ext.c_str()) == 0; } std::string Filename::getFullPath() const { return _path; } std::string Filename::getFullName() const { size_t slash = _path.rfind('/'); if (slash == std::string::npos) slash = _path.rfind('\\'); if (slash == std::string::npos) return _path; return _path.substr(slash + 1); } std::string Filename::getName() const { size_t slash = _path.rfind('/'); size_t dot = _path.rfind('.'); if (slash == std::string::npos) slash = _path.rfind('\\'); if (dot == std::string::npos) dot = _path.size(); if (slash == std::string::npos) return _path.substr(0, dot); if (dot < slash) dot = _path.size(); return _path.substr(slash + 1, dot - slash - 1); } std::string Filename::getExtension() const { size_t slash = _path.rfind('/'); size_t dot = _path.rfind('.'); if (slash == std::string::npos) slash = _path.rfind('\\'); if (slash == std::string::npos) slash = 0; if (dot == std::string::npos) return ""; if (dot < slash) return ""; return _path.substr(dot + 1); } std::string Filename::getPath() const { size_t slash = _path.rfind('/'); if (slash == std::string::npos) slash = _path.rfind('\\'); if (slash == std::string::npos) return ""; return _path.substr(0, slash + 1); } // File interface // While this does massive duplication of the code above, it's required to make sure that // unconverted tools are backwards-compatible File::File() { _file = NULL; _mode = FILEMODE_READ; _xormode = 0; } File::File(const Filename &filepath, const char *mode) { _file = NULL; _mode = FILEMODE_READ; _xormode = 0; open(filepath, mode); } File::~File() { close(); } void File::open(const Filename &filepath, const char *mode) { // Clean up previously opened file close(); _file = fopen(filepath.getFullPath().c_str(), mode); if (!_file) { std::string fixedPath = fixPathCase(filepath.getFullPath()); _file = fopen(fixedPath.c_str(), mode); } FileMode m = FILEMODE_READ; do { switch(*mode) { case 'w': m = FILEMODE_WRITE; break; case 'r': m = FILEMODE_READ; break; case 'b': m = FileMode(m | FILEMODE_BINARY); break; case '+': m = FileMode(m | FILEMODE_READ | FILEMODE_WRITE); break; default: throw FileException(std::string("Unsupported FileMode ") + mode); } } while (*++mode); _mode = m; _name = filepath; _xormode = 0; if (!_file) throw FileException("Could not open file " + filepath.getFullPath()); } void File::close() { if (_file) fclose(_file); _file = NULL; } void File::setXorMode(uint8 xormode) { _xormode = xormode; } int File::readChar() { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_READ) == 0) throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")"); int u8 = fgetc(_file); if (u8 == EOF) throw FileException("Read beyond the end of file (" + _name.getFullPath() + ")"); u8 ^= _xormode; return u8; } uint8 File::readByte() { int u8 = readChar(); return (uint8)u8; } uint16 File::readUint16BE() { uint16 ret = 0; ret |= uint16(readByte() << 8ul); ret |= uint16(readByte()); return ret; } uint16 File::readUint16LE() { uint16 ret = 0; ret |= uint16(readByte()); ret |= uint16(readByte() << 8ul); return ret; } uint32 File::readUint32BE() { uint32 ret = 0; ret |= uint32(readByte() << 24); ret |= uint32(readByte() << 16); ret |= uint32(readByte() << 8); ret |= uint32(readByte()); return ret; } uint32 File::readUint32LE() { uint32 ret = 0; ret |= uint32(readByte()); ret |= uint32(readByte() << 8); ret |= uint32(readByte() << 16); ret |= uint32(readByte() << 24); return ret; } int16 File::readSint16BE() { int16 ret = 0; ret |= int16(readByte() << 8ul); ret |= int16(readByte()); return ret; } int16 File::readSint16LE() { int16 ret = 0; ret |= int16(readByte()); ret |= int16(readByte() << 8ul); return ret; } int32 File::readSint32BE() { int32 ret = 0; ret |= int32(readByte() << 24); ret |= int32(readByte() << 16); ret |= int32(readByte() << 8); ret |= int32(readByte()); return ret; } int32 File::readSint32LE() { int32 ret = 0; ret |= int32(readByte()); ret |= int32(readByte() << 8); ret |= int32(readByte() << 16); ret |= int32(readByte() << 24); return ret; } void File::read_throwsOnError(void *dataPtr, size_t dataSize) { size_t data_read = read_noThrow(dataPtr, dataSize); if (data_read != dataSize) throw FileException("Read beyond the end of file (" + _name.getFullPath() + ")"); } size_t File::read_noThrow(void *dataPtr, size_t dataSize) { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_READ) == 0) throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")"); return fread(dataPtr, 1, dataSize, _file); } std::string File::readString() { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_READ) == 0) throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")"); std::string s; try { char c; while ((c = readByte())) { s += c; } } catch (FileException &) { // pass, we reached EOF } return s; } std::string File::readString(size_t len) { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_READ) == 0) throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")"); std::string s('\0', len); std::string::iterator is = s.begin(); char c; while ((c = readByte())) { *is = c; } return s; } void File::scanString(char *result) { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_READ) == 0) throw FileException("Tried to write to file opened in read mode (" + _name.getFullPath() + ")"); fscanf(_file, "%s", result); } void File::writeChar(char i) { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_WRITE) == 0) throw FileException("Tried to write to a file opened in read mode (" + _name.getFullPath() + ")"); i ^= _xormode; if (fwrite(&i, 1, 1, _file) != 1) throw FileException("Could not write to file (" + _name.getFullPath() + ")"); } void File::writeByte(uint8 b) { writeChar(b); } void File::writeUint16BE(uint16 value) { writeByte((uint8)(value >> 8)); writeByte((uint8)(value)); } void File::writeUint16LE(uint16 value) { writeByte((uint8)(value)); writeByte((uint8)(value >> 8)); } void File::writeUint32BE(uint32 value) { writeByte((uint8)(value >> 24)); writeByte((uint8)(value >> 16)); writeByte((uint8)(value >> 8)); writeByte((uint8)(value)); } void File::writeUint32LE(uint32 value) { writeByte((uint8)(value)); writeByte((uint8)(value >> 8)); writeByte((uint8)(value >> 16)); writeByte((uint8)(value >> 24)); } size_t File::write(const void *dataPtr, size_t dataSize) { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_WRITE) == 0) throw FileException("Tried to write to file opened in read mode (" + _name.getFullPath() + ")"); assert(_xormode == 0); // FIXME: This method does not work in XOR mode (and probably shouldn't) size_t data_read = fwrite(dataPtr, 1, dataSize, _file); if (data_read != dataSize) throw FileException("Could not write to file (" + _name.getFullPath() + ")"); return data_read; } void File::print(const char *format, ...) { if (!_file) throw FileException("File is not open"); if ((_mode & FILEMODE_WRITE) == 0) throw FileException("Tried to write to file opened in read mode (" + _name.getFullPath() + ")"); va_list va; va_start(va, format); vfprintf(_file, format, va); va_end(va); } void File::seek(long offset, int origin) { if (!_file) throw FileException("File is not open"); if (fseek(_file, offset, origin) != 0) throw FileException("Could not seek in file (" + _name.getFullPath() + ")"); } void File::rewind() { return ::rewind(_file); } int File::pos() const { return ftell(_file); } int File::err() const { return ferror(_file); } void File::clearErr() { clearerr(_file); } bool File::eos() const { return feof(_file) != 0; } uint32 File::size() const { uint32 sz; uint32 p = ftell(_file); fseek(_file, 0, SEEK_END); sz = ftell(_file); fseek(_file, p, SEEK_SET); return sz; } int removeFile(const char *path) { return unlink(path); } bool isDirectory(const char *path) { struct stat st; if (stat(path, &st) == 0) { return S_ISDIR(st.st_mode); } // Try to fix the case std::string fixedPath = fixPathCase(path); return stat(fixedPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode); } std::string fixPathCase(const std::string& originalPath) { std::string result = originalPath; std::deque parts; struct stat st; // Remove the last part of the path until we get to a path that exists while (stat(result.c_str(), &st) != 0) { size_t slash = result.rfind('/', result.size() - 2); if (slash == std::string::npos) slash = result.rfind('\\', result.size() - 2); if (slash == std::string::npos) { parts.push_back(result); result.clear(); break; } else { parts.push_back(result.substr(slash + 1)); result.erase(slash + 1); if (slash == 0) break; } } // Reconstruct the path by changing the case while (!parts.empty()) { std::string directory = parts.back(); // Try first original case, then all lower case and finally all upper case std::string path = result + directory; if (stat(path.c_str(), &st) == 0) { result = path; } else { std::transform(directory.begin(), directory.end(), directory.begin(), ::tolower); path = result + directory; if (stat(path.c_str(), &st) == 0) { result = path; } else { std::transform(directory.begin(), directory.end(), directory.begin(), ::toupper); path = result + directory; if (stat(path.c_str(), &st) == 0) { result = path; } else { // Does not exists whatever the case used. // Add back all the remaining parts and return. while (!parts.empty()) { result += parts.back(); parts.pop_back(); } return result; } } } parts.pop_back(); } return result; } } // End of namespace Common