mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 18:19:55 +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
|
||||
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.
|
||||
|
||||
TODO: Merge with SliceArray, find how to do string-specific
|
||||
template specializations in a useful manner.
|
||||
*/
|
||||
template <int LEN>
|
||||
union NAME_T
|
||||
|
@ -42,6 +45,7 @@ union NAME_T
|
|||
|
||||
typedef NAME_T<4> NAME;
|
||||
typedef NAME_T<32> NAME32;
|
||||
typedef NAME_T<64> NAME64;
|
||||
typedef NAME_T<256> NAME256;
|
||||
|
||||
class ESMReader
|
||||
|
@ -53,6 +57,9 @@ class ESMReader
|
|||
|
||||
NAME recName, subName;
|
||||
|
||||
// True if subName has been read but not used.
|
||||
bool subCached;
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
struct HEDRstruct
|
||||
|
@ -67,9 +74,19 @@ class ESMReader
|
|||
NAME256 desc; // File description
|
||||
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)
|
||||
|
||||
HEDRstruct header;
|
||||
SaveData saveData;
|
||||
|
||||
public:
|
||||
enum Version
|
||||
|
@ -80,9 +97,9 @@ public:
|
|||
|
||||
enum FileType
|
||||
{
|
||||
FT_ESP = 0,
|
||||
FT_ESM = 1,
|
||||
FT_ESS = 32
|
||||
FT_ESP = 0, // Plugin
|
||||
FT_ESM = 1, // Master
|
||||
FT_ESS = 32 // Savegame
|
||||
};
|
||||
|
||||
void open(Mangle::Stream::StreamPtr _esm, const string &name)
|
||||
|
@ -92,6 +109,7 @@ public:
|
|||
leftFile = esm->size();
|
||||
leftRec = 0;
|
||||
leftSub = 0;
|
||||
subCached = false;
|
||||
recName.val = 0;
|
||||
subName.val = 0;
|
||||
|
||||
|
@ -111,16 +129,38 @@ public:
|
|||
header.version != VER_13)
|
||||
fail("Unsupported file format version");
|
||||
|
||||
cout << "Author: " << header.author.toString() << endl;
|
||||
cout << "Description: " << header.desc.toString() << endl;
|
||||
cout << "Author: " << header.author.toString() << endl;
|
||||
|
||||
while(isNextSub("MAST"))
|
||||
{
|
||||
// TODO: read master data here
|
||||
skipHSub();
|
||||
cout << "Master: " << getHString() << endl;
|
||||
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)
|
||||
|
@ -143,6 +183,13 @@ public:
|
|||
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
|
||||
template <typename X>
|
||||
void getHT(X &x)
|
||||
|
@ -153,9 +200,30 @@ public:
|
|||
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());
|
||||
}
|
||||
|
||||
// Get the next record name
|
||||
NAME getRecName()
|
||||
/** 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)
|
||||
{
|
||||
if(!hasMoreRecs())
|
||||
fail("No more records, getRecName() failed");
|
||||
getName(recName);
|
||||
leftFile -= 4;
|
||||
return recName;
|
||||
if(!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.
|
||||
subCached = (subName != name);
|
||||
|
||||
// If subCached is false, then subName == name.
|
||||
return !subCached;
|
||||
}
|
||||
|
||||
// Read subrecord name. I've optimized this slightly, since it gets
|
||||
// called a LOT.
|
||||
// Read subrecord name. This gets called a LOT, so I've optimized it
|
||||
// slightly.
|
||||
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
|
||||
// reading the subrecord data anyway.
|
||||
esm->read(subName.name, 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 as well. leftSub contains size of current sub-record.
|
||||
*/
|
||||
|
@ -195,16 +295,34 @@ public:
|
|||
if(leftRec < 4)
|
||||
fail("End of record while reading sub-record header");
|
||||
|
||||
// Get subrecord size
|
||||
getT(leftSub);
|
||||
|
||||
// Adjust number of record bytes left
|
||||
leftRec -= leftSub + 4;
|
||||
|
||||
// Check that sizes add up
|
||||
// Check that sizes added up
|
||||
if(leftRec < 0)
|
||||
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
|
||||
follows the header, ie beyond the entire record. You should use
|
||||
leftRec to orient yourself inside the record itself.
|
||||
|
@ -232,11 +350,34 @@ public:
|
|||
|
||||
bool hasMoreRecs() { return leftFile > 0; }
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* Lowest level data reading and misc methods
|
||||
*
|
||||
*************************************************************************/
|
||||
|
||||
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 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
|
||||
void fail(const std::string &msg)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue