mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
feat(zone): read zone files of every version the client reads
Zone files older than version 30 were refused. The client reads them (1.10.64): - LuzFile::ReadLUZFile reads a version below 20 as 20; - LuzReader::ReadScenes reads a scene of a version 20-29 file as a bare u32 SceneTable ID, with no file name; - ZoneLoader::ReadZoneFile takes those scenes in SceneTable ID order, looks each one up in the CDClient SceneTable and, when there is a row, makes it a scene with the next index (0, 1, ...; -1 past 255) as its ID and the row's sceneName as its file; a scene with no row is left out. ZoneFile now reads these files (FileFormatVersion::Oldest = 20 for anything older), keeps the SceneTable ID on ZoneScene::sceneTableID, and ZoneFile::ResolveSceneTable does the lookup the client does; the world looks the names up in the CDClient SceneTable. The only such file on disk (version 20) now reads. Check in game: nothing to check on live worlds (all are version 36+). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <cstdint>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "BinaryIO.h"
|
#include "BinaryIO.h"
|
||||||
@@ -12,10 +12,8 @@
|
|||||||
|
|
||||||
void ZoneFile::ReadHeader(std::istream& file) {
|
void ZoneFile::ReadHeader(std::istream& file) {
|
||||||
BinaryIO::BinaryRead(file, fileFormatVersion);
|
BinaryIO::BinaryRead(file, fileFormatVersion);
|
||||||
// Before PrePreAlpha a scene is only its ID, with no file to load it from (LuzReader::ReadScenes)
|
// The client reads anything older than 20 as 20 (LuzFile::ReadLUZFile)
|
||||||
if (fileFormatVersion < FileFormatVersion::PrePreAlpha) {
|
if (fileFormatVersion < FileFormatVersion::Oldest) fileFormatVersion = FileFormatVersion::Oldest;
|
||||||
throw std::runtime_error("Zone file version " + std::to_string(static_cast<uint32_t>(fileFormatVersion)) + " is older than " + std::to_string(static_cast<uint32_t>(FileFormatVersion::PrePreAlpha)) + ": its scenes have no files");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileFormatVersion >= FileFormatVersion::Alpha) BinaryIO::BinaryRead(file, mapRevision);
|
if (fileFormatVersion >= FileFormatVersion::Alpha) BinaryIO::BinaryRead(file, mapRevision);
|
||||||
|
|
||||||
@@ -75,6 +73,14 @@ void ZoneFile::Read(std::istream& file) {
|
|||||||
void ZoneFile::ReadScene(std::istream& file) {
|
void ZoneFile::ReadScene(std::istream& file) {
|
||||||
ZoneScene scene;
|
ZoneScene scene;
|
||||||
|
|
||||||
|
// Before PrePreAlpha a scene is only its SceneTable ID; ResolveSceneTable finds its file (LuzReader::ReadScenes)
|
||||||
|
if (fileFormatVersion < FileFormatVersion::PrePreAlpha) {
|
||||||
|
BinaryIO::BinaryRead(file, scene.sceneTableID);
|
||||||
|
scene.id = scene.sceneTableID;
|
||||||
|
scenes.push_back(std::move(scene));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BinaryIO::ReadString<uint8_t>(file, scene.filename, BinaryIO::ReadType::String);
|
BinaryIO::ReadString<uint8_t>(file, scene.filename, BinaryIO::ReadType::String);
|
||||||
|
|
||||||
if (fileFormatVersion >= FileFormatVersion::LatePreAlpha) {
|
if (fileFormatVersion >= FileFormatVersion::LatePreAlpha) {
|
||||||
@@ -98,6 +104,26 @@ void ZoneFile::ReadScene(std::istream& file) {
|
|||||||
scenes.push_back(std::move(scene));
|
scenes.push_back(std::move(scene));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZoneFile::ResolveSceneTable(const std::function<std::optional<std::string>(uint32_t)>& sceneName) {
|
||||||
|
if (fileFormatVersion >= FileFormatVersion::PrePreAlpha) return;
|
||||||
|
// ZoneLoader::ReadZoneFile: the scenes go by their SceneTable ID; each one with a row there gets the next index
|
||||||
|
// (0, 1, ...; -1 past 255) as its ID and the row's sceneName as its file, the others are left out
|
||||||
|
std::ranges::stable_sort(scenes, {}, &ZoneScene::sceneTableID);
|
||||||
|
std::vector<ZoneScene> resolved;
|
||||||
|
uint32_t index = 0;
|
||||||
|
for (auto& scene : scenes) {
|
||||||
|
if (!resolved.empty() && resolved.back().sceneTableID == scene.sceneTableID) continue;
|
||||||
|
const auto name = sceneName(scene.sceneTableID);
|
||||||
|
if (!name) continue;
|
||||||
|
scene.id = index < 256 ? index : UINT32_MAX;
|
||||||
|
scene.filename = *name;
|
||||||
|
scene.name = *name;
|
||||||
|
resolved.push_back(std::move(scene));
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
scenes = std::move(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
void ZoneFile::ReadZoneBoundaries(std::istream& file) {
|
void ZoneFile::ReadZoneBoundaries(std::istream& file) {
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
BinaryIO::BinaryRead(file, count);
|
BinaryIO::BinaryRead(file, count);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -27,6 +29,7 @@ struct WaypointCommand {
|
|||||||
struct ZoneScene {
|
struct ZoneScene {
|
||||||
std::string filename;
|
std::string filename;
|
||||||
uint32_t id{};
|
uint32_t id{};
|
||||||
|
uint32_t sceneTableID{}; // before PrePreAlpha: the scene's row in the CDClient SceneTable (see ResolveSceneTable)
|
||||||
eSceneType sceneType{};
|
eSceneType sceneType{};
|
||||||
std::string name;
|
std::string name;
|
||||||
NiPoint3 unknown1;
|
NiPoint3 unknown1;
|
||||||
@@ -199,6 +202,7 @@ struct Path {
|
|||||||
|
|
||||||
struct ZoneFile {
|
struct ZoneFile {
|
||||||
enum class FileFormatVersion : uint32_t { //Times are guessed.
|
enum class FileFormatVersion : uint32_t { //Times are guessed.
|
||||||
|
Oldest = 20, // the client reads anything older as this
|
||||||
PrePreAlpha = 30,
|
PrePreAlpha = 30,
|
||||||
PreAlpha = 32,
|
PreAlpha = 32,
|
||||||
LatePreAlpha = 33,
|
LatePreAlpha = 33,
|
||||||
@@ -243,6 +247,13 @@ struct ZoneFile {
|
|||||||
*/
|
*/
|
||||||
void ReadHeader(std::istream& file);
|
void ReadHeader(std::istream& file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before PrePreAlpha a scene is only its SceneTable ID. As the client does (ZoneLoader::ReadZoneFile), the scenes
|
||||||
|
* are taken in SceneTable ID order, each one `sceneName` finds a row for gets that row's sceneName as its file and
|
||||||
|
* name and the next index as its ID, and the others are left out. Nothing to do for newer files.
|
||||||
|
*/
|
||||||
|
void ResolveSceneTable(const std::function<std::optional<std::string>(uint32_t)>& sceneName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ReadScene(std::istream& file);
|
void ReadScene(std::istream& file);
|
||||||
void ReadZoneBoundaries(std::istream& file);
|
void ReadZoneBoundaries(std::istream& file);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "AssetManager.h"
|
#include "AssetManager.h"
|
||||||
#include "CDClientManager.h"
|
#include "CDClientManager.h"
|
||||||
#include "CDZoneTableTable.h"
|
#include "CDZoneTableTable.h"
|
||||||
|
#include "CDClientDatabase.h"
|
||||||
#include "Spawner.h"
|
#include "Spawner.h"
|
||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
#include "dpWorld.h"
|
#include "dpWorld.h"
|
||||||
@@ -56,6 +57,14 @@ void Zone::LoadZoneIntoMemory() {
|
|||||||
// The file itself is read by ZoneFile (dCommon), which the dashboard uses too
|
// The file itself is read by ZoneFile (dCommon), which the dashboard uses too
|
||||||
ZoneFile zoneFile;
|
ZoneFile zoneFile;
|
||||||
zoneFile.Read(file);
|
zoneFile.Read(file);
|
||||||
|
// The oldest files name their scenes by SceneTable ID
|
||||||
|
zoneFile.ResolveSceneTable([](uint32_t sceneTableID) -> std::optional<std::string> {
|
||||||
|
auto query = CDClientDatabase::CreatePreppedStmt("SELECT sceneName FROM SceneTable WHERE sceneID = ?;");
|
||||||
|
query.bind(1, static_cast<int32_t>(sceneTableID));
|
||||||
|
auto result = query.execQuery();
|
||||||
|
if (result.eof() || result.fieldIsNull("sceneName")) return std::nullopt;
|
||||||
|
return std::string(result.getStringField("sceneName"));
|
||||||
|
});
|
||||||
m_FileFormatVersion = zoneFile.fileFormatVersion;
|
m_FileFormatVersion = zoneFile.fileFormatVersion;
|
||||||
m_WorldID = zoneFile.worldID;
|
m_WorldID = zoneFile.worldID;
|
||||||
if (static_cast<LWOMAPID>(m_WorldID) != m_ZoneID.GetMapID()) LOG("WorldID: %i doesn't match MapID %i! Is this intended?", m_WorldID, m_ZoneID.GetMapID());
|
if (static_cast<LWOMAPID>(m_WorldID) != m_ZoneID.GetMapID()) LOG("WorldID: %i doesn't match MapID %i! Is this intended?", m_WorldID, m_ZoneID.GetMapID());
|
||||||
|
|||||||
@@ -138,14 +138,33 @@ TEST(ZoneFileTests, LateAlphaSceneCountIsAU32) {
|
|||||||
EXPECT_EQ(zone.zoneRawPath, "zone.raw");
|
EXPECT_EQ(zone.zoneRawPath, "zone.raw");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before version 30 a scene is only an ID, with no file to load
|
// Before version 30 a scene is only its SceneTable ID; below 20 the file reads as 20
|
||||||
TEST(ZoneFileTests, VersionsBeforePrePreAlphaThrow) {
|
TEST(ZoneFileTests, ReadsVersionsBeforePrePreAlpha) {
|
||||||
ZoneBytes w;
|
ZoneBytes w;
|
||||||
w.Put<uint32_t>(20).Put<uint32_t>(53).Put<uint8_t>(1).Put<uint32_t>(53).Put<uint8_t>(0).Text(".raw");
|
w.Put<uint32_t>(12).Put<uint32_t>(53).Put<uint8_t>(4); // version, world, scene count
|
||||||
|
w.Put<uint32_t>(9).Put<uint32_t>(3).Put<uint32_t>(7).Put<uint32_t>(3); // SceneTable IDs
|
||||||
|
w.Put<uint8_t>(0).Text("zone.raw");
|
||||||
std::istringstream stream(w.data);
|
std::istringstream stream(w.data);
|
||||||
ZoneFile zone;
|
ZoneFile zone;
|
||||||
EXPECT_THROW(zone.Read(stream), std::runtime_error);
|
zone.Read(stream);
|
||||||
EXPECT_TRUE(zone.scenes.empty());
|
EXPECT_FALSE(stream.fail());
|
||||||
|
EXPECT_EQ(stream.peek(), std::char_traits<char>::eof());
|
||||||
|
EXPECT_EQ(zone.fileFormatVersion, ZoneFile::FileFormatVersion::Oldest);
|
||||||
|
EXPECT_EQ(zone.zoneRawPath, "zone.raw");
|
||||||
|
ASSERT_EQ(zone.scenes.size(), 4u);
|
||||||
|
EXPECT_TRUE(zone.scenes[0].filename.empty());
|
||||||
|
|
||||||
|
// In SceneTable ID order, once each; 7 has no row
|
||||||
|
zone.ResolveSceneTable([](uint32_t id) -> std::optional<std::string> {
|
||||||
|
if (id == 7) return std::nullopt;
|
||||||
|
return "scene" + std::to_string(id) + ".lvl";
|
||||||
|
});
|
||||||
|
ASSERT_EQ(zone.scenes.size(), 2u);
|
||||||
|
EXPECT_EQ(zone.scenes[0].id, 0u);
|
||||||
|
EXPECT_EQ(zone.scenes[0].filename, "scene3.lvl");
|
||||||
|
EXPECT_EQ(zone.scenes[0].name, "scene3.lvl");
|
||||||
|
EXPECT_EQ(zone.scenes[1].id, 1u);
|
||||||
|
EXPECT_EQ(zone.scenes[1].filename, "scene9.lvl");
|
||||||
}
|
}
|
||||||
|
|
||||||
// A PrePreAlpha (30) file ends at its terrain file's name: no zone name, description, transitions or paths
|
// A PrePreAlpha (30) file ends at its terrain file's name: no zone name, description, transitions or paths
|
||||||
|
|||||||
Reference in New Issue
Block a user