From 8c27dca1dff85f354d5bef1912d6687d5f8e4b1f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 19:29:28 +0300 Subject: [PATCH 1/3] ESM4: add a way to get the current form version Differentiate between Fallout 4 and TES4 version 1.0 plugins --- components/esm/common.hpp | 5 +++-- components/esm4/loadnavi.cpp | 5 ++--- components/esm4/loadnavm.cpp | 2 +- components/esm4/loadnpc.cpp | 2 +- components/esm4/loadrace.cpp | 2 +- components/esm4/reader.cpp | 9 --------- components/esm4/reader.hpp | 3 +++ 7 files changed, 11 insertions(+), 17 deletions(-) diff --git a/components/esm/common.hpp b/components/esm/common.hpp index c0e6151e24..90c60ad6ab 100644 --- a/components/esm/common.hpp +++ b/components/esm/common.hpp @@ -25,12 +25,13 @@ namespace ESM VER_120 = 0x3f99999a, // TES3 VER_130 = 0x3fa66666, // TES3 VER_080 = 0x3f4ccccd, // TES4 - VER_100 = 0x3f800000, // TES4 + VER_100 = 0x3f800000, // TES4, FO4 VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney VER_133 = 0x3faa3d71, // FONV HonestHearts VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues VER_094 = 0x3f70a3d7, // TES5/FO3 - VER_170 = 0x3fd9999a // TES5 + VER_170 = 0x3fd9999a, // TES5 + VER_095 = 0x3f733333, // FO4 }; // Defines another files (esm or esp) that this file depends upon. diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 09868d258f..5b73af606e 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -249,9 +249,8 @@ void ESM4::Navigation::load(ESM4::Reader& reader) } case ESM4::SUB_NVPP: { - // FIXME: this is both the version for FO4 and for some TES4 files - // How to distinguish? - if (esmVer == ESM::VER_100) + // FIXME: FO4 updates the format + if (reader.hasFormVersion() && (esmVer == ESM::VER_095 || esmVer == ESM::VER_100)) { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 6298964f28..828fd77ca1 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -213,7 +213,7 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) { // See FIXME in ESM4::Navigation::load. // FO4 updates the format - if (esmVer == ESM::VER_100) + if (reader.hasFormVersion() && (esmVer == ESM::VER_095 || esmVer == ESM::VER_100)) { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 65292e63a8..251af13630 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -39,7 +39,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); - mIsTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + mIsTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100) && !reader.hasFormVersion(); mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; // mIsTES5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; // WARN: FO3 is also VER_094 diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp index 28f91787e6..7434a7f87f 100644 --- a/components/esm4/loadrace.cpp +++ b/components/esm4/loadrace.cpp @@ -38,7 +38,7 @@ void ESM4::Race::load(ESM4::Reader& reader) mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); - bool isTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100) && !reader.hasFormVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; bool isFO3 = false; diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index f81a971e15..ce7f534786 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -201,15 +201,6 @@ namespace ESM4 // restart from the beginning (i.e. "TES4" record header) mStream->seekg(0, mStream->beg); -#if 0 - unsigned int esmVer = mHeader.mData.version.ui; - bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; - //bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; - //bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; - - // TES4 header size is 4 bytes smaller than TES5 header - mCtx.recHeaderSize = isTes4 ? sizeof(ESM4::RecordHeader) - 4 : sizeof(ESM4::RecordHeader); -#endif getRecordHeader(); if (mCtx.recordHeader.record.typeId == REC_TES4) { diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index a095c23e85..e18c614885 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -249,6 +249,9 @@ namespace ESM4 inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } inline unsigned int numRecords() const { return mHeader.mData.records; } + inline bool hasFormVersion() const { return mCtx.recHeaderSize == sizeof(RecordHeader); } + inline unsigned int formVersion() const { return mCtx.recordHeader.record.version; } + void buildLStringIndex(); void getLocalizedString(std::string& str); From 9f8f2dd925450c6ec41bf498be44af9dcdb03830 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 20:24:48 +0300 Subject: [PATCH 2/3] Use parent worldspace terrain when requested --- apps/openmw/mwrender/landmanager.cpp | 10 +++++++++- apps/openmw/mwrender/terrainstorage.cpp | 9 +++++++++ components/esm4/loadwrld.cpp | 4 ++++ components/esm4/loadwrld.hpp | 22 +++++++++++++--------- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 835fb9b204..f8a3ebd962 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -20,10 +21,17 @@ namespace MWRender osg::ref_ptr LandManager::getLand(ESM::ExteriorCellLocation cellIndex) { + const MWBase::World& world = *MWBase::Environment::get().getWorld(); + if (ESM::isEsm4Ext(cellIndex.mWorldspace)) + { + const ESM4::World* worldspace = world.getStore().get().find(cellIndex.mWorldspace); + if (!worldspace->mParent.isZeroOrUnset() && worldspace->mParentUseFlags & ESM4::World::UseFlag_Land) + cellIndex.mWorldspace = worldspace->mParent; + } + if (const std::optional> obj = mCache->getRefFromObjectCacheOrNone(cellIndex)) return static_cast(obj->get()); - const MWBase::World& world = *MWBase::Environment::get().getWorld(); osg::ref_ptr landObj = nullptr; if (ESM::isEsm4Ext(cellIndex.mWorldspace)) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 11fdbd774f..a20da97f0f 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,6 +1,7 @@ #include "terrainstorage.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" @@ -33,6 +34,10 @@ namespace MWRender if (ESM::isEsm4Ext(cellLocation.mWorldspace)) { + const ESM4::World* worldspace = esmStore.get().find(cellLocation.mWorldspace); + if (!worldspace->mParent.isZeroOrUnset() && worldspace->mParentUseFlags & ESM4::World::UseFlag_Land) + cellLocation.mWorldspace = worldspace->mParent; + return esmStore.get().search(cellLocation) != nullptr; } else @@ -64,6 +69,10 @@ namespace MWRender if (ESM::isEsm4Ext(worldspace)) { + const ESM4::World* worldRec = esmStore.get().find(worldspace); + if (!worldRec->mParent.isZeroOrUnset() && worldRec->mParentUseFlags & ESM4::World::UseFlag_Land) + worldspace = worldRec->mParent; + const auto& lands = esmStore.get().getLands(); for (const auto& [landPos, _] : lands) { diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp index c0a3044437..b29cb37eb5 100644 --- a/components/esm4/loadwrld.cpp +++ b/components/esm4/loadwrld.cpp @@ -185,6 +185,10 @@ void ESM4::World::load(ESM4::Reader& reader) mWaterLevel = 0.f; } } + + // TES4 doesn't define PNAM. Exact parent worldspace behavior needs research + if (!reader.hasFormVersion()) + mParentUseFlags = 0xFFFF; } } diff --git a/components/esm4/loadwrld.hpp b/components/esm4/loadwrld.hpp index d8b0ba2177..f951ce4e8b 100644 --- a/components/esm4/loadwrld.hpp +++ b/components/esm4/loadwrld.hpp @@ -55,6 +55,18 @@ namespace ESM4 WLD_NoGrass = 0x80 // No Grass }; + enum UseFlags + { + UseFlag_Land = 0x01, + UseFlag_LOD = 0x02, + UseFlag_Map = 0x04, + UseFlag_Water = 0x08, + UseFlag_Climate = 0x10, + UseFlag_Imagespace = 0x20, // Unused in TES5 + UseFlag_SkyCell = 0x40, + // cc9cii: 0x80 == needs water adjustment? Set for WastelandNV + }; + struct REFRcoord { ESM::FormId formId; @@ -115,15 +127,7 @@ namespace ESM4 // ---------------------- ESM::FormId mMusic; - // 0x01 use Land data - // 0x02 use LOD data - // 0x04 use Map data - // 0x08 use Water data - // 0x10 use Climate data - // 0x20 use Image Space data (Climate for TES5) - // 0x40 use SkyCell (TES5) - // 0x80 needs water adjustment (this isn't for parent I think? FONV only set for wastelandnv) - std::uint16_t mParentUseFlags; // FO3/FONV + std::uint16_t mParentUseFlags{ 0 }; // cache formId's of children (e.g. CELL, ROAD) std::vector mCells; From 8d655054f127416d915f6e8f992c2fb08554bace Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 20:45:39 +0300 Subject: [PATCH 3/3] esmtool: Print human-readable ESM4 file format version --- apps/esmtool/tes4.cpp | 2 +- components/esm4/reader.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/esmtool/tes4.cpp b/apps/esmtool/tes4.cpp index 6791694a7c..5b657da573 100644 --- a/apps/esmtool/tes4.cpp +++ b/apps/esmtool/tes4.cpp @@ -562,7 +562,7 @@ namespace EsmTool { std::cout << "Author: " << reader.getAuthor() << '\n' << "Description: " << reader.getDesc() << '\n' - << "File format version: " << reader.esmVersion() << '\n'; + << "File format version: " << reader.esmVersionF() << '\n'; if (const std::vector& masterData = reader.getGameFiles(); !masterData.empty()) { diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index e18c614885..c63dbd1548 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -247,6 +247,7 @@ namespace ESM4 void setRecHeaderSize(const std::size_t size); inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } + inline float esmVersionF() const { return mHeader.mData.version.f; } inline unsigned int numRecords() const { return mHeader.mData.records; } inline bool hasFormVersion() const { return mCtx.recHeaderSize == sizeof(RecordHeader); }