From 2cd69d677ac8d221219f3bc6fe430622a91baa01 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 09:10:14 -0700 Subject: [PATCH] fix(wob): cap group/portal/doodad header counts on save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WoB load enforces 4096 groups / 8192 portals / 65536 doodads. Save previously wrote raw size() and iterated all entries — a build exceeding any cap would be rejected wholesale on round-trip. Cap each count at the load limit and use indexed loops so the written body matches the header count even if the in-memory data goes over. --- src/pipeline/wowee_building.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/pipeline/wowee_building.cpp b/src/pipeline/wowee_building.cpp index 6cd00b29..f9c194a3 100644 --- a/src/pipeline/wowee_building.cpp +++ b/src/pipeline/wowee_building.cpp @@ -210,9 +210,12 @@ bool WoweeBuildingLoader::save(const WoweeBuilding& bld, const std::string& base if (!f) return false; f.write(reinterpret_cast(&WOB_MAGIC), 4); - uint32_t gc = static_cast(bld.groups.size()); - uint32_t pc = static_cast(bld.portals.size()); - uint32_t dc = static_cast(bld.doodads.size()); + // Cap header counts at the load-side limits so a pathological build + // can't write a file the loader rejects whole. Same caps the load + // bounds-check uses (4096 groups / 8192 portals / 65536 doodads). + uint32_t gc = static_cast(std::min(bld.groups.size(), 4096)); + uint32_t pc = static_cast(std::min(bld.portals.size(), 8192)); + uint32_t dc = static_cast(std::min(bld.doodads.size(), 65536)); f.write(reinterpret_cast(&gc), 4); f.write(reinterpret_cast(&pc), 4); f.write(reinterpret_cast(&dc), 4); @@ -232,7 +235,9 @@ bool WoweeBuildingLoader::save(const WoweeBuilding& bld, const std::string& base }; writeStr(bld.name); - for (const auto& grp : bld.groups) { + // Iterate using the capped counts so the body matches the header. + for (uint32_t gi = 0; gi < gc; gi++) { + const auto& grp = bld.groups[gi]; writeStr(grp.name); uint32_t vc = static_cast(grp.vertices.size()); @@ -294,7 +299,8 @@ bool WoweeBuildingLoader::save(const WoweeBuilding& bld, const std::string& base } } - for (const auto& portal : bld.portals) { + for (uint32_t pi = 0; pi < pc; pi++) { + const auto& portal = bld.portals[pi]; f.write(reinterpret_cast(&portal.groupA), 4); f.write(reinterpret_cast(&portal.groupB), 4); uint32_t pvCount = static_cast(portal.vertices.size()); @@ -311,7 +317,8 @@ bool WoweeBuildingLoader::save(const WoweeBuilding& bld, const std::string& base f.write(reinterpret_cast(sanPortal.data()), pvCount * 12); } - for (const auto& dp : bld.doodads) { + for (uint32_t di = 0; di < dc; di++) { + const auto& dp = bld.doodads[di]; writeStr(dp.modelPath); glm::vec3 pos = dp.position, rot = dp.rotation; for (int k = 0; k < 3; k++) {