diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp
index 64319d320e..4015a4b8bf 100644
--- a/apps/opencs/model/doc/savingstages.cpp
+++ b/apps/opencs/model/doc/savingstages.cpp
@@ -340,7 +340,7 @@ void CSMDoc::WriteCellCollectionStage::writeReferences(
                 char ignore;
                 istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1];
 
-                refRecord.mRefNum.save(writer, false, "MVRF");
+                writer.writeFormId(refRecord.mRefNum, false, "MVRF");
                 writer.writeHNT("CNDT", moved.mTarget);
             }
 
diff --git a/apps/openmw/mwlua/luaevents.cpp b/apps/openmw/mwlua/luaevents.cpp
index 37828d4b13..b036fea3b6 100644
--- a/apps/openmw/mwlua/luaevents.cpp
+++ b/apps/openmw/mwlua/luaevents.cpp
@@ -55,7 +55,7 @@ namespace MWLua
     static void saveEvent(ESM::ESMWriter& esm, const ESM::RefNum& dest, const Event& event)
     {
         esm.writeHNString("LUAE", event.mEventName);
-        dest.save(esm, true);
+        esm.writeFormId(dest, true);
         if (!event.mEventData.empty())
             saveLuaBinaryData(esm, event.mEventData);
     }
@@ -67,8 +67,7 @@ namespace MWLua
         while (esm.isNextSub("LUAE"))
         {
             std::string name = esm.getHString();
-            ESM::RefNum dest;
-            dest.load(esm, true);
+            ESM::RefNum dest = esm.getFormId(true);
             std::string data = loadLuaBinaryData(esm);
             try
             {
diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp
index ba3c9e1a3c..9ccc1caaf8 100644
--- a/apps/openmw/mwlua/worldview.cpp
+++ b/apps/openmw/mwlua/worldview.cpp
@@ -82,15 +82,13 @@ namespace MWLua
     void WorldView::load(ESM::ESMReader& esm)
     {
         esm.getHNT(mSimulationTime, "LUAW");
-        ObjectId lastAssignedId;
-        lastAssignedId.load(esm, true);
-        MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(lastAssignedId);
+        MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(esm.getFormId(true));
     }
 
     void WorldView::save(ESM::ESMWriter& esm) const
     {
         esm.writeHNT("LUAW", mSimulationTime);
-        MWBase::Environment::get().getWorldModel()->getLastGeneratedRefNum().save(esm, true);
+        esm.writeFormId(MWBase::Environment::get().getWorldModel()->getLastGeneratedRefNum(), true);
     }
 
     void WorldView::ObjectGroup::updateList()
diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp
index 75da41af3f..b038e76b16 100644
--- a/apps/openmw/mwrender/renderingmanager.hpp
+++ b/apps/openmw/mwrender/renderingmanager.hpp
@@ -42,7 +42,8 @@ namespace osgViewer
 namespace ESM
 {
     struct Cell;
-    struct RefNum;
+    struct FormId;
+    using RefNum = FormId;
 }
 
 namespace Terrain
diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp
index 27d81e1ea1..74d8c94f98 100644
--- a/apps/openmw/mwscript/globalscripts.hpp
+++ b/apps/openmw/mwscript/globalscripts.hpp
@@ -21,7 +21,8 @@ namespace ESM
 {
     class ESMWriter;
     class ESMReader;
-    struct RefNum;
+    struct FormId;
+    using RefNum = FormId;
 }
 
 namespace Loading
diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp
index ced128db40..7442dfe6a4 100644
--- a/apps/openmw/mwworld/cellref.cpp
+++ b/apps/openmw/mwworld/cellref.cpp
@@ -25,7 +25,7 @@ namespace MWWorld
     const ESM::RefNum& CellRef::getRefNum() const
     {
         return std::visit(ESM::VisitOverload{
-                              [&](const ESM4::Reference& /*ref*/) -> const ESM::RefNum& { return emptyRefNum; },
+                              [&](const ESM4::Reference& ref) -> const ESM::RefNum& { return ref.mFormId; },
                               [&](const ESM::CellRef& ref) -> const ESM::RefNum& { return ref.mRefNum; },
                           },
             mCellRef.mVariant);
@@ -33,36 +33,33 @@ namespace MWWorld
 
     const ESM::RefNum& CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum)
     {
-        auto esm3Visit = [&](ESM::CellRef& ref) -> const ESM::RefNum& {
-            if (!ref.mRefNum.isSet())
-            {
-                // Generated RefNums have negative mContentFile
-                assert(lastAssignedRefNum.mContentFile < 0);
-                lastAssignedRefNum.mIndex++;
-                if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed
-                {
-                    if (lastAssignedRefNum.mContentFile > std::numeric_limits<int32_t>::min())
-                        lastAssignedRefNum.mContentFile--;
-                    else
-                        Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum";
-                }
-                ref.mRefNum = lastAssignedRefNum;
-                mChanged = true;
-            }
-            return ref.mRefNum;
-        };
-        return std::visit(
-            ESM::VisitOverload{
-                [&](ESM4::Reference& /*ref*/) -> const ESM::RefNum& { return emptyRefNum; },
-                esm3Visit,
-            },
+        ESM::RefNum& refNum = std::visit(ESM::VisitOverload{
+                                             [&](ESM4::Reference& ref) -> ESM::RefNum& { return ref.mFormId; },
+                                             [&](ESM::CellRef& ref) -> ESM::RefNum& { return ref.mRefNum; },
+                                         },
             mCellRef.mVariant);
+        if (!refNum.isSet())
+        {
+            // Generated RefNums have negative mContentFile
+            assert(lastAssignedRefNum.mContentFile < 0);
+            lastAssignedRefNum.mIndex++;
+            if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed
+            {
+                if (lastAssignedRefNum.mContentFile > std::numeric_limits<int32_t>::min())
+                    lastAssignedRefNum.mContentFile--;
+                else
+                    Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum";
+            }
+            refNum = lastAssignedRefNum;
+            mChanged = true;
+        }
+        return refNum;
     }
 
     void CellRef::unsetRefNum()
     {
         std::visit(ESM::VisitOverload{
-                       [&](ESM4::Reference& /*ref*/) {},
+                       [&](ESM4::Reference& ref) { ref.mFormId = emptyRefNum; },
                        [&](ESM::CellRef& ref) { ref.mRefNum = emptyRefNum; },
                    },
             mCellRef.mVariant);
diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp
index eef9e757ee..82e310c156 100644
--- a/apps/openmw/mwworld/cellstore.cpp
+++ b/apps/openmw/mwworld/cellstore.cpp
@@ -762,55 +762,18 @@ namespace MWWorld
     template <typename ReferenceInvocable>
     static void visitCell4References(const ESM4::Cell& cell, ESM::ReadersCache& readers, ReferenceInvocable&& invocable)
     {
-        auto stream = Files::openBinaryInputFileStream(cell.mReaderContext.filename);
-        stream->seekg(0);
-
-        ESM4::Reader readerESM4(
-            std::move(stream), cell.mReaderContext.filename, MWBase::Environment::get().getResourceSystem()->getVFS());
-
-        readerESM4.setEncoder(readers.getStatelessEncoder());
-        bool contextValid = cell.mReaderContext.filePos != std::streampos(-1);
-        if (contextValid)
-            readerESM4.restoreContext(cell.mReaderContext);
-
-        while (
-            (ESM::RefId::formIdRefId(readerESM4.currCell()) == cell.mId || !contextValid) && readerESM4.hasMoreRecs())
+        for (const ESM4::Reference& ref : MWBase::Environment::get().getWorld()->getStore().get<ESM4::Reference>())
         {
-            if (!contextValid)
-                readerESM4.exitGroupCheck();
-
-            auto onRecord = [&](ESM4::Reader& reader) {
-                auto recordType = static_cast<ESM4::RecordTypes>(reader.hdr().record.typeId);
-                ESM::RecNameInts esm4RecName = static_cast<ESM::RecNameInts>(ESM::esm4Recname(recordType));
-                if (esm4RecName == ESM::RecNameInts::REC_REFR4 && contextValid)
-                {
-                    reader.getRecordData();
-                    ESM4::Reference ref;
-                    ref.load(reader);
-                    invocable(ref);
-                    return true;
-                }
-                else if (esm4RecName == ESM::RecNameInts::REC_CELL4)
-                {
-                    reader.getRecordData();
-                    ESM4::Cell cellToLoad;
-                    cellToLoad.load(reader); // This is necessary to exit or to find the correct cell
-                    if (cellToLoad.mId == cell.mId)
-                        contextValid = true;
-                    return true;
-                }
-
-                return false;
-            };
-
-            if (!ESM4::ReaderUtils::readItem(readerESM4, onRecord, [&](ESM4::Reader& reader) {}))
-                break;
+            if (ref.mParent == cell.mId)
+            {
+                invocable(ref);
+            }
         }
     }
 
     void CellStore::listRefs(const ESM4::Cell& cell)
     {
-        visitCell4References(cell, mReaders, [&](ESM4::Reference& ref) { mIds.push_back(ref.mBaseObj); });
+        visitCell4References(cell, mReaders, [&](const ESM4::Reference& ref) { mIds.push_back(ref.mBaseObj); });
     }
 
     void CellStore::listRefs()
@@ -874,7 +837,7 @@ namespace MWWorld
 
     void CellStore::loadRefs(const ESM4::Cell& cell, std::map<ESM::RefNum, ESM::RefId>& refNumToID)
     {
-        visitCell4References(cell, mReaders, [&](ESM4::Reference& ref) { loadRef(ref, false); });
+        visitCell4References(cell, mReaders, [&](const ESM4::Reference& ref) { loadRef(ref, false); });
     }
 
     void CellStore::loadRefs()
@@ -1020,7 +983,7 @@ namespace MWWorld
             ESM::RefNum refNum = base->mRef.getRefNum();
             ESM::RefId movedTo = store->getCell()->getId();
 
-            refNum.save(writer, true, "MVRF");
+            writer.writeFormId(refNum, true, "MVRF");
             writer.writeCellId(movedTo);
         }
     }
@@ -1076,8 +1039,7 @@ namespace MWWorld
         while (reader.isNextSub("MVRF"))
         {
             reader.cacheSubName();
-            ESM::RefNum refnum;
-            refnum.load(reader, true, "MVRF");
+            ESM::RefNum refnum = reader.getFormId(true, "MVRF");
             ESM::RefId movedToId = reader.getCellId();
             if (refnum.hasContentFile())
             {
diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp
index 451f480e51..23b0a84443 100644
--- a/apps/openmw/mwworld/cellstore.hpp
+++ b/apps/openmw/mwworld/cellstore.hpp
@@ -27,7 +27,8 @@ namespace ESM
     class ReadersCache;
     struct Cell;
     struct CellState;
-    struct RefNum;
+    struct FormId;
+    using RefNum = FormId;
     struct Activator;
     struct Potion;
     struct Apparatus;
diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp
index 24f63eafc4..9190d53ea5 100644
--- a/apps/openmw/mwworld/esmloader.cpp
+++ b/apps/openmw/mwworld/esmloader.cpp
@@ -9,6 +9,7 @@
 #include <components/esm4/reader.hpp>
 #include <components/files/conversion.hpp>
 #include <components/files/openfile.hpp>
+#include <components/misc/strings/lower.hpp>
 #include <components/resource/resourcesystem.hpp>
 
 #include "../mwbase/environment.hpp"
@@ -66,10 +67,13 @@ namespace MWWorld
                 ESM4::Reader readerESM4(
                     std::move(stream), filepath, MWBase::Environment::get().getResourceSystem()->getVFS());
                 readerESM4.setEncoder(mReaders.getStatelessEncoder());
+                readerESM4.setModIndex(index);
+                readerESM4.updateModIndices(mNameToIndex);
                 mStore.loadESM4(readerESM4);
                 break;
             }
         }
+        mNameToIndex[Misc::StringUtils::lowerCase(Files::pathToUnicodeString(filepath.filename()))] = index;
     }
 
 } /* namespace MWWorld */
diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp
index 253c58db70..53bff939c4 100644
--- a/apps/openmw/mwworld/esmloader.hpp
+++ b/apps/openmw/mwworld/esmloader.hpp
@@ -1,6 +1,7 @@
 #ifndef ESMLOADER_HPP
 #define ESMLOADER_HPP
 
+#include <map>
 #include <optional>
 #include <vector>
 
@@ -38,6 +39,7 @@ namespace MWWorld
         ESM::Dialogue* mDialogue;
         std::optional<int> mMasterFileFormat;
         std::vector<int>& mESMVersions;
+        std::map<std::string, int> mNameToIndex;
     };
 
 } /* namespace MWWorld */
diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp
index 5fff40262b..a2375859f0 100644
--- a/apps/openmw/mwworld/esmstore.hpp
+++ b/apps/openmw/mwworld/esmstore.hpp
@@ -30,6 +30,7 @@ namespace ESM4
     struct Static;
     struct Cell;
     struct Light;
+    struct Reference;
 }
 
 namespace ESM
@@ -105,7 +106,7 @@ namespace MWWorld
             // Special entry which is hardcoded and not loaded from an ESM
             Store<ESM::Attribute>,
 
-            Store<ESM4::Static>, Store<ESM4::Cell>, Store<ESM4::Light>>;
+            Store<ESM4::Static>, Store<ESM4::Cell>, Store<ESM4::Light>, Store<ESM4::Reference>>;
 
     private:
         template <typename T>
diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp
index d756d5a5b8..df0b0bcb3e 100644
--- a/apps/openmw/mwworld/worldimp.cpp
+++ b/apps/openmw/mwworld/worldimp.cpp
@@ -2848,8 +2848,9 @@ namespace MWWorld
         {
             foundCell = findInteriorPosition(cellName, pos);
         }
-        catch (std::exception&)
+        catch (std::exception& e)
         {
+            Log(Debug::Error) << e.what();
         }
         if (foundCell.empty())
         {
diff --git a/apps/openmw/mwworld/worldmodel.hpp b/apps/openmw/mwworld/worldmodel.hpp
index 10292c3ca5..f415a9d48a 100644
--- a/apps/openmw/mwworld/worldmodel.hpp
+++ b/apps/openmw/mwworld/worldmodel.hpp
@@ -18,7 +18,6 @@ namespace ESM
     class ESMWriter;
     class ReadersCache;
     struct Cell;
-    struct RefNum;
 }
 
 namespace ESM4
diff --git a/apps/openmw_test_suite/esm/testrefid.cpp b/apps/openmw_test_suite/esm/testrefid.cpp
index 65fe27605c..8e25ddbb6d 100644
--- a/apps/openmw_test_suite/esm/testrefid.cpp
+++ b/apps/openmw_test_suite/esm/testrefid.cpp
@@ -26,7 +26,7 @@ namespace ESM
 
         TEST(ESMRefIdTest, formIdRefIdIsNotEmpty)
         {
-            const RefId refId = RefId::formIdRefId(42);
+            const RefId refId = RefId::formIdRefId({ 42, 0 });
             EXPECT_FALSE(refId.empty());
         }
 
@@ -53,7 +53,7 @@ namespace ESM
         TEST(ESMRefIdTest, defaultConstructedIsNotEqualToFormIdRefId)
         {
             const RefId a;
-            const RefId b = RefId::formIdRefId(42);
+            const RefId b = RefId::formIdRefId({ 42, 0 });
             EXPECT_NE(a, b);
         }
 
@@ -98,8 +98,8 @@ namespace ESM
 
         TEST(ESMRefIdTest, equalityIsDefinedForFormRefIdAndRefId)
         {
-            const FormIdRefId formIdRefId(42);
-            const RefId refId = RefId::formIdRefId(42);
+            const FormIdRefId formIdRefId({ 42, 0 });
+            const RefId refId = RefId::formIdRefId({ 42, 0 });
             EXPECT_EQ(formIdRefId, refId);
         }
 
@@ -125,8 +125,8 @@ namespace ESM
 
         TEST(ESMRefIdTest, lessThanIsDefinedForFormRefIdAndRefId)
         {
-            const FormIdRefId formIdRefId(13);
-            const RefId refId = RefId::formIdRefId(42);
+            const FormIdRefId formIdRefId({ 13, 0 });
+            const RefId refId = RefId::formIdRefId({ 42, 0 });
             EXPECT_LT(formIdRefId, refId);
         }
 
@@ -164,14 +164,14 @@ namespace ESM
         TEST(ESMRefIdTest, stringRefIdHasStrongOrderWithFormId)
         {
             const RefId stringRefId = RefId::stringRefId("a");
-            const RefId formIdRefId = RefId::formIdRefId(42);
+            const RefId formIdRefId = RefId::formIdRefId({ 42, 0 });
             EXPECT_TRUE(stringRefId < formIdRefId);
             EXPECT_FALSE(formIdRefId < stringRefId);
         }
 
         TEST(ESMRefIdTest, formIdRefIdHasStrongOrderWithStringView)
         {
-            const RefId formIdRefId = RefId::formIdRefId(42);
+            const RefId formIdRefId = RefId::formIdRefId({ 42, 0 });
             const std::string_view stringView = "42";
             EXPECT_TRUE(stringView < formIdRefId);
             EXPECT_FALSE(formIdRefId < stringView);
@@ -192,7 +192,7 @@ namespace ESM
         TEST(ESMRefIdTest, stringRefIdIsNotEqualToFormId)
         {
             const RefId stringRefId = RefId::stringRefId("\0");
-            const RefId formIdRefId = RefId::formIdRefId(0);
+            const RefId formIdRefId = RefId::formIdRefId({ 0, 0 });
             EXPECT_NE(stringRefId, formIdRefId);
         }
 
@@ -235,7 +235,7 @@ namespace ESM
             { RefId(), std::string() },
             { RefId::stringRefId("foo"), "foo" },
             { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), { 'a', 0, -1, '\n', '\t' } },
-            { RefId::formIdRefId(42), "0x2a" },
+            { RefId::formIdRefId({ 42, 0 }), "0x2a" },
             { RefId::generated(42), "0x2a" },
             { RefId::index(REC_ARMO, 42), "ARMO:0x2a" },
             { RefId::esm3ExteriorCell(-13, 42), "-13:42" },
@@ -268,7 +268,7 @@ namespace ESM
             { RefId::stringRefId("foo"), "\"foo\"" },
             { RefId::stringRefId("BAR"), "\"BAR\"" },
             { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), "\"a\\x0\\xFF\\xA\\x9\"" },
-            { RefId::formIdRefId(42), "FormId:0x2a" },
+            { RefId::formIdRefId({ 42, 0 }), "FormId:0x2a" },
             { RefId::generated(42), "Generated:0x2a" },
             { RefId::index(REC_ARMO, 42), "Index:ARMO:0x2a" },
             { RefId::esm3ExteriorCell(-13, 42), "Esm3ExteriorCell:-13:42" },
@@ -295,10 +295,11 @@ namespace ESM
             { RefId::stringRefId("foo"), "foo" },
             { RefId::stringRefId("BAR"), "bar" },
             { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), { 'a', 0, -1, '\n', '\t' } },
-            { RefId::formIdRefId(0), "FormId:0x0" },
-            { RefId::formIdRefId(1), "FormId:0x1" },
-            { RefId::formIdRefId(0x1f), "FormId:0x1f" },
-            { RefId::formIdRefId(std::numeric_limits<ESM4::FormId>::max()), "FormId:0xffffffff" },
+            { RefId::formIdRefId({ 0, 0 }), "FormId:0x0" },
+            { RefId::formIdRefId({ 1, 0 }), "FormId:0x1" },
+            { RefId::formIdRefId({ 0x1f, 0 }), "FormId:0x1f" },
+            { RefId::formIdRefId({ 0x1f, 2 }), "FormId:0x200001f" },
+            { RefId::formIdRefId({ 0xffffff, 0x1abc }), "FormId:0x1abcffffff" },
             { RefId::generated(0), "Generated:0x0" },
             { RefId::generated(1), "Generated:0x1" },
             { RefId::generated(0x1f), "Generated:0x1f" },
@@ -344,7 +345,7 @@ namespace ESM
         template <>
         struct GenerateRefId<FormIdRefId>
         {
-            static RefId call() { return RefId::formIdRefId(42); }
+            static RefId call() { return RefId::formIdRefId({ 42, 0 }); }
         };
 
         template <>
diff --git a/apps/openmw_test_suite/esm3/testesmwriter.cpp b/apps/openmw_test_suite/esm3/testesmwriter.cpp
index 7ea5be14fc..9e9ae9947e 100644
--- a/apps/openmw_test_suite/esm3/testesmwriter.cpp
+++ b/apps/openmw_test_suite/esm3/testesmwriter.cpp
@@ -76,7 +76,7 @@ namespace ESM
         const std::vector<std::pair<RefId, std::size_t>> refIdSizes = {
             { RefId(), 57 },
             { RefId::stringRefId(std::string(32, 'a')), 89 },
-            { RefId::formIdRefId(0x1f), 61 },
+            { RefId::formIdRefId({ 0x1f, 0 }), 65 },
             { RefId::generated(0x1f), 65 },
             { RefId::index(REC_INGR, 0x1f), 65 },
             { RefId::esm3ExteriorCell(-42, 42), 65 },
diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt
index 8435fa342d..58f0981e7f 100644
--- a/components/CMakeLists.txt
+++ b/components/CMakeLists.txt
@@ -86,6 +86,7 @@ add_component_dir (to_utf8
     )
 
 add_component_dir(esm attr common defs esmcommon records util luascripts format refid esmbridge
+    formid
     formidrefid
     stringrefid
     generatedrefid
diff --git a/components/esm/formid.cpp b/components/esm/formid.cpp
new file mode 100644
index 0000000000..a0b4c56a70
--- /dev/null
+++ b/components/esm/formid.cpp
@@ -0,0 +1,15 @@
+#include "formid.hpp"
+
+std::string ESM::FormId::toString() const
+{
+    return std::to_string(mIndex) + "_" + std::to_string(mContentFile);
+}
+
+uint32_t ESM::FormId::toUint32() const
+{
+    if (isSet() && !hasContentFile())
+        throw std::runtime_error("Generated FormId can not be converted to 32bit format");
+    if (mContentFile > 0xfe)
+        throw std::runtime_error("FormId with mContentFile > 0xFE can not be converted to 32bit format");
+    return (mIndex & 0xffffff) | ((hasContentFile() ? mContentFile : 0xff) << 24);
+}
diff --git a/components/esm/formid.hpp b/components/esm/formid.hpp
new file mode 100644
index 0000000000..ba25a0fa26
--- /dev/null
+++ b/components/esm/formid.hpp
@@ -0,0 +1,63 @@
+#ifndef COMPONENT_ESM_FORMID_H
+#define COMPONENT_ESM_FORMID_H
+
+#include <cstdint>
+#include <cstring>
+#include <ostream>
+#include <string>
+#include <tuple>
+
+namespace ESM
+{
+    using FormId32 = uint32_t;
+    struct FormId
+    {
+        uint32_t mIndex = 0;
+        int32_t mContentFile = -1;
+
+        bool hasContentFile() const { return mContentFile >= 0; }
+        bool isSet() const { return mIndex != 0 || mContentFile != -1; }
+
+        // Used in ESM4 as a null reference
+        bool isZero() const { return mIndex == 0 && mContentFile == 0; }
+
+        std::string toString() const;
+        FormId32 toUint32() const;
+        static FormId fromUint32(FormId32 v) { return { v & 0xffffff, static_cast<int32_t>(v >> 24) }; }
+    };
+
+    inline constexpr bool operator==(const FormId& left, const FormId& right)
+    {
+        return left.mIndex == right.mIndex && left.mContentFile == right.mContentFile;
+    }
+
+    inline constexpr bool operator<(const FormId& left, const FormId& right)
+    {
+        return std::tie(left.mIndex, left.mContentFile) < std::tie(right.mIndex, right.mContentFile);
+    }
+
+    inline std::ostream& operator<<(std::ostream& stream, const FormId& formId)
+    {
+        return stream << formId.toString();
+    }
+}
+
+namespace std
+{
+
+    // Needed to use ESM::FormId as a key in std::unordered_map
+    template <>
+    struct hash<ESM::FormId>
+    {
+        size_t operator()(const ESM::FormId& formId) const
+        {
+            static_assert(sizeof(ESM::FormId) == sizeof(size_t));
+            size_t s;
+            memcpy(&s, &formId, sizeof(size_t));
+            return hash<size_t>()(s);
+        }
+    };
+
+}
+
+#endif // COMPONENT_ESM_FORMID_H
diff --git a/components/esm/formidrefid.cpp b/components/esm/formidrefid.cpp
index 91790e503a..8e452d6ecd 100644
--- a/components/esm/formidrefid.cpp
+++ b/components/esm/formidrefid.cpp
@@ -1,23 +1,28 @@
 #include "formidrefid.hpp"
 
-#include "serializerefid.hpp"
-
+#include <cassert>
 #include <ostream>
 
+#include "serializerefid.hpp"
+
 namespace ESM
 {
     std::string FormIdRefId::toString() const
     {
         std::string result;
-        result.resize(getHexIntegralSize(mValue) + 2, '\0');
-        serializeHexIntegral(mValue, 0, result);
+        assert((mValue.mIndex & 0xff000000) == 0);
+        size_t v = (static_cast<size_t>(mValue.mContentFile) << 24) | mValue.mIndex;
+        result.resize(getHexIntegralSize(v) + 2, '\0');
+        serializeHexIntegral(v, 0, result);
         return result;
     }
 
     std::string FormIdRefId::toDebugString() const
     {
         std::string result;
-        serializeRefIdValue(mValue, formIdRefIdPrefix, result);
+        assert((mValue.mIndex & 0xff000000) == 0);
+        size_t v = (static_cast<size_t>(mValue.mContentFile) << 24) | mValue.mIndex;
+        serializeRefIdValue(v, formIdRefIdPrefix, result);
         return result;
     }
 
diff --git a/components/esm/formidrefid.hpp b/components/esm/formidrefid.hpp
index 30f868f4da..176dc4ec84 100644
--- a/components/esm/formidrefid.hpp
+++ b/components/esm/formidrefid.hpp
@@ -4,7 +4,7 @@
 #include <functional>
 #include <iosfwd>
 
-#include <components/esm4/formid.hpp>
+#include <components/esm/formid.hpp>
 
 namespace ESM
 {
@@ -13,12 +13,12 @@ namespace ESM
     public:
         constexpr FormIdRefId() = default;
 
-        constexpr explicit FormIdRefId(ESM4::FormId value) noexcept
+        constexpr explicit FormIdRefId(ESM::FormId value) noexcept
             : mValue(value)
         {
         }
 
-        ESM4::FormId getValue() const { return mValue; }
+        ESM::FormId getValue() const { return mValue; }
 
         std::string toString() const;
 
@@ -33,7 +33,7 @@ namespace ESM
         friend struct std::hash<FormIdRefId>;
 
     private:
-        ESM4::FormId mValue = 0;
+        ESM::FormId mValue;
     };
 }
 
@@ -42,10 +42,7 @@ namespace std
     template <>
     struct hash<ESM::FormIdRefId>
     {
-        std::size_t operator()(ESM::FormIdRefId value) const noexcept
-        {
-            return std::hash<ESM4::FormId>{}(value.mValue);
-        }
+        std::size_t operator()(ESM::FormIdRefId value) const noexcept { return std::hash<ESM::FormId>{}(value.mValue); }
     };
 }
 
diff --git a/components/esm/refid.cpp b/components/esm/refid.cpp
index b24ef82cf6..4011fca8ec 100644
--- a/components/esm/refid.cpp
+++ b/components/esm/refid.cpp
@@ -225,7 +225,12 @@ namespace ESM
             return ESM::RefId();
 
         if (value.starts_with(formIdRefIdPrefix))
-            return ESM::RefId::formIdRefId(deserializeHexIntegral<ESM4::FormId>(formIdRefIdPrefix.size(), value));
+        {
+            uint64_t v = deserializeHexIntegral<uint64_t>(formIdRefIdPrefix.size(), value);
+            uint32_t index = static_cast<uint32_t>(v) & 0xffffff;
+            int contentFile = static_cast<int>(v >> 24);
+            return ESM::RefId::formIdRefId({ index, contentFile });
+        }
 
         if (value.starts_with(generatedRefIdPrefix))
             return ESM::RefId::generated(deserializeHexIntegral<std::uint64_t>(generatedRefIdPrefix.size(), value));
diff --git a/components/esm/refid.hpp b/components/esm/refid.hpp
index 3fafdb0b74..3722c8db68 100644
--- a/components/esm/refid.hpp
+++ b/components/esm/refid.hpp
@@ -62,8 +62,8 @@ namespace ESM
         // Constructs RefId from a string using a pointer to a static set of strings.
         static RefId stringRefId(std::string_view value);
 
-        // Constructs RefId from ESM4 FormId storing the value in-place.
-        static RefId formIdRefId(ESM4::FormId value) noexcept { return RefId(FormIdRefId(value)); }
+        // Constructs RefId from FormId storing the value in-place.
+        static RefId formIdRefId(FormId value) noexcept { return RefId(FormIdRefId(value)); }
 
         // Constructs RefId from uint64 storing the value in-place. Should be used for generated records where id is a
         // global counter.
diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp
index c03200d3ae..97d56d1a2b 100644
--- a/components/esm3/activespells.cpp
+++ b/components/esm3/activespells.cpp
@@ -17,7 +17,7 @@ namespace ESM
                 esm.writeHNString("DISP", params.mDisplayName);
                 esm.writeHNT("TYPE", params.mType);
                 if (params.mItem.isSet())
-                    params.mItem.save(esm, true, "ITEM");
+                    esm.writeFormId(params.mItem, true, "ITEM");
                 if (params.mWorsenings >= 0)
                 {
                     esm.writeHNT("WORS", params.mWorsenings);
@@ -56,7 +56,7 @@ namespace ESM
                 {
                     esm.getHNT(params.mType, "TYPE");
                     if (esm.peekNextSub("ITEM"))
-                        params.mItem.load(esm, true, "ITEM");
+                        params.mItem = esm.getFormId(true, "ITEM");
                 }
                 if (esm.isNextSub("WORS"))
                 {
diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp
index d765497948..fafab6bcac 100644
--- a/components/esm3/cellref.cpp
+++ b/components/esm3/cellref.cpp
@@ -24,7 +24,7 @@ namespace ESM
             if constexpr (load)
             {
                 cellRef.blank();
-                cellRef.mRefNum.load(esm, wideRefNum);
+                cellRef.mRefNum = esm.getFormId(wideRefNum);
                 cellRef.mRefID = esm.getHNORefId("NAME");
 
                 if (cellRef.mRefID.empty())
@@ -33,7 +33,7 @@ namespace ESM
             }
             else
             {
-                RefNum{}.load(esm, wideRefNum);
+                esm.getFormId(wideRefNum);
                 esm.skipHNORefId("NAME");
             }
         }
@@ -154,32 +154,6 @@ namespace ESM
         }
     }
 
-    void RefNum::load(ESMReader& esm, bool wide, NAME tag)
-    {
-        if (wide)
-            esm.getHNTSized<8>(*this, tag);
-        else
-            esm.getHNT(mIndex, tag);
-    }
-
-    void RefNum::save(ESMWriter& esm, bool wide, NAME tag) const
-    {
-        if (wide)
-            esm.writeHNT(tag, *this, 8);
-        else
-        {
-            if (isSet() && !hasContentFile())
-                throw std::runtime_error("Generated RefNum can not be saved in 32bit format");
-            int refNum = (mIndex & 0xffffff) | ((hasContentFile() ? mContentFile : 0xff) << 24);
-            esm.writeHNT(tag, refNum, 4);
-        }
-    }
-
-    std::string RefNum::toString() const
-    {
-        return std::to_string(mIndex) + "_" + std::to_string(mContentFile);
-    }
-
     void CellRef::load(ESMReader& esm, bool& isDeleted, bool wideRefNum)
     {
         loadId(esm, wideRefNum);
@@ -198,7 +172,7 @@ namespace ESM
 
     void CellRef::save(ESMWriter& esm, bool wideRefNum, bool inInventory, bool isDeleted) const
     {
-        mRefNum.save(esm, wideRefNum);
+        esm.writeFormId(mRefNum, wideRefNum);
 
         esm.writeHNCRefId("NAME", mRefID);
 
diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp
index 0ea118178f..8a5416d674 100644
--- a/components/esm3/cellref.hpp
+++ b/components/esm3/cellref.hpp
@@ -4,6 +4,7 @@
 #include <limits>
 #include <string>
 
+#include "components/esm/common.hpp"
 #include "components/esm/defs.hpp"
 #include "components/esm/esmcommon.hpp"
 #include "components/esm/refid.hpp"
@@ -15,21 +16,7 @@ namespace ESM
 
     const int UnbreakableLock = std::numeric_limits<int>::max();
 
-    struct RefNum
-    {
-        unsigned int mIndex = 0;
-        int mContentFile = -1;
-
-        void load(ESMReader& esm, bool wide = false, NAME tag = "FRMR");
-
-        void save(ESMWriter& esm, bool wide = false, NAME tag = "FRMR") const;
-
-        inline bool hasContentFile() const { return mContentFile >= 0; }
-
-        inline bool isSet() const { return mIndex != 0 || mContentFile != -1; }
-
-        std::string toString() const;
-    };
+    using RefNum = ESM::FormId;
 
     /* Cell reference. This represents ONE object (of many) inside the
     cell. The cell references are not loaded as part of the normal
@@ -119,39 +106,6 @@ namespace ESM
     };
 
     void skipLoadCellRef(ESMReader& esm, bool wideRefNum = false);
-
-    inline bool operator==(const RefNum& left, const RefNum& right)
-    {
-        return left.mIndex == right.mIndex && left.mContentFile == right.mContentFile;
-    }
-
-    inline bool operator<(const RefNum& left, const RefNum& right)
-    {
-        if (left.mIndex < right.mIndex)
-            return true;
-        if (left.mIndex > right.mIndex)
-            return false;
-        return left.mContentFile < right.mContentFile;
-    }
-
-}
-
-namespace std
-{
-
-    // Needed to use ESM::RefNum as a key in std::unordered_map
-    template <>
-    struct hash<ESM::RefNum>
-    {
-        size_t operator()(const ESM::RefNum& refNum) const
-        {
-            assert(sizeof(ESM::RefNum) == sizeof(size_t));
-            size_t s;
-            memcpy(&s, &refNum, sizeof(size_t));
-            return hash<size_t>()(s);
-        }
-    };
-
 }
 
 #endif
diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp
index 42a2b2898a..1cafac7e78 100644
--- a/components/esm3/esmreader.cpp
+++ b/components/esm3/esmreader.cpp
@@ -248,6 +248,16 @@ namespace ESM
         getHExact(p, size);
     }
 
+    FormId ESMReader::getFormId(bool wide, NAME tag)
+    {
+        FormId res;
+        if (wide)
+            getHNTSized<8>(res, tag);
+        else
+            getHNT(res.mIndex, tag);
+        return res;
+    }
+
     // Get the next subrecord name and check if it matches the parameter
     void ESMReader::getSubNameIs(NAME name)
     {
@@ -473,7 +483,7 @@ namespace ESM
                 return RefId::stringRefId(getStringView(size - sizeof(refIdType)));
             case RefIdType::FormId:
             {
-                ESM4::FormId formId{};
+                FormId formId{};
                 getT(formId);
                 return RefId::formIdRefId(formId);
             }
diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp
index 703f10be0c..71611e58be 100644
--- a/components/esm3/esmreader.hpp
+++ b/components/esm3/esmreader.hpp
@@ -195,6 +195,8 @@ namespace ESM
         // Read the given number of bytes from a named subrecord
         void getHNExact(void* p, int size, NAME name);
 
+        ESM::FormId getFormId(bool wide = false, NAME tag = "FRMR");
+
         /*************************************************************************
          *
          *  Low level sub-record methods
diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp
index 59a3711b2d..2a64948d24 100644
--- a/components/esm3/esmwriter.cpp
+++ b/components/esm3/esmwriter.cpp
@@ -340,6 +340,14 @@ namespace ESM
         mStream->write(data, size);
     }
 
+    void ESMWriter::writeFormId(const FormId& formId, bool wide, NAME tag)
+    {
+        if (wide)
+            writeHNT(tag, formId, 8);
+        else
+            writeHNT(tag, formId.toUint32(), 4);
+    }
+
     void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder)
     {
         mEncoder = encoder;
diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp
index acf9488d9a..5086005b1f 100644
--- a/components/esm3/esmwriter.hpp
+++ b/components/esm3/esmwriter.hpp
@@ -179,6 +179,8 @@ namespace ESM
 
         void write(const char* data, size_t size);
 
+        void writeFormId(const ESM::FormId&, bool wide = false, NAME tag = "FRMR");
+
     private:
         std::list<RecordData> mRecords;
         std::ostream* mStream;
diff --git a/components/esm3/globalscript.cpp b/components/esm3/globalscript.cpp
index 73ca10018c..b0436c0e61 100644
--- a/components/esm3/globalscript.cpp
+++ b/components/esm3/globalscript.cpp
@@ -18,7 +18,7 @@ namespace ESM
         mTargetRef = RefNum{};
         mTargetId = esm.getHNORefId("TARG");
         if (esm.peekNextSub("FRMR"))
-            mTargetRef.load(esm, true, "FRMR");
+            mTargetRef = esm.getFormId(true, "FRMR");
     }
 
     void GlobalScript::save(ESMWriter& esm) const
@@ -34,7 +34,7 @@ namespace ESM
         {
             esm.writeHNORefId("TARG", mTargetId);
             if (mTargetRef.isSet())
-                mTargetRef.save(esm, true, "FRMR");
+                esm.writeFormId(mTargetRef, true, "FRMR");
         }
     }
 
diff --git a/components/esm4/actor.hpp b/components/esm4/actor.hpp
index 1020008240..e8a946f134 100644
--- a/components/esm4/actor.hpp
+++ b/components/esm4/actor.hpp
@@ -109,7 +109,7 @@ namespace ESM4
 
     struct ActorFaction
     {
-        FormId faction;
+        FormId32 faction;
         std::int8_t rank;
         std::uint8_t unknown1;
         std::uint8_t unknown2;
diff --git a/components/esm4/cellgrid.hpp b/components/esm4/cellgrid.hpp
index 70f2546e3d..7f1a2e8452 100644
--- a/components/esm4/cellgrid.hpp
+++ b/components/esm4/cellgrid.hpp
@@ -27,18 +27,14 @@
 #ifndef OPENMW_COMPONENTS_ESM4_CELLGRID_H
 #define OPENMW_COMPONENTS_ESM4_CELLGRID_H
 
-#include <cstdint>
+#include <variant>
 
 #include "formid.hpp"
 #include "grid.hpp"
 
 namespace ESM4
 {
-    union CellGrid
-    {
-        FormId cellId;
-        Grid grid;
-    };
+    using CellGrid = std::variant<ESM::FormId, Grid>;
 }
 
 #endif // OPENMW_COMPONENTS_ESM4_CELLGRID_H
diff --git a/components/esm4/effect.hpp b/components/esm4/effect.hpp
index c83f9790fb..f49a6749b4 100644
--- a/components/esm4/effect.hpp
+++ b/components/esm4/effect.hpp
@@ -42,7 +42,7 @@ namespace ESM4
 
     struct ScriptEffect
     {
-        FormId formId; // Script effect (Magic effect must be SEFF)
+        FormId32 formId; // Script effect (Magic effect must be SEFF)
         std::int32_t school; // Magic school. See Magic schools for more information.
         EFI_Label visualEffect; // Visual effect name or 0x00000000 if None
         std::uint8_t flags; // 0x01 = Hostile
diff --git a/components/esm4/formid.cpp b/components/esm4/formid.cpp
index c7655ce816..d091bdf8be 100644
--- a/components/esm4/formid.cpp
+++ b/components/esm4/formid.cpp
@@ -22,56 +22,20 @@
 */
 #include "formid.hpp"
 
-#include <charconv>
 #include <stdexcept>
 #include <string>
-#include <system_error>
 
 namespace ESM4
 {
-    void formIdToString(FormId formId, std::string& str)
+    std::string formIdToString(const FormId& formId)
     {
+        std::string str;
         char buf[8 + 1];
-        int res = snprintf(buf, 8 + 1, "%08X", formId);
+        int res = snprintf(buf, 8 + 1, "%08X", formId.toUint32());
         if (res > 0 && res < 8 + 1)
             str.assign(buf);
         else
             throw std::runtime_error("Possible buffer overflow while converting formId");
-    }
-
-    std::string formIdToString(FormId formId)
-    {
-        std::string str;
-        formIdToString(formId, str);
         return str;
     }
-
-    bool isFormId(const std::string& str, FormId* id)
-    {
-        if (str.size() != 8)
-            return false;
-
-        unsigned long value = 0;
-
-        if (auto [_ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value, 16); ec != std::errc())
-            return false;
-
-        if (id != nullptr)
-            *id = static_cast<FormId>(value);
-
-        return true;
-    }
-
-    FormId stringToFormId(const std::string& str)
-    {
-        if (str.size() != 8)
-            throw std::out_of_range("StringToFormId: incorrect string size");
-
-        unsigned long value = 0;
-
-        if (auto [_ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value, 16); ec != std::errc())
-            throw std::invalid_argument("StringToFormId: string not a valid hexadecimal number");
-
-        return static_cast<FormId>(value);
-    }
 }
diff --git a/components/esm4/formid.hpp b/components/esm4/formid.hpp
index d2baaa989d..284da67ad7 100644
--- a/components/esm4/formid.hpp
+++ b/components/esm4/formid.hpp
@@ -23,20 +23,15 @@
 #ifndef ESM4_FORMID_H
 #define ESM4_FORMID_H
 
-#include <cstdint>
 #include <string>
 
+#include <components/esm/formid.hpp>
+
 namespace ESM4
 {
-    typedef std::uint32_t FormId;
-
-    void formIdToString(FormId formId, std::string& str);
-
-    std::string formIdToString(FormId formId);
-
-    bool isFormId(const std::string& str, FormId* id = nullptr);
-
-    FormId stringToFormId(const std::string& str);
+    using FormId = ESM::FormId;
+    using FormId32 = uint32_t;
+    std::string formIdToString(const FormId& formId);
 }
 
 #endif // ESM4_FORMID_H
diff --git a/components/esm4/inventory.hpp b/components/esm4/inventory.hpp
index d072749b47..eb49c5ef06 100644
--- a/components/esm4/inventory.hpp
+++ b/components/esm4/inventory.hpp
@@ -39,14 +39,14 @@ namespace ESM4
     {
         std::int16_t level;
         std::uint16_t unknown; // sometimes missing
-        FormId item;
+        FormId32 item;
         std::int16_t count;
         std::uint16_t unknown2; // sometimes missing
     };
 
     struct InventoryItem // NPC_, CREA, CONT
     {
-        FormId item;
+        FormId32 item;
         std::uint32_t count;
     };
 #pragma pack(pop)
diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp
index 97f23e9c49..d947b7c4d8 100644
--- a/components/esm4/loadachr.cpp
+++ b/components/esm4/loadachr.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::ActorCharacter::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     mParent = reader.currCell(); // NOTE: only for persistent achr? (aren't they all persistent?)
@@ -64,8 +64,8 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader)
                 break;
             case ESM4::SUB_XESP:
             {
-                reader.get(mEsp);
-                reader.adjustFormId(mEsp.parent);
+                reader.getFormId(mEsp.parent);
+                reader.get(mEsp.flags);
                 break;
             }
             case ESM4::SUB_XRGD: // ragdoll
diff --git a/components/esm4/loadacre.cpp b/components/esm4/loadacre.cpp
index 8dfa9104a1..e152376757 100644
--- a/components/esm4/loadacre.cpp
+++ b/components/esm4/loadacre.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::ActorCreature::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
@@ -57,8 +57,8 @@ void ESM4::ActorCreature::load(ESM4::Reader& reader)
                 break;
             case ESM4::SUB_XESP:
             {
-                reader.get(mEsp);
-                reader.adjustFormId(mEsp.parent);
+                reader.getFormId(mEsp.parent);
+                reader.get(mEsp.flags);
                 break;
             }
             case ESM4::SUB_XOWN:
diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp
index 3b86a2e3c4..c69c6d0fd6 100644
--- a/components/esm4/loadacti.cpp
+++ b/components/esm4/loadacti.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Activator::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp
index 7a2427dc45..0c8205fba7 100644
--- a/components/esm4/loadalch.cpp
+++ b/components/esm4/loadalch.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::Potion::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadalch.hpp b/components/esm4/loadalch.hpp
index 73d399df05..6ba720ab32 100644
--- a/components/esm4/loadalch.hpp
+++ b/components/esm4/loadalch.hpp
@@ -49,9 +49,9 @@ namespace ESM4
         {
             std::int32_t value;
             std::uint32_t flags;
-            FormId withdrawl;
+            FormId32 withdrawl;
             float chanceAddition;
-            FormId sound;
+            FormId32 sound;
         };
 #pragma pack(pop)
 
diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp
index a8c6de2432..3f1ada6e0f 100644
--- a/components/esm4/loadaloc.cpp
+++ b/components/esm4/loadaloc.cpp
@@ -38,7 +38,7 @@
 
 void ESM4::MediaLocationController::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp
index d0ad2e1709..de2a99eb80 100644
--- a/components/esm4/loadammo.cpp
+++ b/components/esm4/loadammo.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Ammunition::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp
index 2fadc6c9c2..3a540d1634 100644
--- a/components/esm4/loadanio.cpp
+++ b/components/esm4/loadanio.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::AnimObject::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp
index 72369b7e0e..68991b8cc1 100644
--- a/components/esm4/loadappa.cpp
+++ b/components/esm4/loadappa.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Apparatus::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp
index aaed806ff5..f801ab2d35 100644
--- a/components/esm4/loadarma.cpp
+++ b/components/esm4/loadarma.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::ArmorAddon::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp
index eebacb516f..dde2fe6768 100644
--- a/components/esm4/loadarmo.cpp
+++ b/components/esm4/loadarmo.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Armor::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     std::uint32_t esmVer = reader.esmVersion();
diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp
index acfa28085b..4fef48359c 100644
--- a/components/esm4/loadaspc.cpp
+++ b/components/esm4/loadaspc.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::AcousticSpace::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp
index 3419ad4c12..d8b4004aef 100644
--- a/components/esm4/loadbook.cpp
+++ b/components/esm4/loadbook.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Book::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     // std::uint32_t esmVer = reader.esmVersion(); // currently unused
diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp
index 9bc6844388..8fb17cceb8 100644
--- a/components/esm4/loadbptd.cpp
+++ b/components/esm4/loadbptd.cpp
@@ -46,7 +46,7 @@ void ESM4::BodyPartData::BodyPart::clear()
 
 void ESM4::BodyPartData::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadbptd.hpp b/components/esm4/loadbptd.hpp
index d92727743f..6d86c36499 100644
--- a/components/esm4/loadbptd.hpp
+++ b/components/esm4/loadbptd.hpp
@@ -66,14 +66,14 @@ namespace ESM4
 
             std::uint8_t explExplosionChance; // %
             std::uint16_t explDebrisCount;
-            FormId explDebris;
-            FormId explExplosion;
+            FormId32 explDebris;
+            FormId32 explExplosion;
             float trackingMaxAngle;
             float explDebrisScale;
 
             std::int32_t sevDebrisCount;
-            FormId sevDebris;
-            FormId sevExplosion;
+            FormId32 sevDebris;
+            FormId32 sevExplosion;
             float sevDebrisScale;
 
             // Struct - Gore Effects Positioning
@@ -84,8 +84,8 @@ namespace ESM4
             float rotY;
             float rotZ;
 
-            FormId sevImpactDataSet;
-            FormId explImpactDataSet;
+            FormId32 sevImpactDataSet;
+            FormId32 explImpactDataSet;
             uint8_t sevDecalCount;
             uint8_t explDecalCount;
             uint16_t Unknown;
diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp
index 421c20f91c..22d9a1035d 100644
--- a/components/esm4/loadcell.cpp
+++ b/components/esm4/loadcell.cpp
@@ -50,7 +50,7 @@
 // longer/shorter/same as loading the subrecords.
 void ESM4::Cell::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mId = ESM::RefId::formIdRefId(mFormId);
     mFlags = reader.hdr().record.flags;
@@ -66,8 +66,7 @@ void ESM4::Cell::load(ESM4::Reader& reader)
         && reader.grp().label.grid[0] == 0)
     {
         ESM4::CellGrid currCellGrid;
-        currCellGrid.grid.x = 0;
-        currCellGrid.grid.y = 0;
+        currCellGrid = Grid{ 0, 0 };
         reader.setCurrCellGrid(currCellGrid); // side effect: sets mCellGridValid  true
     }
 
@@ -123,10 +122,7 @@ void ESM4::Cell::load(ESM4::Reader& reader)
 
                 // Remember cell grid for later (loading LAND, NAVM which should be CELL temporary children)
                 // Note that grids only apply for external cells.  For interior cells use the cell's formid.
-                ESM4::CellGrid currCell;
-                currCell.grid.x = (int16_t)mX;
-                currCell.grid.y = (int16_t)mY;
-                reader.setCurrCellGrid(currCell);
+                reader.setCurrCellGrid(Grid{ static_cast<int16_t>(mX), static_cast<int16_t>(mY) });
 
                 break;
             }
@@ -156,7 +152,7 @@ void ESM4::Cell::load(ESM4::Reader& reader)
             }
             case ESM4::SUB_XCLR: // for exterior cells
             {
-                mRegions.resize(subHdr.dataSize / sizeof(FormId));
+                mRegions.resize(subHdr.dataSize / sizeof(FormId32));
                 for (std::vector<FormId>::iterator it = mRegions.begin(); it != mRegions.end(); ++it)
                 {
                     reader.getFormId(*it);
diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp
index fd1a3043ec..97abb88751 100644
--- a/components/esm4/loadcell.hpp
+++ b/components/esm4/loadcell.hpp
@@ -44,7 +44,6 @@ namespace ESM4
     class Writer;
     struct ReaderContext;
     struct CellGroup;
-    typedef std::uint32_t FormId;
 
     enum CellFlags // TES4                     TES5
     { // -----------------------  ------------------------------------
diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp
index c037260218..a5c571e9a2 100644
--- a/components/esm4/loadclas.cpp
+++ b/components/esm4/loadclas.cpp
@@ -39,7 +39,7 @@
 void ESM4::Class::load(ESM4::Reader& reader)
 {
     // mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted?
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     mFlags = reader.hdr().record.flags;
 
     while (reader.getSubRecordHeader())
diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp
index 259bfb6104..61223dd5d4 100644
--- a/components/esm4/loadclfm.cpp
+++ b/components/esm4/loadclfm.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Colour::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp
index e29f81c032..1d7dc72474 100644
--- a/components/esm4/loadclot.cpp
+++ b/components/esm4/loadclot.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Clothing::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp
index 34b2279446..4cb00d83a1 100644
--- a/components/esm4/loadcont.cpp
+++ b/components/esm4/loadcont.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Container::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp
index 803a5f3615..86bf3d2f78 100644
--- a/components/esm4/loadcrea.cpp
+++ b/components/esm4/loadcrea.cpp
@@ -42,7 +42,7 @@
 
 void ESM4::Creature::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp
index bc9b859609..b2a1040cea 100644
--- a/components/esm4/loaddial.cpp
+++ b/components/esm4/loaddial.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::Dialogue::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp
index d5d8a2569a..c324df5ae0 100644
--- a/components/esm4/loaddobj.cpp
+++ b/components/esm4/loaddobj.cpp
@@ -39,7 +39,7 @@
 
 void ESM4::DefaultObj::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp
index 828a04adf5..f3f261e5fc 100644
--- a/components/esm4/loaddoor.cpp
+++ b/components/esm4/loaddoor.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Door::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp
index 0120479dcc..253d7d3109 100644
--- a/components/esm4/loadeyes.cpp
+++ b/components/esm4/loadeyes.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Eyes::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp
index 2d250cd933..68591b4b97 100644
--- a/components/esm4/loadflor.cpp
+++ b/components/esm4/loadflor.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Flora::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp
index 90356d6afc..8bc30d7d05 100644
--- a/components/esm4/loadflst.cpp
+++ b/components/esm4/loadflst.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::FormIdList::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp
index f32b907d45..5e0ad81c0d 100644
--- a/components/esm4/loadfurn.cpp
+++ b/components/esm4/loadfurn.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Furniture::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp
index 605a1cb683..e49446eef2 100644
--- a/components/esm4/loadglob.cpp
+++ b/components/esm4/loadglob.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::GlobalVariable::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadgmst.cpp b/components/esm4/loadgmst.cpp
index 7fb5d7af5c..e2e4694922 100644
--- a/components/esm4/loadgmst.cpp
+++ b/components/esm4/loadgmst.cpp
@@ -11,8 +11,7 @@ namespace ESM4
         GameSetting::Data readData(FormId formId, std::string_view editorId, Reader& reader)
         {
             if (editorId.empty())
-                throw std::runtime_error(
-                    "Unknown ESM4 GMST (" + std::to_string(formId) + ") data type: editor id is empty");
+                throw std::runtime_error("Unknown ESM4 GMST (" + formId.toString() + ") data type: editor id is empty");
             const char type = editorId[0];
             switch (type)
             {
@@ -36,14 +35,14 @@ namespace ESM4
                 }
                 default:
                     throw std::runtime_error(
-                        "Unsupported ESM4 GMST (" + std::to_string(formId) + ") data type: " + std::string(editorId));
+                        "Unsupported ESM4 GMST (" + formId.toString() + ") data type: " + std::string(editorId));
             }
         }
     }
 
     void GameSetting::load(Reader& reader)
     {
-        mFormId = reader.hdr().record.id;
+        mFormId = reader.hdr().record.getFormId();
         reader.adjustFormId(mFormId);
         mFlags = reader.hdr().record.flags;
 
@@ -59,8 +58,8 @@ namespace ESM4
                     mData = readData(mFormId, mEditorId, reader);
                     break;
                 default:
-                    throw std::runtime_error("Unknown ESM4 GMST (" + std::to_string(mFormId) + ") subrecord "
-                        + ESM::printName(subHdr.typeId));
+                    throw std::runtime_error(
+                        "Unknown ESM4 GMST (" + mFormId.toString() + ") subrecord " + ESM::printName(subHdr.typeId));
             }
         }
     }
diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp
index 547589236f..7a6bf629e2 100644
--- a/components/esm4/loadgras.cpp
+++ b/components/esm4/loadgras.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Grass::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp
index 4da4203387..6fee6a4c1c 100644
--- a/components/esm4/loadhair.cpp
+++ b/components/esm4/loadhair.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Hair::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp
index 69cb3bb674..f308a6a3a2 100644
--- a/components/esm4/loadhdpt.cpp
+++ b/components/esm4/loadhdpt.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::HeadPart::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp
index cf0e993207..1b12b220d0 100644
--- a/components/esm4/loadidle.cpp
+++ b/components/esm4/loadidle.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::IdleAnimation::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp
index 801cd9465b..571c9c8a85 100644
--- a/components/esm4/loadidlm.cpp
+++ b/components/esm4/loadidlm.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::IdleMarker::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp
index ad043978bd..939c377b82 100644
--- a/components/esm4/loadimod.cpp
+++ b/components/esm4/loadimod.cpp
@@ -36,7 +36,7 @@
 
 void ESM4::ItemMod::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp
index b37411accc..c232d80e52 100644
--- a/components/esm4/loadinfo.cpp
+++ b/components/esm4/loadinfo.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::DialogInfo::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp
index 4756be8e63..33ced513bc 100644
--- a/components/esm4/loadingr.cpp
+++ b/components/esm4/loadingr.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Ingredient::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp
index 0ac8ae22e8..d179f15dcc 100644
--- a/components/esm4/loadkeym.cpp
+++ b/components/esm4/loadkeym.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Key::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp
index 0d09fe8332..201fd7a96f 100644
--- a/components/esm4/loadland.cpp
+++ b/components/esm4/loadland.cpp
@@ -55,7 +55,7 @@
 //
 void ESM4::Land::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     mDataTypes = 0;
@@ -181,8 +181,9 @@ void ESM4::Land::load(ESM4::Reader& reader)
             }
             case ESM4::SUB_VTEX: // only in Oblivion?
             {
-                int count = (int)reader.subRecordHeader().dataSize / sizeof(FormId);
-                assert((reader.subRecordHeader().dataSize % sizeof(FormId)) == 0 && "ESM4::LAND VTEX data size error");
+                int count = (int)reader.subRecordHeader().dataSize / sizeof(FormId32);
+                assert(
+                    (reader.subRecordHeader().dataSize % sizeof(FormId32)) == 0 && "ESM4::LAND VTEX data size error");
 
                 if (count)
                 {
@@ -212,7 +213,7 @@ void ESM4::Land::load(ESM4::Reader& reader)
     bool missing = false;
     for (int i = 0; i < 4; ++i)
     {
-        if (mTextures[i].base.formId == 0)
+        if (mTextures[i].base.formId.isZero())
         {
             // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl;
             // std::cout << "layers " << mTextures[i].layers.size() << std::endl;
diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp
index 1382a7237b..7706277109 100644
--- a/components/esm4/loadlgtm.cpp
+++ b/components/esm4/loadlgtm.cpp
@@ -37,7 +37,7 @@
 
 void ESM4::LightingTemplate::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadlgtm.hpp b/components/esm4/loadlgtm.hpp
index d835ef7a67..ef32b24242 100644
--- a/components/esm4/loadlgtm.hpp
+++ b/components/esm4/loadlgtm.hpp
@@ -39,7 +39,6 @@ namespace ESM4
 {
     class Reader;
     class Writer;
-    typedef std::uint32_t FormId;
 
     struct LightingTemplate
     {
diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp
index 6c222a6f60..f0b15e5a2a 100644
--- a/components/esm4/loadligh.cpp
+++ b/components/esm4/loadligh.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Light::load(ESM4::Reader& reader)
 {
-    FormId formId = reader.hdr().record.id;
+    FormId formId = reader.hdr().record.getFormId();
     reader.adjustFormId(formId);
     mId = ESM::RefId::formIdRefId(formId);
     mFlags = reader.hdr().record.flags;
diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp
index d36f492cce..866ae49d28 100644
--- a/components/esm4/loadltex.cpp
+++ b/components/esm4/loadltex.cpp
@@ -39,7 +39,7 @@
 
 void ESM4::LandTexture::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     std::uint32_t esmVer = reader.esmVersion();
diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp
index 3a3a2cc42a..af3b36232b 100644
--- a/components/esm4/loadlvlc.cpp
+++ b/components/esm4/loadlvlc.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::LevelledCreature::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp
index 637814641c..a199bc1b38 100644
--- a/components/esm4/loadlvli.cpp
+++ b/components/esm4/loadlvli.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::LevelledItem::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp
index cf5f70c27c..6705e18921 100644
--- a/components/esm4/loadlvln.cpp
+++ b/components/esm4/loadlvln.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::LevelledNpc::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     // std::uint32_t esmVer = reader.esmVersion(); // currently unused
diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp
index 41b78d4e23..921db78214 100644
--- a/components/esm4/loadmato.cpp
+++ b/components/esm4/loadmato.cpp
@@ -34,7 +34,7 @@
 void ESM4::Material::load(ESM4::Reader& reader)
 {
     // mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted?
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     mFlags = reader.hdr().record.flags;
 
     while (reader.getSubRecordHeader())
diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp
index 62ad5c5396..dbb136298c 100644
--- a/components/esm4/loadmisc.cpp
+++ b/components/esm4/loadmisc.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::MiscItem::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp
index 02b5a8dc0a..d76791a0d5 100644
--- a/components/esm4/loadmset.cpp
+++ b/components/esm4/loadmset.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::MediaSet::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp
index 0f6a3d6cdb..73782f70d8 100644
--- a/components/esm4/loadmstt.cpp
+++ b/components/esm4/loadmstt.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::MovableStatic::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp
index 4b554807a6..77201959f5 100644
--- a/components/esm4/loadmusc.cpp
+++ b/components/esm4/loadmusc.cpp
@@ -38,7 +38,7 @@
 
 void ESM4::Music::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp
index 7b8f669f17..3a0d630abe 100644
--- a/components/esm4/loadnavi.cpp
+++ b/components/esm4/loadnavi.cpp
@@ -151,10 +151,12 @@ void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader)
     reader.get(worldSpaceId);
     // FLG_Tamriel    = 0x0000003c, // grid info follows, possibly Tamriel?
     // FLG_Morrowind  = 0x01380000, // grid info follows, probably Skywind
-    if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000)
+    if (worldSpaceId == FormId{ 0x3c, 0 } || worldSpaceId == FormId{ 0x380000, 1 })
     {
-        reader.get(cellGrid.grid.y); // NOTE: reverse order
-        reader.get(cellGrid.grid.x);
+        Grid grid;
+        reader.get(grid.y); // NOTE: reverse order
+        reader.get(grid.x);
+        cellGrid = grid;
 // FIXME: debugging only
 #if 0
     std::string padding;
@@ -167,7 +169,9 @@ void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader)
     }
     else
     {
-        reader.get(cellGrid.cellId);
+        FormId cellId;
+        reader.get(cellId);
+        cellGrid = cellId;
 
 #if 0
         if (worldSpaceId == 0) // interior
@@ -237,7 +241,7 @@ void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader)
 //
 void ESM4::Navigation::load(ESM4::Reader& reader)
 {
-    // mFormId = reader.hdr().record.id;
+    // mFormId = reader.hdr().record.getFormId();
     // mFlags  = reader.hdr().record.flags;
     std::uint32_t esmVer = reader.esmVersion();
     bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
@@ -312,8 +316,9 @@ void ESM4::Navigation::load(ESM4::Reader& reader)
                     std::cout << "node " << std::hex << node // FIXME: debugging only
                         << ", index " << index << ", i " << std::dec << total+i << std::endl;
 #endif
+                    FormId nodeFormId = FormId::fromUint32(node); // should we apply reader.adjustFormId?
                     // std::pair<std::map<FormId, std::uint32_t>::iterator, bool> res =
-                    mPathIndexMap.insert(std::make_pair(node, index));
+                    mPathIndexMap.emplace(nodeFormId, index);
                     // FIXME: this throws if more than one file is being loaded
                     // if (!res.second)
                     // throw std::runtime_error ("node already exists in the preferred path index map");
diff --git a/components/esm4/loadnavi.hpp b/components/esm4/loadnavi.hpp
index 9a804efe0a..12022dd9ec 100644
--- a/components/esm4/loadnavi.hpp
+++ b/components/esm4/loadnavi.hpp
@@ -45,7 +45,7 @@ namespace ESM4
         struct DoorRef
         {
             std::uint32_t unknown;
-            FormId formId;
+            FormId32 formId;
         };
 
         struct Triangle
diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp
index 0def0dbdb7..0c9b0f5dc6 100644
--- a/components/esm4/loadnavm.cpp
+++ b/components/esm4/loadnavm.cpp
@@ -47,7 +47,7 @@ void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader)
     reader.get(worldSpaceId);
     // FLG_Tamriel    = 0x0000003c, // grid info follows, possibly Tamriel?
     // FLG_Morrowind  = 0x01380000, // grid info follows, probably Skywind
-    if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000)
+    if (worldSpaceId == FormId{ 0x3c, 0 } || worldSpaceId == FormId{ 380000, 1 })
     {
         //   ^
         // Y |                   X Y Index
@@ -65,8 +65,10 @@ void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader)
         //
         // Formula seems to be floor(Skywind coord / 2) <cmath>
         //
-        reader.get(cellGrid.grid.y); // NOTE: reverse order
-        reader.get(cellGrid.grid.x);
+        Grid grid;
+        reader.get(grid.y); // NOTE: reverse order
+        reader.get(grid.x);
+        cellGrid = grid;
 // FIXME: debugging only
 #if 0
         std::string padding;
@@ -79,7 +81,9 @@ void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader)
     }
     else
     {
-        reader.get(cellGrid.cellId);
+        FormId cellId;
+        reader.get(cellId);
+        cellGrid = cellId;
 
 #if 0
         std::string padding; // FIXME
@@ -186,7 +190,7 @@ void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader)
 
 void ESM4::NavMesh::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     mFlags = reader.hdr().record.flags;
 
     // std::cout << "NavMesh 0x" << std::hex << this << std::endl; // FIXME
diff --git a/components/esm4/loadnavm.hpp b/components/esm4/loadnavm.hpp
index a62ed47854..d134db006d 100644
--- a/components/esm4/loadnavm.hpp
+++ b/components/esm4/loadnavm.hpp
@@ -56,7 +56,7 @@ namespace ESM4
         struct ExtConnection
         {
             std::uint32_t unknown;
-            FormId navMesh;
+            FormId32 navMesh;
             std::uint16_t triangleIndex;
         };
 
@@ -64,7 +64,7 @@ namespace ESM4
         {
             std::uint16_t triangleIndex;
             std::uint32_t unknown;
-            FormId doorRef;
+            FormId32 doorRef;
         };
 #pragma pack(pop)
 
diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp
index f0611758c7..8403931db7 100644
--- a/components/esm4/loadnote.cpp
+++ b/components/esm4/loadnote.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Note::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp
index 2b8f953127..c6428177c9 100644
--- a/components/esm4/loadnpc.cpp
+++ b/components/esm4/loadnpc.cpp
@@ -42,7 +42,7 @@
 
 void ESM4::Npc::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp
index f8035d5082..f7e8390bae 100644
--- a/components/esm4/loadotft.cpp
+++ b/components/esm4/loadotft.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Outfit::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
@@ -48,7 +48,7 @@ void ESM4::Outfit::load(ESM4::Reader& reader)
                 break;
             case ESM4::SUB_INAM:
             {
-                std::size_t numObj = subHdr.dataSize / sizeof(FormId);
+                std::size_t numObj = subHdr.dataSize / sizeof(FormId32);
                 for (std::size_t i = 0; i < numObj; ++i)
                 {
                     FormId formId;
diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp
index 90515c5b05..885e4a8c5f 100644
--- a/components/esm4/loadpack.cpp
+++ b/components/esm4/loadpack.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::AIPackage::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadpack.hpp b/components/esm4/loadpack.hpp
index 002782dd08..c0968aa207 100644
--- a/components/esm4/loadpack.hpp
+++ b/components/esm4/loadpack.hpp
@@ -60,14 +60,14 @@ namespace ESM4
         {
             std::int32_t type = 0xff; // 0 = near ref, 1 = in cell, 2 = current loc, 3 = editor loc, 4 = obj id, 5 = obj
                                       // type, 0xff = no location data
-            FormId location; // uint32_t if type = 5
+            FormId32 location; // uint32_t if type = 5
             std::int32_t radius;
         };
 
         struct PTDT // target
         {
             std::int32_t type = 0xff; // 0 = specific ref, 1 = obj id, 2 = obj type, 0xff = no target data
-            FormId target; // uint32_t if type = 2
+            FormId32 target; // uint32_t if type = 2
             std::int32_t distance;
         };
 
@@ -81,8 +81,8 @@ namespace ESM4
             std::uint8_t unknown3; // probably padding
             float compValue;
             std::int32_t fnIndex;
-            FormId param1;
-            FormId param2;
+            FormId32 param1;
+            FormId32 param2;
             std::uint32_t unknown4; // probably padding
         };
 #pragma pack(pop)
diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp
index 5f0ee69df3..19b6df822a 100644
--- a/components/esm4/loadpgrd.cpp
+++ b/components/esm4/loadpgrd.cpp
@@ -36,7 +36,7 @@
 
 void ESM4::Pathgrid::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp
index faf701300b..1cbea4c572 100644
--- a/components/esm4/loadpgre.cpp
+++ b/components/esm4/loadpgre.cpp
@@ -36,7 +36,7 @@
 
 void ESM4::PlacedGrenade::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp
index 8c10ab0146..5056c86280 100644
--- a/components/esm4/loadpwat.cpp
+++ b/components/esm4/loadpwat.cpp
@@ -36,7 +36,7 @@
 
 void ESM4::PlaceableWater::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp
index 992f3b5993..fe0e63e979 100644
--- a/components/esm4/loadqust.cpp
+++ b/components/esm4/loadqust.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::Quest::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp
index f81130a4ce..ae0af2cd4f 100644
--- a/components/esm4/loadrace.cpp
+++ b/components/esm4/loadrace.cpp
@@ -37,7 +37,7 @@
 
 void ESM4::Race::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
@@ -418,7 +418,7 @@ void ESM4::Race::load(ESM4::Reader& reader)
             //
             case ESM4::SUB_HNAM:
             {
-                std::size_t numHairChoices = subHdr.dataSize / sizeof(FormId);
+                std::size_t numHairChoices = subHdr.dataSize / sizeof(FormId32);
                 mHairChoices.resize(numHairChoices);
                 for (unsigned int i = 0; i < numHairChoices; ++i)
                     reader.get(mHairChoices.at(i));
@@ -427,7 +427,7 @@ void ESM4::Race::load(ESM4::Reader& reader)
             }
             case ESM4::SUB_ENAM:
             {
-                std::size_t numEyeChoices = subHdr.dataSize / sizeof(FormId);
+                std::size_t numEyeChoices = subHdr.dataSize / sizeof(FormId32);
                 mEyeChoices.resize(numEyeChoices);
                 for (unsigned int i = 0; i < numEyeChoices; ++i)
                     reader.get(mEyeChoices.at(i));
@@ -538,7 +538,7 @@ void ESM4::Race::load(ESM4::Reader& reader)
                 break;
             case ESM4::SUB_KWDA:
             {
-                std::uint32_t formid;
+                FormId formid;
                 for (unsigned int i = 0; i < mNumKeywords; ++i)
                     reader.getFormId(formid);
                 break;
diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp
index d5ce25ab40..ad2c92e43c 100644
--- a/components/esm4/loadrace.hpp
+++ b/components/esm4/loadrace.hpp
@@ -39,7 +39,6 @@ namespace ESM4
 {
     class Reader;
     class Writer;
-    typedef std::uint32_t FormId;
 
     struct Race
     {
diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp
index 0d61dac9a6..23b4f68c54 100644
--- a/components/esm4/loadrefr.cpp
+++ b/components/esm4/loadrefr.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Reference::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mId = ESM::RefId::formIdRefId(mFormId);
     mFlags = reader.hdr().record.flags;
@@ -93,8 +93,8 @@ void ESM4::Reference::load(ESM4::Reader& reader)
                 break;
             case ESM4::SUB_XESP:
             {
-                reader.get(mEsp);
-                reader.adjustFormId(mEsp.parent);
+                reader.getFormId(mEsp.parent);
+                reader.get(mEsp.flags);
                 // std::cout << "REFR  parent: " << formIdToString(mEsp.parent) << " ref " << formIdToString(mFormId)
                 //<< ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME
                 break;
diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp
index da1a9f08fc..a2eeebce68 100644
--- a/components/esm4/loadregn.cpp
+++ b/components/esm4/loadregn.cpp
@@ -41,7 +41,7 @@
 
 void ESM4::Region::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadregn.hpp b/components/esm4/loadregn.hpp
index 30576dbf8e..521e1c8dba 100644
--- a/components/esm4/loadregn.hpp
+++ b/components/esm4/loadregn.hpp
@@ -63,7 +63,7 @@ namespace ESM4
 
         struct RegionSound
         {
-            FormId sound;
+            FormId32 sound;
             std::uint32_t flags; // 0 pleasant, 1 cloudy, 2 rainy, 3 snowy
             std::uint32_t chance;
         };
diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp
index e4707d3877..24d39c94cc 100644
--- a/components/esm4/loadroad.cpp
+++ b/components/esm4/loadroad.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::Road::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     mParent = reader.currWorld();
diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp
index 0182500924..89c4f58754 100644
--- a/components/esm4/loadsbsp.cpp
+++ b/components/esm4/loadsbsp.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::SubSpace::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp
index 14eaab5f5c..e33ba71ab2 100644
--- a/components/esm4/loadscol.cpp
+++ b/components/esm4/loadscol.cpp
@@ -36,7 +36,7 @@
 
 void ESM4::StaticCollection::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp
index 6a06bcdec2..6d74e2f7ee 100644
--- a/components/esm4/loadscpt.cpp
+++ b/components/esm4/loadscpt.cpp
@@ -35,7 +35,7 @@
 
 void ESM4::Script::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp
index ed0a1e9205..30cc8818fd 100644
--- a/components/esm4/loadscrl.cpp
+++ b/components/esm4/loadscrl.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Scroll::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp
index 27402f520e..580e5ba59c 100644
--- a/components/esm4/loadsgst.cpp
+++ b/components/esm4/loadsgst.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::SigilStone::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp
index 8d42c06169..a1d6fdf235 100644
--- a/components/esm4/loadslgm.cpp
+++ b/components/esm4/loadslgm.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::SoulGem::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp
index f5a74fd091..d0f9fac3b4 100644
--- a/components/esm4/loadsndr.cpp
+++ b/components/esm4/loadsndr.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::SoundReference::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp
index 7e69cc952c..44cfc24776 100644
--- a/components/esm4/loadsoun.cpp
+++ b/components/esm4/loadsoun.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Sound::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp
index c0ab630a13..5dcff407ec 100644
--- a/components/esm4/loadstat.cpp
+++ b/components/esm4/loadstat.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Static::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mId = ESM::RefId::formIdRefId(mFormId);
     mFlags = reader.hdr().record.flags;
diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp
index 362ea65a3c..29bbdc6bca 100644
--- a/components/esm4/loadtact.cpp
+++ b/components/esm4/loadtact.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::TalkingActivator::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp
index 91b056bc92..db4107b2e9 100644
--- a/components/esm4/loadterm.cpp
+++ b/components/esm4/loadterm.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::Terminal::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp
index 8549a1bf02..86334910ae 100644
--- a/components/esm4/loadtes4.cpp
+++ b/components/esm4/loadtes4.cpp
@@ -85,11 +85,13 @@ void ESM4::Header::load(ESM4::Reader& reader)
             }
             case ESM4::SUB_ONAM:
             {
-                mOverrides.resize(subHdr.dataSize / sizeof(FormId));
-                for (unsigned int& mOverride : mOverrides)
+                mOverrides.resize(subHdr.dataSize / sizeof(FormId32));
+                for (FormId& mOverride : mOverrides)
                 {
-                    if (!reader.getExact(mOverride))
+                    uint32_t v;
+                    if (!reader.getExact(v))
                         throw std::runtime_error("TES4 ONAM data read error");
+                    mOverride = FormId::fromUint32(v);
 #if 0
                     std::string padding;
                     padding.insert(0, reader.stackSize()*2, ' ');
diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp
index b0c2656595..fa8dfbca49 100644
--- a/components/esm4/loadtree.cpp
+++ b/components/esm4/loadtree.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Tree::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp
index 20e058ebbc..73d5611653 100644
--- a/components/esm4/loadtxst.cpp
+++ b/components/esm4/loadtxst.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::TextureSet::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp
index d8fdfe26f7..5feec1c8ba 100644
--- a/components/esm4/loadweap.cpp
+++ b/components/esm4/loadweap.cpp
@@ -33,7 +33,7 @@
 
 void ESM4::Weapon::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
     std::uint32_t esmVer = reader.esmVersion();
diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp
index 9cc56cd9fd..dd80b6aaf7 100644
--- a/components/esm4/loadwrld.cpp
+++ b/components/esm4/loadwrld.cpp
@@ -34,7 +34,7 @@
 
 void ESM4::World::load(ESM4::Reader& reader)
 {
-    mFormId = reader.hdr().record.id;
+    mFormId = reader.hdr().record.getFormId();
     reader.adjustFormId(mFormId);
     mFlags = reader.hdr().record.flags;
 
diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp
index f62cd36262..220cf6914e 100644
--- a/components/esm4/reader.cpp
+++ b/components/esm4/reader.cpp
@@ -64,13 +64,11 @@ namespace ESM4
         , filePos(0)
         , fileRead(0)
         , recordRead(0)
-        , currWorld(0)
-        , currCell(0)
+        , currWorld({ 0, 0 })
+        , currCell({ 0, 0 })
+        , currCellGrid(FormId{ 0, 0 })
         , cellGridValid(false)
     {
-        currCellGrid.cellId = 0;
-        currCellGrid.grid.x = 0;
-        currCellGrid.grid.y = 0;
         subRecordHeader.typeId = 0;
         subRecordHeader.dataSize = 0;
     }
@@ -260,7 +258,7 @@ namespace ESM4
             stream->read((char*)&stringId, sizeof(stringId));
             stream->read((char*)&sp.offset, sizeof(sp.offset));
             sp.offset += (std::uint32_t)dataStart;
-            mLStringIndex[stringId] = sp;
+            mLStringIndex[FormId::fromUint32(stringId)] = sp;
         }
         // assert (dataStart - stream->tell() == 0 && "String file start of data section mismatch");
     }
@@ -273,7 +271,7 @@ namespace ESM4
         std::uint32_t stringId; // FormId
         get(stringId);
         if (stringId) // TES5 FoxRace, BOOK
-            getLocalizedStringImpl(stringId, str);
+            getLocalizedStringImpl(FormId::fromUint32(stringId), str);
     }
 
     // FIXME: very messy and probably slow/inefficient
@@ -577,31 +575,15 @@ namespace ESM4
         return mCtx.currCellGrid;
     }
 
-    // NOTE: the parameter 'files' must have the file names in the loaded order
-    void Reader::updateModIndices(const std::vector<std::string>& files)
+    void Reader::updateModIndices(const std::map<std::string, int>& fileToModIndex)
     {
-        if (files.size() >= 0xff)
-            throw std::runtime_error("ESM4::Reader::updateModIndices too many files"); // 0xff is reserved
-
-        // NOTE: this map is rebuilt each time this method is called (i.e. each time a file is loaded)
-        // Perhaps there is an opportunity to optimize this by saving the result somewhere.
-        // But then, the number of files is at most around 250 so perhaps keeping it simple might be better.
-
-        // build a lookup map
-        std::unordered_map<std::string, size_t> fileIndex;
-
-        for (size_t i = 0; i < files.size(); ++i) // ATTENTION: assumes current file is not included
-            fileIndex[Misc::StringUtils::lowerCase(files[i])] = i;
-
         mCtx.parentFileIndices.resize(mHeader.mMaster.size());
         for (unsigned int i = 0; i < mHeader.mMaster.size(); ++i)
         {
             // locate the position of the dependency in already loaded files
-            std::unordered_map<std::string, size_t>::const_iterator it
-                = fileIndex.find(Misc::StringUtils::lowerCase(mHeader.mMaster[i].name));
-
-            if (it != fileIndex.end())
-                mCtx.parentFileIndices[i] = (std::uint32_t)((it->second << 24) & 0xff000000);
+            auto it = fileToModIndex.find(Misc::StringUtils::lowerCase(mHeader.mMaster[i].name));
+            if (it != fileToModIndex.end())
+                mCtx.parentFileIndices[i] = it->second;
             else
                 throw std::runtime_error("ESM4::Reader::updateModIndices required dependency file not loaded");
 #if 0
@@ -609,9 +591,6 @@ namespace ESM4
                   << formIdToString(mCtx.parentFileIndices[i]) << std::endl;
 #endif
         }
-
-        if (!mCtx.parentFileIndices.empty() && mCtx.parentFileIndices[0] != 0)
-            throw std::runtime_error("ESM4::Reader::updateModIndices base modIndex is not zero");
     }
 
     // ModIndex adjusted formId according to master file dependencies
@@ -626,21 +605,25 @@ namespace ESM4
     //        (see https://www.uesp.net/wiki/Tes4Mod:Formid#ModIndex_Zero)
     void Reader::adjustFormId(FormId& id)
     {
-        if (mCtx.parentFileIndices.empty())
-            return;
-
-        std::size_t index = (id >> 24) & 0xff;
-
-        if (index < mCtx.parentFileIndices.size())
-            id = mCtx.parentFileIndices[index] | (id & 0x00ffffff);
+        if (id.hasContentFile() && id.mContentFile < static_cast<int>(mCtx.parentFileIndices.size()))
+            id.mContentFile = mCtx.parentFileIndices[id.mContentFile];
         else
-            id = mCtx.modIndex | (id & 0x00ffffff);
+            id.mContentFile = mCtx.modIndex;
+    }
+
+    void Reader::adjustFormId(FormId32& id)
+    {
+        FormId formId = FormId::fromUint32(id);
+        adjustFormId(formId);
+        id = formId.toUint32();
     }
 
     bool Reader::getFormId(FormId& id)
     {
-        if (!getExact(id))
+        FormId32 v;
+        if (!getExact(v))
             return false;
+        id = FormId::fromUint32(v);
 
         adjustFormId(id);
         return true;
@@ -796,7 +779,7 @@ namespace ESM4
             case ESM4::Grp_CellTemporaryChild:
             case ESM4::Grp_CellVisibleDistChild:
             {
-                ss << ": FormId 0x" << formIdToString(label.value);
+                ss << ": FormId 0x" << formIdToString(FormId::fromUint32(label.value));
                 break;
             }
             default:
diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp
index 3caaf66f6d..b6b4b6017e 100644
--- a/components/esm4/reader.hpp
+++ b/components/esm4/reader.hpp
@@ -69,10 +69,12 @@ namespace ESM4
         std::uint32_t typeId;
         std::uint32_t dataSize; // does *not* include 24 bytes (20 for TES4) of header
         std::uint32_t flags;
-        FormId id;
+        FormId32 id;
         std::uint32_t revision;
         std::uint16_t version; // not in TES4
         std::uint16_t unknown; // not in TES4
+
+        FormId getFormId() const { return FormId::fromUint32(id); }
     };
 
     union RecordHeader
@@ -255,8 +257,8 @@ namespace ESM4
 
         // The object setting up this reader needs to supply the file's load order index
         // so that the formId's in this file can be adjusted with the file (i.e. mod) index.
-        void setModIndex(std::uint32_t index) { mCtx.modIndex = (index << 24) & 0xff000000; }
-        void updateModIndices(const std::vector<std::string>& files);
+        void setModIndex(std::uint32_t index) { mCtx.modIndex = index; }
+        void updateModIndices(const std::map<std::string, int>& fileToModIndex);
 
         // Maybe should throw an exception if called when not valid?
         const CellGrid& currCellGrid() const;
@@ -333,6 +335,9 @@ namespace ESM4
         // ModIndex adjusted formId according to master file dependencies
         void adjustFormId(FormId& id);
 
+        // Temporary. Doesn't support mod index > 255
+        void adjustFormId(FormId32& id);
+
         bool getFormId(FormId& id);
 
         void adjustGRUPFormId();
diff --git a/components/esm4/reference.hpp b/components/esm4/reference.hpp
index 0a0c96e712..6987e811f9 100644
--- a/components/esm4/reference.hpp
+++ b/components/esm4/reference.hpp
@@ -48,6 +48,7 @@ namespace ESM4
         Vector3 pos;
         Vector3 rot; // angles are in radian, rz applied first and rx applied last
     };
+#pragma pack(pop)
 
     // REFR, ACHR, ACRE
     struct EnableParent
@@ -55,7 +56,6 @@ namespace ESM4
         FormId parent;
         std::uint32_t flags; // 0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In
     };
-#pragma pack(pop)
 
     struct LODReference
     {
diff --git a/components/esm4/script.hpp b/components/esm4/script.hpp
index d1cd18018d..cc9bf993b6 100644
--- a/components/esm4/script.hpp
+++ b/components/esm4/script.hpp
@@ -33,6 +33,8 @@
 #include <string>
 #include <vector>
 
+#include "formid.hpp"
+
 namespace ESM4
 {
     enum EmotionType
@@ -324,7 +326,7 @@ namespace ESM4
         std::uint32_t unknown1;
         std::uint32_t responseNo; // 1 byte + padding
         // below FO3/FONV
-        FormId sound; // when 20 bytes usually 0 but there are exceptions (FO3 INFO FormId = 0x0002241f)
+        FormId32 sound; // when 20 bytes usually 0 but there are exceptions (FO3 INFO FormId = 0x0002241f)
         std::uint32_t flags; // 1 byte + padding (0x01 = use emotion anim)
     };
 
@@ -337,7 +339,7 @@ namespace ESM4
         std::uint32_t param2;
         std::uint32_t runOn; // 0 subject, 1 target, 2 reference, 3 combat target, 4 linked reference
         // below FO3/FONV/TES5
-        FormId reference;
+        FormId32 reference;
     };
 
     struct ScriptHeader