diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..5cf974020 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mangle"] + path = mangle + url = git://github.com/korslund/mangle.git diff --git a/bsa/bsa_archive.cpp b/bsa/bsa_archive.cpp index 086c23ea2..34c2bd85b 100644 --- a/bsa/bsa_archive.cpp +++ b/bsa/bsa_archive.cpp @@ -32,8 +32,7 @@ #include #include "bsa_file.h" -// This archive does not cover one .bsa file, instead it interacts -// with the all the loaded bsas and treates them as one archive. +/// An OGRE Archive wrapping a BSAFile archive class BSAArchive : public Archive { BSAFile arc; @@ -43,7 +42,7 @@ public: : Archive(name, "BSA") { arc.open(name); } - bool isCaseSensitive(void) const { return false; } + bool isCaseSensitive() const { return false; } // The archive is loaded in the constructor, and never unloaded. void load() {} @@ -145,6 +144,4 @@ public: void destroyInstance( Archive* arch) { delete arch; } }; -BSAArchiveFactory mBSAFactory; - #endif diff --git a/bsa/bsa_file.cpp b/bsa/bsa_file.cpp new file mode 100644 index 000000000..bc0a9338c --- /dev/null +++ b/bsa/bsa_file.cpp @@ -0,0 +1,194 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (bsa_file.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + 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 + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + */ + +#include "bsa_file.h" + +#include "../mangle/stream/servers/file_stream.h" +#include "../mangle/stream/filters/slice_stream.h" + +#include +#include + +#include "../tools/str_exception.h" + +using namespace std; +using namespace Mangle::Stream; + +/// Error handling +void BSAFile::fail(const string &msg) +{ + throw str_exception("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; + offsets.resize(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(int i=0;i fsize) + fail("Archive contains offsets outside itself"); + + // Add the file name to the lookup + lookup[fs.name] = i; + } + + isLoaded = true; +} + +/// Get the index of a given file name, or -1 if not found +int BSAFile::getIndex(const char *str) +{ + Lookup::iterator it; + it = lookup.find(str); + + if(it == lookup.end()) return -1; + else + { + int res = it->second; + assert(res >= 0 && res < files.size()); + return res; + } +} + +/// Open an archive file. +void BSAFile::open(const string &file) +{ + filename = file; + input = StreamPtr(new FileStream(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) +{ + 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]; + + return StreamPtr(new SliceStream(input, fs.offset, fs.fileSize)); +} diff --git a/bsa/bsa_file.h b/bsa/bsa_file.h index 52a354afb..9ecd3e6b0 100644 --- a/bsa/bsa_file.h +++ b/bsa/bsa_file.h @@ -24,24 +24,20 @@ #ifndef _BSA_FILE_H_ #define _BSA_FILE_H_ -#include "mangle/stream/servers/file_stream.h" -#include "mangle/stream/filters/slice_stream.h" +#include "../mangle/stream/stream.h" #include -#include #include -#include +#include #include #include -#include "../tools/str_exception.h" - /** This class is used to read "Bethesda Archive Files", or BSAs. */ class BSAFile { - private: + public: /// Represents one file entry in the archive struct FileStruct @@ -55,11 +51,15 @@ class BSAFile char* name; }; + typedef std::vector FileList; + + private: + /// The archive source - Mangle::Stream::Stream *input; + Mangle::Stream::StreamPtr input; /// Table of files in this archive - std::vector files; + FileList files; /// Filename string buffer std::vector stringBuf; @@ -85,133 +85,13 @@ class BSAFile Lookup lookup; /// Error handling - void fail(const std::string &msg) - { - throw str_exception("BSA Error: " + msg + "\nArchive: " + filename); - } + void fail(const std::string &msg); /// Read header information from the input source - void 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 - std::vector offsets; - offsets.resize(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(size->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(int i=0;i fsize) - fail("Archive contains offsets outside itself"); - - // Add the file name to the lookup - lookup[fs.name] = i; - } - - isLoaded = true; - } + void readHeader(); /// Get the index of a given file name, or -1 if not found - int getIndex(const char *str) - { - Lookup::iterator it; - it = lookup.find(str); - - if(it == lookup.end()) return -1; - else - { - int res = it->second; - assert(res >= 0 && res < files.size()); - return res; - } - } + int getIndex(const char *str); public: @@ -221,25 +101,15 @@ class BSAFile */ BSAFile() - : input(NULL), isLoaded(false) {} + : input(), isLoaded(false) {} /// Open an archive file. - void open(const std::string &file) - { - filename = file; - input = new Mangle::Stream::FileStream(file); - read(); - } + 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::Stream* inp, const std::string &name) - { - filename = name; - input = inp; - read(); - } + void open(Mangle::Stream::StreamPtr inp, const std::string &name); /* ----------------------------------- * Archive file routines @@ -247,10 +117,7 @@ class BSAFile */ /// Check if a file exists - bool exists(const char *file) - { - return getIndex(file) != -1; - } + bool exists(const char *file) { return getIndex(file) != -1; } /** Open a file contained in the archive. Throws an exception if the file doesn't exist. @@ -258,16 +125,11 @@ class BSAFile NOTE: All files opened from one archive will share a common file handle. This is NOT thread safe. */ - Mangle::Stream::Stream *getFile(const char *file) - { - int i = getIndex(file); - if(i == -1) - fail("File not found: " + std::string(file)); - - FileStruct &fs = files[i]; + Mangle::Stream::StreamPtr getFile(const char *file); - return new SliceStream(input, fs.offset, fs.fileSize); - } + /// Get a list of all files + const FileList &getList() const + { return files; } }; #endif diff --git a/bsa/tests/.gitignore b/bsa/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/bsa/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/bsa/tests/Makefile b/bsa/tests/Makefile new file mode 100644 index 000000000..6eabaf9e2 --- /dev/null +++ b/bsa/tests/Makefile @@ -0,0 +1,6 @@ +GCC=g++ + +all: bsa_file_test + +bsa_file_test: bsa_file_test.cpp ../bsa_file.cpp + $(GCC) $^ -o $@ diff --git a/bsa/tests/bsa_file_test.cpp b/bsa/tests/bsa_file_test.cpp new file mode 100644 index 000000000..e30a79916 --- /dev/null +++ b/bsa/tests/bsa_file_test.cpp @@ -0,0 +1,43 @@ +#include "../bsa_file.h" + +/* + Test of the BSAFile class + + This test requires that data/Morrowind.bsa exists in the root + directory of OpenMW. + + */ + +#include + +using namespace std; + +BSAFile bsa; + +void find(const char* file) +{ + cout << "Does file '" << file << "' exist?\n "; + if(bsa.exists(file)) + { + cout << "Yes.\n "; + cout << bsa.getFile(file)->size() << " bytes\n"; + } + else cout << "No.\n"; +} + +int main() +{ + cout << "Reading Morrowind.bsa\n"; + bsa.open("../../data/Morrowind.bsa"); + + const BSAFile::FileList &files = bsa.getList(); + + cout << "First 10 files in archive:\n"; + for(int i=0; i<10; i++) + cout << " " << files[i].name + << " (" << files[i].fileSize << " bytes @" + << files[i].offset << ")\n"; + + find("meshes\\r\\xnetch_betty.nif"); + find("humdrum"); +} diff --git a/mangle b/mangle new file mode 160000 index 000000000..5e70bc7bd --- /dev/null +++ b/mangle @@ -0,0 +1 @@ +Subproject commit 5e70bc7bd768b9fb6016c02656841d199cbb3759 diff --git a/tools/str_exception.h b/tools/str_exception.h index 3017b512b..75eb5e849 100644 --- a/tools/str_exception.h +++ b/tools/str_exception.h @@ -12,6 +12,7 @@ class str_exception : public std::exception public: str_exception(const std::string &m) : msg(m) {} + ~str_exception() throw() {} const char* what() const throw() { return msg.c_str(); } };