1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-28 17:09:41 +00:00

Update NIF Reader class and related code

Update BSStreamHeader definitions
Fix 10.0.1.8 loading
Explicitly avoid loading 20.3.1.2
This commit is contained in:
Alexei Kotov 2023-09-10 00:04:17 +03:00
parent ef896faa90
commit 535290a83d
2 changed files with 133 additions and 111 deletions

View file

@ -25,13 +25,13 @@ namespace Nif
{ {
Reader::Reader(NIFFile& file) Reader::Reader(NIFFile& file)
: ver(file.mVersion) : mVersion(file.mVersion)
, userVer(file.mUserVersion) , mUserVersion(file.mUserVersion)
, bethVer(file.mBethVersion) , mBethVersion(file.mBethVersion)
, filename(file.mPath) , mFilename(file.mPath)
, hash(file.mHash) , mHash(file.mHash)
, records(file.mRecords) , mRecords(file.mRecords)
, roots(file.mRoots) , mRoots(file.mRoots)
, mUseSkinning(file.mUseSkinning) , mUseSkinning(file.mUseSkinning)
{ {
} }
@ -315,7 +315,7 @@ namespace Nif
/// Make the factory map used for parsing the file /// Make the factory map used for parsing the file
static const std::map<std::string, CreateRecord> factories = makeFactory(); static const std::map<std::string, CreateRecord> factories = makeFactory();
std::string Reader::printVersion(unsigned int version) std::string Reader::versionToString(std::uint32_t version)
{ {
int major = (version >> 24) & 0xFF; int major = (version >> 24) & 0xFF;
int minor = (version >> 16) & 0xFF; int minor = (version >> 16) & 0xFF;
@ -329,8 +329,8 @@ namespace Nif
void Reader::parse(Files::IStreamPtr&& stream) void Reader::parse(Files::IStreamPtr&& stream)
{ {
const std::array<std::uint64_t, 2> fileHash = Files::getHash(filename, *stream); const std::array<std::uint64_t, 2> fileHash = Files::getHash(mFilename, *stream);
hash.append(reinterpret_cast<const char*>(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); mHash.append(reinterpret_cast<const char*>(fileHash.data()), fileHash.size() * sizeof(std::uint64_t));
NIFStream nif(*this, std::move(stream)); NIFStream nif(*this, std::move(stream));
@ -343,151 +343,172 @@ namespace Nif
const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(), const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(),
[&](const std::string& verString) { return head.starts_with(verString); }); [&](const std::string& verString) { return head.starts_with(verString); });
if (!supportedHeader) if (!supportedHeader)
throw Nif::Exception("Invalid NIF header: " + head, filename); throw Nif::Exception("Invalid NIF header: " + head, mFilename);
// Get BCD version // Get BCD version
ver = nif.getUInt(); nif.read(mVersion);
// 4.0.0.0 is an older, practically identical version of the format. // 4.0.0.0 is an older, practically identical version of the format.
// It's not used by Morrowind assets but Morrowind supports it. // It's not used by Morrowind assets but Morrowind supports it.
static const std::array<uint32_t, 2> supportedVers = { static const std::array<uint32_t, 2> supportedVers = {
NIFStream::generateVersion(4, 0, 0, 0), NIFStream::generateVersion(4, 0, 0, 0),
NIFFile::VER_MW, NIFFile::VER_MW,
}; };
const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end(); const bool supportedVersion
= std::find(supportedVers.begin(), supportedVers.end(), mVersion) != supportedVers.end();
const bool writeDebugLog = sWriteNifDebugLog; const bool writeDebugLog = sWriteNifDebugLog;
if (!supportedVersion) if (!supportedVersion)
{ {
if (!sLoadUnsupportedFiles) if (!sLoadUnsupportedFiles)
throw Nif::Exception("Unsupported NIF version: " + printVersion(ver), filename); throw Nif::Exception("Unsupported NIF version: " + versionToString(mVersion), mFilename);
if (writeDebugLog) if (writeDebugLog)
Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << printVersion(ver) Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << versionToString(mVersion)
<< ". Proceed with caution! File: " << filename; << ". Proceed with caution! File: " << mFilename;
} }
// NIF data endianness const bool hasEndianness = mVersion >= NIFStream::generateVersion(20, 0, 0, 4);
if (ver >= NIFStream::generateVersion(20, 0, 0, 4)) const bool hasUserVersion = mVersion >= NIFStream::generateVersion(10, 0, 1, 8);
const bool hasRecTypeListings = mVersion >= NIFStream::generateVersion(5, 0, 0, 1);
const bool hasRecTypeHashes = mVersion == NIFStream::generateVersion(20, 3, 1, 2);
const bool hasRecordSizes = mVersion >= NIFStream::generateVersion(20, 2, 0, 5);
const bool hasGroups = mVersion >= NIFStream::generateVersion(5, 0, 0, 6);
const bool hasStringTable = mVersion >= NIFStream::generateVersion(20, 1, 0, 1);
const bool hasRecordSeparators
= mVersion >= NIFStream::generateVersion(10, 0, 0, 0) && mVersion < NIFStream::generateVersion(10, 2, 0, 0);
// Record type list
std::vector<std::string> recTypes;
// Record type mapping for each record
std::vector<std::uint16_t> recTypeIndices;
{ {
unsigned char endianness = nif.getChar(); std::uint8_t endianness = 1;
if (hasEndianness)
nif.read(endianness);
// TODO: find some big-endian files and investigate the difference
if (endianness == 0) if (endianness == 0)
throw Nif::Exception("Big endian NIF files are unsupported", filename); throw Nif::Exception("Big endian NIF files are unsupported", mFilename);
} }
// User version if (hasUserVersion)
if (ver > NIFStream::generateVersion(10, 0, 1, 8)) nif.read(mUserVersion);
userVer = nif.getUInt();
// Number of records mRecords.resize(nif.get<std::uint32_t>());
const std::size_t recNum = nif.getUInt();
records.resize(recNum);
// Bethesda stream header // Bethesda stream header
// It contains Bethesda format version and (useless) export information
if (ver == NIFFile::VER_OB_OLD
|| (userVer >= 3
&& ((ver == NIFFile::VER_OB || ver == NIFFile::VER_BGS)
|| (ver >= NIFStream::generateVersion(10, 1, 0, 0) && ver <= NIFStream::generateVersion(20, 0, 0, 4)
&& userVer <= 11))))
{ {
bethVer = nif.getUInt(); bool hasBSStreamHeader = false;
nif.getExportString(); // Author if (mVersion == NIFFile::VER_OB_OLD)
if (bethVer > NIFFile::BETHVER_FO4) hasBSStreamHeader = true;
nif.getUInt(); // Unknown else if (mUserVersion >= 3 && mVersion >= NIFStream::generateVersion(10, 1, 0, 0))
nif.getExportString(); // Process script
nif.getExportString(); // Export script
if (bethVer == NIFFile::BETHVER_FO4)
nif.getExportString(); // Max file path
}
std::vector<std::string> recTypes;
std::vector<unsigned short> recTypeIndices;
const bool hasRecTypeListings = ver >= NIFStream::generateVersion(5, 0, 0, 1);
if (hasRecTypeListings)
{
unsigned short recTypeNum = nif.getUShort();
// Record type list
nif.getSizedStrings(recTypes, recTypeNum);
// Record type mapping for each record
nif.readVector(recTypeIndices, recNum);
if (ver >= NIFStream::generateVersion(5, 0, 0, 6)) // Groups
{ {
if (ver >= NIFStream::generateVersion(20, 1, 0, 1)) // String table if (mVersion <= NIFFile::VER_OB || mVersion == NIFFile::VER_BGS)
{ hasBSStreamHeader = mUserVersion <= 11 || mVersion >= NIFFile::VER_OB;
if (ver >= NIFStream::generateVersion(20, 2, 0, 5)) // Record sizes }
{
std::vector<unsigned int> recSizes; // Currently unused if (hasBSStreamHeader)
nif.readVector(recSizes, recNum); {
} nif.read(mBethVersion);
const std::size_t stringNum = nif.getUInt(); nif.getExportString(); // Author
nif.getUInt(); // Max string length if (mBethVersion >= 131)
nif.getSizedStrings(strings, stringNum); nif.get<std::uint32_t>(); // Unknown
} else
std::vector<unsigned int> groups; // Currently unused nif.getExportString(); // Process script
unsigned int groupNum = nif.getUInt(); nif.getExportString(); // Export script
nif.readVector(groups, groupNum); if (mBethVersion >= 103)
nif.getExportString(); // Max file path
} }
} }
const bool hasRecordSeparators if (hasRecTypeListings)
= ver >= NIFStream::generateVersion(10, 0, 0, 0) && ver < NIFStream::generateVersion(10, 2, 0, 0); {
for (std::size_t i = 0; i < recNum; i++) // TODO: 20.3.1.2 uses DJB hashes instead of strings
if (hasRecTypeHashes)
throw Nif::Exception("Hashed record types are unsupported", mFilename);
else
{
nif.getSizedStrings(recTypes, nif.get<std::uint16_t>());
nif.readVector(recTypeIndices, mRecords.size());
}
}
if (hasRecordSizes) // Record sizes
{
std::vector<std::uint32_t> recSizes; // Currently unused
nif.readVector(recSizes, mRecords.size());
}
if (hasStringTable)
{
std::uint32_t stringNum, maxStringLength;
nif.read(stringNum);
nif.read(maxStringLength);
nif.getSizedStrings(mStrings, stringNum);
}
if (hasGroups)
{
std::vector<std::uint32_t> groups; // Currently unused
nif.readVector(groups, nif.get<std::uint32_t>());
}
for (std::size_t i = 0; i < mRecords.size(); i++)
{ {
std::unique_ptr<Record> r; std::unique_ptr<Record> r;
std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString(); std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.get<std::string>();
if (rec.empty()) if (rec.empty())
{ {
std::stringstream error; std::stringstream error;
error << "Record type is blank (index " << i << ")"; error << "Record type is blank (index " << i << ")";
throw Nif::Exception(error.str(), filename); throw Nif::Exception(error.str(), mFilename);
} }
// Record separator. Some Havok records in Oblivion do not have it. // Record separator. Some Havok records in Oblivion do not have it.
if (hasRecordSeparators && !rec.starts_with("bhk")) if (hasRecordSeparators && !rec.starts_with("bhk"))
if (nif.getInt()) if (nif.get<int32_t>())
Log(Debug::Warning) << "NIFFile Warning: Record of type " << rec << ", index " << i Log(Debug::Warning) << "NIFFile Warning: Record of type " << rec << ", index " << i
<< " is preceded by a non-zero separator. File: " << filename; << " is preceded by a non-zero separator. File: " << mFilename;
const auto entry = factories.find(rec); const auto entry = factories.find(rec);
if (entry == factories.end()) if (entry == factories.end())
throw Nif::Exception("Unknown record type " + rec, filename); throw Nif::Exception("Unknown record type " + rec, mFilename);
r = entry->second(); r = entry->second();
if (!supportedVersion && writeDebugLog) if (!supportedVersion && writeDebugLog)
Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " ("
<< filename << ")"; << mFilename << ")";
assert(r != nullptr); assert(r != nullptr);
assert(r->recType != RC_MISSING); assert(r->recType != RC_MISSING);
r->recName = rec; r->recName = rec;
r->recIndex = i; r->recIndex = i;
r->read(&nif); r->read(&nif);
records[i] = std::move(r); mRecords[i] = std::move(r);
} }
const std::size_t rootNum = nif.getUInt();
roots.resize(rootNum);
// Determine which records are roots // Determine which records are roots
for (std::size_t i = 0; i < rootNum; i++) mRoots.resize(nif.get<uint32_t>());
for (std::size_t i = 0; i < mRoots.size(); i++)
{ {
int idx = nif.getInt(); std::int32_t idx;
if (idx >= 0 && static_cast<std::size_t>(idx) < records.size()) nif.read(idx);
if (idx >= 0 && static_cast<std::size_t>(idx) < mRecords.size())
{ {
roots[i] = records[idx].get(); mRoots[i] = mRecords[idx].get();
} }
else else
{ {
roots[i] = nullptr; mRoots[i] = nullptr;
Log(Debug::Warning) << "NIFFile Warning: Root " << i + 1 << " does not point to a record: index " << idx Log(Debug::Warning) << "NIFFile Warning: Root " << i + 1 << " does not point to a record: index " << idx
<< ". File: " << filename; << ". File: " << mFilename;
} }
} }
// Once parsing is done, do post-processing. // Once parsing is done, do post-processing.
for (const auto& record : records) for (const auto& record : mRecords)
record->post(*this); record->post(*this);
} }
@ -513,7 +534,7 @@ namespace Nif
{ {
if (index == std::numeric_limits<std::uint32_t>::max()) if (index == std::numeric_limits<std::uint32_t>::max())
return std::string(); return std::string();
return strings.at(index); return mStrings.at(index);
} }
} }

View file

@ -30,13 +30,14 @@ namespace Nif
BETHVER_SKY = 83, // Skyrim BETHVER_SKY = 83, // Skyrim
BETHVER_SSE = 100, // Skyrim SE BETHVER_SSE = 100, // Skyrim SE
BETHVER_FO4 = 130, // Fallout 4 BETHVER_FO4 = 130, // Fallout 4
BETHVER_F76 = 155 // Fallout 76 BETHVER_F76 = 155, // Fallout 76
BETHVER_STF = 172, // Starfield
}; };
/// File version, user version, Bethesda version /// File version, user version, Bethesda version
unsigned int mVersion = 0; std::uint32_t mVersion = 0;
unsigned int mUserVersion = 0; std::uint32_t mUserVersion = 0;
unsigned int mBethVersion = 0; std::uint32_t mBethVersion = 0;
/// File name, used for error messages and opening the file /// File name, used for error messages and opening the file
std::filesystem::path mPath; std::filesystem::path mPath;
@ -76,13 +77,13 @@ namespace Nif
const std::string& getHash() const { return mFile->mHash; } const std::string& getHash() const { return mFile->mHash; }
/// Get the version of the NIF format used /// Get the version of the NIF format used
unsigned int getVersion() const { return mFile->mVersion; } std::uint32_t getVersion() const { return mFile->mVersion; }
/// Get the user version of the NIF format used /// Get the user version of the NIF format used
unsigned int getUserVersion() const { return mFile->mUserVersion; } std::uint32_t getUserVersion() const { return mFile->mUserVersion; }
/// Get the Bethesda version of the NIF format used /// Get the Bethesda version of the NIF format used
unsigned int getBethVersion() const { return mFile->mBethVersion; } std::uint32_t getBethVersion() const { return mFile->mBethVersion; }
bool getUseSkinning() const { return mFile->mUseSkinning; } bool getUseSkinning() const { return mFile->mUseSkinning; }
@ -93,22 +94,22 @@ namespace Nif
class Reader class Reader
{ {
/// File version, user version, Bethesda version /// File version, user version, Bethesda version
unsigned int& ver; std::uint32_t& mVersion;
unsigned int& userVer; std::uint32_t& mUserVersion;
unsigned int& bethVer; std::uint32_t& mBethVersion;
/// File name, used for error messages and opening the file /// File name, used for error messages and opening the file
std::filesystem::path& filename; std::filesystem::path& mFilename;
std::string& hash; std::string& mHash;
/// Record list /// Record list
std::vector<std::unique_ptr<Record>>& records; std::vector<std::unique_ptr<Record>>& mRecords;
/// Root list. This is a select portion of the pointers from records /// Root list. This is a select portion of the pointers from records
std::vector<Record*>& roots; std::vector<Record*>& mRoots;
/// String table /// String table
std::vector<std::string> strings; std::vector<std::string> mStrings;
bool& mUseSkinning; bool& mUseSkinning;
@ -117,7 +118,7 @@ namespace Nif
/// Get the file's version in a human readable form /// Get the file's version in a human readable form
///\returns A string containing a human readable NIF version number ///\returns A string containing a human readable NIF version number
std::string printVersion(unsigned int version); std::string versionToString(std::uint32_t version);
public: public:
/// Open a NIF stream. The name is used for error messages. /// Open a NIF stream. The name is used for error messages.
@ -127,26 +128,26 @@ namespace Nif
void parse(Files::IStreamPtr&& stream); void parse(Files::IStreamPtr&& stream);
/// Get a given record /// Get a given record
Record* getRecord(size_t index) const { return records.at(index).get(); } Record* getRecord(size_t index) const { return mRecords.at(index).get(); }
/// Get a given string from the file's string table /// Get a given string from the file's string table
std::string getString(uint32_t index) const; std::string getString(std::uint32_t index) const;
/// Set whether there is skinning contained in this NIF file. /// Set whether there is skinning contained in this NIF file.
/// @note This is just a hint for users of the NIF file and has no effect on the loading procedure. /// @note This is just a hint for users of the NIF file and has no effect on the loading procedure.
void setUseSkinning(bool skinning); void setUseSkinning(bool skinning);
/// Get the name of the file /// Get the name of the file
std::filesystem::path getFilename() const { return filename; } std::filesystem::path getFilename() const { return mFilename; }
/// Get the version of the NIF format used /// Get the version of the NIF format used
unsigned int getVersion() const { return ver; } std::uint32_t getVersion() const { return mVersion; }
/// Get the user version of the NIF format used /// Get the user version of the NIF format used
unsigned int getUserVersion() const { return userVer; } std::uint32_t getUserVersion() const { return mUserVersion; }
/// Get the Bethesda version of the NIF format used /// Get the Bethesda version of the NIF format used
unsigned int getBethVersion() const { return bethVer; } std::uint32_t getBethVersion() const { return mBethVersion; }
static void setLoadUnsupportedFiles(bool load); static void setLoadUnsupportedFiles(bool load);