diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ec686c4e28..6543f40d21 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -211,6 +211,7 @@ target_link_libraries(openmw-cs ${OGRE_Overlay_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${ESM4_LIBRARIES} ${ZLIB_LIBRARY} ${MURMURHASH_LIBRARIES} ${BSAOPTHASH_LIBRARIES} diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index d723ddaacd..1f9ebfe732 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -8,10 +8,13 @@ #include #include +#include #include #include #include +#include + #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -933,9 +936,24 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mReader = new ESM::ESMReader; mReader->setEncoder (&mEncoder); - mReader->setIndex(mReaderIndex++); + mReader->setIndex(mReaderIndex++); // NOTE: auto increment mReader->open (path.string()); + int esmVer = mReader->getVer(); + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_17; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + if (isTes4 || isTes5 || isFONV) + { + mReader->close(); + delete mReader; + mReader = new ESM::ESM4Reader(isTes4); // TES4 headers are 4 bytes shorter + mReader->setEncoder(&mEncoder); + mReader->setIndex(mReaderIndex-1); // use the same index + static_cast(mReader)->reader().setModIndex(mReaderIndex-1); + static_cast(mReader)->openTes4File(path.string()); + static_cast(mReader)->reader().updateModIndicies(mLoadedFiles); + } mLoadedFiles.push_back(path.filename().string()); // at this point mReader->mHeader.mMaster have been populated for the file being loaded @@ -986,6 +1004,16 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (!mReader) throw std::logic_error ("can't continue loading, because no load has been started"); + int esmVer = mReader->getVer(); + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_17; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + // Check if previous record/group was the final one in this group. Must be done before + // calling mReader->hasMoreRecs() below, because all records may have been processed when + // the previous group is popped off the stack. + if (isTes4 || isTes5 || isFONV) + static_cast(mReader)->reader().checkGroupStatus(); + if (!mReader->hasMoreRecs()) { if (mBase) @@ -1005,6 +1033,9 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) return true; } + if (isTes4 || isTes5 || isFONV) + return loadTes4Group(messages); + ESM::NAME n = mReader->getRecName(); mReader->getRecHeader(); @@ -1278,3 +1309,127 @@ const CSMWorld::Data& CSMWorld::Data::self () { return *this; } +bool CSMWorld::Data::loadTes4Group (CSMDoc::Messages& messages) +{ + ESM4::Reader& reader = static_cast(mReader)->reader(); + + // check for EOF, sometimes there is a empty group at the end e.g. FONV DeadMoney.esm + if (!reader.getRecordHeader() || !mReader->hasMoreRecs()) + return false; + + const ESM4::RecordHeader& hdr = reader.hdr(); + + if (hdr.record.typeId != ESM4::REC_GRUP) + return loadTes4Record(hdr, messages); + + // Skip groups that are of no interest. See also: + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format#Hierarchical_Top_Groups + switch (hdr.group.type) + { + case ESM4::Grp_RecordType: + { + // FIXME: rewrite to workaround reliability issue + if (hdr.group.label.value == ESM4::REC_NAVI || hdr.group.label.value == ESM4::REC_WRLD || + hdr.group.label.value == ESM4::REC_REGN || hdr.group.label.value == ESM4::REC_STAT || + hdr.group.label.value == ESM4::REC_ANIO || hdr.group.label.value == ESM4::REC_CONT || + hdr.group.label.value == ESM4::REC_MISC || hdr.group.label.value == ESM4::REC_ACTI || + hdr.group.label.value == ESM4::REC_ARMO || hdr.group.label.value == ESM4::REC_NPC_ || + hdr.group.label.value == ESM4::REC_FLOR || hdr.group.label.value == ESM4::REC_GRAS || + hdr.group.label.value == ESM4::REC_TREE || hdr.group.label.value == ESM4::REC_LIGH || + hdr.group.label.value == ESM4::REC_BOOK || hdr.group.label.value == ESM4::REC_FURN || + hdr.group.label.value == ESM4::REC_SOUN || hdr.group.label.value == ESM4::REC_WEAP || + hdr.group.label.value == ESM4::REC_DOOR || hdr.group.label.value == ESM4::REC_AMMO || + hdr.group.label.value == ESM4::REC_CLOT || hdr.group.label.value == ESM4::REC_ALCH || + hdr.group.label.value == ESM4::REC_APPA || hdr.group.label.value == ESM4::REC_INGR || + hdr.group.label.value == ESM4::REC_SGST || hdr.group.label.value == ESM4::REC_SLGM || + hdr.group.label.value == ESM4::REC_KEYM || hdr.group.label.value == ESM4::REC_HAIR || + hdr.group.label.value == ESM4::REC_EYES || hdr.group.label.value == ESM4::REC_CELL || + hdr.group.label.value == ESM4::REC_CREA || hdr.group.label.value == ESM4::REC_LVLC || + hdr.group.label.value == ESM4::REC_LVLI || hdr.group.label.value == ESM4::REC_MATO || + hdr.group.label.value == ESM4::REC_IDLE || hdr.group.label.value == ESM4::REC_LTEX + ) + { + // NOTE: The label field of a group is not reliable. See: + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format + // + // ASCII Q 0x51 0101 0001 + // A 0x41 0100 0001 + // + // Ignore flag 0000 1000 (i.e. probably unrelated) + // + // Workaround by getting the record header and checking its typeId + reader.saveGroupStatus(); + // FIXME: comment may no longer be releavant + loadTes4Group(messages); // CELL group with record type may have sub-groups + } + else + { + //std::cout << "Skipping group... " // FIXME: testing only + //<< ESM4::printLabel(hdr.group.label, hdr.group.type) << std::endl; + + reader.skipGroup(); + return false; + } + + break; + } + case ESM4::Grp_CellChild: + { + reader.adjustGRUPFormId(); // not needed or even shouldn't be done? (only labels anyway) + reader.saveGroupStatus(); + if (!mReader->hasMoreRecs()) + return false; // may have been an empty group followed by EOF + + loadTes4Group(messages); + + break; + } + case ESM4::Grp_WorldChild: + case ESM4::Grp_TopicChild: + // FIXME: need to save context if skipping + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + { + reader.adjustGRUPFormId(); // not needed or even shouldn't be done? (only labels anyway) + reader.saveGroupStatus(); + if (!mReader->hasMoreRecs()) + return false; // may have been an empty group followed by EOF + + loadTes4Group(messages); + + break; + } + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + { + reader.saveGroupStatus(); + loadTes4Group(messages); + + break; + } + default: + break; + } + + return false; +} + +// Deal with Tes4 records separately, as some have the same name as Tes3, e.g. REC_CELL +bool CSMWorld::Data::loadTes4Record (const ESM4::RecordHeader& hdr, CSMDoc::Messages& messages) +{ + ESM4::Reader& reader = static_cast(mReader)->reader(); + + switch (hdr.record.typeId) + { + + // FIXME: removed for now + + default: + reader.skipRecordData(); + } + + return false; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 47046d9de3..998e2866a9 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -57,6 +57,11 @@ namespace ESM struct Dialogue; } +namespace ESM4 +{ + union RecordHeader; +} + namespace CSMWorld { class ResourcesManager; @@ -127,6 +132,9 @@ namespace CSMWorld const Data& self (); + bool loadTes4Group (CSMDoc::Messages& messages); + bool loadTes4Record (const ESM4::RecordHeader& hdr, CSMDoc::Messages& messages); + public: Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager);