The WoB top-level header sanity bounds catch obviously-bad totals, but
each group's vc/ic/tc was still unbounded. A corrupted group could
declare 4G vertices and OOM the resize before the next group even
started. Now per-group: vc<=1M, ic<=4M, tc<=1K, name<=1KB.
Per-bone-anim cap of 10K keyframes still let a malicious file allocate
up to 1024 anims × 512 bones × 10K keys = 5.24B keyframes — multi-GB
pre-OOM allocation. Now tracks total across the whole model and stops
allocating after 10M (real models stay well under 100K). When the cap
is hit we still seek past the remaining payload to keep file alignment
intact for whatever follows.
Header sanity bounds for WOC matching the WOM/WoB ones. A whole-tile
collision mesh maxes at ~32K terrain tris + a few thousand building
overlay tris; triCount > 2M is corrupted and would OOM. Tile coords
are 0..63 in WoW; clamp to 32 with warning when bogus.
Adds upfront sanity bounds to both WoB and WOM load:
WOM: vert<=1M, index<=4M, tex<=1K
WOB: groups<=4K, portals<=8K, doodads<=64K
Real WoW models stay well under these limits (M2 vert is uint16 anyway).
Without these checks a corrupted header could trigger a multi-GB
allocation and OOM the process before we finish reading the body. Also
caps name length to 1KB on WoB load (already done on WOM).
Three security/robustness guards on unpackZone:
1. fileCount > 1M or infoSize > 16MB rejected upfront — would OOM on
the next allocation.
2. Per-file dataSize > 256MB rejected — single malicious entry could
exhaust memory mid-extraction.
3. Path traversal ('..' or absolute paths) rejected — would write
outside destDir/<zoneName>/ and clobber system files.
WCPs are user-shareable archives, so a hostile pack downloaded from a
forum should not be able to OOM the editor or write to /etc.
Mirrors the WOM index-clamp + texture-path-length guards. Out-of-range
indices into the WMO group's vertex buffer would crash the GPU draw.
Texture path length over 1KB indicates a corrupted/truncated WoB; clamp
to 0 to prevent allocating 65KB-string buffers per bad entry.
Out-of-range indices were a silent vector overrun on the GPU side that
could crash the vertex shader on some drivers. Replace with 0 rather
than dropping so triangle counts stay aligned (a degenerate triangle is
harmless, an off-by-one indexing the wrong vertex is silent corruption).
Texture path length over 1KB is almost certainly a corrupted or
truncated file — was previously read into a 65KB-string allocation per
entry which could exhaust memory on a malicious file.
Same NaN scrub during fromM2 conversion that fromWMO got. Ensures a
corrupt source M2 (mangled MPQ block, partial extraction) doesn't
silently produce a NaN-laced WOM. Also feeds the cleaned positions
into boundMin/boundMax so the saved WOM bounds are clean too.
Sanitize at conversion time too, not just on WoB load. Avoids the
case where a corrupt source WMO (extracted from a partially-decoded
MPQ) silently poisons the WOB the editor exports — and the WOB
load-time guard from the previous commit only catches it on later
reload, not during the first conversion. Also catches the boundRadius
calculation which would otherwise inherit a NaN from one bad vertex.
The walkability classifier did glm::normalize(cross(...)) without
guarding for zero-length cross. A flat-on-itself triangle (e.g. all
three vertices at the same height in a hole-edge case) produces NaN
normal, NaN walkability flag, and crashes the downstream nz check.
Now the cross-length is computed once and the triangle is skipped if
it's effectively zero.
NaN vertices in collision triangles produce NaNs in ray-triangle
intersection (used by movement collision queries), making the player
phase through walls or fall through the floor. Degenerate triangles
(zero-area, collinear) similarly produce NaN normals. Now both are
sanitized/skipped on load and the count is reported in the log.
Bone interpolation returns NaN for any NaN input. A single bad keyframe
in any animation would corrupt the entire skeleton during playback —
even bones that weren't being keyed in that animation got NaN final
matrices via parent-chain multiplication. Also catches movingSpeed which
leaks into the engine's displacement maths.
Bones with NaN pivots produce broken skeleton matrices that ripple into
every child bone via the parent-chain multiplication. Out-of-range
parentBone indices would cause a use-after-free during bone-matrix
computation. Both now defensively clamped.
Same NaN guard as the just-applied WOM one. Sanitizes position/normal/
texCoord/color components after the bulk read. WMO renderer's matrix
math is sensitive — a single NaN position could desync the entire
group's draw state.
Even after the bound-field guards, individual vertex floats (position,
normal, texCoord) could still poison the GPU. NaN positions would crash
the M2 vertex shader on some drivers (silent device-lost). Now each
component defaults to 0 (or 1 for normal Z) when non-finite — vertex
ends up at origin instead of corrupting the whole pipeline.
WOM bound fields drive M2 culling and collision AABBs — non-finite
values would either cull the model out entirely or crash the cull math.
Now boundRadius defaults to 1.0 when invalid, and each boundMin/boundMax
component defaults to 0 when non-finite.
A WHM with non-finite height values would produce non-finite vertex
positions in the terrain mesh, breaking collision queries, pathing,
and the GPU's matrix math. Both the chunk base (one float per chunk)
and the 145 per-vertex heights are now individually validated.
NaN positions in waypoints would teleport the creature to chaos coords
mid-patrol. Cap wait time at 600000ms (10 min) — prevents obvious typos
(e.g. 24h = 86400000) from producing a creature that effectively never
moves.
Mirrors the recent scale guards. NaN/inf positions or rotations make
the M2 renderer's matrix math produce chaos-shaped instances or crash
during normal computation. Now both fields are reset to zero on load
when any component is non-finite.
Orientation values from edited JSON could be negative or wrap multiple
revolutions; now normalised once at load. Scale was already clamped
on small-positive but didn't reject NaN/inf — now rejects both with the
same defensive check object_placer just got.
Same defensive check the WoB doodad load just got — guards against
corrupted/partial-write JSON where scale ends up 0/NaN/inf. Without
this an invisible (scale=0) or crashed (NaN matrix) placement could
silently bork a loaded zone.
Without this guard, a corrupted or partially-written WoB with scale=0
would render the doodad at zero size (invisible) and a NaN/inf would
crash the renderer's matrix math. Now defaults to 1.0 for any non-
finite or near-zero value.
validateChains warnings were only going to the log file. Most users
don't tail the log while editing, so a broken chain (quest pointing at
a deleted nextQuestId, circular dependency) would only be discovered
when testing in-game. Now also shows a 5s toast with the issue count.
Quest links (questGiverNpcId, turnInNpcId, KillCreature targetName) all
key off CreatureSpawn.id, but loadFromFile always assigned new ids via
nextId(). So saving and reloading would silently break every quest hook
in the zone. Now JSON stores id, the loader reads it back when present
(legacy files fall back to nextId()), and idCounter_ is bumped past
loaded values to prevent future collisions. Same fix as the recent
PlacedObject.uniqueId one.
When a user picks a creature preset, faction stays 0 (server treats this
as 'use template') unless the user already typed a value. Now defaults
based on the preset category:
Critter -> 250 (critter, indifferent to all)
hostile preset -> 14 (monster, hostile to all)
friendly preset -> 35 (friendly to all)
Means picking 'Wolf' from the preset list immediately produces a hostile
NPC that actually attacks players in-game without further configuration.
Stationary and Patrol creatures should have wander_distance=0; only
Wander behaviour uses it. Previously the editor's wanderRadius template
default of 10.0 was being written for every spawn, making stationary
guards drift around in-game.
WHM: clarify chunkCount=256, vertsPerChunk=145 are fixed, baseHeight is
a float, heights are 145 floats. WOM: spell out boundRadius+min+max as
separate fields (was 'bounds(28)'), nameLen prefix, indices uint32, and
texPath length-prefixing. WOM2 trailing block reorganized into a single
list with bone+anim sequence shown as one block. Reading the spec now
yields a working parser without source-diving.
The previous WOB section was loose ('bounds(4)' was actually boundRadius;
group size annotations missed the prefixed string lengths). Updated to
show every byte: 4-byte field sizes, 12-byte vec3s, 2-byte length
prefixes, 48-byte interleaved vertices. Reverse-engineering a WOB from
the spec is now possible without reading the source.
Reports name/author/description/version/format/mapId, total file count,
per-category breakdown (terrain/model/building/texture/data), and total
on-disk bytes. Useful for inspecting third-party WCPs before importing
or for sanity-checking your own exports.
packZone stores files relative to the zone subdirectory (e.g. just
'MyZone_32_32.adt'), so unpacking to 'custom_zones/' produced files at
'custom_zones/MyZone_32_32.adt' — without the zone subdir the loader
expects. Now reads the info JSON to extract the zone name and unpacks
to 'custom_zones/<zoneName>/' so imported zones load correctly.
The CreatureSpawn struct default of 3.0 made every newly placed NPC
appear as an oversized 3x-scale creature, very obviously not what users
wanted. Existing JSON spawn files load their stored scale unchanged
(only impacts newly placed templates).
uniqueId was always regenerated on load, so re-saving the same zone
produced a different uniqueId per placement each time. Since
syncToTerrain copies obj.uniqueId into MDDF/MODF, the ADT also rotated
uniqueIds across cycles. Now JSON stores uniqueId, the loader reads it
back when present (falling back to nextUniqueId() for legacy files),
and uniqueIdCounter_ is bumped past any loaded value so future
placements never collide.
The respawnTimeMs field was loaded/saved/exported but never editable
through the UI. Added a DragFloat showing seconds (range 5-86400) in
both the template and the selected-NPC editors. SQL export already
divides by 1000 for AzerothCore's spawntimesecs column.
Last batch of stat fields missing from the selected-NPC editor. Now any
property a user could set on the template can also be edited on an
already-placed NPC, without removing and re-placing.
Adds Hostile/Quest/Vendor/Inn/Train/Bank/Auc/Repair/Flight checkboxes
to the selected-NPC editor matching the template editor's set. Lets
users toggle these on already-placed NPCs without removing and
re-placing them.
The CreatureSpawn struct has a faction field that was already exported
to creature_template.faction but wasn't editable. Added an InputInt with
a tooltip listing the common AzerothCore FactionTemplate IDs (Stormwind,
Monster, Beast, Friendly, Critter, etc.) so users can pick the right
hostility/disposition without referencing the DBC manually.
EditorViewport gains setActiveMapName() so rebuildObjects can pass
per-zone prefixes (output/<map>/models|buildings/, custom_zones/<map>/...)
to tryLoadByGamePath. EditorApp wires it from loadADT, loadWMOInstance,
and createNewTerrain. Now the editor's preview mirrors the main game's
priority: per-zone WOM/WOB beats global custom_zones/, beats game data.
Mirrors the WOM tryLoadByGamePath API: probes custom_zones/buildings/ +
output/buildings/ by default, with optional extraPrefixes (e.g. per-zone
output/<map>/buildings/) checked first. Both the editor and the main
game's terrain_manager now use the helper, removing duplicate inline
lookup loops in two more places.
The shared helper only probed custom_zones/models/ + output/models/, but
the editor's exportZone writes to output/<map>/models/. Added an
extraPrefixes parameter that's tried before the defaults — main game's
terrain_manager now passes ['output/<map>/models/', 'custom_zones/<map>/
models/'] so per-zone WOM exports override globals. Also removes the
last duplicate WOM-loading code from terrain_manager.
CreatureSpawn struct gains four AzerothCore-standard NPC flag bits:
trainer -> npcflag 0x10
repair -> npcflag 0x1000
banker -> npcflag 0x20000
auctioneer -> npcflag 0x200000
Saved/loaded via the JSON spawn file, exported to creature_template.npcflag,
exposed as checkboxes in the NPC template panel. Lets users build full
city NPCs (city auctioneer, weapon trainer, etc.) without dropping to SQL.
The editor's M2 placement path tries WOM (custom_zones/models/, output/
models/) before falling back to game M2 files. WMO placement just went
straight to game files. Now mirrors the M2 path: probes
custom_zones/buildings/ + output/buildings/ for a .wob, converts via
toWMOModel, falls back to MPQ-extracted WMO only on miss. Lets exported
zones render their custom buildings without needing the original WMO.
Mirrors the editor's WMO scale fix. WMOReady gains a scale field that
is computed from the loaded MODF placement.scale (u16 / 1024) and
forwarded to wmoRenderer->createInstance(). Without this the main
game ignored MODF scale even on WotLK ADTs that use it.
WMORenderer::createInstance accepts a scale parameter (default 1.0),
but the editor's call site ignored obj.scale. So a WMO sized to 2.0 in
the panel still rendered at 1.0. Now passes obj.scale through, so the
loaded MODF scale + any user gizmo scaling work end-to-end.
Helps users understand the search base — 'Pool: 1234 M2 56 WMO' tells
them why their filter might be matching too many results, and gives a
quick view into how much asset variety the loaded data has.