Avoid Mangle for BSA accesses

The way it was set up was not very efficient, and we're using Ogre for resource
management anyway, so it's best to just use that.
actorid
Chris Robinson 13 years ago
parent 94c3fb81d1
commit a8ebb39883

@ -28,13 +28,11 @@
#include <OgreArchiveFactory.h> #include <OgreArchiveFactory.h>
#include <OgreArchiveManager.h> #include <OgreArchiveManager.h>
#include "bsa_file.hpp" #include "bsa_file.hpp"
#include <libs/mangle/stream/clients/ogre_datastream.hpp>
namespace namespace
{ {
using namespace Ogre; using namespace Ogre;
using namespace Mangle::Stream;
using namespace Bsa; using namespace Bsa;
struct ciLessBoost : std::binary_function<std::string, std::string, bool> struct ciLessBoost : std::binary_function<std::string, std::string, bool>
@ -239,10 +237,7 @@ public:
// Open the file // Open the file
StreamPtr strm = narc->getFile(passed.c_str()); return narc->getFile(passed.c_str());
// Wrap it into an Ogre::DataStream.
return DataStreamPtr(new Mangle2OgreStream(strm));
} }
bool exists(const String& filename) { bool exists(const String& filename) {

@ -23,171 +23,235 @@
#include "bsa_file.hpp" #include "bsa_file.hpp"
#include <libs/mangle/stream/servers/file_stream.hpp>
#include <libs/mangle/stream/filters/slice_stream.hpp>
#include <stdexcept> #include <stdexcept>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <OgreDataStream.h>
using namespace std; using namespace std;
using namespace Mangle::Stream;
using namespace Bsa; 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<char*>(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 /// Error handling
void BSAFile::fail(const string &msg) 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 /// Read header information from the input source
void BSAFile::readHeader() void BSAFile::readHeader()
{ {
/* /*
* The layout of a BSA archive is as follows: * The layout of a BSA archive is as follows:
* *
* - 12 bytes header, contains 3 ints: * - 12 bytes header, contains 3 ints:
* id number - equal to 0x100 * id number - equal to 0x100
* dirsize - size of the directory block (see below) * dirsize - size of the directory block (see below)
* numfiles - number of files * numfiles - number of files
* *
* ---------- start of directory block ----------- * ---------- start of directory block -----------
* *
* - 8 bytes*numfiles, each record contains: * - 8 bytes*numfiles, each record contains:
* fileSize * fileSize
* offset into data buffer (see below) * offset into data buffer (see below)
* *
* - 4 bytes*numfiles, each record is an offset into the following name buffer * - 4 bytes*numfiles, each record is an offset into the following name buffer
* *
* - name buffer, indexed by the previous table, each string is * - name buffer, indexed by the previous table, each string is
* null-terminated. Size is (dirsize - 12*numfiles). * null-terminated. Size is (dirsize - 12*numfiles).
* *
* ---------- end of directory block ------------- * ---------- end of directory block -------------
* *
* - 8*filenum - hash table block, we currently ignore this * - 8*filenum - hash table block, we currently ignore this
* *
* ----------- start of data buffer -------------- * ----------- start of data buffer --------------
* *
* - The rest of the archive is file data, indexed by the * - The rest of the archive is file data, indexed by the
* offsets in the directory block. The offsets start at 0 at * offsets in the directory block. The offsets start at 0 at
* the beginning of this buffer. * the beginning of this buffer.
* *
*/ */
assert(!isLoaded); assert(!isLoaded);
assert(input);
assert(input->hasSize); std::ifstream input(filename.c_str(), std::ios_base::binary);
assert(input->hasPosition);
assert(input->isSeekable); // Total archive size
size_t fsize = 0;
// Total archive size if(input.seekg(0, std::ios_base::end))
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<uint32_t> 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<filenum;i++)
{ {
FileStruct &fs = files[i]; fsize = input.tellg();
fs.fileSize = offsets[i*2]; input.seekg(0);
fs.offset = offsets[i*2+1] + fileDataOffset; }
fs.name = &stringBuf[offsets[2*filenum+i]];
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(reinterpret_cast<char*>(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<uint32_t> offsets(3*filenum);
input.read(reinterpret_cast<char*>(&offsets[0]), 12*filenum);
if(fs.offset + fs.fileSize > fsize) // Read the string table
fail("Archive contains offsets outside itself"); stringBuf.resize(dirsize-12*filenum);
input.read(&stringBuf[0], stringBuf.size());
// Add the file name to the lookup // Check our position
lookup[fs.name] = i; 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<filenum;i++)
{
FileStruct &fs = files[i];
fs.fileSize = offsets[i*2];
fs.offset = offsets[i*2+1] + fileDataOffset;
fs.name = &stringBuf[offsets[2*filenum+i]];
if(fs.offset + fs.fileSize > 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 /// Get the index of a given file name, or -1 if not found
int BSAFile::getIndex(const char *str) const int BSAFile::getIndex(const char *str) const
{ {
Lookup::const_iterator it; Lookup::const_iterator it = lookup.find(str);
it = lookup.find(str); if(it == lookup.end())
return -1;
if(it == lookup.end()) return -1; int res = it->second;
else assert(res >= 0 && (size_t)res < files.size());
{ return res;
int res = it->second;
assert(res >= 0 && res < static_cast<int> (files.size()));
return res;
}
} }
/// Open an archive file. /// Open an archive file.
void BSAFile::open(const string &file) void BSAFile::open(const string &file)
{ {
filename = file; filename = file;
input = StreamPtr(new FileStream(file)); readHeader();
readHeader();
} }
/** Open an archive from a generic stream. The 'name' parameter is Ogre::DataStreamPtr BSAFile::getFile(const char *file)
used for error messages.
*/
void BSAFile::open(StreamPtr inp, const string &name)
{ {
filename = name; assert(file);
input = inp; int i = getIndex(file);
readHeader(); if(i == -1)
} fail("File not found: " + string(file));
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)); const FileStruct &fs = files[i];
return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, fs.offset, fs.fileSize));
} }

@ -24,13 +24,15 @@
#ifndef BSA_BSA_FILE_H #ifndef BSA_BSA_FILE_H
#define BSA_BSA_FILE_H #define BSA_BSA_FILE_H
#include <libs/mangle/stream/stream.hpp>
#include <libs/platform/stdint.h> #include <libs/platform/stdint.h>
#include <libs/platform/strings.h> #include <libs/platform/strings.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
#include <OgreDataStream.h>
namespace Bsa namespace Bsa
{ {
@ -39,98 +41,88 @@ namespace Bsa
*/ */
class BSAFile class BSAFile
{ {
public: public:
/// Represents one file entry in the archive
/// Represents one file entry in the archive struct FileStruct
struct FileStruct {
{ // File size and offset in file. We store the offset from the
// File size and offset in file. We store the offset from the // beginning of the file, not the offset into the data buffer
// beginning of the file, not the offset into the data buffer // (which is what is stored in the archive.)
// (which is what is stored in the archive.) uint32_t fileSize, offset;
uint32_t fileSize, offset;
// Zero-terminated file name
// Zero-terminated file name const char *name;
char* name; };
}; typedef std::vector<FileStruct> FileList;
typedef std::vector<FileStruct> FileList; private:
/// Table of files in this archive
private: FileList files;
/// The archive source /// Filename string buffer
Mangle::Stream::StreamPtr input; std::vector<char> stringBuf;
/// Table of files in this archive /// True when an archive has been loaded
FileList files; bool isLoaded;
/// Filename string buffer /// Used for error messages
std::vector<char> stringBuf; std::string filename;
/// True when an archive has been loaded /// Case insensitive string comparison
bool isLoaded; struct iltstr
{
/// Used for error messages bool operator()(const char *s1, const char *s2) const
std::string filename; { return strcasecmp(s1,s2) < 0; }
};
/// Case insensitive string comparison
struct iltstr /** A map used for fast file name lookup. The value is the index into
{ the files[] vector above. The iltstr ensures that file name
bool operator()(const char *s1, const char *s2) const checks are case insensitive.
{ return strcasecmp(s1,s2) < 0; } */
}; typedef std::map<const char*, int, iltstr> Lookup;
Lookup lookup;
/** A map used for fast file name lookup. The value is the index into
the files[] vector above. The iltstr ensures that file name /// Error handling
checks are case insensitive. void fail(const std::string &msg);
*/
typedef std::map<const char*, int, iltstr> Lookup; /// Read header information from the input source
Lookup lookup; void readHeader();
/// Error handling /// Get the index of a given file name, or -1 if not found
void fail(const std::string &msg); int getIndex(const char *str) const;
/// Read header information from the input source public:
void readHeader(); /* -----------------------------------
* BSA management methods
/// Get the index of a given file name, or -1 if not found * -----------------------------------
int getIndex(const char *str) const; */
public: BSAFile()
: isLoaded(false)
/* ----------------------------------- { }
* BSA management methods
* ----------------------------------- /// Open an archive file.
*/ void open(const std::string &file);
BSAFile() /* -----------------------------------
: input(), isLoaded(false) {} * Archive file routines
* -----------------------------------
/// Open an archive file. */
void open(const std::string &file);
/// Check if a file exists
/** Open an archive from a generic stream. The 'name' parameter is bool exists(const char *file) const
used for error messages. { return getIndex(file) != -1; }
*/
void open(Mangle::Stream::StreamPtr inp, const std::string &name); /** Open a file contained in the archive. Throws an exception if the
file doesn't exist.
/* -----------------------------------
* Archive file routines NOTE: All files opened from one archive will share a common file
* ----------------------------------- handle. This is NOT thread safe.
*/ */
Ogre::DataStreamPtr getFile(const char *file);
/// Check if a file exists
bool exists(const char *file) const { return getIndex(file) != -1; } /// Get a list of all files
const FileList &getList() const
/** 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
{ return files; } { return files; }
}; };

Loading…
Cancel
Save