mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 11:26:37 +00:00 
			
		
		
		
	Merge branch 'esm_signed_left' into 'master'
Use signed type for left record and files size in ESM3 reader context See merge request OpenMW/openmw!2722
This commit is contained in:
		
						commit
						f1b8de8cfc
					
				
					 7 changed files with 177 additions and 41 deletions
				
			
		|  | @ -90,6 +90,7 @@ file(GLOB UNITTEST_SRC_FILES | ||||||
|     fx/technique.cpp |     fx/technique.cpp | ||||||
| 
 | 
 | ||||||
|     esm3/readerscache.cpp |     esm3/readerscache.cpp | ||||||
|  |     esm3/testsaveload.cpp | ||||||
| 
 | 
 | ||||||
|     nifosg/testnifloader.cpp |     nifosg/testnifloader.cpp | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | #include <components/esm/fourcc.hpp> | ||||||
| #include <components/esm3/esmreader.hpp> | #include <components/esm3/esmreader.hpp> | ||||||
| #include <components/esm3/esmwriter.hpp> | #include <components/esm3/esmwriter.hpp> | ||||||
| #include <components/esm3/variant.hpp> | #include <components/esm3/variant.hpp> | ||||||
|  | @ -12,6 +13,8 @@ namespace | ||||||
|     using namespace testing; |     using namespace testing; | ||||||
|     using namespace ESM; |     using namespace ESM; | ||||||
| 
 | 
 | ||||||
|  |     constexpr std::uint32_t fakeRecordId = fourCC("FAKE"); | ||||||
|  | 
 | ||||||
|     Variant makeVariant(VarType type) |     Variant makeVariant(VarType type) | ||||||
|     { |     { | ||||||
|         Variant v; |         Variant v; | ||||||
|  | @ -430,25 +433,28 @@ namespace | ||||||
|         std::ostringstream out; |         std::ostringstream out; | ||||||
|         ESMWriter writer; |         ESMWriter writer; | ||||||
|         writer.save(out); |         writer.save(out); | ||||||
|  |         writer.startRecord(fakeRecordId); | ||||||
|         variant.write(writer, format); |         variant.write(writer, format); | ||||||
|  |         writer.endRecord(fakeRecordId); | ||||||
|         writer.close(); |         writer.close(); | ||||||
|         return out.str(); |         return out.str(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Variant read(const Variant::Format format, const std::string& data) |     void read(const Variant::Format format, const std::string& data, Variant& result) | ||||||
|     { |     { | ||||||
|         Variant result; |  | ||||||
|         ESMReader reader; |         ESMReader reader; | ||||||
|         reader.open(std::make_unique<std::istringstream>(data), ""); |         reader.open(std::make_unique<std::istringstream>(data), "stream"); | ||||||
|  |         ASSERT_TRUE(reader.hasMoreRecs()); | ||||||
|  |         ASSERT_EQ(reader.getRecName().toInt(), fakeRecordId); | ||||||
|  |         reader.getRecHeader(); | ||||||
|         result.read(reader, format); |         result.read(reader, format); | ||||||
|         return result; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Variant writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize) |     void writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize, Variant& result) | ||||||
|     { |     { | ||||||
|         const std::string data = write(variant, format); |         const std::string data = write(variant, format); | ||||||
|         EXPECT_EQ(data.size(), dataSize); |         EXPECT_EQ(data.size(), dataSize); | ||||||
|         return read(format, data); |         read(format, data, result); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     struct ESMVariantToESMTest : TestWithParam<WriteToESMTestCase> |     struct ESMVariantToESMTest : TestWithParam<WriteToESMTestCase> | ||||||
|  | @ -458,36 +464,27 @@ namespace | ||||||
|     TEST_P(ESMVariantToESMTest, deserialized_is_equal_to_serialized) |     TEST_P(ESMVariantToESMTest, deserialized_is_equal_to_serialized) | ||||||
|     { |     { | ||||||
|         const auto param = GetParam(); |         const auto param = GetParam(); | ||||||
|         const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize); |         ESM::Variant result; | ||||||
|  |         writeAndRead(param.mVariant, param.mFormat, param.mDataSize, result); | ||||||
|         ASSERT_EQ(param.mVariant, result); |         ASSERT_EQ(param.mVariant, result); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMTest, |     const std::array deserializedParams = { | ||||||
|         Values(WriteToESMTestCase{ Variant(), Variant::Format_Gmst, 324 }, |         WriteToESMTestCase{ Variant(), Variant::Format_Gmst, 340 }, | ||||||
|             WriteToESMTestCase{ Variant(int{ 42 }), Variant::Format_Global, 345 }, |         WriteToESMTestCase{ Variant(int{ 42 }), Variant::Format_Global, 361 }, | ||||||
|             WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Global, 345 }, |         WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Global, 361 }, | ||||||
|             WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Info, 336 }, |         WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Info, 352 }, | ||||||
|             WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Local, 336 }, |         WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Local, 352 }, | ||||||
|             WriteToESMTestCase{ makeVariant(VT_Short, 42), Variant::Format_Global, 345 }, |         WriteToESMTestCase{ makeVariant(VT_Short, 42), Variant::Format_Global, 361 }, | ||||||
|             WriteToESMTestCase{ makeVariant(VT_Short, 42), Variant::Format_Local, 334 }, |         WriteToESMTestCase{ makeVariant(VT_Short, 42), Variant::Format_Local, 350 }, | ||||||
|             WriteToESMTestCase{ makeVariant(VT_Int, 42), Variant::Format_Info, 336 }, |         WriteToESMTestCase{ makeVariant(VT_Int, 42), Variant::Format_Info, 352 }, | ||||||
|             WriteToESMTestCase{ makeVariant(VT_Int, 42), Variant::Format_Local, 336 })); |         WriteToESMTestCase{ makeVariant(VT_Int, 42), Variant::Format_Local, 352 }, | ||||||
| 
 |         WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Gmst, 352 }, | ||||||
|     struct ESMVariantToESMNoneTest : TestWithParam<WriteToESMTestCase> |         WriteToESMTestCase{ Variant(std::string("foo")), Variant::Format_Gmst, 351 }, | ||||||
|     { |         WriteToESMTestCase{ makeVariant(VT_Int, 42), Variant::Format_Gmst, 352 }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     TEST_P(ESMVariantToESMNoneTest, deserialized_is_none) |     INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMTest, ValuesIn(deserializedParams)); | ||||||
|     { |  | ||||||
|         const auto param = GetParam(); |  | ||||||
|         const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize); |  | ||||||
|         ASSERT_EQ(Variant(), result); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMNoneTest, |  | ||||||
|         Values(WriteToESMTestCase{ Variant(float{ 2.7f }), Variant::Format_Gmst, 336 }, |  | ||||||
|             WriteToESMTestCase{ Variant(std::string("foo")), Variant::Format_Gmst, 335 }, |  | ||||||
|             WriteToESMTestCase{ makeVariant(VT_Int, 42), Variant::Format_Gmst, 336 })); |  | ||||||
| 
 | 
 | ||||||
|     struct ESMVariantWriteToESMFailTest : TestWithParam<WriteToESMTestCase> |     struct ESMVariantWriteToESMFailTest : TestWithParam<WriteToESMTestCase> | ||||||
|     { |     { | ||||||
|  |  | ||||||
							
								
								
									
										133
									
								
								apps/openmw_test_suite/esm3/testsaveload.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								apps/openmw_test_suite/esm3/testsaveload.cpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | ||||||
|  | #include <components/esm/fourcc.hpp> | ||||||
|  | #include <components/esm3/esmreader.hpp> | ||||||
|  | #include <components/esm3/esmwriter.hpp> | ||||||
|  | #include <components/esm3/player.hpp> | ||||||
|  | 
 | ||||||
|  | #include <gmock/gmock.h> | ||||||
|  | #include <gtest/gtest.h> | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <memory> | ||||||
|  | #include <random> | ||||||
|  | 
 | ||||||
|  | namespace ESM | ||||||
|  | { | ||||||
|  |     namespace | ||||||
|  |     { | ||||||
|  |         using namespace ::testing; | ||||||
|  | 
 | ||||||
|  |         constexpr std::array formats = { | ||||||
|  |             CurrentSaveGameFormatVersion, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         constexpr std::uint32_t fakeRecordId = fourCC("FAKE"); | ||||||
|  | 
 | ||||||
|  |         template <class T> | ||||||
|  |         void save(const T& record, ESMWriter& writer) | ||||||
|  |         { | ||||||
|  |             record.save(writer); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void save(const CellRef& record, ESMWriter& writer) | ||||||
|  |         { | ||||||
|  |             record.save(writer, true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         template <typename T> | ||||||
|  |         std::unique_ptr<std::istream> makeEsmStream(const T& record, FormatVersion formatVersion) | ||||||
|  |         { | ||||||
|  |             ESMWriter writer; | ||||||
|  |             auto stream = std::make_unique<std::stringstream>(); | ||||||
|  |             writer.setFormatVersion(formatVersion); | ||||||
|  |             writer.save(*stream); | ||||||
|  |             writer.startRecord(fakeRecordId); | ||||||
|  |             save(record, writer); | ||||||
|  |             writer.endRecord(fakeRecordId); | ||||||
|  |             return stream; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         template <class T> | ||||||
|  |         void load(ESMReader& reader, T& record) | ||||||
|  |         { | ||||||
|  |             record.load(reader); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void load(ESMReader& reader, CellRef& record) | ||||||
|  |         { | ||||||
|  |             bool deleted = false; | ||||||
|  |             record.load(reader, deleted, true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         template <typename T> | ||||||
|  |         void saveAndLoadRecord(const T& record, FormatVersion formatVersion, T& result) | ||||||
|  |         { | ||||||
|  |             ESMReader reader; | ||||||
|  |             reader.open(makeEsmStream(record, formatVersion), "stream"); | ||||||
|  |             ASSERT_TRUE(reader.hasMoreRecs()); | ||||||
|  |             ASSERT_EQ(reader.getRecName().toInt(), fakeRecordId); | ||||||
|  |             reader.getRecHeader(); | ||||||
|  |             load(reader, result); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         struct Esm3SaveLoadRecordTest : public TestWithParam<FormatVersion> | ||||||
|  |         { | ||||||
|  |             std::minstd_rand mRandom; | ||||||
|  |             std::uniform_int_distribution<short> mRefIdDistribution{ 'a', 'z' }; | ||||||
|  | 
 | ||||||
|  |             RefId generateRandomRefId(std::size_t size = 33) | ||||||
|  |             { | ||||||
|  |                 std::string value; | ||||||
|  |                 while (value.size() < size) | ||||||
|  |                     value.push_back(static_cast<char>(mRefIdDistribution(mRandom))); | ||||||
|  |                 return RefId::stringRefId(value); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         TEST_P(Esm3SaveLoadRecordTest, playerShouldNotChange) | ||||||
|  |         { | ||||||
|  |             std::minstd_rand random; | ||||||
|  |             Player record{}; | ||||||
|  |             record.mObject.blank(); | ||||||
|  |             record.mBirthsign = generateRandomRefId(); | ||||||
|  |             record.mObject.mRef.mRefID = generateRandomRefId(); | ||||||
|  |             std::generate_n(std::inserter(record.mPreviousItems, record.mPreviousItems.end()), 2, | ||||||
|  |                 [&] { return std::make_pair(generateRandomRefId(), generateRandomRefId()); }); | ||||||
|  |             Player result; | ||||||
|  |             saveAndLoadRecord(record, GetParam(), result); | ||||||
|  |             EXPECT_EQ(record.mBirthsign, result.mBirthsign); | ||||||
|  |             EXPECT_EQ(record.mPreviousItems, result.mPreviousItems); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         TEST_P(Esm3SaveLoadRecordTest, cellRefShouldNotChange) | ||||||
|  |         { | ||||||
|  |             CellRef record; | ||||||
|  |             record.blank(); | ||||||
|  |             record.mRefID = generateRandomRefId(); | ||||||
|  |             record.mOwner = generateRandomRefId(); | ||||||
|  |             record.mSoul = generateRandomRefId(); | ||||||
|  |             record.mFaction = generateRandomRefId(); | ||||||
|  |             record.mKey = generateRandomRefId(); | ||||||
|  |             CellRef result; | ||||||
|  |             saveAndLoadRecord(record, GetParam(), result); | ||||||
|  |             EXPECT_EQ(record.mRefID, result.mRefID); | ||||||
|  |             EXPECT_EQ(record.mOwner, result.mOwner); | ||||||
|  |             EXPECT_EQ(record.mSoul, result.mSoul); | ||||||
|  |             EXPECT_EQ(record.mFaction, result.mFaction); | ||||||
|  |             EXPECT_EQ(record.mKey, result.mKey); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         TEST_P(Esm3SaveLoadRecordTest, creatureStatsShouldNotChange) | ||||||
|  |         { | ||||||
|  |             CreatureStats record; | ||||||
|  |             record.blank(); | ||||||
|  |             record.mLastHitAttemptObject = generateRandomRefId(); | ||||||
|  |             record.mLastHitObject = generateRandomRefId(); | ||||||
|  |             CreatureStats result; | ||||||
|  |             saveAndLoadRecord(record, GetParam(), result); | ||||||
|  |             EXPECT_EQ(record.mLastHitAttemptObject, result.mLastHitAttemptObject); | ||||||
|  |             EXPECT_EQ(record.mLastHitObject, result.mLastHitObject); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(formats)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -188,8 +188,9 @@ namespace ESM | ||||||
|     struct ESM_Context |     struct ESM_Context | ||||||
|     { |     { | ||||||
|         std::filesystem::path filename; |         std::filesystem::path filename; | ||||||
|         uint32_t leftRec, leftSub; |         std::streamsize leftRec; | ||||||
|         size_t leftFile; |         std::uint32_t leftSub; | ||||||
|  |         std::streamsize leftFile; | ||||||
|         NAME recName, subName; |         NAME recName, subName; | ||||||
|         // When working with multiple esX files, we will generate lists of all files that
 |         // When working with multiple esX files, we will generate lists of all files that
 | ||||||
|         //  actually contribute to a specific cell. Therefore, we need to store the index
 |         //  actually contribute to a specific cell. Therefore, we need to store the index
 | ||||||
|  |  | ||||||
|  | @ -169,7 +169,7 @@ namespace ESM | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             if (isSet() && !hasContentFile()) |             if (isSet() && !hasContentFile()) | ||||||
|                 Log(Debug::Error) << "Generated RefNum can not be saved in 32bit format"; |                 throw std::runtime_error("Generated RefNum can not be saved in 32bit format"); | ||||||
|             int refNum = (mIndex & 0xffffff) | ((hasContentFile() ? mContentFile : 0xff) << 24); |             int refNum = (mIndex & 0xffffff) | ((hasContentFile() ? mContentFile : 0xff) << 24); | ||||||
|             esm.writeHNT(tag, refNum, 4); |             esm.writeHNT(tag, refNum, 4); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -297,16 +297,18 @@ namespace ESM | ||||||
| 
 | 
 | ||||||
|     void ESMReader::getSubHeader() |     void ESMReader::getSubHeader() | ||||||
|     { |     { | ||||||
|         if (mCtx.leftRec < sizeof(mCtx.leftSub)) |         if (mCtx.leftRec < static_cast<std::streamsize>(sizeof(mCtx.leftSub))) | ||||||
|             fail("End of record while reading sub-record header"); |             fail("End of record while reading sub-record header: " + std::to_string(mCtx.leftRec) + " < " | ||||||
|  |                 + std::to_string(sizeof(mCtx.leftSub))); | ||||||
| 
 | 
 | ||||||
|         // Get subrecord size
 |         // Get subrecord size
 | ||||||
|         getT(mCtx.leftSub); |         getUint(mCtx.leftSub); | ||||||
|         mCtx.leftRec -= sizeof(mCtx.leftSub); |         mCtx.leftRec -= sizeof(mCtx.leftSub); | ||||||
| 
 | 
 | ||||||
|         // Adjust number of record bytes left
 |         // Adjust number of record bytes left
 | ||||||
|         if (mCtx.leftRec < mCtx.leftSub) |         if (mCtx.leftRec < mCtx.leftSub) | ||||||
|             fail("Record size is larger than rest of file"); |             fail("Record size is larger than rest of file: " + std::to_string(mCtx.leftRec) + " < " | ||||||
|  |                 + std::to_string(mCtx.leftSub)); | ||||||
|         mCtx.leftRec -= mCtx.leftSub; |         mCtx.leftRec -= mCtx.leftSub; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -335,12 +337,14 @@ namespace ESM | ||||||
|     void ESMReader::getRecHeader(uint32_t& flags) |     void ESMReader::getRecHeader(uint32_t& flags) | ||||||
|     { |     { | ||||||
|         // General error checking
 |         // General error checking
 | ||||||
|         if (mCtx.leftFile < 3 * sizeof(uint32_t)) |         if (mCtx.leftFile < static_cast<std::streamsize>(3 * sizeof(uint32_t))) | ||||||
|             fail("End of file while reading record header"); |             fail("End of file while reading record header"); | ||||||
|         if (mCtx.leftRec) |         if (mCtx.leftRec) | ||||||
|             fail("Previous record contains unread bytes"); |             fail("Previous record contains unread bytes"); | ||||||
| 
 | 
 | ||||||
|         getUint(mCtx.leftRec); |         std::uint32_t leftRec = 0; | ||||||
|  |         getUint(leftRec); | ||||||
|  |         mCtx.leftRec = static_cast<std::streamsize>(leftRec); | ||||||
|         getUint(flags); // This header entry is always zero
 |         getUint(flags); // This header entry is always zero
 | ||||||
|         getUint(flags); |         getUint(flags); | ||||||
|         mCtx.leftFile -= 3 * sizeof(uint32_t); |         mCtx.leftFile -= 3 * sizeof(uint32_t); | ||||||
|  |  | ||||||
|  | @ -297,7 +297,7 @@ namespace ESM | ||||||
|     private: |     private: | ||||||
|         [[noreturn]] void reportSubSizeMismatch(size_t want, size_t got) |         [[noreturn]] void reportSubSizeMismatch(size_t want, size_t got) | ||||||
|         { |         { | ||||||
|             fail("record size mismatch, requested " + std::to_string(want) + ", got" + std::to_string(got)); |             fail("record size mismatch, requested " + std::to_string(want) + ", got " + std::to_string(got)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         void clearCtx(); |         void clearCtx(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue