Little more work on ESM reader

actorid
Nicolay Korslund 15 years ago
parent 7c9b1adc8d
commit aa6a30edeb

@ -1,6 +1,7 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <stdint.h> #include <stdint.h>
#include <string.h>
#include <assert.h> #include <assert.h>
using namespace std; using namespace std;
@ -35,6 +36,8 @@ union NAME_T
bool operator==(int v) { return v == val; } bool operator==(int v) { return v == val; }
bool operator!=(int v) { return v != val; } bool operator!=(int v) { return v != val; }
string toString() { return string(name, strnlen(name, LEN)); }
}; };
typedef NAME_T<4> NAME; typedef NAME_T<4> NAME;
@ -45,29 +48,79 @@ class ESMReader
{ {
Mangle::Stream::StreamPtr esm; Mangle::Stream::StreamPtr esm;
size_t leftFile; size_t leftFile;
uint32_t leftRec; uint32_t leftRec, leftSub;
string filename; string filename;
NAME recName, subName; NAME recName, subName;
#pragma pack(push)
#pragma pack(1)
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.
};
#pragma pack(pop)
HEDRstruct header;
public: public:
enum Version
{
VER_12 = 0x3f99999a,
VER_13 = 0x3fa66666
};
enum FileType
{
FT_ESP = 0,
FT_ESM = 1,
FT_ESS = 32
};
void open(Mangle::Stream::StreamPtr _esm, const string &name) void open(Mangle::Stream::StreamPtr _esm, const string &name)
{ {
esm = _esm; esm = _esm;
filename = name; filename = name;
leftFile = esm->size(); leftFile = esm->size();
leftRec = 0; leftRec = 0;
leftSub = 0;
recName.val = 0; recName.val = 0;
subName.val = 0; subName.val = 0;
cout << "left: " << leftFile << endl; // TODO: determine special file status from file name
if(getRecName() != "TES3") if(getRecName() != "TES3")
fail("Not a valid Morrowind file"); fail("Not a valid Morrowind file");
cout << "left: " << leftFile << endl;
// The flags are always zero // The flags are always zero
uint32_t flags; uint32_t flags;
getRecHeader(flags); getRecHeader(flags);
// Get the header
getHNT(header, "HEDR");
if(header.version != VER_12 &&
header.version != VER_13)
fail("Unsupported file format version");
cout << "Author: " << header.author.toString() << endl;
cout << "Description: " << header.desc.toString() << endl;
while(isNextSub("MAST"))
{
// TODO: read master data here
skipHSub();
}
// TODO: Read extra savegame data
} }
void open(const string &file) void open(const string &file)
@ -76,12 +129,45 @@ public:
open(StreamPtr(new FileStream(file)), file); open(StreamPtr(new FileStream(file)), 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);
}
// Get data of a given type/size, including subrecord header
template <typename X>
void getHT(X &x)
{
getSubHeader();
if(leftSub != sizeof(X))
fail("getHT(): subrecord size mismatch");
getT(x);
}
/************************************************************************* /*************************************************************************
* *
* Low level reading methods * Low level reading methods
* *
*************************************************************************/ *************************************************************************/
// Get the next subrecord name and check if it matches the parameter
void getSubNameIs(const char* name)
{
getSubName();
if(subName != name)
fail("Expected subrecord " + string(name) + " but got " + subName.toString());
}
// Get the next record name
NAME getRecName() NAME getRecName()
{ {
if(!hasMoreRecs()) if(!hasMoreRecs())
@ -91,6 +177,34 @@ public:
return recName; return recName;
} }
// Read subrecord name. I've optimized this slightly, since it gets
// called a LOT.
void getSubName()
{
// Don't bother with error checking, we will catch an EOF upon
// reading the subrecord data anyway.
esm->read(subName.name, 4);
leftRec -= 4;
}
/* Sub-record head This updates leftRec beyond the current
sub-record as well. leftSub contains size of current sub-record.
*/
void getSubHeader()
{
if(leftRec < 4)
fail("End of record while reading sub-record header");
getT(leftSub);
// Adjust number of record bytes left
leftRec -= leftSub + 4;
// Check that sizes add up
if(leftRec < 0)
fail("Not enough bytes left in record for this subrecord.");
}
/* 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.
@ -118,16 +232,18 @@ public:
bool hasMoreRecs() { return leftFile > 0; } bool hasMoreRecs() { return leftFile > 0; }
void getName(NAME &name) { esm->read(&name,4); } template <typename X>
void getUint(uint32_t &u) { esm->read(&u,4); } void getT(X &x) { esm->read(&x, sizeof(X)); }
void getName(NAME &name) { getT(name); }
void getUint(uint32_t &u) { getT(u); }
/// Used for error handling /// Used for error handling
void fail(const std::string &msg) void fail(const std::string &msg)
{ {
std::string err = "ESM Error: " + msg; std::string err = "ESM Error: " + msg;
err += "\n File: " + filename; err += "\n File: " + filename;
err += "\n Record: " + string(recName.name,4); err += "\n Record: " + recName.toString();
err += "\n Subrecord: " + string(subName.name,4); err += "\n Subrecord: " + subName.toString();
throw str_exception(err); throw str_exception(err);
} }
}; };
@ -135,7 +251,10 @@ public:
int main(int argc, char**argv) int main(int argc, char**argv)
{ {
if(argc != 2) if(argc != 2)
{
cout << "Specify an ES file\n";
return 1; return 1;
}
ESMReader esm; ESMReader esm;
esm.open(argv[1]); esm.open(argv[1]);

Loading…
Cancel
Save