diff --git a/esm/.gitignore b/esm/.gitignore index 3f74ae3340..be5edceaaf 100644 --- a/esm/.gitignore +++ b/esm/.gitignore @@ -1,4 +1,2 @@ old -*.esm -*.esp -reader +*.d diff --git a/esm/Makefile b/esm/Makefile deleted file mode 100644 index 9b51817ea9..0000000000 --- a/esm/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -all: reader - -reader: reader.cpp ../tools/stringops.cpp diff --git a/esm/reader.cpp b/esm/esm_reader.hpp similarity index 77% rename from esm/reader.cpp rename to esm/esm_reader.hpp index 3e16ddb7be..c552cd102a 100644 --- a/esm/reader.cpp +++ b/esm/esm_reader.hpp @@ -1,14 +1,15 @@ -#include +#ifndef _ESM_READER_H +#define _ESM_READER_H + #include #include #include #include -using namespace std; +#include #include "../mangle/stream/stream.h" #include "../mangle/stream/servers/file_stream.h" #include "../mangle/tools/str_exception.h" - #include "../tools/stringops.h" /* A structure used for holding fixed-length strings. In the case of @@ -30,16 +31,16 @@ union NAME_T } bool operator!=(const char *str) { return !((*this)==str); } - bool operator==(const string &str) + bool operator==(const std::string &str) { return (*this) == str.c_str(); } - bool operator!=(const string &str) { return !((*this)==str); } + bool operator!=(const std::string &str) { return !((*this)==str); } bool operator==(int v) { return v == val; } bool operator!=(int v) { return v != val; } - string toString() { return string(name, strnlen(name, LEN)); } + std::string toString() { return std::string(name, strnlen(name, LEN)); } }; typedef NAME_T<4> NAME; @@ -49,15 +50,13 @@ typedef NAME_T<256> NAME256; class ESMReader { - Mangle::Stream::StreamPtr esm; - size_t leftFile; - uint32_t leftRec, leftSub; - string filename; - - NAME recName, subName; +public: - // True if subName has been read but not used. - bool subCached; + /************************************************************************* + * + * Public type definitions + * + *************************************************************************/ #pragma pack(push) #pragma pack(1) @@ -74,6 +73,14 @@ class ESMReader int records; // Number of records? Not used. }; + // Defines another files (esm or esp) that this file depends upon. + struct MasterData + { + std::string name; + uint64_t size; + }; + + // Data that is only present in save game files struct SaveData { float pos[6]; // Player position and rotation @@ -84,11 +91,8 @@ class ESMReader }; #pragma pack(pop) - HEDRstruct header; - SaveData saveData; - int spf; // Special file signifier (see SpecialFile below) + typedef std::vector MasterList; -public: enum Version { VER_12 = 0x3f99999a, @@ -113,16 +117,48 @@ public: SF_Bloodmoon }; - void open(Mangle::Stream::StreamPtr _esm, const string &name) + /************************************************************************* + * + * Information retrieval + * + *************************************************************************/ + + int getVer() { return header.version; } + float getFVer() { return *((float*)&header.version); } + int getSpecial() { return spf; } + const std::string getAuthor() { return header.author.toString(); } + const std::string getDesc() { return header.desc.toString(); } + const SaveData &getSaveData() { return saveData; } + const MasterList &getMasters() { return masters; } + + + /************************************************************************* + * + * Opening and closing + * + *************************************************************************/ + + /// Close the file, resets all information. After calling close() + /// the structure may be reused to load a new file. + void close() { - esm = _esm; - filename = name; - leftFile = esm->size(); + esm.reset(); + filename.clear(); + leftFile = 0; leftRec = 0; leftSub = 0; subCached = false; recName.val = 0; subName.val = 0; + } + + /// Load ES file from a new stream. Calls close() automatically. + void open(Mangle::Stream::StreamPtr _esm, const std::string &name) + { + close(); + esm = _esm; + filename = name; + leftFile = esm->size(); // Flag certain files for special treatment, based on the file // name. @@ -135,9 +171,7 @@ public: if(getRecName() != "TES3") fail("Not a valid Morrowind file"); - // The flags are always zero - uint32_t flags; - getRecHeader(flags); + getRecHeader(); // Get the header getHNT(header, "HEDR"); @@ -146,13 +180,12 @@ public: header.version != VER_13) fail("Unsupported file format version"); - cout << "Description: " << header.desc.toString() << endl; - cout << "Author: " << header.author.toString() << endl; - while(isNextSub("MAST")) { - cout << "Master: " << getHString() << endl; - cout << " size: " << getHNLong("DATA") << endl; + MasterData m; + m.name = getHString(); + m.size = getHNLong("DATA"); + masters.push_back(m); } if(header.type == FT_ESS) @@ -180,7 +213,7 @@ public: } } - void open(const string &file) + void open(const std::string &file) { using namespace Mangle::Stream; open(StreamPtr(new FileStream(file)), file); @@ -217,7 +250,13 @@ public: getT(x); } - string getHString() + std::string getHNString(const char* name) + { + getSubNameIs(name); + return getHString(); + } + + std::string getHString() { getSubHeader(); @@ -249,7 +288,7 @@ public: { getSubName(); if(subName != name) - fail("Expected subrecord " + string(name) + " but got " + subName.toString()); + fail("Expected subrecord " + std::string(name) + " but got " + subName.toString()); } /** Checks if the next sub record name matches the parameter. If it @@ -293,7 +332,7 @@ public: void skipHSub() { getSubHeader(); - esm->seek(esm->tell()+leftSub); + skip(leftSub); } // Skip sub record and check its size @@ -340,10 +379,27 @@ public: return recName; } + // Skip the rest of this record. Assumes the name and header have + // already been read + void skipRecord() + { + skip(leftRec); + leftRec = 0; + } + + // Skip an entire record, including the header (but not the name) + void skipHRecord() + { + if(!leftFile) return; + getRecHeader(); + skipRecord(); + } + /* 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. */ + void getRecHeader() { uint32_t u; getRecHeader(u); } void getRecHeader(uint32_t &flags) { // General error checking @@ -390,11 +446,13 @@ public: // Not very optimized, but we'll fix that later char *ptr = new char[size]; esm->read(ptr,size); - string res(ptr,size); + std::string res(ptr,size); delete[] ptr; return res; } + void skip(int bytes) { esm->seek(esm->tell()+bytes); } + /// Used for error handling void fail(const std::string &msg) { @@ -404,18 +462,22 @@ public: err += "\n Subrecord: " + subName.toString(); throw str_exception(err); } -}; -int main(int argc, char**argv) -{ - if(argc != 2) - { - cout << "Specify an ES file\n"; - return 1; - } +private: + Mangle::Stream::StreamPtr esm; + size_t leftFile; + uint32_t leftRec, leftSub; + std::string filename; - ESMReader esm; - esm.open(argv[1]); + NAME recName, subName; + + // True if subName has been read but not used. + bool subCached; + + HEDRstruct header; + SaveData saveData; + MasterList masters; + int spf; // Special file signifier (see SpecialFile below) +}; - return 0; -} +#endif diff --git a/esm/loadbody.hpp b/esm/loadbody.hpp new file mode 100644 index 0000000000..5417f5c707 --- /dev/null +++ b/esm/loadbody.hpp @@ -0,0 +1,59 @@ +#ifndef _ESM_BODY_H +#define _ESM_BODY_H + +#include "esm_reader.hpp" + +struct BodyPart +{ + enum MeshPart + { + MP_Head = 0, + MP_Hair = 1, + MP_Neck = 2, + MP_Chest = 3, + MP_Groin = 4, + MP_Hand = 5, + MP_Wrist = 6, + MP_Forearm = 7, + MP_Upperarm = 8, + MP_Foot = 9, + MP_Ankle = 10, + MP_Knee = 11, + MP_Upperleg = 12, + MP_Clavicle = 13, + MP_Tail = 14 + }; + + enum Flags + { + BPF_Female = 1, + BPF_Playable = 2 + }; + + enum MeshType + { + MT_Skin = 0, + MT_Clothing = 1, + MT_Armor = 2 + }; + + struct BYDTstruct + { + char part; + char vampire; + char flags; + char type; + }; + + BYDTstruct data; + std::string model, name; + + void load(ESMReader &esm) + { + model = esm.getHNString("MODL"); + name = esm.getHNString("FNAM"); + esm.getHNT(data, "BYDT"); + } +}; + +#endif diff --git a/esm/records.hpp b/esm/records.hpp new file mode 100644 index 0000000000..d395bcd922 --- /dev/null +++ b/esm/records.hpp @@ -0,0 +1,54 @@ +#ifndef _ESM_RECORDS_H +#define _ESM_RECORDS_H + +//#include "loadarmo.hpp" +#include "loadbody.hpp" + +// Integer versions of all the record names, used for faster lookup +enum RecNameInts + { + REC_ACTI = 0x49544341, + REC_ALCH = 0x48434c41, + REC_APPA = 0x41505041, + REC_ARMO = 0x4f4d5241, + REC_BODY = 0x59444f42, + REC_BOOK = 0x4b4f4f42, + REC_BSGN = 0x4e475342, + REC_CELL = 0x4c4c4543, + REC_CLAS = 0x53414c43, + REC_CLOT = 0x544f4c43, + REC_CONT = 0x544e4f43, + REC_CREA = 0x41455243, + REC_CREC = 0x43455243, + REC_DIAL = 0x4c414944, + REC_DOOR = 0x524f4f44, + REC_ENCH = 0x48434e45, + REC_FACT = 0x54434146, + REC_GLOB = 0x424f4c47, + REC_GMST = 0x54534d47, + REC_INFO = 0x4f464e49, + REC_INGR = 0x52474e49, + REC_LEVC = 0x4356454c, + REC_LEVI = 0x4956454c, + REC_LIGH = 0x4847494c, + REC_LOCK = 0x4b434f4c, + REC_LTEX = 0x5845544c, + REC_MGEF = 0x4645474d, + REC_MISC = 0x4353494d, + REC_NPC_ = 0x5f43504e, + REC_NPCC = 0x4343504e, + REC_PROB = 0x424f5250, + REC_RACE = 0x45434152, + REC_REGN = 0x4e474552, + REC_REPA = 0x41504552, + REC_SCPT = 0x54504353, + REC_SKIL = 0x4c494b53, + REC_SNDG = 0x47444e53, + REC_SOUN = 0x4e554f53, + REC_SPEL = 0x4c455053, + REC_SSCR = 0x52435353, + REC_STAT = 0x54415453, + REC_WEAP = 0x50414557 + }; + +#endif diff --git a/esm/tests/.gitignore b/esm/tests/.gitignore new file mode 100644 index 0000000000..93eb3f5388 --- /dev/null +++ b/esm/tests/.gitignore @@ -0,0 +1,5 @@ +*.esp +*.esm +*.ess +*_test +esmtool diff --git a/esm/tests/Makefile b/esm/tests/Makefile new file mode 100644 index 0000000000..61aeb1670b --- /dev/null +++ b/esm/tests/Makefile @@ -0,0 +1,7 @@ +all: esmtool + +esmtool: esmtool.cpp ../esm_reader.hpp ../records.hpp + g++ esmtool.cpp ../../tools/stringops.cpp -o $@ + +clean: + rm *_test esmtool diff --git a/esm/tests/esmtool.cpp b/esm/tests/esmtool.cpp new file mode 100644 index 0000000000..ac8a4880bb --- /dev/null +++ b/esm/tests/esmtool.cpp @@ -0,0 +1,54 @@ +#include "../esm_reader.hpp" +#include "../records.hpp" + +#include + +using namespace std; + +int main(int argc, char**argv) +{ + if(argc != 2) + { + cout << "Specify an ES file\n"; + return 1; + } + + ESMReader esm; + esm.open(argv[1]); + + cout << "\nFile: " << argv[1] << endl; + cout << "Author: " << esm.getAuthor() << endl; + cout << "Description: " << esm.getDesc() << endl; + cout << "File format version: " << esm.getFVer() << endl; + cout << "Special flag: " << esm.getSpecial() << endl; + cout << "Masters:\n"; + ESMReader::MasterList m = esm.getMasters(); + for(int i=0;i - WWW: http://openmw.snaptoad.com/ - - This file (loadbody.d) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -module esm.loadbody; -import esm.imports; - -/* - * Body part mesh. NPCs have several mesh files, one for each element - * in the enum below. One BODY record is given for each part. - */ - -struct BodyPart -{ - enum MeshType : byte - { - Head = 0, - Hair = 1, - Neck = 2, - Chest = 3, - Groin = 4, - Hand = 5, - Wrist = 6, - Forearm = 7, - Upperarm = 8, - Foot = 9, - Ankle = 10, - Knee = 11, - Upperleg = 12, - Clavicle = 13, - Tail = 14 - } - - enum Type : byte - { - Skin = 0, - Clothing = 1, - Armor = 2 - } - - enum Flags : byte - { - Female = 1, - Playable = 2 - } - - align(1) struct BYDTstruct - { - MeshType part; - byte vampire; - Flags flags; - Type type; - static assert(BYDTstruct.sizeof==4); - } - - BYDTstruct data; - - mixin LoadT; - - MeshIndex model; - - void load() - {with(esFile){ - model = getMesh(); - name = getHNString("FNAM"); - readHNExact(&data, data.sizeof, "BYDT"); - - // don't need to run makeProto here yet, no BodyPart monster - // class. - }} - -} - -ListID!(BodyPart) bodyParts;