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:
Aaron Kimbrell
2026-09-29 05:10:07 -05:00
parent 00b62edfa0
commit d4fc595e2d
4 changed files with 75 additions and 10 deletions

View File

@@ -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