You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
9.9 KiB
C++
370 lines
9.9 KiB
C++
#ifndef _ESM_READER_H
|
|
#define _ESM_READER_H
|
|
|
|
#include <string.h>
|
|
|
|
#include <string>
|
|
#include <libs/platform/stdint.h>
|
|
#include <libs/platform/string.h>
|
|
#include <assert.h>
|
|
#include <vector>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
|
|
#include <libs/mangle/stream/stream.hpp>
|
|
#include <libs/mangle/stream/servers/file_stream.hpp>
|
|
#include <components/misc/stringops.hpp>
|
|
|
|
#include <components/to_utf8/to_utf8.hpp>
|
|
|
|
namespace ESM {
|
|
|
|
enum Version
|
|
{
|
|
VER_12 = 0x3f99999a,
|
|
VER_13 = 0x3fa66666
|
|
};
|
|
|
|
enum FileType
|
|
{
|
|
FT_ESP = 0, // Plugin
|
|
FT_ESM = 1, // Master
|
|
FT_ESS = 32 // Savegame
|
|
};
|
|
|
|
// Used to mark special files. The original ESM files are given
|
|
// special treatment in a few places, most noticably in loading and
|
|
// filtering out "dirtly" GMST entries correctly.
|
|
enum SpecialFile
|
|
{
|
|
SF_Other,
|
|
SF_Morrowind,
|
|
SF_Tribunal,
|
|
SF_Bloodmoon
|
|
};
|
|
|
|
/* A structure used for holding fixed-length strings. In the case of
|
|
LEN=4, it can be more efficient to match the string as a 32 bit
|
|
number, therefore the struct is implemented as a union with an int.
|
|
*/
|
|
template <int LEN>
|
|
union NAME_T
|
|
{
|
|
char name[LEN];
|
|
int32_t val;
|
|
|
|
bool operator==(const char *str)
|
|
{
|
|
for(int i=0; i<LEN; i++)
|
|
if(name[i] != str[i]) return false;
|
|
else if(name[i] == 0) return true;
|
|
return str[LEN] == 0;
|
|
}
|
|
bool operator!=(const char *str) { return !((*this)==str); }
|
|
|
|
bool operator==(const std::string &str)
|
|
{
|
|
return (*this) == str.c_str();
|
|
}
|
|
bool operator!=(const std::string &str) { return !((*this)==str); }
|
|
|
|
bool operator==(int v) { return v == val; }
|
|
bool operator!=(int v) { return v != val; }
|
|
|
|
std::string toString() const { return std::string(name, strnlen(name, LEN)); }
|
|
};
|
|
|
|
typedef NAME_T<4> NAME;
|
|
typedef NAME_T<32> NAME32;
|
|
typedef NAME_T<64> NAME64;
|
|
typedef NAME_T<256> NAME256;
|
|
|
|
#pragma pack(push)
|
|
#pragma pack(1)
|
|
/// File header data for all ES files
|
|
struct HEDRstruct
|
|
{
|
|
/* File format version. This is actually a float, the supported
|
|
versions are 1.2 and 1.3. These correspond to:
|
|
1.2 = 0x3f99999a and 1.3 = 0x3fa66666
|
|
*/
|
|
int version;
|
|
int type; // 0=esp, 1=esm, 32=ess
|
|
NAME32 author; // Author's name
|
|
NAME256 desc; // File description
|
|
int records; // Number of records? Not used.
|
|
};
|
|
|
|
// Defines another files (esm or esp) that this file depends upon.
|
|
struct MasterData
|
|
{
|
|
std::string name;
|
|
uint64_t size;
|
|
};
|
|
|
|
// Data that is only present in save game files
|
|
struct SaveData
|
|
{
|
|
float pos[6]; // Player position and rotation
|
|
NAME64 cell; // Cell name
|
|
float unk2; // Unknown value - possibly game time?
|
|
NAME32 player; // Player name
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
|
|
/* This struct defines a file 'context' which can be saved and later
|
|
restored by an ESMReader instance. It will save the position within
|
|
a file, and when restored will let you read from that position as
|
|
if you never left it.
|
|
*/
|
|
struct ESM_Context
|
|
{
|
|
std::string filename;
|
|
uint32_t leftRec, leftSub;
|
|
size_t leftFile;
|
|
NAME recName, subName;
|
|
HEDRstruct header;
|
|
|
|
// True if subName has been read but not used.
|
|
bool subCached;
|
|
|
|
// File position. Only used for stored contexts, not regularly
|
|
// updated within the reader itself.
|
|
size_t filePos;
|
|
};
|
|
|
|
class ESMReader
|
|
{
|
|
public:
|
|
|
|
/*************************************************************************
|
|
*
|
|
* Public type definitions
|
|
*
|
|
*************************************************************************/
|
|
|
|
typedef std::vector<MasterData> MasterList;
|
|
|
|
/*************************************************************************
|
|
*
|
|
* Information retrieval
|
|
*
|
|
*************************************************************************/
|
|
|
|
int getVer() { return mCtx.header.version; }
|
|
float getFVer() { return *((float*)&mCtx.header.version); }
|
|
int getSpecial() { return mSpf; }
|
|
const std::string getAuthor() { return mCtx.header.author.toString(); }
|
|
const std::string getDesc() { return mCtx.header.desc.toString(); }
|
|
const SaveData &getSaveData() { return mSaveData; }
|
|
const MasterList &getMasters() { return mMasters; }
|
|
const NAME &retSubName() { return mCtx.subName; }
|
|
uint32_t getSubSize() { return mCtx.leftSub; }
|
|
|
|
/*************************************************************************
|
|
*
|
|
* Opening and closing
|
|
*
|
|
*************************************************************************/
|
|
|
|
/** Save the current file position and information in a ESM_Context
|
|
struct
|
|
*/
|
|
ESM_Context getContext();
|
|
|
|
/** Restore a previously saved context */
|
|
void restoreContext(const ESM_Context &rc);
|
|
|
|
/** Close the file, resets all information. After calling close()
|
|
the structure may be reused to load a new file.
|
|
*/
|
|
void close();
|
|
|
|
/// Raw opening. Opens the file and sets everything up but doesn't
|
|
/// parse the header.
|
|
void openRaw(Mangle::Stream::StreamPtr _esm, const std::string &name);
|
|
|
|
/// Load ES file from a new stream, parses the header. Closes the
|
|
/// currently open file first, if any.
|
|
void open(Mangle::Stream::StreamPtr _esm, const std::string &name);
|
|
|
|
void open(const std::string &file);
|
|
|
|
void openRaw(const std::string &file);
|
|
|
|
/*************************************************************************
|
|
*
|
|
* Medium-level reading shortcuts
|
|
*
|
|
*************************************************************************/
|
|
|
|
// Read data of a given type, stored in a subrecord of a given name
|
|
template <typename X>
|
|
void getHNT(X &x, const char* name)
|
|
{
|
|
getSubNameIs(name);
|
|
getHT(x);
|
|
}
|
|
|
|
// Optional version of getHNT
|
|
template <typename X>
|
|
void getHNOT(X &x, const char* name)
|
|
{
|
|
if(isNextSub(name))
|
|
getHT(x);
|
|
}
|
|
|
|
// Version with extra size checking, to make sure the compiler
|
|
// doesn't mess up our struct padding.
|
|
template <typename X>
|
|
void getHNT(X &x, const char* name, int size)
|
|
{
|
|
assert(sizeof(X) == size);
|
|
getSubNameIs(name);
|
|
getHT(x);
|
|
}
|
|
|
|
int64_t getHNLong(const char *name);
|
|
|
|
// Get data of a given type/size, including subrecord header
|
|
template <typename X>
|
|
void getHT(X &x)
|
|
{
|
|
getSubHeader();
|
|
if (mCtx.leftSub != sizeof(X))
|
|
fail("getHT(): subrecord size mismatch");
|
|
getT(x);
|
|
}
|
|
|
|
// Version with extra size checking, to make sure the compiler
|
|
// doesn't mess up our struct padding.
|
|
template <typename X>
|
|
void getHT(X &x, int size)
|
|
{
|
|
assert(sizeof(X) == size);
|
|
getHT(x);
|
|
}
|
|
|
|
// Read a string by the given name if it is the next record.
|
|
std::string getHNOString(const char* name);
|
|
|
|
// Read a string with the given sub-record name
|
|
std::string getHNString(const char* name);
|
|
|
|
// Read a string, including the sub-record header (but not the name)
|
|
std::string getHString();
|
|
|
|
// Read the given number of bytes from a subrecord
|
|
void getHExact(void*p, int size);
|
|
|
|
// Read the given number of bytes from a named subrecord
|
|
void getHNExact(void*p, int size, const char* name);
|
|
|
|
/*************************************************************************
|
|
*
|
|
* Low level sub-record methods
|
|
*
|
|
*************************************************************************/
|
|
|
|
// Get the next subrecord name and check if it matches the parameter
|
|
void getSubNameIs(const char* name);
|
|
|
|
/** Checks if the next sub record name matches the parameter. If it
|
|
does, it is read into 'subName' just as if getSubName() was
|
|
called. If not, the read name will still be available for future
|
|
calls to getSubName(), isNextSub() and getSubNameIs().
|
|
*/
|
|
bool isNextSub(const char* name);
|
|
|
|
// Read subrecord name. This gets called a LOT, so I've optimized it
|
|
// slightly.
|
|
void getSubName();
|
|
|
|
// This is specially optimized for LoadINFO.
|
|
bool isEmptyOrGetName();
|
|
|
|
// Skip current sub record, including header (but not including
|
|
// name.)
|
|
void skipHSub();
|
|
|
|
// Skip sub record and check its size
|
|
void skipHSubSize(int size);
|
|
|
|
/* Sub-record header. This updates leftRec beyond the current
|
|
sub-record as well. leftSub contains size of current sub-record.
|
|
*/
|
|
void getSubHeader();
|
|
|
|
/** Get sub header and check the size
|
|
*/
|
|
void getSubHeaderIs(int size);
|
|
|
|
/*************************************************************************
|
|
*
|
|
* Low level record methods
|
|
*
|
|
*************************************************************************/
|
|
|
|
// Get the next record name
|
|
NAME getRecName();
|
|
|
|
// Skip the rest of this record. Assumes the name and header have
|
|
// already been read
|
|
void skipRecord();
|
|
|
|
// Skip an entire record, including the header (but not the name)
|
|
void skipHRecord();
|
|
|
|
/* Read record header. This updatesleftFile BEYOND the data that
|
|
follows the header, ie beyond the entire record. You should use
|
|
leftRec to orient yourself inside the record itself.
|
|
*/
|
|
void getRecHeader() { uint32_t u; getRecHeader(u); }
|
|
void getRecHeader(uint32_t &flags);
|
|
|
|
bool hasMoreRecs() { return mCtx.leftFile > 0; }
|
|
bool hasMoreSubs() { return mCtx.leftRec > 0; }
|
|
|
|
|
|
/*************************************************************************
|
|
*
|
|
* Lowest level data reading and misc methods
|
|
*
|
|
*************************************************************************/
|
|
|
|
template <typename X>
|
|
void getT(X &x) { getExact(&x, sizeof(X)); }
|
|
|
|
void getExact(void*x, int size);
|
|
void getName(NAME &name) { getT(name); }
|
|
void getUint(uint32_t &u) { getT(u); }
|
|
|
|
// Read the next 'size' bytes and return them as a string. Converts
|
|
// them from native encoding to UTF8 in the process.
|
|
std::string getString(int size);
|
|
|
|
void skip(int bytes) { mEsm->seek(mEsm->tell()+bytes); }
|
|
uint64_t getOffset() { return mEsm->tell(); }
|
|
|
|
/// Used for error handling
|
|
void fail(const std::string &msg);
|
|
|
|
/// Sets font encoding for ESM strings
|
|
void setEncoding(const std::string& encoding);
|
|
|
|
private:
|
|
Mangle::Stream::StreamPtr mEsm;
|
|
|
|
ESM_Context mCtx;
|
|
|
|
// Special file signifier (see SpecialFile enum above)
|
|
int mSpf;
|
|
|
|
SaveData mSaveData;
|
|
MasterList mMasters;
|
|
ToUTF8::FromType mEncoding;
|
|
};
|
|
}
|
|
#endif
|