mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-03 23:56:43 +00:00 
			
		
		
		
	Finished ESM header decoder
This commit is contained in:
		
							parent
							
								
									aa6a30edeb
								
							
						
					
					
						commit
						f803830d03
					
				
					 1 changed files with 160 additions and 19 deletions
				
			
		
							
								
								
									
										179
									
								
								esm/reader.cpp
									
									
									
									
									
								
							
							
						
						
									
										179
									
								
								esm/reader.cpp
									
									
									
									
									
								
							| 
						 | 
					@ -12,6 +12,9 @@ using namespace std;
 | 
				
			||||||
/* A structure used for holding fixed-length strings. In the case of
 | 
					/* 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
 | 
					   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.
 | 
					   number, therefore the struct is implemented as a union with an int.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   TODO: Merge with SliceArray, find how to do string-specific
 | 
				
			||||||
 | 
					   template specializations in a useful manner.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
template <int LEN>
 | 
					template <int LEN>
 | 
				
			||||||
union NAME_T
 | 
					union NAME_T
 | 
				
			||||||
| 
						 | 
					@ -42,6 +45,7 @@ union NAME_T
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef NAME_T<4> NAME;
 | 
					typedef NAME_T<4> NAME;
 | 
				
			||||||
typedef NAME_T<32> NAME32;
 | 
					typedef NAME_T<32> NAME32;
 | 
				
			||||||
 | 
					typedef NAME_T<64> NAME64;
 | 
				
			||||||
typedef NAME_T<256> NAME256;
 | 
					typedef NAME_T<256> NAME256;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ESMReader
 | 
					class ESMReader
 | 
				
			||||||
| 
						 | 
					@ -53,6 +57,9 @@ class ESMReader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  NAME recName, subName;
 | 
					  NAME recName, subName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // True if subName has been read but not used.
 | 
				
			||||||
 | 
					  bool subCached;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#pragma pack(push)
 | 
					#pragma pack(push)
 | 
				
			||||||
#pragma pack(1)
 | 
					#pragma pack(1)
 | 
				
			||||||
  struct HEDRstruct
 | 
					  struct HEDRstruct
 | 
				
			||||||
| 
						 | 
					@ -67,9 +74,19 @@ class ESMReader
 | 
				
			||||||
    NAME256 desc;       // File description
 | 
					    NAME256 desc;       // File description
 | 
				
			||||||
    int records;        // Number of records? Not used.
 | 
					    int records;        // Number of records? Not used.
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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)
 | 
					#pragma pack(pop)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  HEDRstruct header;
 | 
					  HEDRstruct header;
 | 
				
			||||||
 | 
					  SaveData saveData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
  enum Version
 | 
					  enum Version
 | 
				
			||||||
| 
						 | 
					@ -80,9 +97,9 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  enum FileType
 | 
					  enum FileType
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      FT_ESP = 0,
 | 
					      FT_ESP = 0,       // Plugin
 | 
				
			||||||
      FT_ESM = 1,
 | 
					      FT_ESM = 1,       // Master
 | 
				
			||||||
      FT_ESS = 32
 | 
					      FT_ESS = 32       // Savegame
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void open(Mangle::Stream::StreamPtr _esm, const string &name)
 | 
					  void open(Mangle::Stream::StreamPtr _esm, const string &name)
 | 
				
			||||||
| 
						 | 
					@ -92,6 +109,7 @@ public:
 | 
				
			||||||
    leftFile = esm->size();
 | 
					    leftFile = esm->size();
 | 
				
			||||||
    leftRec = 0;
 | 
					    leftRec = 0;
 | 
				
			||||||
    leftSub = 0;
 | 
					    leftSub = 0;
 | 
				
			||||||
 | 
					    subCached = false;
 | 
				
			||||||
    recName.val = 0;
 | 
					    recName.val = 0;
 | 
				
			||||||
    subName.val = 0;
 | 
					    subName.val = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,16 +129,38 @@ public:
 | 
				
			||||||
       header.version != VER_13)
 | 
					       header.version != VER_13)
 | 
				
			||||||
      fail("Unsupported file format version");
 | 
					      fail("Unsupported file format version");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cout << "Author: " << header.author.toString() << endl;
 | 
					 | 
				
			||||||
    cout << "Description: " << header.desc.toString() << endl;
 | 
					    cout << "Description: " << header.desc.toString() << endl;
 | 
				
			||||||
 | 
					    cout << "Author: " << header.author.toString() << endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while(isNextSub("MAST"))
 | 
					    while(isNextSub("MAST"))
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        // TODO: read master data here
 | 
					        cout << "Master: " << getHString() << endl;
 | 
				
			||||||
        skipHSub();
 | 
					        cout << "  size: " << getHNLong("DATA") << endl;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Read extra savegame data
 | 
					    if(header.type == FT_ESS)
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        // Savegame-related data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Player position etc
 | 
				
			||||||
 | 
					        getHNT(saveData, "GMDT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* 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 open(const string &file)
 | 
					  void open(const string &file)
 | 
				
			||||||
| 
						 | 
					@ -143,6 +183,13 @@ public:
 | 
				
			||||||
    getHT(x);
 | 
					    getHT(x);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int64_t getHNLong(const char *name)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    int64_t val;
 | 
				
			||||||
 | 
					    getHNT(val, name);
 | 
				
			||||||
 | 
					    return val;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get data of a given type/size, including subrecord header
 | 
					  // Get data of a given type/size, including subrecord header
 | 
				
			||||||
  template <typename X>
 | 
					  template <typename X>
 | 
				
			||||||
  void getHT(X &x)
 | 
					  void getHT(X &x)
 | 
				
			||||||
| 
						 | 
					@ -153,9 +200,30 @@ public:
 | 
				
			||||||
    getT(x);
 | 
					    getT(x);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  string 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(leftSub == 0)
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        // Skip the following zero byte
 | 
				
			||||||
 | 
					        leftRec--;
 | 
				
			||||||
 | 
					        char c;
 | 
				
			||||||
 | 
					        esm->read(&c,1);
 | 
				
			||||||
 | 
					        return "";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return getString(leftSub);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /*************************************************************************
 | 
					  /*************************************************************************
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   *  Low level reading methods
 | 
					   *  Low level sub-record methods
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   *************************************************************************/
 | 
					   *************************************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -167,26 +235,58 @@ public:
 | 
				
			||||||
      fail("Expected subrecord " + string(name) + " but got " + subName.toString());
 | 
					      fail("Expected subrecord " + string(name) + " but got " + subName.toString());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get the next record name
 | 
					  /** Checks if the next sub record name matches the parameter. If it
 | 
				
			||||||
  NAME getRecName()
 | 
					      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)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    if(!hasMoreRecs())
 | 
					    if(!leftRec) return false;
 | 
				
			||||||
      fail("No more records, getRecName() failed");
 | 
					
 | 
				
			||||||
    getName(recName);
 | 
					    getSubName();
 | 
				
			||||||
    leftFile -= 4;
 | 
					
 | 
				
			||||||
    return recName;
 | 
					    // If the name didn't match, then mark the it as 'cached' so it's
 | 
				
			||||||
 | 
					    // available for the next call to getSubName.
 | 
				
			||||||
 | 
					    subCached = (subName != name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If subCached is false, then subName == name.
 | 
				
			||||||
 | 
					    return !subCached;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Read subrecord name. I've optimized this slightly, since it gets
 | 
					  // Read subrecord name. This gets called a LOT, so I've optimized it
 | 
				
			||||||
  // called a LOT.
 | 
					  // slightly.
 | 
				
			||||||
  void getSubName()
 | 
					  void getSubName()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					      // If the name has already been read, do nothing
 | 
				
			||||||
 | 
					      if(subCached)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          subCached = false;
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Don't bother with error checking, we will catch an EOF upon
 | 
					      // Don't bother with error checking, we will catch an EOF upon
 | 
				
			||||||
      // reading the subrecord data anyway.
 | 
					      // reading the subrecord data anyway.
 | 
				
			||||||
      esm->read(subName.name, 4);
 | 
					      esm->read(subName.name, 4);
 | 
				
			||||||
      leftRec -= 4;
 | 
					      leftRec -= 4;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Skip current sub record, including header (but not including
 | 
				
			||||||
 | 
					  // name.)
 | 
				
			||||||
 | 
					  void skipHSub()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      getSubHeader();
 | 
				
			||||||
 | 
					      esm->seek(esm->tell()+leftSub);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Skip sub record and check its size
 | 
				
			||||||
 | 
					  void skipHSubSize(int size)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      skipHSub();
 | 
				
			||||||
 | 
					      if(leftSub != size)
 | 
				
			||||||
 | 
						fail("skipHSubSize() mismatch");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /* Sub-record head This updates leftRec beyond the current
 | 
					  /* Sub-record head This updates leftRec beyond the current
 | 
				
			||||||
     sub-record as well. leftSub contains size of current sub-record.
 | 
					     sub-record as well. leftSub contains size of current sub-record.
 | 
				
			||||||
  */
 | 
					  */
 | 
				
			||||||
| 
						 | 
					@ -195,16 +295,34 @@ public:
 | 
				
			||||||
      if(leftRec < 4)
 | 
					      if(leftRec < 4)
 | 
				
			||||||
	fail("End of record while reading sub-record header");
 | 
						fail("End of record while reading sub-record header");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Get subrecord size
 | 
				
			||||||
      getT(leftSub);
 | 
					      getT(leftSub);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Adjust number of record bytes left
 | 
					      // Adjust number of record bytes left
 | 
				
			||||||
      leftRec -= leftSub + 4;
 | 
					      leftRec -= leftSub + 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Check that sizes add up
 | 
					      // Check that sizes added up
 | 
				
			||||||
      if(leftRec < 0)
 | 
					      if(leftRec < 0)
 | 
				
			||||||
	fail("Not enough bytes left in record for this subrecord.");
 | 
						fail("Not enough bytes left in record for this subrecord.");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*************************************************************************
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   *  Low level record methods
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   *************************************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Get the next record name
 | 
				
			||||||
 | 
					  NAME getRecName()
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    if(!hasMoreRecs())
 | 
				
			||||||
 | 
					      fail("No more records, getRecName() failed");
 | 
				
			||||||
 | 
					    getName(recName);
 | 
				
			||||||
 | 
					    leftFile -= 4;
 | 
				
			||||||
 | 
					    return recName;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /* Read record header. This updatesleftFile BEYOND the data that
 | 
					  /* Read record header. This updatesleftFile BEYOND the data that
 | 
				
			||||||
     follows the header, ie beyond the entire record. You should use
 | 
					     follows the header, ie beyond the entire record. You should use
 | 
				
			||||||
     leftRec to orient yourself inside the record itself.
 | 
					     leftRec to orient yourself inside the record itself.
 | 
				
			||||||
| 
						 | 
					@ -232,11 +350,34 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool hasMoreRecs() { return leftFile > 0; }
 | 
					  bool hasMoreRecs() { return leftFile > 0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*************************************************************************
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   *  Lowest level data reading and misc methods
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   *************************************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  template <typename X>
 | 
					  template <typename X>
 | 
				
			||||||
  void getT(X &x) { esm->read(&x, sizeof(X)); }
 | 
					  void getT(X &x)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    int t = esm->read(&x, sizeof(X));
 | 
				
			||||||
 | 
					    if(t != sizeof(X))
 | 
				
			||||||
 | 
					      fail("Read error");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  void getName(NAME &name) { getT(name); }
 | 
					  void getName(NAME &name) { getT(name); }
 | 
				
			||||||
  void getUint(uint32_t &u) { getT(u); }
 | 
					  void getUint(uint32_t &u) { getT(u); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Read the next size bytes and return them as a string
 | 
				
			||||||
 | 
					  std::string getString(int size)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    // Not very optimized, but we'll fix that later
 | 
				
			||||||
 | 
					    char *ptr = new char[size];
 | 
				
			||||||
 | 
					    esm->read(ptr,size);
 | 
				
			||||||
 | 
					    string res(ptr,size);
 | 
				
			||||||
 | 
					    delete[] ptr;
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Used for error handling
 | 
					  /// Used for error handling
 | 
				
			||||||
  void fail(const std::string &msg)
 | 
					  void fail(const std::string &msg)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue