mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 17:56:37 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			367 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "esmreader.hpp"
 | |
| #include <stdexcept>
 | |
| 
 | |
| namespace ESM
 | |
| {
 | |
| 
 | |
| using namespace Misc;
 | |
| 
 | |
| ESM_Context ESMReader::getContext()
 | |
| {
 | |
|     // Update the file position before returning
 | |
|     mCtx.filePos = mEsm->tell();
 | |
|     return mCtx;
 | |
| }
 | |
| 
 | |
| 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->seek(mCtx.filePos);
 | |
| }
 | |
| 
 | |
| void ESMReader::close()
 | |
| {
 | |
|     mEsm.setNull();
 | |
|     mCtx.filename.clear();
 | |
|     mCtx.leftFile = 0;
 | |
|     mCtx.leftRec = 0;
 | |
|     mCtx.leftSub = 0;
 | |
|     mCtx.subCached = false;
 | |
|     mCtx.recName.val = 0;
 | |
|     mCtx.subName.val = 0;
 | |
| }
 | |
| 
 | |
| void ESMReader::openRaw(Ogre::DataStreamPtr _esm, const std::string &name)
 | |
| {
 | |
|     close();
 | |
|     mEsm = _esm;
 | |
|     mCtx.filename = name;
 | |
|     mCtx.leftFile = mEsm->size();
 | |
| 
 | |
|     // Flag certain files for special treatment, based on the file
 | |
|     // name.
 | |
|     const char *cstr = mCtx.filename.c_str();
 | |
|     if (iends(cstr, "Morrowind.esm"))
 | |
|         mSpf = SF_Morrowind;
 | |
|     else if (iends(cstr, "Tribunal.esm"))
 | |
|         mSpf = SF_Tribunal;
 | |
|     else if (iends(cstr, "Bloodmoon.esm"))
 | |
|         mSpf = SF_Bloodmoon;
 | |
|     else
 | |
|         mSpf = SF_Other;
 | |
| }
 | |
| 
 | |
| void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name)
 | |
| {
 | |
|     openRaw(_esm, name);
 | |
| 
 | |
|     if (getRecName() != "TES3")
 | |
|         fail("Not a valid Morrowind file");
 | |
| 
 | |
|     getRecHeader();
 | |
| 
 | |
|     // Get the header
 | |
|     getHNT(mCtx.header, "HEDR", 300);
 | |
| 
 | |
|     if (mCtx.header.version != VER_12 && mCtx.header.version != VER_13)
 | |
|         fail("Unsupported file format version");
 | |
| 
 | |
|     while (isNextSub("MAST"))
 | |
|     {
 | |
|         MasterData m;
 | |
|         m.name = getHString();
 | |
|         m.size = getHNLong("DATA");
 | |
|         mMasters.push_back(m);
 | |
|     }
 | |
| 
 | |
|     if (mCtx.header.type == FT_ESS)
 | |
|     {
 | |
|         // Savegame-related data
 | |
| 
 | |
|         // Player position etc
 | |
|         getHNT(mSaveData, "GMDT", 124);
 | |
| 
 | |
|         /* Image properties, five ints. Is always:
 | |
|          Red-mask:   0xff0000
 | |
|          Blue-mask:  0x00ff00
 | |
|          Green-mask: 0x0000ff
 | |
|          Alpha-mask: 0x000000
 | |
|          Bpp:        32
 | |
|          */
 | |
|         getSubNameIs("SCRD");
 | |
|         skipHSubSize(20);
 | |
| 
 | |
|         /* Savegame screenshot:
 | |
|          128x128 pixels * 4 bytes per pixel
 | |
|          */
 | |
|         getSubNameIs("SCRS");
 | |
|         skipHSubSize(65536);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void ESMReader::open(const std::string &file)
 | |
| {
 | |
|     std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary);
 | |
|     // Ogre will delete the stream for us
 | |
|     open(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file);
 | |
| }
 | |
| 
 | |
| void ESMReader::openRaw(const std::string &file)
 | |
| {
 | |
|     std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary);
 | |
|     // Ogre will delete the stream for us
 | |
|     openRaw(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), 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;
 | |
|         mEsm->read(&c, 1);
 | |
|         return "";
 | |
|     }
 | |
| 
 | |
|     return getString(mCtx.leftSub);
 | |
| }
 | |
| 
 | |
| void ESMReader::getHExact(void*p, int size)
 | |
| {
 | |
|     getSubHeader();
 | |
|     if (size != static_cast<int> (mCtx.leftSub))
 | |
|         fail("getHExact() size mismatch");
 | |
|     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;
 | |
| }
 | |
| 
 | |
| // 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.
 | |
|     mEsm->read(mCtx.subName.name, 4);
 | |
|     mCtx.leftRec -= 4;
 | |
| }
 | |
| 
 | |
| bool ESMReader::isEmptyOrGetName()
 | |
| {
 | |
|     if (mCtx.leftRec)
 | |
|     {
 | |
|         mEsm->read(mCtx.subName.name, 4);
 | |
|         mCtx.leftRec -= 4;
 | |
|         return false;
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void ESMReader::skipHSub()
 | |
| {
 | |
|     getSubHeader();
 | |
|     skip(mCtx.leftSub);
 | |
| }
 | |
| 
 | |
| void ESMReader::skipHSubSize(int size)
 | |
| {
 | |
|     skipHSub();
 | |
|     if (static_cast<int> (mCtx.leftSub) != size)
 | |
|         fail("skipHSubSize() mismatch");
 | |
| }
 | |
| 
 | |
| 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<int> (mCtx.leftSub))
 | |
|         fail("getSubHeaderIs(): Sub header mismatch");
 | |
| }
 | |
| 
 | |
| NAME ESMReader::getRecName()
 | |
| {
 | |
|     if (!hasMoreRecs())
 | |
|         fail("No more records, getRecName() failed");
 | |
|     getName(mCtx.recName);
 | |
|     mCtx.leftFile -= 4;
 | |
| 
 | |
|     // 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;
 | |
| }
 | |
| 
 | |
| void ESMReader::skipHRecord()
 | |
| {
 | |
|     if (!mCtx.leftFile)
 | |
|         return;
 | |
|     getRecHeader();
 | |
|     skipRecord();
 | |
| }
 | |
| 
 | |
| 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)
 | |
| {
 | |
|     int t = mEsm->read(x, size);
 | |
|     if (t != size)
 | |
|         fail("Read error");
 | |
| }
 | |
| 
 | |
| std::string ESMReader::getString(int size)
 | |
| {
 | |
|     char *ptr = ToUTF8::getBuffer(size);
 | |
|     mEsm->read(ptr, size);
 | |
| 
 | |
|     // Convert to UTF8 and return
 | |
|     return ToUTF8::getUtf8(mEncoding);
 | |
| }
 | |
| 
 | |
| 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.isNull())
 | |
|         ss << "\n  Offset: 0x" << hex << mEsm->tell();
 | |
|     throw std::runtime_error(ss.str());
 | |
| }
 | |
| 
 | |
| void ESMReader::setEncoding(const std::string& encoding)
 | |
| {
 | |
|   if (encoding == "win1250")
 | |
|   {
 | |
|     mEncoding = ToUTF8::WINDOWS_1250;
 | |
|   }
 | |
|   else if (encoding == "win1251")
 | |
|   {
 | |
|     mEncoding = ToUTF8::WINDOWS_1251;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     // Default Latin encoding
 | |
|     mEncoding = ToUTF8::WINDOWS_1252;
 | |
|   }
 | |
| }
 | |
| 
 | |
| }
 |