mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-28 12:09:53 +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:
parent
ef896faa90
commit
535290a83d
2 changed files with 133 additions and 111 deletions
|
@ -25,13 +25,13 @@ namespace Nif
|
|||
{
|
||||
|
||||
Reader::Reader(NIFFile& file)
|
||||
: ver(file.mVersion)
|
||||
, userVer(file.mUserVersion)
|
||||
, bethVer(file.mBethVersion)
|
||||
, filename(file.mPath)
|
||||
, hash(file.mHash)
|
||||
, records(file.mRecords)
|
||||
, roots(file.mRoots)
|
||||
: mVersion(file.mVersion)
|
||||
, mUserVersion(file.mUserVersion)
|
||||
, mBethVersion(file.mBethVersion)
|
||||
, mFilename(file.mPath)
|
||||
, mHash(file.mHash)
|
||||
, mRecords(file.mRecords)
|
||||
, mRoots(file.mRoots)
|
||||
, mUseSkinning(file.mUseSkinning)
|
||||
{
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ namespace Nif
|
|||
/// Make the factory map used for parsing the file
|
||||
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 minor = (version >> 16) & 0xFF;
|
||||
|
@ -329,8 +329,8 @@ namespace Nif
|
|||
|
||||
void Reader::parse(Files::IStreamPtr&& stream)
|
||||
{
|
||||
const std::array<std::uint64_t, 2> fileHash = Files::getHash(filename, *stream);
|
||||
hash.append(reinterpret_cast<const char*>(fileHash.data()), fileHash.size() * sizeof(std::uint64_t));
|
||||
const std::array<std::uint64_t, 2> fileHash = Files::getHash(mFilename, *stream);
|
||||
mHash.append(reinterpret_cast<const char*>(fileHash.data()), fileHash.size() * sizeof(std::uint64_t));
|
||||
|
||||
NIFStream nif(*this, std::move(stream));
|
||||
|
||||
|
@ -343,151 +343,172 @@ namespace Nif
|
|||
const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(),
|
||||
[&](const std::string& verString) { return head.starts_with(verString); });
|
||||
if (!supportedHeader)
|
||||
throw Nif::Exception("Invalid NIF header: " + head, filename);
|
||||
throw Nif::Exception("Invalid NIF header: " + head, mFilename);
|
||||
|
||||
// Get BCD version
|
||||
ver = nif.getUInt();
|
||||
nif.read(mVersion);
|
||||
// 4.0.0.0 is an older, practically identical version of the format.
|
||||
// It's not used by Morrowind assets but Morrowind supports it.
|
||||
static const std::array<uint32_t, 2> supportedVers = {
|
||||
NIFStream::generateVersion(4, 0, 0, 0),
|
||||
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;
|
||||
if (!supportedVersion)
|
||||
{
|
||||
if (!sLoadUnsupportedFiles)
|
||||
throw Nif::Exception("Unsupported NIF version: " + printVersion(ver), filename);
|
||||
throw Nif::Exception("Unsupported NIF version: " + versionToString(mVersion), mFilename);
|
||||
if (writeDebugLog)
|
||||
Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << printVersion(ver)
|
||||
<< ". Proceed with caution! File: " << filename;
|
||||
Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << versionToString(mVersion)
|
||||
<< ". Proceed with caution! File: " << mFilename;
|
||||
}
|
||||
|
||||
// NIF data endianness
|
||||
if (ver >= NIFStream::generateVersion(20, 0, 0, 4))
|
||||
const bool hasEndianness = mVersion >= 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)
|
||||
throw Nif::Exception("Big endian NIF files are unsupported", filename);
|
||||
throw Nif::Exception("Big endian NIF files are unsupported", mFilename);
|
||||
}
|
||||
|
||||
// User version
|
||||
if (ver > NIFStream::generateVersion(10, 0, 1, 8))
|
||||
userVer = nif.getUInt();
|
||||
if (hasUserVersion)
|
||||
nif.read(mUserVersion);
|
||||
|
||||
// Number of records
|
||||
const std::size_t recNum = nif.getUInt();
|
||||
records.resize(recNum);
|
||||
mRecords.resize(nif.get<std::uint32_t>());
|
||||
|
||||
// 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();
|
||||
nif.getExportString(); // Author
|
||||
if (bethVer > NIFFile::BETHVER_FO4)
|
||||
nif.getUInt(); // Unknown
|
||||
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
|
||||
bool hasBSStreamHeader = false;
|
||||
if (mVersion == NIFFile::VER_OB_OLD)
|
||||
hasBSStreamHeader = true;
|
||||
else if (mUserVersion >= 3 && mVersion >= NIFStream::generateVersion(10, 1, 0, 0))
|
||||
{
|
||||
if (ver >= NIFStream::generateVersion(20, 1, 0, 1)) // String table
|
||||
{
|
||||
if (ver >= NIFStream::generateVersion(20, 2, 0, 5)) // Record sizes
|
||||
{
|
||||
std::vector<unsigned int> recSizes; // Currently unused
|
||||
nif.readVector(recSizes, recNum);
|
||||
}
|
||||
const std::size_t stringNum = nif.getUInt();
|
||||
nif.getUInt(); // Max string length
|
||||
nif.getSizedStrings(strings, stringNum);
|
||||
}
|
||||
std::vector<unsigned int> groups; // Currently unused
|
||||
unsigned int groupNum = nif.getUInt();
|
||||
nif.readVector(groups, groupNum);
|
||||
if (mVersion <= NIFFile::VER_OB || mVersion == NIFFile::VER_BGS)
|
||||
hasBSStreamHeader = mUserVersion <= 11 || mVersion >= NIFFile::VER_OB;
|
||||
}
|
||||
|
||||
if (hasBSStreamHeader)
|
||||
{
|
||||
nif.read(mBethVersion);
|
||||
nif.getExportString(); // Author
|
||||
if (mBethVersion >= 131)
|
||||
nif.get<std::uint32_t>(); // Unknown
|
||||
else
|
||||
nif.getExportString(); // Process script
|
||||
nif.getExportString(); // Export script
|
||||
if (mBethVersion >= 103)
|
||||
nif.getExportString(); // Max file path
|
||||
}
|
||||
}
|
||||
|
||||
const bool hasRecordSeparators
|
||||
= ver >= NIFStream::generateVersion(10, 0, 0, 0) && ver < NIFStream::generateVersion(10, 2, 0, 0);
|
||||
for (std::size_t i = 0; i < recNum; i++)
|
||||
if (hasRecTypeListings)
|
||||
{
|
||||
// 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::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString();
|
||||
std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.get<std::string>();
|
||||
if (rec.empty())
|
||||
{
|
||||
std::stringstream error;
|
||||
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.
|
||||
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
|
||||
<< " is preceded by a non-zero separator. File: " << filename;
|
||||
<< " is preceded by a non-zero separator. File: " << mFilename;
|
||||
|
||||
const auto entry = factories.find(rec);
|
||||
|
||||
if (entry == factories.end())
|
||||
throw Nif::Exception("Unknown record type " + rec, filename);
|
||||
throw Nif::Exception("Unknown record type " + rec, mFilename);
|
||||
|
||||
r = entry->second();
|
||||
|
||||
if (!supportedVersion && writeDebugLog)
|
||||
Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " ("
|
||||
<< filename << ")";
|
||||
<< mFilename << ")";
|
||||
|
||||
assert(r != nullptr);
|
||||
assert(r->recType != RC_MISSING);
|
||||
r->recName = rec;
|
||||
r->recIndex = i;
|
||||
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
|
||||
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();
|
||||
if (idx >= 0 && static_cast<std::size_t>(idx) < records.size())
|
||||
std::int32_t idx;
|
||||
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
|
||||
{
|
||||
roots[i] = nullptr;
|
||||
mRoots[i] = nullptr;
|
||||
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.
|
||||
for (const auto& record : records)
|
||||
for (const auto& record : mRecords)
|
||||
record->post(*this);
|
||||
}
|
||||
|
||||
|
@ -513,7 +534,7 @@ namespace Nif
|
|||
{
|
||||
if (index == std::numeric_limits<std::uint32_t>::max())
|
||||
return std::string();
|
||||
return strings.at(index);
|
||||
return mStrings.at(index);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,13 +30,14 @@ namespace Nif
|
|||
BETHVER_SKY = 83, // Skyrim
|
||||
BETHVER_SSE = 100, // Skyrim SE
|
||||
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
|
||||
unsigned int mVersion = 0;
|
||||
unsigned int mUserVersion = 0;
|
||||
unsigned int mBethVersion = 0;
|
||||
std::uint32_t mVersion = 0;
|
||||
std::uint32_t mUserVersion = 0;
|
||||
std::uint32_t mBethVersion = 0;
|
||||
|
||||
/// File name, used for error messages and opening the file
|
||||
std::filesystem::path mPath;
|
||||
|
@ -76,13 +77,13 @@ namespace Nif
|
|||
const std::string& getHash() const { return mFile->mHash; }
|
||||
|
||||
/// 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
|
||||
unsigned int getUserVersion() const { return mFile->mUserVersion; }
|
||||
std::uint32_t getUserVersion() const { return mFile->mUserVersion; }
|
||||
|
||||
/// 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; }
|
||||
|
||||
|
@ -93,22 +94,22 @@ namespace Nif
|
|||
class Reader
|
||||
{
|
||||
/// File version, user version, Bethesda version
|
||||
unsigned int& ver;
|
||||
unsigned int& userVer;
|
||||
unsigned int& bethVer;
|
||||
std::uint32_t& mVersion;
|
||||
std::uint32_t& mUserVersion;
|
||||
std::uint32_t& mBethVersion;
|
||||
|
||||
/// File name, used for error messages and opening the file
|
||||
std::filesystem::path& filename;
|
||||
std::string& hash;
|
||||
std::filesystem::path& mFilename;
|
||||
std::string& mHash;
|
||||
|
||||
/// 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
|
||||
std::vector<Record*>& roots;
|
||||
std::vector<Record*>& mRoots;
|
||||
|
||||
/// String table
|
||||
std::vector<std::string> strings;
|
||||
std::vector<std::string> mStrings;
|
||||
|
||||
bool& mUseSkinning;
|
||||
|
||||
|
@ -117,7 +118,7 @@ namespace Nif
|
|||
|
||||
/// Get the file's version in a human readable form
|
||||
///\returns A string containing a human readable NIF version number
|
||||
std::string printVersion(unsigned int version);
|
||||
std::string versionToString(std::uint32_t version);
|
||||
|
||||
public:
|
||||
/// Open a NIF stream. The name is used for error messages.
|
||||
|
@ -127,26 +128,26 @@ namespace Nif
|
|||
void parse(Files::IStreamPtr&& stream);
|
||||
|
||||
/// 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
|
||||
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.
|
||||
/// @note This is just a hint for users of the NIF file and has no effect on the loading procedure.
|
||||
void setUseSkinning(bool skinning);
|
||||
|
||||
/// 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
|
||||
unsigned int getVersion() const { return ver; }
|
||||
std::uint32_t getVersion() const { return mVersion; }
|
||||
|
||||
/// 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
|
||||
unsigned int getBethVersion() const { return bethVer; }
|
||||
std::uint32_t getBethVersion() const { return mBethVersion; }
|
||||
|
||||
static void setLoadUnsupportedFiles(bool load);
|
||||
|
||||
|
|
Loading…
Reference in a new issue