diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index e9ce3f615..07921da69 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -28,13 +28,11 @@ #include #include #include "bsa_file.hpp" -#include namespace { using namespace Ogre; -using namespace Mangle::Stream; using namespace Bsa; struct ciLessBoost : std::binary_function @@ -239,10 +237,7 @@ public: // Open the file - StreamPtr strm = narc->getFile(passed.c_str()); - - // Wrap it into an Ogre::DataStream. - return DataStreamPtr(new Mangle2OgreStream(strm)); + return narc->getFile(passed.c_str()); } bool exists(const String& filename) { diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index f19606703..b5145a4e4 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -23,171 +23,235 @@ #include "bsa_file.hpp" -#include -#include - #include #include #include +#include + using namespace std; -using namespace Mangle::Stream; using namespace Bsa; +class ConstrainedDataStream : public Ogre::DataStream { + std::ifstream mStream; + const size_t mStart; + size_t mPos; + bool mIsEOF; + +public: + ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) + : mStream(fname.c_str(), std::ios_base::binary), mStart(start), mPos(0), mIsEOF(false) + { + mSize = length; + if(!mStream.seekg(mStart, std::ios_base::beg)) + throw std::runtime_error("Error seeking to start of BSA entry"); + } + + ConstrainedDataStream(const Ogre::String &name, const Ogre::String &fname, + size_t start, size_t length) + : Ogre::DataStream(name), mStream(fname.c_str(), std::ios_base::binary), + mStart(start), mPos(0), mIsEOF(false) + { + mSize = length; + if(!mStream.seekg(mStart, std::ios_base::beg)) + throw std::runtime_error("Error seeking to start of BSA entry"); + } + + + virtual size_t read(void *buf, size_t count) + { + mStream.clear(); + + if(count > mSize-mPos) + { + count = mSize-mPos; + mIsEOF = true; + } + mStream.read(reinterpret_cast(buf), count); + + count = mStream.gcount(); + mPos += count; + return count; + } + + virtual void skip(long count) + { + if((count >= 0 && (size_t)count <= mSize-mPos) || + (count < 0 && (size_t)-count <= mPos)) + { + mStream.clear(); + if(mStream.seekg(count, std::ios_base::cur)) + { + mPos += count; + mIsEOF = false; + } + } + } + + virtual void seek(size_t pos) + { + if(pos < mSize) + { + mStream.clear(); + if(mStream.seekg(pos+mStart, std::ios_base::beg)) + { + mPos = pos; + mIsEOF = false; + } + } + } + + virtual size_t tell() const + { return mPos; } + + virtual bool eof() const + { return mIsEOF; } + + virtual void close() + { mStream.close(); } +}; + + /// Error handling void BSAFile::fail(const string &msg) { - throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + filename); + throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + filename); } /// Read header information from the input source void BSAFile::readHeader() { - /* - * The layout of a BSA archive is as follows: - * - * - 12 bytes header, contains 3 ints: - * id number - equal to 0x100 - * dirsize - size of the directory block (see below) - * numfiles - number of files - * - * ---------- start of directory block ----------- - * - * - 8 bytes*numfiles, each record contains: - * fileSize - * offset into data buffer (see below) - * - * - 4 bytes*numfiles, each record is an offset into the following name buffer - * - * - name buffer, indexed by the previous table, each string is - * null-terminated. Size is (dirsize - 12*numfiles). - * - * ---------- end of directory block ------------- - * - * - 8*filenum - hash table block, we currently ignore this - * - * ----------- start of data buffer -------------- - * - * - The rest of the archive is file data, indexed by the - * offsets in the directory block. The offsets start at 0 at - * the beginning of this buffer. - * - */ - assert(!isLoaded); - assert(input); - assert(input->hasSize); - assert(input->hasPosition); - assert(input->isSeekable); - - // Total archive size - size_t fsize = input->size(); - - if( fsize < 12 ) - fail("File too small to be a valid BSA archive"); - - // Get essential header numbers - size_t dirsize, filenum; - - { - // First 12 bytes - uint32_t head[3]; - - input->read(head, 12); - - if(head[0] != 0x100) - fail("Unrecognized BSA header"); - - // Total number of bytes used in size/offset-table + filename - // sections. - dirsize = head[1]; - - // Number of files - filenum = head[2]; - } - - // Each file must take up at least 21 bytes of data in the bsa. So - // if files*21 overflows the file size then we are guaranteed that - // the archive is corrupt. - if( (filenum*21 > fsize -12) || - (dirsize+8*filenum > fsize -12) ) - fail("Directory information larger than entire archive"); - - // Read the offset info into a temporary buffer - vector offsets(3*filenum); - input->read(&offsets[0], 12*filenum); - - // Read the string table - stringBuf.resize(dirsize-12*filenum); - input->read(&stringBuf[0], stringBuf.size()); - - // Check our position - assert(input->tell() == 12+dirsize); - - // Calculate the offset of the data buffer. All file offsets are - // relative to this. 12 header bytes + directory + hash table - // (skipped) - size_t fileDataOffset = 12 + dirsize + 8*filenum; - - // Set up the the FileStruct table - files.resize(filenum); - for(size_t i=0;i(head), 12); + + if(head[0] != 0x100) + fail("Unrecognized BSA header"); + + // Total number of bytes used in size/offset-table + filename + // sections. + dirsize = head[1]; + + // Number of files + filenum = head[2]; + } + + // Each file must take up at least 21 bytes of data in the bsa. So + // if files*21 overflows the file size then we are guaranteed that + // the archive is corrupt. + if((filenum*21 > fsize -12) || (dirsize+8*filenum > fsize -12) ) + fail("Directory information larger than entire archive"); + + // Read the offset info into a temporary buffer + vector offsets(3*filenum); + input.read(reinterpret_cast(&offsets[0]), 12*filenum); - if(fs.offset + fs.fileSize > fsize) - fail("Archive contains offsets outside itself"); + // Read the string table + stringBuf.resize(dirsize-12*filenum); + input.read(&stringBuf[0], stringBuf.size()); - // Add the file name to the lookup - lookup[fs.name] = i; + // Check our position + assert(input.tellg() == 12+dirsize); + + // Calculate the offset of the data buffer. All file offsets are + // relative to this. 12 header bytes + directory + hash table + // (skipped) + size_t fileDataOffset = 12 + dirsize + 8*filenum; + + // Set up the the FileStruct table + files.resize(filenum); + for(size_t i=0;i fsize) + fail("Archive contains offsets outside itself"); + + // Add the file name to the lookup + lookup[fs.name] = i; } - isLoaded = true; + isLoaded = true; } /// Get the index of a given file name, or -1 if not found int BSAFile::getIndex(const char *str) const { - Lookup::const_iterator it; - it = lookup.find(str); + Lookup::const_iterator it = lookup.find(str); + if(it == lookup.end()) + return -1; - if(it == lookup.end()) return -1; - else - { - int res = it->second; - assert(res >= 0 && res < static_cast (files.size())); - return res; - } + int res = it->second; + assert(res >= 0 && (size_t)res < files.size()); + return res; } /// Open an archive file. void BSAFile::open(const string &file) { - filename = file; - input = StreamPtr(new FileStream(file)); - readHeader(); + filename = file; + readHeader(); } -/** Open an archive from a generic stream. The 'name' parameter is - used for error messages. -*/ -void BSAFile::open(StreamPtr inp, const string &name) +Ogre::DataStreamPtr BSAFile::getFile(const char *file) { - filename = name; - input = inp; - readHeader(); -} - -StreamPtr BSAFile::getFile(const char *file) -{ - assert(file); - int i = getIndex(file); - if(i == -1) - fail("File not found: " + string(file)); - - FileStruct &fs = files[i]; + assert(file); + int i = getIndex(file); + if(i == -1) + fail("File not found: " + string(file)); - return StreamPtr(new SliceStream(input, fs.offset, fs.fileSize)); + const FileStruct &fs = files[i]; + return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, fs.offset, fs.fileSize)); } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 95fac0f4d..afe0c739c 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -24,13 +24,15 @@ #ifndef BSA_BSA_FILE_H #define BSA_BSA_FILE_H -#include #include #include #include #include #include +#include + + namespace Bsa { @@ -39,98 +41,88 @@ namespace Bsa */ class BSAFile { - public: - - /// Represents one file entry in the archive - struct FileStruct - { - // File size and offset in file. We store the offset from the - // beginning of the file, not the offset into the data buffer - // (which is what is stored in the archive.) - uint32_t fileSize, offset; - - // Zero-terminated file name - char* name; - }; - - typedef std::vector FileList; - - private: - - /// The archive source - Mangle::Stream::StreamPtr input; - - /// Table of files in this archive - FileList files; - - /// Filename string buffer - std::vector stringBuf; - - /// True when an archive has been loaded - bool isLoaded; - - /// Used for error messages - std::string filename; - - /// Case insensitive string comparison - struct iltstr - { - bool operator()(const char *s1, const char *s2) const - { return strcasecmp(s1,s2) < 0; } - }; - - /** A map used for fast file name lookup. The value is the index into - the files[] vector above. The iltstr ensures that file name - checks are case insensitive. - */ - typedef std::map Lookup; - Lookup lookup; - - /// Error handling - void fail(const std::string &msg); - - /// Read header information from the input source - void readHeader(); - - /// Get the index of a given file name, or -1 if not found - int getIndex(const char *str) const; - - public: - - /* ----------------------------------- - * BSA management methods - * ----------------------------------- - */ - - BSAFile() - : input(), isLoaded(false) {} - - /// Open an archive file. - void open(const std::string &file); - - /** Open an archive from a generic stream. The 'name' parameter is - used for error messages. - */ - void open(Mangle::Stream::StreamPtr inp, const std::string &name); - - /* ----------------------------------- - * Archive file routines - * ----------------------------------- - */ - - /// Check if a file exists - bool exists(const char *file) const { return getIndex(file) != -1; } - - /** Open a file contained in the archive. Throws an exception if the - file doesn't exist. - - NOTE: All files opened from one archive will share a common file - handle. This is NOT thread safe. - */ - Mangle::Stream::StreamPtr getFile(const char *file); - - /// Get a list of all files - const FileList &getList() const +public: + /// Represents one file entry in the archive + struct FileStruct + { + // File size and offset in file. We store the offset from the + // beginning of the file, not the offset into the data buffer + // (which is what is stored in the archive.) + uint32_t fileSize, offset; + + // Zero-terminated file name + const char *name; + }; + typedef std::vector FileList; + +private: + /// Table of files in this archive + FileList files; + + /// Filename string buffer + std::vector stringBuf; + + /// True when an archive has been loaded + bool isLoaded; + + /// Used for error messages + std::string filename; + + /// Case insensitive string comparison + struct iltstr + { + bool operator()(const char *s1, const char *s2) const + { return strcasecmp(s1,s2) < 0; } + }; + + /** A map used for fast file name lookup. The value is the index into + the files[] vector above. The iltstr ensures that file name + checks are case insensitive. + */ + typedef std::map Lookup; + Lookup lookup; + + /// Error handling + void fail(const std::string &msg); + + /// Read header information from the input source + void readHeader(); + + /// Get the index of a given file name, or -1 if not found + int getIndex(const char *str) const; + +public: + /* ----------------------------------- + * BSA management methods + * ----------------------------------- + */ + + BSAFile() + : isLoaded(false) + { } + + /// Open an archive file. + void open(const std::string &file); + + /* ----------------------------------- + * Archive file routines + * ----------------------------------- + */ + + /// Check if a file exists + bool exists(const char *file) const + { return getIndex(file) != -1; } + + /** Open a file contained in the archive. Throws an exception if the + file doesn't exist. + + NOTE: All files opened from one archive will share a common file + handle. This is NOT thread safe. + */ + Ogre::DataStreamPtr getFile(const char *file); + + /// Get a list of all files + const FileList &getList() const { return files; } };