mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
The client reads a terrain chunk of a file older than version 32 (1.10.64): - its color map as width x width BGRA pixels, keeping the (width - 1) x (width - 1) before the last row and column, as RGBA (RAWReadColorandLightMaps); the reader kept all the pixels as written; - its texture blend map's pixels as BGRA, kept as RGBA (0x0103aaf0); - before version 31 no scene map, only a byte: the client's scene map is all scene 0 (RAWReadSceneMap); the reader had none. A chunk of width or height 0, which the client reads (no heights, no color map), no longer fails the whole file. Live terrain files are version 32, so they read as before. Check in game: nothing to check (no live terrain changes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
144 lines
6.3 KiB
C++
144 lines
6.3 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <cstring>
|
|
#include <sstream>
|
|
|
|
#include "Raw.h"
|
|
#include "TerrainMap.h"
|
|
|
|
namespace {
|
|
// A version 32 terrain file (.raw) laid out as the client's are (little-endian)
|
|
struct RawWriter {
|
|
std::string data;
|
|
template<typename T> RawWriter& Put(T value) { data.append(reinterpret_cast<const char*>(&value), sizeof(T)); return *this; }
|
|
RawWriter& Bytes(size_t count, uint8_t value) { data.append(count, static_cast<char>(value)); return *this; }
|
|
|
|
// A size x size chunk at (x, z) with maps of `mapSize`, one flair and one scene per half of the scene map
|
|
void Chunk(uint32_t id, uint32_t size, float x, float z, uint32_t mapSize, uint8_t sceneA, uint8_t sceneB, bool mesh) {
|
|
Put(id).Put(size).Put(size).Put(x).Put(z);
|
|
for (uint32_t texture : { 10u, 11u, 12u, 13u }) Put(texture);
|
|
Put(2.0f);
|
|
for (uint32_t i = 0; i < size * size; i++) Put(static_cast<float>(i));
|
|
Put(mapSize).Bytes(static_cast<size_t>(mapSize) * mapSize * 4, 0x80); // color map
|
|
Put<uint32_t>(4).Bytes(4, 'L'); // light map (a DDS in real files)
|
|
Put(mapSize).Bytes(static_cast<size_t>(mapSize) * mapSize * 4, 0x40); // texture blend map
|
|
Put<uint8_t>(15); // which textures are used
|
|
Put<uint32_t>(3).Bytes(3, 'B'); // blend map DDS
|
|
Put<uint32_t>(1); // one flair
|
|
Put<uint32_t>(49).Put(0.5f).Put(x + 1).Put(7.0f).Put(z + 1).Put(0.0f).Put(1.5f).Put(0.0f);
|
|
Put<uint8_t>(25).Put<uint8_t>(54).Put<uint8_t>(10).Put<uint8_t>(63);
|
|
for (uint32_t i = 0; i < mapSize * mapSize; i++) Put<uint8_t>(i < mapSize * mapSize / 2 ? sceneA : sceneB);
|
|
if (!mesh) { Put<uint32_t>(0); return; }
|
|
Put<uint32_t>(2).Put<uint16_t>(65535).Put<uint16_t>(0); // vertex usage
|
|
for (int i = 0; i < 16; i++) Put<uint16_t>(4); // vertices per block
|
|
for (int i = 0; i < 16; i++) { Put<uint16_t>(3); for (uint16_t v : { 0, 1, 2 }) Put(v); }
|
|
}
|
|
};
|
|
|
|
std::string SampleRaw() {
|
|
RawWriter w;
|
|
w.Put<uint16_t>(32).Put<uint8_t>(0).Put<uint32_t>(2).Put<uint32_t>(2).Put<uint32_t>(1);
|
|
w.Chunk(0, 3, -4.0f, 0.0f, 4, 1, 2, true);
|
|
w.Chunk(1, 3, 0.0f, 0.0f, 4, 2, 3, false);
|
|
return w.data;
|
|
}
|
|
|
|
bool Read(const std::string& data, Raw::Raw& raw) {
|
|
std::istringstream stream(data);
|
|
return Raw::ReadRaw(stream, raw);
|
|
}
|
|
}
|
|
|
|
TEST(RawTerrainTests, ReadsEveryLayer) {
|
|
Raw::Raw raw;
|
|
ASSERT_TRUE(Read(SampleRaw(), raw));
|
|
EXPECT_EQ(raw.version, 32);
|
|
ASSERT_EQ(raw.chunks.size(), 2u);
|
|
const auto& chunk = raw.chunks[0];
|
|
EXPECT_EQ(chunk.textureIds, (std::vector<uint32_t>{ 10, 11, 12, 13 }));
|
|
EXPECT_FLOAT_EQ(chunk.scaleFactor, 2.0f);
|
|
ASSERT_EQ(chunk.heightMap.size(), 9u);
|
|
EXPECT_FLOAT_EQ(chunk.heightMap[8], 8.0f);
|
|
EXPECT_EQ(chunk.colorMapResolution, 4u);
|
|
EXPECT_EQ(chunk.colorMap.size(), 64u);
|
|
EXPECT_EQ(chunk.lightMap.size(), 4u);
|
|
EXPECT_EQ(chunk.textureMapResolution, 4u);
|
|
EXPECT_EQ(chunk.textureMap[0], 0x40);
|
|
EXPECT_EQ(chunk.textureSettings, 15);
|
|
EXPECT_EQ(chunk.blendMap.size(), 3u);
|
|
ASSERT_EQ(chunk.flairs.size(), 1u);
|
|
EXPECT_EQ(chunk.flairs[0].id, 49u);
|
|
EXPECT_FLOAT_EQ(chunk.flairs[0].position.x, -3.0f);
|
|
EXPECT_FLOAT_EQ(chunk.flairs[0].rotation.y, 1.5f);
|
|
EXPECT_EQ(chunk.flairs[0].colorG, 54);
|
|
ASSERT_EQ(chunk.sceneMap.size(), 16u);
|
|
EXPECT_EQ(chunk.vertSize, 2u);
|
|
EXPECT_EQ(chunk.meshTri.size(), 16u);
|
|
EXPECT_EQ(raw.chunks[1].vertSize, 0u);
|
|
|
|
// The scene map runs like the heights: cell (i, j) is scene row i
|
|
EXPECT_EQ(chunk.GetSceneIDAtGrid(0, 0), 1);
|
|
EXPECT_EQ(chunk.GetSceneIDAtGrid(2, 2), 2);
|
|
EXPECT_EQ(raw.chunks[1].GetSceneIDAtGrid(2, 0), 3);
|
|
EXPECT_FLOAT_EQ(raw.minBoundsX, -4.0f);
|
|
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<uint16_t>(version).Put<uint8_t>(0).Put<uint32_t>(1).Put<uint32_t>(1).Put<uint32_t>(1);
|
|
w.Put<uint32_t>(0).Put<uint32_t>(3).Put<uint32_t>(3).Put(0.0f).Put(0.0f);
|
|
w.Put<uint32_t>(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<uint8_t>(1).Put<uint8_t>(2).Put<uint8_t>(3).Put<uint8_t>(static_cast<uint8_t>(i)); // BGRA
|
|
w.Put<uint32_t>(1).Put<uint8_t>(1).Put<uint8_t>(2).Put<uint8_t>(3).Put<uint8_t>(4);
|
|
w.Put<uint32_t>(0); // no flairs
|
|
if (version == 31) for (int i = 0; i < 9; i++) w.Put<uint8_t>(static_cast<uint8_t>(i));
|
|
else w.Put<uint8_t>(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<uint8_t>{ 3, 2, 1, 0, 3, 2, 1, 1, 3, 2, 1, 3, 3, 2, 1, 4 }));
|
|
EXPECT_EQ(chunk.textureMap, (std::vector<uint8_t>{ 3, 2, 1, 4 }));
|
|
if (version == 31) EXPECT_EQ(chunk.sceneMap, (std::vector<uint8_t>{ 0, 1, 3, 4 }));
|
|
else EXPECT_EQ(chunk.sceneMap, (std::vector<uint8_t>(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 }) {
|
|
Raw::Raw out;
|
|
EXPECT_FALSE(Read(raw.substr(0, length), out)) << length;
|
|
}
|
|
// Sizes are capped before anything is allocated
|
|
auto huge = raw;
|
|
const uint32_t side = 100000;
|
|
std::memcpy(huge.data() + 19, &side, sizeof(side)); // the first chunk's width
|
|
Raw::Raw out;
|
|
EXPECT_FALSE(Read(huge, out));
|
|
EXPECT_FALSE(TerrainMap::Read(huge).has_value());
|
|
}
|
|
|
|
TEST(RawTerrainTests, TerrainMapUsesTheSameReader) {
|
|
const auto grid = TerrainMap::Parse(SampleRaw());
|
|
ASSERT_TRUE(grid.has_value());
|
|
EXPECT_FLOAT_EQ(grid->minX, -4.0f);
|
|
EXPECT_FLOAT_EQ(grid->step, 2.0f);
|
|
EXPECT_EQ(grid->width, 5u);
|
|
EXPECT_EQ(grid->height, 3u);
|
|
// heights[3 * i + j] is at x = -4 + 2i, z = 2j; the second chunk overwrites the shared edge
|
|
EXPECT_FLOAT_EQ(grid->heights[0], 0.0f);
|
|
EXPECT_FLOAT_EQ(grid->heights[1 * 5 + 1], 4.0f);
|
|
EXPECT_FLOAT_EQ(grid->maxY, 8.0f);
|
|
}
|