mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
fix(level): find a scene file's chunks and sections as the client does
SceneLoader::ReadLvlFile (1.10.64):
- chunked files: the file info chunk starts the file and gives where
the environment, object and particle chunks start, 0 for none
(DoLvlChunk, ReadLvlChunk1000); the reader walked the chunks one after
another by their sizes instead;
- older files: the editor settings are a u32 size and that many bytes
(SceneLoader::ReadEditorSettings, skipped by size); the reader took
them for a u32 and then a count of 12 byte points;
- older files before version 3 have no objects for the client ("Level
file is unsupported").
LevelFile now does the same. Every scene file on disk reads the same
objects as before.
Check in game: every world spawns its objects as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,109 +9,114 @@
|
|||||||
void LevelFile::Read(std::istream& file) {
|
void LevelFile::Read(std::istream& file) {
|
||||||
const uint32_t CHNK_HEADER = ('C' + ('H' << 8) + ('N' << 16) + ('K' << 24));
|
const uint32_t CHNK_HEADER = ('C' + ('H' << 8) + ('N' << 16) + ('K' << 24));
|
||||||
|
|
||||||
while (!file.eof()) {
|
uint32_t magic = 0;
|
||||||
uint32_t initPos = uint32_t(file.tellg());
|
BinaryIO::BinaryRead(file, magic);
|
||||||
uint32_t header = 0;
|
if (magic == CHNK_HEADER) {
|
||||||
BinaryIO::BinaryRead(file, header);
|
// SceneLoader::ReadLvlFile / DoLvlChunk: the file info chunk starts the file and gives where the environment,
|
||||||
if (header == CHNK_HEADER) { //Make sure we're reading a valid CHNK
|
// object and particle chunks start (0 for none); only the objects are read here
|
||||||
ChunkHeader header;
|
ChunkHeader fileInfo = ReadChunkHeader(file, 0);
|
||||||
BinaryIO::BinaryRead(file, header.id);
|
file.seekg(fileInfo.startPosition);
|
||||||
BinaryIO::BinaryRead(file, header.chunkVersion);
|
ReadFileInfoChunk(file, fileInfo);
|
||||||
BinaryIO::BinaryRead(file, header.chunkType);
|
chunkHeaders.insert({ fileInfo.id, fileInfo });
|
||||||
BinaryIO::BinaryRead(file, header.size);
|
for (const auto start : { fileInfo.fileInfo.enviromentChunkStart, fileInfo.fileInfo.objectChunkStart, fileInfo.fileInfo.particleChunkStart }) {
|
||||||
BinaryIO::BinaryRead(file, header.startPosition);
|
if (start == 0) continue;
|
||||||
|
const auto header = ReadChunkHeader(file, start);
|
||||||
uint32_t target = initPos + header.size;
|
chunkHeaders.insert({ header.id, header });
|
||||||
|
if (start != fileInfo.fileInfo.objectChunkStart) continue;
|
||||||
|
// The objects are laid out by the file's version, from its file info chunk
|
||||||
file.seekg(header.startPosition);
|
file.seekg(header.startPosition);
|
||||||
|
ReadSceneObjectDataChunk(file, fileInfo.fileInfo.version);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//We're currently not loading env or particle data
|
// Scenes from before chunks (SceneLoader::ReadLvlSections): a header, then lighting, skydome and editor settings
|
||||||
if (header.id == ChunkTypeID::FileInfo) {
|
// the world skips, then the objects
|
||||||
ReadFileInfoChunk(file, header);
|
file.seekg(0);
|
||||||
} else if (header.id == ChunkTypeID::SceneObjectData) {
|
ChunkHeader header;
|
||||||
// The objects are laid out by the file's version, from its file info chunk
|
header.id = ChunkTypeID::FileInfo;
|
||||||
const auto fileInfo = chunkHeaders.find(ChunkTypeID::FileInfo);
|
BinaryIO::BinaryRead(file, header.chunkVersion);
|
||||||
ReadSceneObjectDataChunk(file, fileInfo == chunkHeaders.end() ? 0 : fileInfo->second.fileInfo.version);
|
BinaryIO::BinaryRead(file, header.chunkType);
|
||||||
|
// Only from version 32 (SceneLoader::ReadLvlHeader)
|
||||||
|
uint8_t important = 0;
|
||||||
|
if (header.chunkVersion >= 32) BinaryIO::BinaryRead(file, important);
|
||||||
|
if (header.chunkVersion > 36) {
|
||||||
|
BinaryIO::BinaryRead(file, header.fileInfo.revision);
|
||||||
|
}
|
||||||
|
// Lighting (SceneLoader::ReadLightingInfo)
|
||||||
|
if (header.chunkVersion >= 45) file.ignore(4);
|
||||||
|
file.ignore(4 * (4 * 3));
|
||||||
|
|
||||||
|
if (header.chunkVersion >= 31) {
|
||||||
|
if (header.chunkVersion >= 39) {
|
||||||
|
file.ignore(12 * 4);
|
||||||
|
|
||||||
|
if (header.chunkVersion >= 40) {
|
||||||
|
uint32_t s = 0;
|
||||||
|
BinaryIO::BinaryRead(file, s);
|
||||||
|
for (uint32_t i = 0; i < s; ++i) {
|
||||||
|
file.ignore(4 * 3); //a uint and two floats
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkHeaders.insert(std::make_pair(header.id, header));
|
|
||||||
file.seekg(target);
|
|
||||||
} else {
|
} else {
|
||||||
if (initPos == std::streamoff(0)) { //Really old chunk version
|
file.ignore(8);
|
||||||
file.seekg(0);
|
}
|
||||||
ChunkHeader header;
|
|
||||||
header.id = ChunkTypeID::FileInfo;
|
|
||||||
BinaryIO::BinaryRead(file, header.chunkVersion);
|
|
||||||
BinaryIO::BinaryRead(file, header.chunkType);
|
|
||||||
// Only from version 32 (SceneLoader::ReadLvlHeader)
|
|
||||||
uint8_t important = 0;
|
|
||||||
if (header.chunkVersion >= 32) BinaryIO::BinaryRead(file, important);
|
|
||||||
if (header.chunkVersion > 36) {
|
|
||||||
BinaryIO::BinaryRead(file, header.fileInfo.revision);
|
|
||||||
}
|
|
||||||
// HARDCODED 3
|
|
||||||
if (header.chunkVersion >= 45) file.ignore(4);
|
|
||||||
file.ignore(4 * (4 * 3));
|
|
||||||
|
|
||||||
if (header.chunkVersion >= 31) {
|
file.ignore(3 * 4);
|
||||||
if (header.chunkVersion >= 39) {
|
}
|
||||||
file.ignore(12 * 4);
|
|
||||||
|
|
||||||
if (header.chunkVersion >= 40) {
|
if (header.chunkVersion >= 36) {
|
||||||
uint32_t s = 0;
|
file.ignore(3 * 4);
|
||||||
BinaryIO::BinaryRead(file, s);
|
}
|
||||||
for (uint32_t i = 0; i < s; ++i) {
|
|
||||||
file.ignore(4 * 3); //a uint and two floats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
file.ignore(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.ignore(3 * 4);
|
if (header.chunkVersion < 42) {
|
||||||
}
|
file.ignore(3 * 4);
|
||||||
|
|
||||||
if (header.chunkVersion >= 36) {
|
if (header.chunkVersion >= 33) {
|
||||||
file.ignore(3 * 4);
|
file.ignore(4 * 4);
|
||||||
}
|
|
||||||
|
|
||||||
if (header.chunkVersion < 42) {
|
|
||||||
file.ignore(3 * 4);
|
|
||||||
|
|
||||||
if (header.chunkVersion >= 33) {
|
|
||||||
file.ignore(4 * 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skydome info
|
|
||||||
uint32_t count = 0;
|
|
||||||
BinaryIO::BinaryRead(file, count);
|
|
||||||
file.ignore(count);
|
|
||||||
|
|
||||||
// Five more strings from version 34 (SceneLoader::ReadSkydomeInfo)
|
|
||||||
if (header.chunkVersion >= 34) {
|
|
||||||
for (uint32_t i = 0; i < 5; ++i) {
|
|
||||||
uint32_t count = 0;
|
|
||||||
BinaryIO::BinaryRead(file, count);
|
|
||||||
file.ignore(count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// editor settings
|
|
||||||
if (!important && header.chunkVersion >= 37){
|
|
||||||
file.ignore(4);
|
|
||||||
|
|
||||||
uint32_t count = 0;
|
|
||||||
BinaryIO::BinaryRead(file, count);
|
|
||||||
file.ignore(count * 12);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
header.id = ChunkTypeID::SceneObjectData;
|
|
||||||
header.fileInfo.version = header.chunkVersion;
|
|
||||||
ReadSceneObjectDataChunk(file, header.fileInfo.version);
|
|
||||||
chunkHeaders.insert(std::make_pair(header.id, header));
|
|
||||||
} break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skydome info; five more strings from version 34 (SceneLoader::ReadSkydomeInfo)
|
||||||
|
uint32_t count = 0;
|
||||||
|
BinaryIO::BinaryRead(file, count);
|
||||||
|
file.ignore(count);
|
||||||
|
|
||||||
|
if (header.chunkVersion >= 34) {
|
||||||
|
for (uint32_t i = 0; i < 5; ++i) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
BinaryIO::BinaryRead(file, count);
|
||||||
|
file.ignore(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// editor settings: their size and then that many bytes (SceneLoader::ReadEditorSettings)
|
||||||
|
if (!important && header.chunkVersion >= 37) {
|
||||||
|
uint32_t size = 0;
|
||||||
|
BinaryIO::BinaryRead(file, size);
|
||||||
|
file.ignore(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The client has no objects for a file older than version 3 ("Level file is unsupported")
|
||||||
|
header.fileInfo.version = header.chunkVersion;
|
||||||
|
if (header.chunkVersion >= 3) {
|
||||||
|
header.id = ChunkTypeID::SceneObjectData;
|
||||||
|
ReadSceneObjectDataChunk(file, header.fileInfo.version);
|
||||||
|
}
|
||||||
|
chunkHeaders.insert(std::make_pair(header.id, header));
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelFile::ChunkHeader LevelFile::ReadChunkHeader(std::istream& file, uint32_t start) {
|
||||||
|
// No check of the magic, as in the client (0x01063a00)
|
||||||
|
file.seekg(start);
|
||||||
|
uint32_t magic = 0;
|
||||||
|
ChunkHeader header;
|
||||||
|
BinaryIO::BinaryRead(file, magic);
|
||||||
|
BinaryIO::BinaryRead(file, header.id);
|
||||||
|
BinaryIO::BinaryRead(file, header.chunkVersion);
|
||||||
|
BinaryIO::BinaryRead(file, header.chunkType);
|
||||||
|
BinaryIO::BinaryRead(file, header.size);
|
||||||
|
BinaryIO::BinaryRead(file, header.startPosition);
|
||||||
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LevelFile::ReadFileInfoChunk(std::istream& file, ChunkHeader& header) {
|
void LevelFile::ReadFileInfoChunk(std::istream& file, ChunkHeader& header) {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ struct LevelFile {
|
|||||||
void Read(std::istream& file);
|
void Read(std::istream& file);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static ChunkHeader ReadChunkHeader(std::istream& file, uint32_t start);
|
||||||
void ReadFileInfoChunk(std::istream& file, ChunkHeader& header);
|
void ReadFileInfoChunk(std::istream& file, ChunkHeader& header);
|
||||||
void ReadSceneObjectDataChunk(std::istream& file, uint32_t version);
|
void ReadSceneObjectDataChunk(std::istream& file, uint32_t version);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -416,6 +416,7 @@ TEST(LevelFileTests, ReadsChunkedObjects) {
|
|||||||
w.Put<uint32_t>(41).Put<uint32_t>(9).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
w.Put<uint32_t>(41).Put<uint32_t>(9).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
||||||
end(info);
|
end(info);
|
||||||
const auto objects = chunk(LevelFile::SceneObjectData);
|
const auto objects = chunk(LevelFile::SceneObjectData);
|
||||||
|
w.At<uint32_t>(info + 20 + 12, static_cast<uint32_t>(objects)); // the file info gives where the objects start
|
||||||
w.Put<uint32_t>(2);
|
w.Put<uint32_t>(2);
|
||||||
w.Object(41, 4945, 10, "respawnname=0:NS_LW_Portal\nloadOnClientOnly=7:1");
|
w.Object(41, 4945, 10, "respawnname=0:NS_LW_Portal\nloadOnClientOnly=7:1");
|
||||||
w.Object(41, 176, -4, "spawntemplate=1:6010");
|
w.Object(41, 176, -4, "spawntemplate=1:6010");
|
||||||
@@ -474,6 +475,36 @@ TEST(LevelFileTests, ReadsObjectsAsTheClient) {
|
|||||||
EXPECT_EQ(level.objects[1].position.x, 5.0f);
|
EXPECT_EQ(level.objects[1].position.x, 5.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Editor settings are skipped by their size; a file older than version 3 has no objects for the client
|
||||||
|
TEST(LevelFileTests, SkipsEditorSettingsBySize) {
|
||||||
|
ZoneBytes w;
|
||||||
|
w.Put<uint16_t>(37).Put<uint16_t>(0).Put<uint8_t>(0).Put<uint32_t>(4); // version, type, important, revision
|
||||||
|
w.data.append(48 + 8 + 12 + 12 + 12 + 16, '\0'); // lighting
|
||||||
|
w.Put<uint32_t>(0); // skydome
|
||||||
|
for (int i = 0; i < 5; i++) w.Put<uint32_t>(0);
|
||||||
|
w.Put<uint32_t>(7);
|
||||||
|
w.data.append(7, '\x55'); // editor settings
|
||||||
|
w.Put<uint32_t>(1);
|
||||||
|
w.Object(37, 4945, 3, "a=0:b");
|
||||||
|
|
||||||
|
std::istringstream stream(w.Done());
|
||||||
|
LevelFile level;
|
||||||
|
level.Read(stream);
|
||||||
|
ASSERT_EQ(level.objects.size(), 1u);
|
||||||
|
EXPECT_EQ(level.objects[0].lot, 4945);
|
||||||
|
EXPECT_EQ(level.chunkHeaders.begin()->second.fileInfo.revision, 4u);
|
||||||
|
|
||||||
|
ZoneBytes old;
|
||||||
|
old.Put<uint16_t>(2).Put<uint16_t>(0);
|
||||||
|
old.data.append(48 + 12, '\0');
|
||||||
|
old.Put<uint32_t>(0).Put<uint32_t>(1);
|
||||||
|
old.Object(2, 1, 0, "a=0:b");
|
||||||
|
std::istringstream oldStream(old.Done());
|
||||||
|
LevelFile oldLevel;
|
||||||
|
oldLevel.Read(oldStream);
|
||||||
|
EXPECT_TRUE(oldLevel.objects.empty());
|
||||||
|
}
|
||||||
|
|
||||||
TEST(LevelFileTests, DamagedFilesKeepWhatWasRead) {
|
TEST(LevelFileTests, DamagedFilesKeepWhatWasRead) {
|
||||||
ZoneBytes w;
|
ZoneBytes w;
|
||||||
w.Put<uint16_t>(30).Put<uint16_t>(0);
|
w.Put<uint16_t>(30).Put<uint16_t>(0);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace {
|
|||||||
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
||||||
w.End(info);
|
w.End(info);
|
||||||
auto objects = w.Chunk(2001);
|
auto objects = w.Chunk(2001);
|
||||||
|
w.At<uint32_t>(info + 32, static_cast<uint32_t>(objects)); // the file info chunk says where the objects start, as in real files
|
||||||
w.Put<uint32_t>(4);
|
w.Put<uint32_t>(4);
|
||||||
w.Object(4945, 10, 20, 30, "respawnname=0:NS_LW_Portal\ncustom_config_names=0:");
|
w.Object(4945, 10, 20, 30, "respawnname=0:NS_LW_Portal\ncustom_config_names=0:");
|
||||||
w.Object(6010, 1, 1, 1, "spawntemplate=1:6010"); // not a spawn point
|
w.Object(6010, 1, 1, 1, "spawntemplate=1:6010"); // not a spawn point
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ TEST(LevelGatingTests, CountsGatedObjects) {
|
|||||||
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
||||||
w.End(info);
|
w.End(info);
|
||||||
auto objects = w.Chunk(2001);
|
auto objects = w.Chunk(2001);
|
||||||
|
w.At<uint32_t>(info + 32, static_cast<uint32_t>(objects)); // the file info chunk says where the objects start, as in real files
|
||||||
w.Put<uint32_t>(5);
|
w.Put<uint32_t>(5);
|
||||||
w.Object(1000, "gatingOnFeature=13:oct2011content");
|
w.Object(1000, "gatingOnFeature=13:oct2011content");
|
||||||
w.Object(1001, "custom_config_names=0:\ngatingOnFeature=13:oct2011content\r");
|
w.Object(1001, "custom_config_names=0:\ngatingOnFeature=13:oct2011content\r");
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace {
|
|||||||
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
||||||
w.End(info);
|
w.End(info);
|
||||||
auto objects = w.Chunk(2001);
|
auto objects = w.Chunk(2001);
|
||||||
|
w.At<uint32_t>(info + 32, static_cast<uint32_t>(objects)); // the file info chunk says where the objects start, as in real files
|
||||||
w.Put<uint32_t>(3);
|
w.Put<uint32_t>(3);
|
||||||
w.Object(10, 4945, 1, 2, 3, "respawnname=0:NS_Portal\r");
|
w.Object(10, 4945, 1, 2, 3, "respawnname=0:NS_Portal\r");
|
||||||
w.Object(11, 176, -4, 0, 8, "spawntemplate=1:6010\nspawner_name=0:Crates");
|
w.Object(11, 176, -4, 0, 8, "spawntemplate=1:6010\nspawner_name=0:Crates");
|
||||||
@@ -121,6 +122,7 @@ TEST(WorldSceneTests, ClientDrawsWhatTheGameDraws) {
|
|||||||
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
w.Put<uint32_t>(41).Put<uint32_t>(1).Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(0);
|
||||||
w.End(info);
|
w.End(info);
|
||||||
auto objects = w.Chunk(2001);
|
auto objects = w.Chunk(2001);
|
||||||
|
w.At<uint32_t>(info + 32, static_cast<uint32_t>(objects)); // the file info chunk says where the objects start, as in real files
|
||||||
w.Put<uint32_t>(6);
|
w.Put<uint32_t>(6);
|
||||||
w.Object(1, 4630, 0, 0, 0, "carver_only=7:1\ncreate_physics=7:1"); // an invisible trigger cube
|
w.Object(1, 4630, 0, 0, 0, "carver_only=7:1\ncreate_physics=7:1"); // an invisible trigger cube
|
||||||
w.Object(2, 5651, 0, 0, 0, "carver_only=7:0"); // loaded, so drawn
|
w.Object(2, 5651, 0, 0, 0, "carver_only=7:0"); // loaded, so drawn
|
||||||
|
|||||||
Reference in New Issue
Block a user