You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/components/esm3/esmreader.hpp

324 lines
11 KiB
C++

#ifndef OPENMW_ESM_READER_H
#define OPENMW_ESM_READER_H
#include <cstdint>
#include <filesystem>
#include <istream>
#include <memory>
#include <vector>
#include <components/to_utf8/to_utf8.hpp>
#include "components/esm/esmcommon.hpp"
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2 years ago
#include "components/esm/refid.hpp"
#include "loadtes3.hpp"
namespace ESM
{
class ReadersCache;
class ESMReader
{
public:
ESMReader();
/*************************************************************************
*
* Information retrieval
*
*************************************************************************/
int getVer() const { return mHeader.mData.version; }
int getRecordCount() const { return mHeader.mData.records; }
float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; }
const std::string& getAuthor() const { return mHeader.mData.author; }
const std::string& getDesc() const { return mHeader.mData.desc; }
const std::vector<Header::MasterData>& getGameFiles() const { return mHeader.mMaster; }
const Header& getHeader() const { return mHeader; }
FormatVersion getFormatVersion() const { return mHeader.mFormatVersion; }
const NAME& retSubName() const { return mCtx.subName; }
uint32_t getSubSize() const { return mCtx.leftSub; }
const std::filesystem::path& getName() const { return mCtx.filename; }
bool isOpen() const { return mEsm != nullptr; }
/*************************************************************************
*
* Opening and closing
*
*************************************************************************/
/** Save the current file position and information in a ESM_Context
struct
*/
ESM_Context getContext();
/** Restore a previously saved context */
void restoreContext(const ESM_Context& rc);
/** Close the file, resets all information. After calling close()
the structure may be reused to load a new file.
*/
void close();
/// Raw opening. Opens the file and sets everything up but doesn't
/// parse the header.
void openRaw(std::unique_ptr<std::istream>&& stream, const std::filesystem::path& name);
/// Load ES file from a new stream, parses the header. Closes the
/// currently open file first, if any.
void open(std::unique_ptr<std::istream>&& stream, const std::filesystem::path& name);
void open(const std::filesystem::path& file);
void openRaw(const std::filesystem::path& filename);
/// Get the current position in the file. Make sure that the file has been opened!
size_t getFileOffset() const { return mEsm->tellg(); }
// This is a quick hack for multiple esm/esp files. Each plugin introduces its own
// terrain palette, but ESMReader does not pass a reference to the correct plugin
// to the individual load() methods. This hack allows to pass this reference
// indirectly to the load() method.
void setIndex(const int index) { mCtx.index = index; }
int getIndex() const { return mCtx.index; }
// Assign parent esX files by tracking their indices in the global list of
// all files/readers used by the engine. This is required for correct adjustRefNum() results
// as required for handling moved, deleted and edited CellRefs.
/// @note Does not validate.
void resolveParentFileIndices(ReadersCache& readers);
const std::vector<int>& getParentFileIndices() const { return mCtx.parentFileIndices; }
/*************************************************************************
*
* Medium-level reading shortcuts
*
*************************************************************************/
// Read data of a given type, stored in a subrecord of a given name
template <typename X>
void getHNT(X& x, NAME name)
{
getSubNameIs(name);
getHT(x);
}
// Optional version of getHNT
template <typename X>
void getHNOT(X& x, NAME name)
{
if (isNextSub(name))
getHT(x);
}
// Version with extra size checking, to make sure the compiler
// doesn't mess up our struct padding.
template <std::size_t size, typename X>
void getHNTSized(X& x, NAME name)
{
static_assert(sizeof(X) == size);
getHNT(x, name);
}
template <std::size_t size, typename X>
void getHNOTSized(X& x, NAME name)
{
static_assert(sizeof(X) == size);
getHNOT(x, name);
}
// Get data of a given type/size, including subrecord header
template <typename X>
void getHT(X& x)
{
getSubHeader();
if (mCtx.leftSub != sizeof(X))
reportSubSizeMismatch(sizeof(X), mCtx.leftSub);
getT(x);
}
template <typename T>
void skipHT()
{
getSubHeader();
if (mCtx.leftSub != sizeof(T))
reportSubSizeMismatch(sizeof(T), mCtx.leftSub);
skipT<T>();
}
// Version with extra size checking, to make sure the compiler
// doesn't mess up our struct padding.
template <std::size_t size, typename X>
void getHTSized(X& x)
{
static_assert(sizeof(X) == size);
getHT(x);
}
template <std::size_t size, typename T>
void skipHTSized()
{
static_assert(sizeof(T) == size);
skipHT<T>();
}
// Read a string by the given name if it is the next record.
std::string getHNOString(NAME name);
ESM::RefId getHNORefId(NAME name);
void skipHNOString(NAME name);
// Read a string with the given sub-record name
std::string getHNString(NAME name);
// Read a string, including the sub-record header (but not the name)
std::string getHString();
RefId getRefId();
void skipHString();
// Read the given number of bytes from a subrecord
void getHExact(void* p, int size);
// Read the given number of bytes from a named subrecord
void getHNExact(void* p, int size, NAME name);
/*************************************************************************
*
* Low level sub-record methods
*
*************************************************************************/
// Get the next subrecord name and check if it matches the parameter
void getSubNameIs(NAME name);
/** Checks if the next sub record name matches the parameter. If it
does, it is read into 'subName' just as if getSubName() was
called. If not, the read name will still be available for future
calls to getSubName(), isNextSub() and getSubNameIs().
*/
bool isNextSub(NAME name);
bool peekNextSub(NAME name);
// Store the current subrecord name for the next call of getSubName()
void cacheSubName() { mCtx.subCached = true; }
// Read subrecord name. This gets called a LOT, so I've optimized it
// slightly.
void getSubName();
// Skip current sub record, including header (but not including
// name.)
void skipHSub();
// Skip sub record and check its size
void skipHSubSize(int size);
// Skip all subrecords until the given subrecord or no more subrecords remaining
void skipHSubUntil(NAME name);
/* Sub-record header. This updates leftRec beyond the current
sub-record as well. leftSub contains size of current sub-record.
*/
void getSubHeader();
/*************************************************************************
*
* Low level record methods
*
*************************************************************************/
// Get the next record name
NAME getRecName();
// Skip the rest of this record. Assumes the name and header have
// already been read
void skipRecord();
/* Read record header. This updatesleftFile BEYOND the data that
follows the header, ie beyond the entire record. You should use
leftRec to orient yourself inside the record itself.
*/
void getRecHeader() { getRecHeader(mRecordFlags); }
void getRecHeader(uint32_t& flags);
bool hasMoreRecs() const { return mCtx.leftFile > 0; }
bool hasMoreSubs() const { return mCtx.leftRec > 0; }
/*************************************************************************
*
* Lowest level data reading and misc methods
*
*************************************************************************/
template <typename X>
void getT(X& x)
{
getExact(&x, sizeof(X));
}
template <typename T>
void skipT()
{
skip(sizeof(T));
}
void getExact(void* x, int size) { mEsm->read((char*)x, size); }
void getName(NAME& name) { getT(name); }
void getUint(uint32_t& u) { getT(u); }
// Read the next 'size' bytes and return them as a string. Converts
// them from native encoding to UTF8 in the process.
std::string getString(int size);
ESM::RefId getRefId(int size);
void skip(std::size_t bytes)
{
char buffer[4096];
if (bytes > std::size(buffer))
mEsm->seekg(getFileOffset() + bytes);
else
mEsm->read(buffer, bytes);
}
/// Used for error handling
[[noreturn]] void fail(const std::string& msg);
/// Sets font encoder for ESM strings
void setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; }
/// Get record flags of last record
unsigned int getRecordFlags() { return mRecordFlags; }
size_t getFileSize() const { return mFileSize; }
private:
[[noreturn]] void reportSubSizeMismatch(size_t want, size_t got)
{
fail("record size mismatch, requested " + std::to_string(want) + ", got " + std::to_string(got));
}
void clearCtx();
std::unique_ptr<std::istream> mEsm;
ESM_Context mCtx;
unsigned int mRecordFlags;
// Special file signifier (see SpecialFile enum above)
// Buffer for ESM strings
std::vector<char> mBuffer;
Header mHeader;
ToUTF8::Utf8Encoder* mEncoder;
size_t mFileSize;
};
}
#endif