Imported Upstream version 0.26.0

This commit is contained in:
Bret Curtis 2013-10-17 16:37:22 +02:00
commit 9a2b6c69b6
1398 changed files with 212217 additions and 0 deletions

View file

@ -0,0 +1,382 @@
/*
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/ .
*/
#include "bsa_archive.hpp"
#include <OgreFileSystem.h>
#include <OgreArchive.h>
#include <OgreArchiveFactory.h>
#include <OgreArchiveManager.h>
#include "bsa_file.hpp"
#include "../files/constrainedfiledatastream.hpp"
using namespace Ogre;
static bool fsstrict = false;
static char strict_normalize_char(char ch)
{
return ch == '\\' ? '/' : ch;
}
static char nonstrict_normalize_char(char ch)
{
return ch == '\\' ? '/' : std::tolower(ch);
}
template<typename T1, typename T2>
static std::string normalize_path(T1 begin, T2 end)
{
std::string normalized;
normalized.reserve(std::distance(begin, end));
char (*normalize_char)(char) = fsstrict ? &strict_normalize_char : &nonstrict_normalize_char;
std::transform(begin, end, std::back_inserter(normalized), normalize_char);
return normalized;
}
/// An OGRE Archive wrapping a BSAFile archive
class DirArchive: public Ogre::Archive
{
typedef std::map <std::string, std::string> index;
index mIndex;
index::const_iterator lookup_filename (std::string const & filename) const
{
std::string normalized = normalize_path (filename.begin (), filename.end ());
return mIndex.find (normalized);
}
public:
DirArchive(const String& name)
: Archive(name, "Dir")
{
typedef boost::filesystem::recursive_directory_iterator directory_iterator;
directory_iterator end;
size_t prefix = name.size ();
if (name.size () > 0 && name [prefix - 1] != '\\' && name [prefix - 1] != '/')
++prefix;
for (directory_iterator i (name); i != end; ++i)
{
if(boost::filesystem::is_directory (*i))
continue;
std::string proper = i->path ().string ();
std::string searchable = normalize_path (proper.begin () + prefix, proper.end ());
mIndex.insert (std::make_pair (searchable, proper));
}
}
bool isCaseSensitive() const { return fsstrict; }
// The archive is loaded in the constructor, and never unloaded.
void load() {}
void unload() {}
DataStreamPtr open(const String& filename, bool readonly = true) const
{
index::const_iterator i = lookup_filename (filename);
if (i == mIndex.end ())
{
std::ostringstream os;
os << "The file '" << filename << "' could not be found.";
throw std::runtime_error (os.str ());
}
return openConstrainedFileDataStream (i->second.c_str ());
}
StringVectorPtr list(bool recursive = true, bool dirs = false)
{
return find ("*", recursive, dirs);
}
FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false)
{
return findFileInfo ("*", recursive, dirs);
}
StringVectorPtr find(const String& pattern, bool recursive = true,
bool dirs = false)
{
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
StringVectorPtr ptr = StringVectorPtr(new StringVector());
for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();++iter)
{
if(Ogre::StringUtil::match(iter->first, normalizedPattern) ||
(recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern)))
ptr->push_back(iter->first);
}
return ptr;
}
bool exists(const String& filename)
{
return lookup_filename(filename) != mIndex.end ();
}
time_t getModifiedTime(const String&) { return 0; }
FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
bool dirs = false) const
{
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
index::const_iterator i = mIndex.find(normalizedPattern);
if(i != mIndex.end())
{
std::string::size_type pt = i->first.rfind('/');
if(pt == std::string::npos)
pt = 0;
FileInfo fi;
fi.archive = const_cast<DirArchive*>(this);
fi.path = i->first.substr(0, pt);
fi.filename = i->first.substr((i->first[pt]=='/') ? pt+1 : pt);
fi.compressedSize = fi.uncompressedSize = 0;
ptr->push_back(fi);
}
else
{
for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();++iter)
{
if(Ogre::StringUtil::match(iter->first, normalizedPattern) ||
(recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern)))
{
std::string::size_type pt = iter->first.rfind('/');
if(pt == std::string::npos)
pt = 0;
FileInfo fi;
fi.archive = const_cast<DirArchive*>(this);
fi.path = iter->first.substr(0, pt);
fi.filename = iter->first.substr((iter->first[pt]=='/') ? pt+1 : pt);
fi.compressedSize = fi.uncompressedSize = 0;
ptr->push_back(fi);
}
}
}
return ptr;
}
};
class BSAArchive : public Archive
{
Bsa::BSAFile arc;
static const char *extractFilename(const Bsa::BSAFile::FileStruct &entry)
{
return entry.name;
}
public:
BSAArchive(const String& name)
: Archive(name, "BSA")
{ arc.open(name); }
bool isCaseSensitive() const { return false; }
// The archive is loaded in the constructor, and never unloaded.
void load() {}
void unload() {}
DataStreamPtr open(const String& filename, bool readonly = true) const
{
// Get a non-const reference to arc. This is a hack and it's all
// OGRE's fault. You should NOT expect an open() command not to
// have any side effects on the archive, and hence this function
// should not have been declared const in the first place.
Bsa::BSAFile *narc = const_cast<Bsa::BSAFile*>(&arc);
// Open the file
return narc->getFile(filename.c_str());
}
bool exists(const String& filename) {
return arc.exists(filename.c_str());
}
time_t getModifiedTime(const String&) { return 0; }
// This is never called as far as I can see.
StringVectorPtr list(bool recursive = true, bool dirs = false)
{
return find ("*", recursive, dirs);
}
// Also never called.
FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false)
{
return findFileInfo ("*", recursive, dirs);
}
StringVectorPtr find(const String& pattern, bool recursive = true,
bool dirs = false)
{
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
const Bsa::BSAFile::FileList &filelist = arc.getList();
StringVectorPtr ptr = StringVectorPtr(new StringVector());
for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();++iter)
{
std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name));
if(Ogre::StringUtil::match(ent, normalizedPattern) ||
(recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern)))
ptr->push_back(iter->name);
}
return ptr;
}
FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
bool dirs = false) const
{
std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
const Bsa::BSAFile::FileList &filelist = arc.getList();
for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();++iter)
{
std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name));
if(Ogre::StringUtil::match(ent, normalizedPattern) ||
(recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern)))
{
std::string::size_type pt = ent.rfind('/');
if(pt == std::string::npos)
pt = 0;
FileInfo fi;
fi.archive = const_cast<BSAArchive*>(this);
fi.path = std::string(iter->name, pt);
fi.filename = std::string(iter->name + ((ent[pt]=='/') ? pt+1 : pt));
fi.compressedSize = fi.uncompressedSize = iter->fileSize;
ptr->push_back(fi);
}
}
return ptr;
}
};
// An archive factory for BSA archives
class BSAArchiveFactory : public ArchiveFactory
{
public:
const String& getType() const
{
static String name = "BSA";
return name;
}
Archive *createInstance( const String& name )
{
return new BSAArchive(name);
}
virtual Archive* createInstance(const String& name, bool readOnly)
{
return new BSAArchive(name);
}
void destroyInstance( Archive* arch) { delete arch; }
};
class DirArchiveFactory : public ArchiveFactory
{
public:
const String& getType() const
{
static String name = "Dir";
return name;
}
Archive *createInstance( const String& name )
{
return new DirArchive(name);
}
virtual Archive* createInstance(const String& name, bool readOnly)
{
return new DirArchive(name);
}
void destroyInstance( Archive* arch) { delete arch; }
};
static bool init = false;
static bool init2 = false;
static void insertBSAFactory()
{
if(!init)
{
ArchiveManager::getSingleton().addArchiveFactory( new BSAArchiveFactory );
init = true;
}
}
static void insertDirFactory()
{
if(!init2)
{
ArchiveManager::getSingleton().addArchiveFactory( new DirArchiveFactory );
init2 = true;
}
}
namespace Bsa
{
// The function below is the only publicly exposed part of this file
void addBSA(const std::string& name, const std::string& group)
{
insertBSAFactory();
ResourceGroupManager::getSingleton().
addResourceLocation(name, "BSA", group, true);
}
void addDir(const std::string& name, const bool& fs, const std::string& group)
{
fsstrict = fs;
insertDirFactory();
ResourceGroupManager::getSingleton().
addResourceLocation(name, "Dir", group, true);
}
}

View file

@ -0,0 +1,42 @@
/*
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.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/ .
*/
#include <string>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <algorithm>
#ifndef BSA_BSA_ARCHIVE_H
#define BSA_BSA_ARCHIVE_H
namespace Bsa
{
/// Add the given BSA file as an input archive in the Ogre resource
/// system.
void addBSA(const std::string& file, const std::string& group="General");
void addDir(const std::string& file, const bool& fs, const std::string& group="General");
}
#endif

176
components/bsa/bsa_file.cpp Normal file
View file

@ -0,0 +1,176 @@
/*
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.hpp"
#include <stdexcept>
#include "../files/constrainedfiledatastream.hpp"
using namespace std;
using namespace Bsa;
/// Error handling
void BSAFile::fail(const string &msg)
{
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);
std::ifstream input(filename.c_str(), std::ios_base::binary);
// Total archive size
size_t fsize = 0;
if(input.seekg(0, std::ios_base::end))
{
fsize = input.tellg();
input.seekg(0);
}
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() == std::streampos(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 = lookup.find(str);
if(it == lookup.end())
return -1;
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;
readHeader();
}
Ogre::DataStreamPtr BSAFile::getFile(const char *file)
{
assert(file);
int i = getIndex(file);
if(i == -1)
fail("File not found: " + string(file));
const FileStruct &fs = files[i];
return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize);
}

128
components/bsa/bsa_file.hpp Normal file
View file

@ -0,0 +1,128 @@
/*
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_BSA_FILE_H
#define BSA_BSA_FILE_H
#include <libs/platform/stdint.h>
#include <libs/platform/strings.h>
#include <string>
#include <vector>
#include <map>
#include <OgreDataStream.h>
namespace Bsa
{
/**
This class is used to read "Bethesda Archive Files", or BSAs.
*/
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
const char *name;
};
typedef std::vector<FileStruct> FileList;
private:
/// Table of files in this archive
FileList files;
/// Filename string buffer
std::vector<char> 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<const char*, int, iltstr> 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.
*/
Ogre::DataStreamPtr getFile(const char *file);
/// Get a list of all files
const FileList &getList() const
{ return files; }
};
}
#endif

View file

@ -0,0 +1,15 @@
GCC=g++
all: bsa_file_test ogre_archive_test
I_OGRE=$(shell pkg-config --cflags OGRE)
L_OGRE=$(shell pkg-config --libs OGRE)
bsa_file_test: bsa_file_test.cpp ../bsa_file.cpp
$(GCC) $^ -o $@
ogre_archive_test: ogre_archive_test.cpp ../bsa_file.cpp ../bsa_archive.cpp
$(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE)
clean:
rm *_test

View file

@ -0,0 +1,44 @@
#include "../bsa_file.hpp"
/*
Test of the BSAFile class
This test requires that data/Morrowind.bsa exists in the root
directory of OpenMW.
*/
#include <iostream>
using namespace std;
using namespace Bsa;
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");
}

View file

@ -0,0 +1,34 @@
#include <Ogre.h>
#include <iostream>
// This is a test of the BSA archive handler for OGRE.
#include "../bsa_archive.hpp"
using namespace Ogre;
using namespace std;
int main()
{
// Disable Ogre logging
new LogManager;
Log *log = LogManager::getSingleton().createLog("");
log->setDebugOutputEnabled(false);
// Set up Root
Root *root = new Root("","","");
// Add the BSA
Bsa::addBSA("../../data/Morrowind.bsa");
// Pick a sample file
String tex = "textures\\tx_natural_cavern_wall13.dds";
cout << "Opening file: " << tex << endl;
// Get it from the resource system
DataStreamPtr data = ResourceGroupManager::getSingleton().openResource(tex, "General");
cout << "Size: " << data->size() << endl;
return 0;
}

View file

@ -0,0 +1,17 @@
Reading Morrowind.bsa
First 10 files in archive:
meshes\m\probe_journeyman_01.nif (6276 bytes @126646052)
textures\menu_rightbuttonup_top.dds (256 bytes @218530052)
textures\menu_rightbuttonup_right.dds (256 bytes @218529796)
textures\menu_rightbuttonup_left.dds (256 bytes @218529540)
textures\menu_rightbuttondown_top.dds (256 bytes @218528196)
meshes\b\b_n_redguard_f_skins.nif (41766 bytes @17809778)
meshes\b\b_n_redguard_m_skins.nif (41950 bytes @18103107)
meshes\b\b_n_redguard_f_wrist.nif (2355 bytes @17858132)
meshes\b\b_n_redguard_m_foot.nif (4141 bytes @17862081)
meshes\b\b_n_redguard_m_knee.nif (2085 bytes @18098101)
Does file 'meshes\r\xnetch_betty.nif' exist?
Yes.
53714 bytes
Does file 'humdrum' exist?
No.

View file

@ -0,0 +1,2 @@
Opening file: textures\tx_natural_cavern_wall13.dds
Size: 43808

18
components/bsa/tests/test.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/bash
make || exit
mkdir -p output
PROGS=*_test
for a in $PROGS; do
if [ -f "output/$a.out" ]; then
echo "Running $a:"
./$a | diff output/$a.out -
else
echo "Creating $a.out"
./$a > "output/$a.out"
git add "output/$a.out"
fi
done