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 <cctype>
|
||||
#include <cstdint>
|
||||
#include <istream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "BinaryIO.h"
|
||||
@@ -12,10 +12,8 @@
|
||||
|
||||
void ZoneFile::ReadHeader(std::istream& file) {
|
||||
BinaryIO::BinaryRead(file, fileFormatVersion);
|
||||
// Before PrePreAlpha a scene is only its ID, with no file to load it from (LuzReader::ReadScenes)
|
||||
if (fileFormatVersion < FileFormatVersion::PrePreAlpha) {
|
||||
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");
|
||||
}
|
||||
// The client reads anything older than 20 as 20 (LuzFile::ReadLUZFile)
|
||||
if (fileFormatVersion < FileFormatVersion::Oldest) fileFormatVersion = FileFormatVersion::Oldest;
|
||||
|
||||
if (fileFormatVersion >= FileFormatVersion::Alpha) BinaryIO::BinaryRead(file, mapRevision);
|
||||
|
||||
@@ -75,6 +73,14 @@ void ZoneFile::Read(std::istream& file) {
|
||||
void ZoneFile::ReadScene(std::istream& file) {
|
||||
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);
|
||||
|
||||
if (fileFormatVersion >= FileFormatVersion::LatePreAlpha) {
|
||||
@@ -98,6 +104,26 @@ void ZoneFile::ReadScene(std::istream& file) {
|
||||
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) {
|
||||
uint8_t count = 0;
|
||||
BinaryIO::BinaryRead(file, count);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -27,6 +29,7 @@ struct WaypointCommand {
|
||||
struct ZoneScene {
|
||||
std::string filename;
|
||||
uint32_t id{};
|
||||
uint32_t sceneTableID{}; // before PrePreAlpha: the scene's row in the CDClient SceneTable (see ResolveSceneTable)
|
||||
eSceneType sceneType{};
|
||||
std::string name;
|
||||
NiPoint3 unknown1;
|
||||
@@ -199,6 +202,7 @@ struct Path {
|
||||
|
||||
struct ZoneFile {
|
||||
enum class FileFormatVersion : uint32_t { //Times are guessed.
|
||||
Oldest = 20, // the client reads anything older as this
|
||||
PrePreAlpha = 30,
|
||||
PreAlpha = 32,
|
||||
LatePreAlpha = 33,
|
||||
@@ -243,6 +247,13 @@ struct ZoneFile {
|
||||
*/
|
||||
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:
|
||||
void ReadScene(std::istream& file);
|
||||
void ReadZoneBoundaries(std::istream& file);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "AssetManager.h"
|
||||
#include "CDClientManager.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
#include "CDClientDatabase.h"
|
||||
#include "Spawner.h"
|
||||
#include "dZoneManager.h"
|
||||
#include "dpWorld.h"
|
||||
@@ -56,6 +57,14 @@ void Zone::LoadZoneIntoMemory() {
|
||||
// The file itself is read by ZoneFile (dCommon), which the dashboard uses too
|
||||
ZoneFile zoneFile;
|
||||
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_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());
|
||||
|
||||
@@ -138,14 +138,33 @@ TEST(ZoneFileTests, LateAlphaSceneCountIsAU32) {
|
||||
EXPECT_EQ(zone.zoneRawPath, "zone.raw");
|
||||
}
|
||||
|
||||
// Before version 30 a scene is only an ID, with no file to load
|
||||
TEST(ZoneFileTests, VersionsBeforePrePreAlphaThrow) {
|
||||
// Before version 30 a scene is only its SceneTable ID; below 20 the file reads as 20
|
||||
TEST(ZoneFileTests, ReadsVersionsBeforePrePreAlpha) {
|
||||
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);
|
||||
ZoneFile zone;
|
||||
EXPECT_THROW(zone.Read(stream), std::runtime_error);
|
||||
EXPECT_TRUE(zone.scenes.empty());
|
||||
zone.Read(stream);
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user