1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-23 06:41:08 +00:00

Initial commit to load ESM4

Some data is actually loaded and store in ESM Store
Any new ESM4 will go through the same code path and be automatically sent to the right store
This commit is contained in:
florent.teppe 2022-12-28 20:48:25 +01:00
parent 80e2cd79ec
commit c721a6cafa
14 changed files with 221 additions and 40 deletions

View File

@ -1,9 +1,13 @@
#include "esmloader.hpp" #include "esmloader.hpp"
#include "esmstore.hpp" #include "esmstore.hpp"
#include <components/esm/format.hpp>
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
#include <components/esm3/readerscache.hpp> #include <components/esm3/readerscache.hpp>
#include <components/esm4/reader.hpp>
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
#include <components/files/openfile.hpp>
#include <fstream>
namespace MWWorld namespace MWWorld
{ {
@ -21,8 +25,21 @@ namespace MWWorld
void EsmLoader::load(const std::filesystem::path& filepath, int& index, Loading::Listener* listener) void EsmLoader::load(const std::filesystem::path& filepath, int& index, Loading::Listener* listener)
{ {
const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast<std::size_t>(index));
auto stream = Files::openBinaryInputFileStream(filepath);
if (!stream->is_open())
{
throw std::runtime_error(std::string("File Failed to open file: ") + std::strerror(errno) + "\n");
return;
}
const ESM::Format format = ESM::readFormat(*stream);
stream->seekg(0);
switch (format)
{
case ESM::Format::Tes3:
{
const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast<std::size_t>(index));
reader->setEncoder(mEncoder); reader->setEncoder(mEncoder);
reader->setIndex(index); reader->setIndex(index);
reader->open(filepath); reader->open(filepath);
@ -43,6 +60,15 @@ namespace MWWorld
&& (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm") && (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm")
|| Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame"))) || Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame")))
mMasterFileFormat = reader->getFormat(); mMasterFileFormat = reader->getFormat();
break;
}
case ESM::Format::Tes4:
{
ESM4::Reader readerESM4(std::move(stream), filepath);
readerESM4.setEncoder(mEncoder->getStatelessEncoder());
mStore.loadESM4(readerESM4, listener, mDialogue);
}
}
} }
} /* namespace MWWorld */ } /* namespace MWWorld */

View File

@ -13,6 +13,11 @@
#include <components/lua/configuration.hpp> #include <components/lua/configuration.hpp>
#include <components/misc/algorithm.hpp> #include <components/misc/algorithm.hpp>
#include <components/esm4/loadcell.hpp>
#include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp>
#include <components/esm4/reader.hpp>
#include <components/esm4/common.hpp> #include <components/esm4/common.hpp>
#include <components/esmloader/load.hpp> #include <components/esmloader/load.hpp>
@ -180,6 +185,84 @@ namespace MWWorld
} }
} }
} }
template <typename T>
static void typedReadRecordESM4(ESM4::Reader& reader, ESMStore& stores, Store<T>& store, int& found)
{
auto recordType = static_cast<ESM4::RecordTypes>(reader.hdr().record.typeId);
ESM::RecNameInts esm4RecName = static_cast<ESM::RecNameInts>(ESM::esm4Recname(recordType));
if constexpr (std::is_convertible_v<Store<T>*, DynamicStore*>)
{
if constexpr (ESM::isESM4Rec(T::sRecordId))
{
if (T::sRecordId == esm4RecName)
{
reader.getRecordData();
T value;
value.load(reader);
store.insertStatic(value);
found++;
}
}
}
}
static void readRecord(ESM4::Reader& reader, ESMStore& store)
{
int found = 0;
std::apply([&reader, &store, &found](
auto&... x) { (ESMStoreImp::typedReadRecordESM4(reader, store, x, found), ...); },
store.mStoreImp->mStores);
assert(found <= 1);
if (found == 0) // unhandled record
reader.skipRecordData();
}
static bool readItem(ESM4::Reader& reader, ESMStore& store)
{
if (!reader.getRecordHeader() || !reader.hasMoreRecs())
return false;
const ESM4::RecordHeader& header = reader.hdr();
if (header.record.typeId == ESM4::REC_GRUP)
return readGroup(reader, store);
readRecord(reader, store);
return true;
}
static bool readGroup(ESM4::Reader& reader, ESMStore& store)
{
const ESM4::RecordHeader& header = reader.hdr();
switch (static_cast<ESM4::GroupType>(header.group.type))
{
case ESM4::Grp_RecordType:
case ESM4::Grp_InteriorCell:
case ESM4::Grp_InteriorSubCell:
case ESM4::Grp_ExteriorCell:
case ESM4::Grp_ExteriorSubCell:
reader.enterGroup();
return readItem(reader, store);
case ESM4::Grp_WorldChild:
case ESM4::Grp_CellChild:
case ESM4::Grp_TopicChild:
case ESM4::Grp_CellPersistentChild:
case ESM4::Grp_CellTemporaryChild:
case ESM4::Grp_CellVisibleDistChild:
reader.adjustGRUPFormId();
reader.enterGroup();
if (!reader.hasMoreRecs())
return false;
return readItem(reader, store);
}
reader.skipGroup();
return true;
}
}; };
int ESMStore::find(const ESM::RefId& id) const int ESMStore::find(const ESM::RefId& id) const
@ -338,6 +421,16 @@ namespace MWWorld
} }
} }
void ESMStore::loadESM4(ESM4::Reader& reader, Loading::Listener* listener, ESM::Dialogue*& dialogue)
{
while (reader.hasMoreRecs())
{
reader.exitGroupCheck();
if (!ESMStoreImp::readItem(reader, *this))
break;
}
}
void ESMStore::setIdType(const ESM::RefId& id, ESM::RecNameInts type) void ESMStore::setIdType(const ESM::RefId& id, ESM::RecNameInts type)
{ {
mStoreImp->mIds[id] = type; mStoreImp->mIds[id] = type;

View File

@ -24,6 +24,14 @@ namespace MWMechanics
class SpellList; class SpellList;
} }
namespace ESM4
{
class Reader;
struct Static;
struct Cell;
struct Reference;
}
namespace ESM namespace ESM
{ {
class ReadersCache; class ReadersCache;
@ -95,7 +103,9 @@ namespace MWWorld
Store<ESM::MagicEffect>, Store<ESM::Skill>, Store<ESM::MagicEffect>, Store<ESM::Skill>,
// Special entry which is hardcoded and not loaded from an ESM // Special entry which is hardcoded and not loaded from an ESM
Store<ESM::Attribute>>; Store<ESM::Attribute>,
Store<ESM4::Static>, Store<ESM4::Cell>, Store<ESM4::Reference>>;
template <typename T> template <typename T>
static constexpr std::size_t getTypeIndex() static constexpr std::size_t getTypeIndex()
@ -162,6 +172,7 @@ namespace MWWorld
void validateDynamic(); void validateDynamic();
void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue);
void loadESM4(ESM4::Reader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue);
template <class T> template <class T>
const Store<T>& get() const const Store<T>& get() const

View File

@ -13,6 +13,10 @@
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <components/esm4/loadcell.hpp>
#include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp>
namespace namespace
{ {
// TODO: Switch to C++23 to get a working version of std::unordered_map::erase // TODO: Switch to C++23 to get a working version of std::unordered_map::erase
@ -161,7 +165,10 @@ namespace MWWorld
if (ptr == nullptr) if (ptr == nullptr)
{ {
std::stringstream msg; std::stringstream msg;
if constexpr (!ESM::isESM4Rec(T::sRecordId))
{
msg << T::getRecordType() << " '" << id << "' not found"; msg << T::getRecordType() << " '" << id << "' not found";
}
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
return ptr; return ptr;
@ -171,8 +178,10 @@ namespace MWWorld
{ {
T record; T record;
bool isDeleted = false; bool isDeleted = false;
if constexpr (!ESM::isESM4Rec(T::sRecordId))
{
record.load(esm, isDeleted); record.load(esm, isDeleted);
}
std::pair<typename Static::iterator, bool> inserted = mStatic.insert_or_assign(record.mId, record); std::pair<typename Static::iterator, bool> inserted = mStatic.insert_or_assign(record.mId, record);
if (inserted.second) if (inserted.second)
@ -293,7 +302,11 @@ namespace MWWorld
for (typename Dynamic::const_iterator iter(mDynamic.begin()); iter != mDynamic.end(); ++iter) for (typename Dynamic::const_iterator iter(mDynamic.begin()); iter != mDynamic.end(); ++iter)
{ {
writer.startRecord(T::sRecordId); writer.startRecord(T::sRecordId);
if constexpr (!ESM::isESM4Rec(T::sRecordId))
{
iter->second.save(writer); iter->second.save(writer);
}
writer.endRecord(T::sRecordId); writer.endRecord(T::sRecordId);
} }
} }
@ -302,8 +315,10 @@ namespace MWWorld
{ {
T record; T record;
bool isDeleted = false; bool isDeleted = false;
if constexpr (!ESM::isESM4Rec(T::sRecordId))
{
record.load(reader, isDeleted); record.load(reader, isDeleted);
}
insert(record, overrideOnly); insert(record, overrideOnly);
return RecordId(record.mId, isDeleted); return RecordId(record.mId, isDeleted);
@ -1196,3 +1211,7 @@ template class MWWorld::TypedDynamicStore<ESM::Spell>;
template class MWWorld::TypedDynamicStore<ESM::StartScript>; template class MWWorld::TypedDynamicStore<ESM::StartScript>;
template class MWWorld::TypedDynamicStore<ESM::Static>; template class MWWorld::TypedDynamicStore<ESM::Static>;
template class MWWorld::TypedDynamicStore<ESM::Weapon>; template class MWWorld::TypedDynamicStore<ESM::Weapon>;
template class MWWorld::TypedDynamicStore<ESM4::Static>;
template class MWWorld::TypedDynamicStore<ESM4::Reference>;
template class MWWorld::TypedDynamicStore<ESM4::Cell>;

View File

@ -333,6 +333,11 @@ namespace ESM
REC_MSET4 = esm4Recname(ESM4::REC_MSET) // Media Set REC_MSET4 = esm4Recname(ESM4::REC_MSET) // Media Set
}; };
constexpr bool isESM4Rec(RecNameInts RecName)
{
return RecName & sEsm4RecnameFlag;
}
/// Common subrecords /// Common subrecords
enum SubRecNameInts enum SubRecNameInts
{ {

View File

@ -34,6 +34,11 @@ namespace ESM
return newRefId; return newRefId;
} }
RefId RefId::formIdRefId(const ESM4::FormId id)
{
return ESM::RefId::stringRefId(ESM4::formIdToString(id));
}
bool RefId::operator==(std::string_view rhs) const bool RefId::operator==(std::string_view rhs) const
{ {
return Misc::StringUtils::ciEqual(mId, rhs); return Misc::StringUtils::ciEqual(mId, rhs);

View File

@ -1,6 +1,7 @@
#ifndef OPENMW_COMPONENTS_ESM_REFID_HPP #ifndef OPENMW_COMPONENTS_ESM_REFID_HPP
#define OPENMW_COMPONENTS_ESM_REFID_HPP #define OPENMW_COMPONENTS_ESM_REFID_HPP
#include <compare> #include <compare>
#include <components/esm4/formid.hpp>
#include <functional> #include <functional>
#include <iosfwd> #include <iosfwd>
#include <string> #include <string>
@ -26,6 +27,7 @@ namespace ESM
// RefIds that are as string in the code. For serialization, and display. Using explicit conversions make it // RefIds that are as string in the code. For serialization, and display. Using explicit conversions make it
// very clear where in the code we need to convert from string to RefId and Vice versa. // very clear where in the code we need to convert from string to RefId and Vice versa.
static RefId stringRefId(std::string_view id); static RefId stringRefId(std::string_view id);
static RefId formIdRefId(const ESM4::FormId id);
const std::string& getRefIdString() const { return mId; } const std::string& getRefIdString() const { return mId; }
private: private:

View File

@ -37,6 +37,7 @@
#include <iostream> // FIXME: debug only #include <iostream> // FIXME: debug only
#include "reader.hpp" #include "reader.hpp"
#include <components/esm/refid.hpp>
// #include "writer.hpp" // #include "writer.hpp"
// TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) // TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent)
@ -48,8 +49,9 @@
// longer/shorter/same as loading the subrecords. // longer/shorter/same as loading the subrecords.
void ESM4::Cell::load(ESM4::Reader& reader) void ESM4::Cell::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.id; auto formId = reader.hdr().record.id;
reader.adjustFormId(mFormId); reader.adjustFormId(formId);
mId = ESM::RefId::formIdRefId(formId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
mParent = reader.currWorld(); mParent = reader.currWorld();
@ -71,7 +73,7 @@ void ESM4::Cell::load(ESM4::Reader& reader)
// WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading // WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading
// cell child groups if we are loading them after restoring the context // cell child groups if we are loading them after restoring the context
// (may be easier to update the context before saving?) // (may be easier to update the context before saving?)
reader.setCurrCell(mFormId); // save for LAND (and other children) to access later reader.setCurrCell(formId); // save for LAND (and other children) to access later
std::uint32_t esmVer = reader.esmVersion(); std::uint32_t esmVer = reader.esmVersion();
bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;

View File

@ -33,6 +33,8 @@
#include "formid.hpp" #include "formid.hpp"
#include "lighting.hpp" #include "lighting.hpp"
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
namespace ESM4 namespace ESM4
{ {
@ -61,7 +63,7 @@ namespace ESM4
{ {
FormId mParent; // world formId (for grouping cells), from the loading sequence FormId mParent; // world formId (for grouping cells), from the loading sequence
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId; std::string mEditorId;
@ -95,6 +97,8 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
void blank(); void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::REC_CELL4;
}; };
} }

View File

@ -34,10 +34,11 @@
void ESM4::Reference::load(ESM4::Reader& reader) void ESM4::Reference::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.id; auto formId = reader.hdr().record.id;
reader.adjustFormId(mFormId); reader.adjustFormId(formId);
mId = ESM::RefId::formIdRefId(formId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
mParent = reader.currCell(); // NOTE: only for persistent refs? mParent = ESM::RefId::formIdRefId(reader.currCell()); // NOTE: only for persistent refs?
// TODO: Let the engine apply this? Saved games? // TODO: Let the engine apply this? Saved games?
// mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false; // mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false;
@ -60,7 +61,9 @@ void ESM4::Reference::load(ESM4::Reader& reader)
break; break;
case ESM4::SUB_NAME: case ESM4::SUB_NAME:
{ {
reader.getFormId(mBaseObj); FormId BaseId;
reader.getFormId(BaseId);
mBaseObj = ESM::RefId::formIdRefId(BaseId);
#if 0 #if 0
if (mFlags & ESM4::Rec_Disabled) if (mFlags & ESM4::Rec_Disabled)
std::cout << "REFR disable at start " << formIdToString(mFormId) << std::cout << "REFR disable at start " << formIdToString(mFormId) <<

View File

@ -30,6 +30,8 @@
#include <cstdint> #include <cstdint>
#include "reference.hpp" // FormId, Placement, EnableParent #include "reference.hpp" // FormId, Placement, EnableParent
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
namespace ESM4 namespace ESM4
{ {
@ -71,15 +73,15 @@ namespace ESM4
struct Reference struct Reference
{ {
FormId mParent; // cell FormId (currently persistent refs only), from the loading sequence ESM::RefId mParent; // cell FormId (currently persistent refs only), from the loading sequence
// NOTE: for exterior cells it will be the dummy cell FormId // NOTE: for exterior cells it will be the dummy cell FormId
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId; std::string mEditorId;
std::string mFullName; std::string mFullName;
FormId mBaseObj; ESM::RefId mBaseObj;
Placement mPlacement; Placement mPlacement;
float mScale = 1.0f; float mScale = 1.0f;
@ -110,6 +112,8 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
void blank(); void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::REC_REFR4;
}; };
} }

View File

@ -34,8 +34,9 @@
void ESM4::Static::load(ESM4::Reader& reader) void ESM4::Static::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.id; FormId formId = reader.hdr().record.id;
reader.adjustFormId(mFormId); reader.adjustFormId(formId);
mId = ESM::RefId::formIdRefId(formId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
while (reader.getSubRecordHeader()) while (reader.getSubRecordHeader())

View File

@ -32,6 +32,8 @@
#include <vector> #include <vector>
#include "formid.hpp" #include "formid.hpp"
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
namespace ESM4 namespace ESM4
{ {
@ -40,7 +42,7 @@ namespace ESM4
struct Static struct Static
{ {
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId; std::string mEditorId;
@ -53,6 +55,8 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::REC_STAT4;
}; };
} }

View File

@ -68,6 +68,8 @@ namespace ToUTF8
/// ASCII-only string. Otherwise returns a view to the input. /// ASCII-only string. Otherwise returns a view to the input.
std::string_view getLegacyEnc(std::string_view input); std::string_view getLegacyEnc(std::string_view input);
const StatelessUtf8Encoder* getStatelessEncoder() const { return &mImpl; }
private: private:
std::string mBuffer; std::string mBuffer;
StatelessUtf8Encoder mImpl; StatelessUtf8Encoder mImpl;