#include "esmreader.hpp" #include namespace ESM { using namespace Misc; std::string ESMReader::getName() const { return mCtx.filename; } ESM_Context ESMReader::getContext() { // Update the file position before returning mCtx.filePos = mEsm->tellg(); return mCtx; } ESMReader::ESMReader() : mIdx(0) , mRecordFlags(0) , mBuffer(50*1024) , mGlobalReaderList(NULL) , mEncoder(NULL) , mFileSize(0) { } int ESMReader::getFormat() const { return mHeader.mFormat; } void ESMReader::restoreContext(const ESM_Context &rc) { // Reopen the file if necessary if (mCtx.filename != rc.filename) openRaw(rc.filename); // Copy the data mCtx = rc; // Make sure we seek to the right place mEsm->seekg(mCtx.filePos); } void ESMReader::close() { mEsm.reset(); mCtx.filename.clear(); mCtx.leftFile = 0; mCtx.leftRec = 0; mCtx.leftSub = 0; mCtx.subCached = false; mCtx.recName.clear(); mCtx.subName.clear(); mHeader.blank(); } void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) { close(); mEsm = _esm; mCtx.filename = name; mEsm->seekg(0, mEsm->end); mCtx.leftFile = mFileSize = mEsm->tellg(); mEsm->seekg(0, mEsm->beg); } void ESMReader::openRaw(const std::string& filename) { openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); } void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) { openRaw(_esm, name); if (getRecName() != "TES3") fail("Not a valid Morrowind file"); getRecHeader(); mHeader.load (*this); } void ESMReader::open(const std::string &file) { open (Files::openConstrainedFileStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) { int64_t val; getHNT(val, name); return val; } std::string ESMReader::getHNOString(const char* name) { if (isNextSub(name)) return getHString(); return ""; } std::string ESMReader::getHNString(const char* name) { getSubNameIs(name); return getHString(); } std::string ESMReader::getHString() { getSubHeader(); // Hack to make MultiMark.esp load. Zero-length strings do not // occur in any of the official mods, but MultiMark makes use of // them. For some reason, they break the rules, and contain a byte // (value 0) even if the header says there is no data. If // Morrowind accepts it, so should we. if (mCtx.leftSub == 0) { // Skip the following zero byte mCtx.leftRec--; char c; getExact(&c, 1); return ""; } return getString(mCtx.leftSub); } void ESMReader::getHExact(void*p, int size) { getSubHeader(); if (size != static_cast (mCtx.leftSub)) { std::stringstream error; error << "getHExact(): size mismatch (requested " << size << ", got " << mCtx.leftSub << ")"; fail(error.str()); } getExact(p, size); } // Read the given number of bytes from a named subrecord void ESMReader::getHNExact(void*p, int size, const char* name) { getSubNameIs(name); getHExact(p, size); } // Get the next subrecord name and check if it matches the parameter void ESMReader::getSubNameIs(const char* name) { getSubName(); if (mCtx.subName != name) fail( "Expected subrecord " + std::string(name) + " but got " + mCtx.subName.toString()); } bool ESMReader::isNextSub(const char* name) { if (!mCtx.leftRec) return false; getSubName(); // If the name didn't match, then mark the it as 'cached' so it's // available for the next call to getSubName. mCtx.subCached = (mCtx.subName != name); // If subCached is false, then subName == name. return !mCtx.subCached; } bool ESMReader::peekNextSub(const char *name) { if (!mCtx.leftRec) return false; getSubName(); mCtx.subCached = true; return mCtx.subName == name; } void ESMReader::cacheSubName() { mCtx.subCached = true; } // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void ESMReader::getSubName() { // If the name has already been read, do nothing if (mCtx.subCached) { mCtx.subCached = false; return; } // reading the subrecord data anyway. const size_t subNameSize = mCtx.subName.data_size(); getExact(mCtx.subName.rw_data(), subNameSize); mCtx.leftRec -= subNameSize; } bool ESMReader::isEmptyOrGetName() { if (mCtx.leftRec) { const size_t subNameSize = mCtx.subName.data_size(); getExact(mCtx.subName.rw_data(), subNameSize); mCtx.leftRec -= subNameSize; return false; } return true; } void ESMReader::skipHSub() { getSubHeader(); skip(mCtx.leftSub); } void ESMReader::skipHSubSize(int size) { skipHSub(); if (static_cast (mCtx.leftSub) != size) fail("skipHSubSize() mismatch"); } void ESMReader::skipHSubUntil(const char *name) { while (hasMoreSubs() && !isNextSub(name)) { mCtx.subCached = false; skipHSub(); } if (hasMoreSubs()) mCtx.subCached = true; } void ESMReader::getSubHeader() { if (mCtx.leftRec < 4) fail("End of record while reading sub-record header"); // Get subrecord size getT(mCtx.leftSub); // Adjust number of record bytes left mCtx.leftRec -= mCtx.leftSub + 4; } void ESMReader::getSubHeaderIs(int size) { getSubHeader(); if (size != static_cast (mCtx.leftSub)) fail("getSubHeaderIs(): Sub header mismatch"); } NAME ESMReader::getRecName() { if (!hasMoreRecs()) fail("No more records, getRecName() failed"); getName(mCtx.recName); mCtx.leftFile -= mCtx.recName.data_size(); // Make sure we don't carry over any old cached subrecord // names. This can happen in some cases when we skip parts of a // record. mCtx.subCached = false; return mCtx.recName; } void ESMReader::skipRecord() { skip(mCtx.leftRec); mCtx.leftRec = 0; mCtx.subCached = false; } void ESMReader::getRecHeader(uint32_t &flags) { // General error checking if (mCtx.leftFile < 12) fail("End of file while reading record header"); if (mCtx.leftRec) fail("Previous record contains unread bytes"); getUint(mCtx.leftRec); getUint(flags);// This header entry is always zero getUint(flags); mCtx.leftFile -= 12; // Check that sizes add up if (mCtx.leftFile < mCtx.leftRec) fail("Record size is larger than rest of file"); // Adjust number of bytes mCtx.left in file mCtx.leftFile -= mCtx.leftRec; } /************************************************************************* * * Lowest level data reading and misc methods * *************************************************************************/ void ESMReader::getExact(void*x, int size) { try { mEsm->read((char*)x, size); } catch (std::exception& e) { fail(std::string("Read error: ") + e.what()); } } std::string ESMReader::getString(int size) { size_t s = size; if (mBuffer.size() <= s) // Add some extra padding to reduce the chance of having to resize // again later. mBuffer.resize(3*s); // And make sure the string is zero terminated mBuffer[s] = 0; // read ESM data char *ptr = &mBuffer[0]; getExact(ptr, size); size = strnlen(ptr, size); // Convert to UTF8 and return if (mEncoder) return mEncoder->getUtf8(ptr, size); return std::string (ptr, size); } void ESMReader::fail(const std::string &msg) { using namespace std; stringstream ss; ss << "ESM Error: " << msg; ss << "\n File: " << mCtx.filename; ss << "\n Record: " << mCtx.recName.toString(); ss << "\n Subrecord: " << mCtx.subName.toString(); if (mEsm.get()) ss << "\n Offset: 0x" << hex << mEsm->tellg(); throw std::runtime_error(ss.str()); } void ESMReader::setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; } size_t ESMReader::getFileOffset() { return mEsm->tellg(); } void ESMReader::skip(int bytes) { mEsm->seekg(getFileOffset()+bytes); } }