diff --git a/apps/esmtool/tes4.cpp b/apps/esmtool/tes4.cpp index f72175ec93..1b85c37c11 100644 --- a/apps/esmtool/tes4.cpp +++ b/apps/esmtool/tes4.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -69,6 +70,19 @@ namespace EsmTool template constexpr bool hasFormId = HasFormId::value; + template > + struct HasRefId : std::false_type + { + }; + + template + struct HasRefId> : std::true_type + { + }; + + template + constexpr bool hasRefId = HasRefId::value; + template > struct HasFlags : std::false_type { @@ -169,6 +183,8 @@ namespace EsmTool std::cout << "\n Record: " << ESM::NAME(reader.hdr().record.typeId).toStringView(); if constexpr (hasFormId) std::cout << "\n FormId: " << value.mFormId; + if constexpr (hasRefId) + std::cout << "\n FormId: " << value.mId; if constexpr (hasFlags) std::cout << "\n Record flags: " << recordFlags(value.mFlags); if constexpr (hasEditorId) @@ -182,60 +198,77 @@ namespace EsmTool std::cout << '\n'; } - void readRecord(const Params& params, ESM4::Reader& reader) + bool readRecord(const Params& params, ESM4::Reader& reader) { switch (static_cast(reader.hdr().record.typeId)) { case ESM4::REC_AACT: break; case ESM4::REC_ACHR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ACRE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ACTI: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ADDN: break; case ESM4::REC_ALCH: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ALOC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_AMMO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ANIO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_APPA: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ARMA: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ARMO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ARTO: break; case ESM4::REC_ASPC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ASTP: break; case ESM4::REC_AVIF: break; case ESM4::REC_BOOK: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_BPTD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CAMS: break; case ESM4::REC_CCRD: break; case ESM4::REC_CELL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CLAS: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CLFM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CLMT: break; case ESM4::REC_CLOT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CMNY: break; case ESM4::REC_COBJ: @@ -243,25 +276,30 @@ namespace EsmTool case ESM4::REC_COLL: break; case ESM4::REC_CONT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CPTH: break; case ESM4::REC_CREA: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CSTY: break; case ESM4::REC_DEBR: break; case ESM4::REC_DIAL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_DLBR: break; case ESM4::REC_DLVW: break; case ESM4::REC_DOBJ: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_DOOR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_DUAL: break; case ESM4::REC_ECZN: @@ -275,81 +313,103 @@ namespace EsmTool case ESM4::REC_EXPL: break; case ESM4::REC_EYES: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_FACT: break; case ESM4::REC_FLOR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_FLST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_FSTP: break; case ESM4::REC_FSTS: break; case ESM4::REC_FURN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_GLOB: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_GMST: break; case ESM4::REC_GRAS: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_GRUP: break; case ESM4::REC_HAIR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_HAZD: break; case ESM4::REC_HDPT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_IDLE: // FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm - // return readTypedRecord(params, reader); + // readTypedRecord(params, reader); + return true; break; case ESM4::REC_IDLM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_IMAD: break; case ESM4::REC_IMGS: break; case ESM4::REC_IMOD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_INFO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_INGR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_IPCT: break; case ESM4::REC_IPDS: break; case ESM4::REC_KEYM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_KYWD: break; case ESM4::REC_LAND: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LCRT: break; case ESM4::REC_LCTN: break; case ESM4::REC_LGTM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LIGH: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LSCR: break; case ESM4::REC_LTEX: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVLC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVLI: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVLN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVSP: break; case ESM4::REC_MATO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MATT: break; case ESM4::REC_MESG: @@ -357,49 +417,66 @@ namespace EsmTool case ESM4::REC_MGEF: break; case ESM4::REC_MISC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MOVT: break; case ESM4::REC_MSET: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MSTT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MUSC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MUST: break; case ESM4::REC_NAVI: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_NAVM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_NOTE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_NPC_: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_OTFT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PACK: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PERK: break; case ESM4::REC_PGRD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PGRE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PHZD: break; case ESM4::REC_PROJ: break; case ESM4::REC_PWAT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_QUST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_RACE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_REFR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_REGN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_RELA: break; case ESM4::REC_REVB: @@ -407,23 +484,30 @@ namespace EsmTool case ESM4::REC_RFCT: break; case ESM4::REC_ROAD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SBSP: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SCEN: break; case ESM4::REC_SCOL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SCPT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SCRL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SGST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SHOU: break; case ESM4::REC_SLGM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SMBN: break; case ESM4::REC_SMEN: @@ -433,97 +517,56 @@ namespace EsmTool case ESM4::REC_SNCT: break; case ESM4::REC_SNDR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SOPM: break; case ESM4::REC_SOUN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SPEL: break; case ESM4::REC_SPGD: break; case ESM4::REC_STAT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TACT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TERM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TES4: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TREE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TXST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_VTYP: break; case ESM4::REC_WATR: break; case ESM4::REC_WEAP: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_WOOP: break; case ESM4::REC_WRLD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_WTHR: break; } if (!params.mQuite) std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n'; - - reader.skipRecordData(); + return false; } - bool readItem(const Params& params, ESM4::Reader& reader); - - bool readGroup(const Params& params, ESM4::Reader& reader) - { - const ESM4::RecordHeader& header = reader.hdr(); - - if (!params.mQuite) - std::cout << "\nGroup: " << toString(static_cast(header.group.type)) << " " - << ESM::NAME(header.group.typeId).toStringView() << '\n'; - - switch (static_cast(header.group.type)) - { - case ESM4::Grp_RecordType: - case ESM4::Grp_InteriorCell: - case ESM4::Grp_InteriorSubCell: - case ESM4::Grp_ExteriorCell: - case ESM4::Grp_ExteriorSubCell: - reader.enterGroup(); - return readItem(params, reader); - case ESM4::Grp_WorldChild: - case ESM4::Grp_CellChild: - case ESM4::Grp_TopicChild: - case ESM4::Grp_CellPersistentChild: - case ESM4::Grp_CellTemporaryChild: - case ESM4::Grp_CellVisibleDistChild: - reader.adjustGRUPFormId(); - reader.enterGroup(); - if (!reader.hasMoreRecs()) - return false; - return readItem(params, reader); - } - - reader.skipGroup(); - - return true; - } - - bool readItem(const Params& params, ESM4::Reader& reader) - { - if (!reader.getRecordHeader() || !reader.hasMoreRecs()) - return false; - - const ESM4::RecordHeader& header = reader.hdr(); - - if (header.record.typeId == ESM4::REC_GRUP) - return readGroup(params, reader); - - readRecord(params, reader); - return true; - } } int loadTes4(const Arguments& info, std::unique_ptr&& stream) @@ -551,12 +594,15 @@ namespace EsmTool } } - while (reader.hasMoreRecs()) - { - reader.exitGroupCheck(); - if (!readItem(params, reader)) - break; - } + auto visitorRec = [¶ms](ESM4::Reader& reader) { return readRecord(params, reader); }; + auto visitorGroup = [¶ms](ESM4::Reader& reader) { + if (params.mQuite) + return; + auto groupType = static_cast(reader.hdr().group.type); + std::cout << "\nGroup: " << toString(groupType) << " " + << ESM::NAME(reader.hdr().group.typeId).toStringView() << '\n'; + }; + ESM4::ReaderUtils::readAll(reader, visitorRec, visitorGroup); } catch (const std::exception& e) { diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 3c1b6acc47..e0280c91b5 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -1,9 +1,14 @@ #include "esmloader.hpp" #include "esmstore.hpp" +#include + +#include #include #include +#include #include +#include namespace MWWorld { @@ -21,28 +26,47 @@ namespace MWWorld void EsmLoader::load(const std::filesystem::path& filepath, int& index, Loading::Listener* listener) { - const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast(index)); - reader->setEncoder(mEncoder); - reader->setIndex(index); - reader->open(filepath); - reader->resolveParentFileIndices(mReaders); + auto stream = Files::openBinaryInputFileStream(filepath); + const ESM::Format format = ESM::readFormat(*stream); + stream->seekg(0); + + switch (format) + { + case ESM::Format::Tes3: + { + const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast(index)); + reader->setEncoder(mEncoder); + reader->setIndex(index); + reader->open(filepath); + reader->resolveParentFileIndices(mReaders); - assert(reader->getGameFiles().size() == reader->getParentFileIndices().size()); - for (std::size_t i = 0, n = reader->getParentFileIndices().size(); i < n; ++i) - if (i == static_cast(reader->getIndex())) - throw std::runtime_error("File " + Files::pathToUnicodeString(reader->getName()) + " asks for parent file " + assert(reader->getGameFiles().size() == reader->getParentFileIndices().size()); + for (std::size_t i = 0, n = reader->getParentFileIndices().size(); i < n; ++i) + if (i == static_cast(reader->getIndex())) + throw std::runtime_error("File " + Files::pathToUnicodeString(reader->getName()) + " asks for parent file " + reader->getGameFiles()[i].name + ", but it is not available or has been loaded in the wrong order. " "Please run the launcher to fix this issue."); - mESMVersions[index] = reader->getVer(); - mStore.load(*reader, listener, mDialogue); + mESMVersions[index] = reader->getVer(); + mStore.load(*reader, listener, mDialogue); - if (!mMasterFileFormat.has_value() - && (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm") - || Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame"))) - mMasterFileFormat = reader->getFormat(); + if (!mMasterFileFormat.has_value() + && (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm") + || Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame"))) + mMasterFileFormat = reader->getFormat(); + break; + } + case ESM::Format::Tes4: + { + ESM4::Reader readerESM4(std::move(stream), filepath); + auto statelessEncoder = mEncoder->getStatelessEncoder(); + readerESM4.setEncoder(&statelessEncoder); + mStore.loadESM4(readerESM4); + break; + } + } } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 03de9fda5d..71f4e1e40d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -14,6 +14,11 @@ #include #include +#include +#include +#include +#include +#include #include #include "../mwmechanics/spelllist.hpp" @@ -180,6 +185,35 @@ namespace MWWorld } } } + + template + static bool typedReadRecordESM4(ESM4::Reader& reader, Store& store) + { + auto recordType = static_cast(reader.hdr().record.typeId); + + ESM::RecNameInts esm4RecName = static_cast(ESM::esm4Recname(recordType)); + if constexpr (std::is_convertible_v*, DynamicStore*> && HasRecordId::value) + { + if constexpr (ESM::isESM4Rec(T::sRecordId)) + { + if (T::sRecordId == esm4RecName) + { + reader.getRecordData(); + T value; + value.load(reader); + store.insertStatic(value); + return true; + } + } + } + return false; + } + + static bool readRecord(ESM4::Reader& reader, ESMStore& store) + { + return std::apply([&reader](auto&... x) { return (ESMStoreImp::typedReadRecordESM4(reader, x) || ...); }, + store.mStoreImp->mStores); + } }; int ESMStore::find(const ESM::RefId& id) const @@ -338,6 +372,12 @@ namespace MWWorld } } + void ESMStore::loadESM4(ESM4::Reader& reader) + { + auto visitorRec = [this](ESM4::Reader& reader) { return ESMStoreImp::readRecord(reader, *this); }; + ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {}); + } + void ESMStore::setIdType(const ESM::RefId& id, ESM::RecNameInts type) { mStoreImp->mIds[id] = type; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 7f5773c957..9d737c3f53 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -24,6 +24,14 @@ namespace MWMechanics class SpellList; } +namespace ESM4 +{ + class Reader; + struct Static; + struct Cell; + struct Reference; +} + namespace ESM { class ReadersCache; @@ -78,7 +86,7 @@ namespace MWWorld class ESMStore { friend struct ESMStoreImp; // This allows StoreImp to extend esmstore without beeing included everywhere - + public: using StoreTuple = std::tuple, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, @@ -95,8 +103,11 @@ namespace MWWorld Store, Store, // Special entry which is hardcoded and not loaded from an ESM - Store>; + Store, + Store, Store, Store>; + + private: template static constexpr std::size_t getTypeIndex() { @@ -162,6 +173,7 @@ namespace MWWorld void validateDynamic(); void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); + void loadESM4(ESM4::Reader& esm); template const Store& get() const @@ -252,6 +264,16 @@ namespace MWWorld template <> const ESM::NPC* ESMStore::insert(const ESM::NPC& npc); + + template > + struct HasRecordId : std::false_type + { + }; + + template + struct HasRecordId> : std::true_type + { + }; } #endif diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 11e58c0edb..bf8d656ecd 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1,18 +1,19 @@ #include "store.hpp" -#include +#include +#include +#include +#include #include #include #include - +#include +#include +#include #include #include -#include -#include -#include - namespace { // TODO: Switch to C++23 to get a working version of std::unordered_map::erase @@ -161,7 +162,15 @@ namespace MWWorld if (ptr == nullptr) { std::stringstream msg; - msg << T::getRecordType() << " '" << id << "' not found"; + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + msg << T::getRecordType(); + } + else + { + msg << "ESM::REC_" << getRecNameString(T::sRecordId).toStringView(); + } + msg << " '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; @@ -171,8 +180,10 @@ namespace MWWorld { T record; bool isDeleted = false; - - record.load(esm, isDeleted); + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + record.load(esm, isDeleted); + } std::pair inserted = mStatic.insert_or_assign(record.mId, record); if (inserted.second) @@ -292,9 +303,12 @@ namespace MWWorld { for (typename Dynamic::const_iterator iter(mDynamic.begin()); iter != mDynamic.end(); ++iter) { - writer.startRecord(T::sRecordId); - iter->second.save(writer); - writer.endRecord(T::sRecordId); + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + writer.startRecord(T::sRecordId); + iter->second.save(writer); + writer.endRecord(T::sRecordId); + } } } template @@ -302,8 +316,10 @@ namespace MWWorld { T record; bool isDeleted = false; - - record.load(reader, isDeleted); + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + record.load(reader, isDeleted); + } insert(record, overrideOnly); return RecordId(record.mId, isDeleted); @@ -1152,6 +1168,20 @@ namespace MWWorld return mKeywordSearch; } + + ESM::FixedString<6> getRecNameString(ESM::RecNameInts recName) + { + ESM::FixedString<6> name; + name.assign(""); + ESM::NAME fourCCName(recName & ~ESM::sEsm4RecnameFlag); + for (int i = 0; i < 4; i++) + name.mData[i] = fourCCName.mData[i]; + if (ESM::isESM4Rec(recName)) + { + name.mData[4] = '4'; + } + return name; + } } template class MWWorld::TypedDynamicStore; @@ -1196,3 +1226,7 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; + +template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index a6f869e362..6acfb6d41e 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -517,6 +517,8 @@ namespace MWWorld const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() const; }; + ESM::FixedString<6> getRecNameString(ESM::RecNameInts recName); + } // end namespace #endif diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index f869f0e448..d2163520f7 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -8,6 +8,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -289,6 +295,41 @@ TEST_F(StoreTest, delete_test) ASSERT_TRUE(mEsmStore.get().getSize() == 1); } +template +static unsigned int hasSameRecordId(const MWWorld::Store& store, ESM::RecNameInts RecName) +{ + if constexpr (MWWorld::HasRecordId::value) + { + return T::sRecordId == RecName ? 1 : 0; + } + else + { + return 0; + } +} + +template +static void testRecNameIntCount(const MWWorld::Store& store, const MWWorld::ESMStore::StoreTuple& stores) +{ + if constexpr (MWWorld::HasRecordId::value) + { + const unsigned int recordIdCount + = std::apply([](auto&&... x) { return (hasSameRecordId(x, T::sRecordId) + ...); }, stores); + ASSERT_EQ(recordIdCount, static_cast(1)) + << "The same RecNameInt is used twice ESM::REC_" << MWWorld::getRecNameString(T::sRecordId).toStringView(); + } +} + +static void testAllRecNameIntUnique(const MWWorld::ESMStore::StoreTuple& stores) +{ + std::apply([&stores](auto&&... x) { (testRecNameIntCount(x, stores), ...); }, stores); +} + +TEST_F(StoreTest, eachRecordTypeShouldHaveUniqueRecordId) +{ + testAllRecNameIntUnique(MWWorld::ESMStore::StoreTuple()); +} + /// Tests overwriting of records. TEST_F(StoreTest, overwrite_test) { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 243ea5841c..c1dbe36798 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -193,6 +193,7 @@ add_component_dir (esm4 reader reference script + readerutils ) add_component_dir (misc diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index f3e1ad640e..431b85b9ae 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -333,6 +333,11 @@ namespace ESM REC_MSET4 = esm4Recname(ESM4::REC_MSET) // Media Set }; + constexpr bool isESM4Rec(RecNameInts RecName) + { + return RecName & sEsm4RecnameFlag; + } + /// Common subrecords enum SubRecNameInts { diff --git a/components/esm/refid.cpp b/components/esm/refid.cpp index 08199df93d..854d403bd5 100644 --- a/components/esm/refid.cpp +++ b/components/esm/refid.cpp @@ -29,6 +29,11 @@ namespace ESM return newRefId; } + RefId RefId::formIdRefId(const ESM4::FormId id) + { + return ESM::RefId::stringRefId(ESM4::formIdToString(id)); + } + bool RefId::operator==(std::string_view rhs) const { return Misc::StringUtils::ciEqual(mId, rhs); diff --git a/components/esm/refid.hpp b/components/esm/refid.hpp index cd3bef27dd..c93ff4df02 100644 --- a/components/esm/refid.hpp +++ b/components/esm/refid.hpp @@ -6,6 +6,8 @@ #include #include +#include + namespace ESM { // RefId is used to represent an Id that identifies an ESM record. These Ids can then be used in @@ -27,6 +29,7 @@ namespace ESM // RefIds that are as string in the code. For serialization, and display. Using explicit conversions make it // very clear where in the code we need to convert from string to RefId and Vice versa. static RefId stringRefId(std::string_view id); + static RefId formIdRefId(const ESM4::FormId id); const std::string& getRefIdString() const { return mId; } private: diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 919c81b8e9..0b829d657f 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -32,12 +32,13 @@ #include #include // FLT_MAX for gcc -#include - #include // FIXME: debug only +#include #include "reader.hpp" -//#include "writer.hpp" +// #include "writer.hpp" + +#include // TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) // @@ -48,8 +49,9 @@ // longer/shorter/same as loading the subrecords. void ESM4::Cell::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.id; - reader.adjustFormId(mFormId); + auto formId = reader.hdr().record.id; + reader.adjustFormId(formId); + mId = ESM::RefId::formIdRefId(formId); mFlags = reader.hdr().record.flags; mParent = reader.currWorld(); @@ -71,7 +73,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) // WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading // cell child groups if we are loading them after restoring the context // (may be easier to update the context before saving?) - reader.setCurrCell(mFormId); // save for LAND (and other children) to access later + reader.setCurrCell(formId); // save for LAND (and other children) to access later std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp index ee77bd2bfc..49e5d048b0 100644 --- a/components/esm4/loadcell.hpp +++ b/components/esm4/loadcell.hpp @@ -34,6 +34,9 @@ #include "formid.hpp" #include "lighting.hpp" +#include +#include + namespace ESM4 { class Reader; @@ -61,7 +64,7 @@ namespace ESM4 { FormId mParent; // world formId (for grouping cells), from the loading sequence - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; @@ -95,6 +98,8 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; void blank(); + + static constexpr ESM::RecNameInts sRecordId = ESM::REC_CELL4; }; } diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index d96f52a741..b95f24e73a 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -30,14 +30,15 @@ #include #include "reader.hpp" -//#include "writer.hpp" +// #include "writer.hpp" void ESM4::Reference::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.id; - reader.adjustFormId(mFormId); + auto formId = reader.hdr().record.id; + reader.adjustFormId(formId); + mId = ESM::RefId::formIdRefId(formId); mFlags = reader.hdr().record.flags; - mParent = reader.currCell(); // NOTE: only for persistent refs? + mParent = ESM::RefId::formIdRefId(reader.currCell()); // NOTE: only for persistent refs? // TODO: Let the engine apply this? Saved games? // mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false; @@ -60,7 +61,9 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; case ESM4::SUB_NAME: { - reader.getFormId(mBaseObj); + FormId BaseId; + reader.getFormId(BaseId); + mBaseObj = ESM::RefId::formIdRefId(BaseId); #if 0 if (mFlags & ESM4::Rec_Disabled) std::cout << "REFR disable at start " << formIdToString(mFormId) << diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index 04d537932e..fb54c35863 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -31,6 +31,9 @@ #include "reference.hpp" // FormId, Placement, EnableParent +#include +#include + namespace ESM4 { class Reader; @@ -71,15 +74,15 @@ namespace ESM4 struct Reference { - FormId mParent; // cell FormId (currently persistent refs only), from the loading sequence - // NOTE: for exterior cells it will be the dummy cell FormId + ESM::RefId mParent; // cell FormId (currently persistent refs only), from the loading sequence + // NOTE: for exterior cells it will be the dummy cell FormId - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; - FormId mBaseObj; + ESM::RefId mBaseObj; Placement mPlacement; float mScale = 1.0f; @@ -110,6 +113,8 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; void blank(); + + static constexpr ESM::RecNameInts sRecordId = ESM::REC_REFR4; }; } diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index e8e789cb1b..edfbf0e4e6 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -30,12 +30,13 @@ #include #include "reader.hpp" -//#include "writer.hpp" +// #include "writer.hpp" void ESM4::Static::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.id; - reader.adjustFormId(mFormId); + FormId formId = reader.hdr().record.id; + reader.adjustFormId(formId); + mId = ESM::RefId::formIdRefId(formId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) diff --git a/components/esm4/loadstat.hpp b/components/esm4/loadstat.hpp index f34ba6a1cf..3b8a78d8fb 100644 --- a/components/esm4/loadstat.hpp +++ b/components/esm4/loadstat.hpp @@ -33,6 +33,9 @@ #include "formid.hpp" +#include +#include + namespace ESM4 { class Reader; @@ -40,7 +43,7 @@ namespace ESM4 struct Static { - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; @@ -53,6 +56,8 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; // void blank(); + + static constexpr ESM::RecNameInts sRecordId = ESM::REC_STAT4; }; } diff --git a/components/esm4/readerutils.hpp b/components/esm4/readerutils.hpp new file mode 100644 index 0000000000..dd202a5f71 --- /dev/null +++ b/components/esm4/readerutils.hpp @@ -0,0 +1,84 @@ +#ifndef OPENMW_COMPONENTS_ESM4_READERUTILS +#define OPENMW_COMPONENTS_ESM4_READERUTILS + +#include +namespace ESM4 +{ + struct ReaderUtils + { + + /* RecordInvocable must be an invocable, takes an ESM4::Reader& as input, and outputs a boolean that indicates + if the record was read or ignored. Will be invoked for every record + + GroupInvocable's invocable must take a ESM4::Reader& as input, doesn't need to output anything. Will be invoked + for every group*/ + template + static void readAll(ESM4::Reader& reader, RecordInvocable&& recordInvocable, GroupInvocable&& groupInvocable) + { + while (reader.hasMoreRecs()) + { + reader.exitGroupCheck(); + if (!readItem(reader, recordInvocable, groupInvocable)) + break; + } + } + + template + static void readRecord(ESM4::Reader& reader, RecordInvocable&& recordInvocable) + { + if (!recordInvocable(reader)) + reader.skipRecordData(); + } + + template + static bool readGroup(ESM4::Reader& reader, RecordInvocable&& recordInvocable, GroupInvocable&& groupInvocable) + { + const ESM4::RecordHeader& header = reader.hdr(); + + groupInvocable(reader); + + switch (static_cast(header.group.type)) + { + case ESM4::Grp_RecordType: + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + reader.enterGroup(); + return readItem(reader, recordInvocable, groupInvocable); + case ESM4::Grp_WorldChild: + case ESM4::Grp_CellChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + reader.adjustGRUPFormId(); + reader.enterGroup(); + if (!reader.hasMoreRecs()) + return false; + return readItem(reader, recordInvocable, groupInvocable); + } + + reader.skipGroup(); + + return true; + } + + template + static bool readItem(ESM4::Reader& reader, RecordInvocable&& recordInvocable, GroupInvocable&& groupInvocable) + { + if (!reader.getRecordHeader() || !reader.hasMoreRecs()) + return false; + + const ESM4::RecordHeader& header = reader.hdr(); + + if (header.record.typeId == ESM4::REC_GRUP) + return readGroup(reader, recordInvocable, groupInvocable); + + readRecord(reader, recordInvocable); + return true; + } + }; +} + +#endif // !OPENMW_COMPONENTS_ESM4_READERUTILS diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index df5462dbb9..11a466e44c 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -68,6 +68,8 @@ namespace ToUTF8 /// ASCII-only string. Otherwise returns a view to the input. std::string_view getLegacyEnc(std::string_view input); + StatelessUtf8Encoder getStatelessEncoder() const { return mImpl; } + private: std::string mBuffer; StatelessUtf8Encoder mImpl;