mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 16:29:55 +00:00
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.
This commit is contained in:
parent
94c3fb81d1
commit
a8ebb39883
3 changed files with 265 additions and 214 deletions
|
@ -28,13 +28,11 @@
|
|||
#include <OgreArchiveFactory.h>
|
||||
#include <OgreArchiveManager.h>
|
||||
#include "bsa_file.hpp"
|
||||
#include <libs/mangle/stream/clients/ogre_datastream.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using namespace Ogre;
|
||||
using namespace Mangle::Stream;
|
||||
using namespace Bsa;
|
||||
|
||||
struct ciLessBoost : std::binary_function<std::string, std::string, bool>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -23,171 +23,235 @@
|
|||
|
||||
#include "bsa_file.hpp"
|
||||
|
||||
#include <libs/mangle/stream/servers/file_stream.hpp>
|
||||
#include <libs/mangle/stream/filters/slice_stream.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <OgreDataStream.h>
|
||||
|
||||
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<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
|
||||
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);
|
||||
/*
|
||||
* 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);
|
||||
|
||||
// Total archive size
|
||||
size_t fsize = input->size();
|
||||
std::ifstream input(filename.c_str(), std::ios_base::binary);
|
||||
|
||||
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++)
|
||||
// Total archive size
|
||||
size_t fsize = 0;
|
||||
if(input.seekg(0, std::ios_base::end))
|
||||
{
|
||||
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;
|
||||
fsize = input.tellg();
|
||||
input.seekg(0);
|
||||
}
|
||||
|
||||
isLoaded = true;
|
||||
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);
|
||||
|
||||
// Read the string table
|
||||
stringBuf.resize(dirsize-12*filenum);
|
||||
input.read(&stringBuf[0], stringBuf.size());
|
||||
|
||||
// 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<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;
|
||||
}
|
||||
|
||||
/// 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<int> (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];
|
||||
|
||||
return StreamPtr(new SliceStream(input, fs.offset, fs.fileSize));
|
||||
assert(file);
|
||||
int i = getIndex(file);
|
||||
if(i == -1)
|
||||
fail("File not found: " + string(file));
|
||||
|
||||
const FileStruct &fs = files[i];
|
||||
return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, fs.offset, fs.fileSize));
|
||||
}
|
||||
|
|
|
@ -24,13 +24,15 @@
|
|||
#ifndef BSA_BSA_FILE_H
|
||||
#define BSA_BSA_FILE_H
|
||||
|
||||
#include <libs/mangle/stream/stream.hpp>
|
||||
#include <libs/platform/stdint.h>
|
||||
#include <libs/platform/strings.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <OgreDataStream.h>
|
||||
|
||||
|
||||
namespace Bsa
|
||||
{
|
||||
|
||||
|
@ -39,98 +41,88 @@ namespace Bsa
|
|||
*/
|
||||
class BSAFile
|
||||
{
|
||||
public:
|
||||
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;
|
||||
|
||||
/// 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<FileStruct> FileList;
|
||||
|
||||
// Zero-terminated file name
|
||||
char* name;
|
||||
};
|
||||
private:
|
||||
/// Table of files in this archive
|
||||
FileList files;
|
||||
|
||||
typedef std::vector<FileStruct> FileList;
|
||||
/// Filename string buffer
|
||||
std::vector<char> stringBuf;
|
||||
|
||||
private:
|
||||
/// True when an archive has been loaded
|
||||
bool isLoaded;
|
||||
|
||||
/// The archive source
|
||||
Mangle::Stream::StreamPtr input;
|
||||
/// Used for error messages
|
||||
std::string filename;
|
||||
|
||||
/// Table of files in this archive
|
||||
FileList files;
|
||||
/// Case insensitive string comparison
|
||||
struct iltstr
|
||||
{
|
||||
bool operator()(const char *s1, const char *s2) const
|
||||
{ return strcasecmp(s1,s2) < 0; }
|
||||
};
|
||||
|
||||
/// Filename string buffer
|
||||
std::vector<char> stringBuf;
|
||||
/** 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<const char*, int, iltstr> Lookup;
|
||||
Lookup lookup;
|
||||
|
||||
/// True when an archive has been loaded
|
||||
bool isLoaded;
|
||||
/// Error handling
|
||||
void fail(const std::string &msg);
|
||||
|
||||
/// Used for error messages
|
||||
std::string filename;
|
||||
/// Read header information from the input source
|
||||
void readHeader();
|
||||
|
||||
/// Case insensitive string comparison
|
||||
struct iltstr
|
||||
{
|
||||
bool operator()(const char *s1, const char *s2) const
|
||||
{ return strcasecmp(s1,s2) < 0; }
|
||||
};
|
||||
/// Get the index of a given file name, or -1 if not found
|
||||
int getIndex(const char *str) const;
|
||||
|
||||
/** 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<const char*, int, iltstr> Lookup;
|
||||
Lookup lookup;
|
||||
public:
|
||||
/* -----------------------------------
|
||||
* BSA management methods
|
||||
* -----------------------------------
|
||||
*/
|
||||
|
||||
/// Error handling
|
||||
void fail(const std::string &msg);
|
||||
BSAFile()
|
||||
: isLoaded(false)
|
||||
{ }
|
||||
|
||||
/// Read header information from the input source
|
||||
void readHeader();
|
||||
/// Open an archive file.
|
||||
void open(const std::string &file);
|
||||
|
||||
/// Get the index of a given file name, or -1 if not found
|
||||
int getIndex(const char *str) const;
|
||||
/* -----------------------------------
|
||||
* Archive file routines
|
||||
* -----------------------------------
|
||||
*/
|
||||
|
||||
public:
|
||||
/// Check if a file exists
|
||||
bool exists(const char *file) const
|
||||
{ return getIndex(file) != -1; }
|
||||
|
||||
/* -----------------------------------
|
||||
* BSA management methods
|
||||
* -----------------------------------
|
||||
*/
|
||||
/** Open a file contained in the archive. Throws an exception if the
|
||||
file doesn't exist.
|
||||
|
||||
BSAFile()
|
||||
: input(), isLoaded(false) {}
|
||||
NOTE: All files opened from one archive will share a common file
|
||||
handle. This is NOT thread safe.
|
||||
*/
|
||||
Ogre::DataStreamPtr getFile(const char *file);
|
||||
|
||||
/// 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
|
||||
/// Get a list of all files
|
||||
const FileList &getList() const
|
||||
{ return files; }
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue