diff --git a/bsa/bsa_archive.cpp b/bsa/bsa_archive.cpp new file mode 100644 index 000000000..086c23ea2 --- /dev/null +++ b/bsa/bsa_archive.cpp @@ -0,0 +1,150 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (cpp_bsaarchive.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/ . + + */ + +#ifndef _BSA_ARCHIVE_H_ +#define _BSA_ARCHIVE_H_ + + +/* This file inserts an archive manager for .bsa files into the OGRE + resource loading system. +*/ + +#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. +class BSAArchive : public Archive +{ + BSAFile arc; + +public: + BSAArchive(const String& name) + : Archive(name, "BSA") + { arc.open(name); } + + bool isCaseSensitive(void) const { return false; } + + // The archive is loaded in the constructor, and never unloaded. + void load() {} + void unload() {} + + // Open a file in the archive. + DataStreamPtr open(const String& filename) const + { + // Open the file, and wrap it into an Ogre::DataStream. + return DataStreamPtr(new MangleDataStream(getFile(filename.c_str())); + } + + // This is never called as far as I can see. + StringVectorPtr list(bool recursive = true, bool dirs = false) + { + //std::cout << "list(" << recursive << ", " << dirs << ")\n"; + StringVectorPtr ptr = StringVectorPtr(new StringVector()); + return ptr; + } + // Also never called. + FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false) + { + //std::cout << "listFileInfo(" << recursive << ", " << dirs << ")\n"; + FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + return ptr; + } + + time_t getModifiedTime(const String&) { return 0; } + + // After load() is called, find("*") is called once. It doesn't seem + // to matter that we return an empty list, exists() gets called on + // the correct files anyway. + StringVectorPtr find(const String& pattern, bool recursive = true, + bool dirs = false) + { + //std::cout << "find(" << pattern << ", " << recursive + // << ", " << dirs << ")\n"; + StringVectorPtr ptr = StringVectorPtr(new StringVector()); + return ptr; + } + + /* Gets called once for each of the ogre formats, *.program, + *.material etc. We ignore all these. + + However, it's also called by MyGUI to find individual textures, + and we can't ignore these since many of the GUI textures are + located in BSAs. So instead we channel it through exists() and + set up a single-element result list if the file is found. + */ + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) + { + /* + std::cout << "findFileInfo(" << pattern << ", " << recursive + << ", " << dirs << ")\n"; + */ + + FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + + // Check if the file exists (only works for single files - wild + // cards and recursive search isn't implemented.) + if(exists(pattern)) + { + FileInfo fi; + fi.archive = this; + fi.filename = pattern; + // It apparently doesn't matter that we return bogus + // information + fi.path = ""; + fi.compressedSize = fi.uncompressedSize = 0; + + ptr->push_back(fi); + } + + return ptr; + } + + // Check if the file exists. + bool exists(const String& filename) { return arc.exists(filename.c_str()); } +}; + +// An archive factory for BSA archives +class BSAArchiveFactory : public ArchiveFactory +{ +public: + virtual ~BSAArchiveFactory() {} + + const String& getType() const + { + static String name = "BSA"; + return name; + } + + Archive *createInstance( const String& name ) + { + return new BSAArchive(name); + } + + void destroyInstance( Archive* arch) { delete arch; } +}; + +BSAArchiveFactory mBSAFactory; + +#endif diff --git a/bsa/bsa_file.h b/bsa/bsa_file.h new file mode 100644 index 000000000..52a354afb --- /dev/null +++ b/bsa/bsa_file.h @@ -0,0 +1,273 @@ +/* + 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.h) 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/ . + + */ + +#ifndef _BSA_FILE_H_ +#define _BSA_FILE_H_ + +#include "mangle/stream/servers/file_stream.h" +#include "mangle/stream/filters/slice_stream.h" + +#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: + + /// 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; + }; + + /// The archive source + Mangle::Stream::Stream *input; + + /// Table of files in this archive + std::vector files; + + /// Filename string buffer + std::vector stringBuf; + + /// True when an archive has been loaded + bool isLoaded; + + /// Used for error messages + std::string filename; + + /// Non-case sensitive 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 non-case sensitive. + */ + typedef std::map Lookup; + Lookup lookup; + + /// Error handling + void fail(const std::string &msg) + { + throw str_exception("BSA Error: " + msg + "\nArchive: " + filename); + } + + /// 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; + } + + /// 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; + } + } + + public: + + /* ----------------------------------- + * BSA management methods + * ----------------------------------- + */ + + BSAFile() + : input(NULL), isLoaded(false) {} + + /// Open an archive file. + void open(const std::string &file) + { + filename = file; + input = new Mangle::Stream::FileStream(file); + read(); + } + + /** 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(); + } + + /* ----------------------------------- + * Archive file routines + * ----------------------------------- + */ + + /// Check if a file exists + 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. + + 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]; + + return new SliceStream(input, fs.offset, fs.fileSize); + } +}; + +#endif diff --git a/tools/str_exception.h b/tools/str_exception.h new file mode 100644 index 000000000..3017b512b --- /dev/null +++ b/tools/str_exception.h @@ -0,0 +1,18 @@ +#ifndef __STR_EXCEPTION_H +#define __STR_EXCEPTION_H + +#include +#include + +/// A simple exception that takes and returns a string +class str_exception : public std::exception +{ + std::string msg; + + public: + + str_exception(const std::string &m) : msg(m) {} + const char* what() const throw() { return msg.c_str(); } +}; + +#endif