diff --git a/dCommon/Raw.cpp b/dCommon/Raw.cpp index 19c356a27..54f39e9c7 100644 --- a/dCommon/Raw.cpp +++ b/dCommon/Raw.cpp @@ -123,11 +123,6 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { const size_t width = static_cast(chunk.width); const size_t height = static_cast(chunk.height); - if (width == 0 || height == 0) { - LOG("Chunk %u has invalid heightmap dimensions: width=%zu, height=%zu", chunk.id, width, height); - return false; - } - if (width > kMaxResolution || height > kMaxResolution) { LOG("Chunk %u heightmap dimensions exceed maximum resolution %u: width=%zu, height=%zu", chunk.id, kMaxResolution, width, height); return false; @@ -147,7 +142,7 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } const size_t totalBytes = heightMapSize * elementSize; - if (totalBytes == 0 || totalBytes > kMaxBlobBytes) { + if (totalBytes > kMaxBlobBytes) { LOG("Chunk %u heightmap total size invalid: bytes=%zu (max %zu)", chunk.id, totalBytes, kMaxBlobBytes); return false; } @@ -165,7 +160,7 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { if (version >= 32) { BinaryIO::BinaryRead(stream, chunk.colorMapResolution); } else { - chunk.colorMapResolution = chunk.width - 1; + chunk.colorMapResolution = chunk.width > 0 ? chunk.width - 1 : 0; // no color map for a width of 0 } if (chunk.colorMapResolution > kMaxResolution) { @@ -181,14 +176,23 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } chunk.colorMap.resize(colorMapPixelCount); stream.read(reinterpret_cast(chunk.colorMap.data()), static_cast(colorMapPixelCount)); - } else { + } else if (chunk.width > 0) { + // width x width BGRA pixels, of which the client keeps the (width - 1) x (width - 1) before the last row and + // column, as RGBA (RAWReadColorandLightMaps) const size_t legacyColorBytes = static_cast(chunk.width) * chunk.width * 4; if (legacyColorBytes > kMaxBlobBytes) { LOG("Chunk legacy colorMap size %zu exceeds maximum %zu bytes", legacyColorBytes, kMaxBlobBytes); return false; } - chunk.colorMap.resize(legacyColorBytes); - stream.read(reinterpret_cast(chunk.colorMap.data()), static_cast(legacyColorBytes)); + std::vector pixels(legacyColorBytes); + stream.read(reinterpret_cast(pixels.data()), static_cast(legacyColorBytes)); + chunk.colorMap.reserve(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution * 4); + for (uint32_t y = 0; y < chunk.colorMapResolution; ++y) { + for (uint32_t x = 0; x < chunk.colorMapResolution; ++x) { + const auto* bgra = &pixels[(static_cast(y) * chunk.width + x) * 4]; + chunk.colorMap.insert(chunk.colorMap.end(), { bgra[2], bgra[1], bgra[0], bgra[3] }); + } + } } if (stream.fail()) { @@ -226,6 +230,10 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } chunk.textureMap.resize(textureMapPixelCount); stream.read(reinterpret_cast(chunk.textureMap.data()), static_cast(textureMapPixelCount)); + // Before version 32 the pixels are BGRA; the client keeps them as RGBA (0x0103aaf0) + if (version < 32) { + for (size_t i = 0; i + 3 < chunk.textureMap.size(); i += 4) std::swap(chunk.textureMap[i], chunk.textureMap[i + 2]); + } if (stream.fail()) { return false; @@ -281,7 +289,7 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { chunk.sceneMap.resize(sceneMapSize); stream.read(reinterpret_cast(chunk.sceneMap.data()), static_cast(sceneMapSize)); } else if (version == 31) { - const size_t sceneMapCells = static_cast(chunk.colorMapResolution + 1) * (chunk.colorMapResolution + 1); + const size_t sceneMapCells = chunk.width > 0 ? static_cast(chunk.colorMapResolution + 1) * (chunk.colorMapResolution + 1) : 0; if (sceneMapCells > kMaxBlobBytes) { LOG("Chunk v31 sceneMap size %zu exceeds maximum %zu bytes", sceneMapCells, kMaxBlobBytes); return false; @@ -295,7 +303,9 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } } } else { + // Before version 31 a chunk has no scene map, only a byte; the client's is all scene 0 (RAWReadSceneMap) stream.seekg(1, std::ios::cur); + chunk.sceneMap.assign(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution, 0); } if (stream.fail()) { diff --git a/tests/dWebTests/RawTerrainTests.cpp b/tests/dWebTests/RawTerrainTests.cpp index 79303d7a3..818695d5a 100644 --- a/tests/dWebTests/RawTerrainTests.cpp +++ b/tests/dWebTests/RawTerrainTests.cpp @@ -84,6 +84,36 @@ TEST(RawTerrainTests, ReadsEveryLayer) { EXPECT_FLOAT_EQ(raw.maxBoundsX, 6.0f); } +// Before version 32: a shader ID, a width x width BGRA color map the client keeps (width - 1) x (width - 1) of as RGBA, +// a BGRA texture map, no light or blend maps, a (width x width) scene map in version 31 and none (all scene 0) before +TEST(RawTerrainTests, ReadsOlderVersions) { + for (const uint16_t version : { 30, 31 }) { + RawWriter w; + w.Put(version).Put(0).Put(1).Put(1).Put(1); + w.Put(0).Put(3).Put(3).Put(0.0f).Put(0.0f); + w.Put(7); + for (uint32_t texture : { 10u, 11u, 12u, 13u }) w.Put(texture); + w.Put(1.0f); + for (int i = 0; i < 9; i++) w.Put(0.0f); + for (int i = 0; i < 9; i++) w.Put(1).Put(2).Put(3).Put(static_cast(i)); // BGRA + w.Put(1).Put(1).Put(2).Put(3).Put(4); + w.Put(0); // no flairs + if (version == 31) for (int i = 0; i < 9; i++) w.Put(static_cast(i)); + else w.Put(0); + + Raw::Raw raw; + ASSERT_TRUE(Read(w.data, raw)) << version; + const auto& chunk = raw.chunks.at(0); + EXPECT_EQ(chunk.shaderId, 7u); + EXPECT_EQ(chunk.colorMapResolution, 2u); + // Rows 0-1, columns 0-1 of the 3 x 3, as RGBA + EXPECT_EQ(chunk.colorMap, (std::vector{ 3, 2, 1, 0, 3, 2, 1, 1, 3, 2, 1, 3, 3, 2, 1, 4 })); + EXPECT_EQ(chunk.textureMap, (std::vector{ 3, 2, 1, 4 })); + if (version == 31) EXPECT_EQ(chunk.sceneMap, (std::vector{ 0, 1, 3, 4 })); + else EXPECT_EQ(chunk.sceneMap, (std::vector(4, 0))); + } +} + TEST(RawTerrainTests, RejectsDamagedFiles) { const auto raw = SampleRaw(); for (const size_t length : { size_t{ 0 }, size_t{ 2 }, size_t{ 20 }, raw.size() / 2, raw.size() - 1 }) {