Commit graph

3656 commits

Author SHA1 Message Date
Kelsi
93edf1bd55 fix(wob): per-group count sanity bounds + group name length cap
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.
2026-05-06 05:49:15 -07:00
Kelsi
cb7f11f2ea fix(wom): cap total keyframes at 10M to prevent OOM on hostile model
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.
2026-05-06 05:46:55 -07:00
Kelsi
a7d62d1af9 fix(woc): reject corrupted triCount + clamp out-of-range tile coords
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.
2026-05-06 05:44:37 -07:00
Kelsi
90289ba48b fix(wob+wom): reject corrupted header counts before allocating
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).
2026-05-06 05:42:50 -07:00
Kelsi
c05d421c29 fix(content-pack): reject malicious WCP headers (oversize counts + path traversal)
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.
2026-05-06 05:38:53 -07:00
Kelsi
53780de6da fix(wob): clamp out-of-range group indices + reject absurd texture path lengths
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.
2026-05-06 05:36:20 -07:00
Kelsi
a2eaf3965a fix(wom): clamp out-of-range indices + reject absurd texture path lengths
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.
2026-05-06 05:34:41 -07:00
Kelsi
fd4354c17d fix(wom): fromM2 sanitizes vertex floats during conversion (matches WOB)
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.
2026-05-06 05:32:47 -07:00
Kelsi
7acde76025 fix(wob): fromWMO sanitizes vertex floats during conversion
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.
2026-05-06 05:29:30 -07:00
Kelsi
2b5f69187e fix(woc): fromTerrain skips degenerate triangles before normalize
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.
2026-05-06 05:26:46 -07:00
Kelsi
70bbed4222 fix(woc): scrub NaN triangle vertices + skip degenerate triangles on load
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.
2026-05-06 05:24:46 -07:00
Kelsi
a3fb267e0b fix(wom): sanitize per-keyframe translation/rotation/scale + movingSpeed NaN
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.
2026-05-06 05:22:32 -07:00
Kelsi
f0abd1794b fix(wom): sanitize bone pivot NaN + clamp out-of-range parentBone
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.
2026-05-06 05:19:24 -07:00
Kelsi
15648e21ec fix(wob): per-vertex NaN/inf scrub on load (matches WOM)
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.
2026-05-06 05:16:16 -07:00
Kelsi
29ae850307 fix(wom): per-vertex NaN/inf scrub on load
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.
2026-05-06 05:14:35 -07:00
Kelsi
1467417b11 fix(wom): sanitize boundRadius / boundMin / boundMax against NaN/inf on load
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.
2026-05-06 05:12:53 -07:00
Kelsi
de983c2728 fix(whm): replace NaN/inf chunk base + per-vertex heights with 0.0 on load
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.
2026-05-06 05:10:04 -07:00
Kelsi
2b02ca6b58 fix(editor): patrol waypoint NaN guard + 10-minute wait-time cap
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.
2026-05-06 05:05:39 -07:00
Kelsi
604d29d375 fix(editor): NPC + object position/rotation NaN guards on JSON load
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.
2026-05-06 05:04:25 -07:00
Kelsi
d525318e9c fix(editor): normalise NPC orientation to [0,360) and guard scale against NaN
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.
2026-05-06 05:02:24 -07:00
Kelsi
1979d921a7 fix(editor): clamp PlacedObject scale to 1.0 when JSON value is invalid
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.
2026-05-06 04:59:28 -07:00
Kelsi
199f18cdac fix(wob): clamp doodad scale to 1.0 when loaded value is 0/NaN/inf
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.
2026-05-06 04:57:09 -07:00
Kelsi
70e48640d8 feat(editor): surface quest-chain validation issues to user via toast
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.
2026-05-06 04:54:51 -07:00
Kelsi
df1eed1c42 fix(editor): preserve CreatureSpawn.id across JSON save/load
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.
2026-05-06 04:53:09 -07:00
Kelsi
00717543a8 feat(editor): preset selection auto-fills sensible AzerothCore faction
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.
2026-05-06 04:48:30 -07:00
Kelsi
70366dc5f6 fix(sql): wander_distance is 0 for non-Wander NPCs
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.
2026-05-06 04:46:57 -07:00
Kelsi
2d41e560f2 docs(format-spec): add quick-reference table mapping formats to replaced Blizzard formats 2026-05-06 04:44:24 -07:00
Kelsi
b1c89823d4 docs(format-spec): tighten WHM + WOM layouts with exact field sizes
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.
2026-05-06 04:42:04 -07:00
Kelsi
15630f7723 docs(format-spec): tighten WOB layout description with exact field sizes/types
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.
2026-05-06 04:38:15 -07:00
Kelsi
079ff5bfb5 feat(editor): --info-wcp CLI prints content pack metadata
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.
2026-05-06 04:36:40 -07:00
Kelsi
8d78b5f8c6 fix(content-pack): unpackZone now creates the zone subdirectory
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.
2026-05-06 04:32:28 -07:00
Kelsi
a0e363f706 feat(editor): WCP export toast reports file size in MB 2026-05-06 04:30:13 -07:00
Kelsi
c0ae924fc7 fix(editor): NPC default scale 1.0 (was 3.0) to match AzerothCore defaults
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).
2026-05-06 04:27:22 -07:00
Kelsi
4d11949048 fix(editor): preserve PlacedObject uniqueId across JSON save/load
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.
2026-05-06 04:23:39 -07:00
Kelsi
882321863a feat(editor): NPC template + selected-NPC editor expose Respawn (s) input
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.
2026-05-06 04:21:42 -07:00
Kelsi
8d006b6b86 feat(editor): selected-NPC editor gains Mana / Min Dmg / Max Dmg / Armor inputs
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.
2026-05-06 04:18:25 -07:00
Kelsi
a7ab2756d6 feat(editor): selected-NPC editor gains role flag checkboxes
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.
2026-05-06 04:17:31 -07:00
Kelsi
9625201952 feat(editor): selected-NPC editor gains Faction input (parity with template) 2026-05-06 04:15:39 -07:00
Kelsi
4e01dd5553 feat(editor): NPC template gains Faction ID input with common-value tooltip
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.
2026-05-06 04:14:33 -07:00
Kelsi
da2e7a4133 feat(editor): viewport WOM/WOB lookups also probe per-zone roots
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.
2026-05-06 04:13:03 -07:00
Kelsi
db068d480b feat(wob): tryLoadByGamePath helper, used by editor + terrain_manager
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.
2026-05-06 04:10:12 -07:00
Kelsi
f36309a96f feat(wom): tryLoadByGamePath accepts extraPrefixes for per-zone search roots
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.
2026-05-06 04:07:16 -07:00
Kelsi
99aaab3aa8 feat(editor): add Trainer/Banker/Auctioneer/Repair NPC flags + SQL export
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.
2026-05-06 04:03:23 -07:00
Kelsi
bc6e60c6e9 polish(editor): placement scale slider matches selected-object range (0.1-50) 2026-05-06 04:00:07 -07:00
Kelsi
a156b6246e polish(editor): NPC selected-editor Facing slider shows 'deg' unit (matches template) 2026-05-06 03:59:10 -07:00
Kelsi
597c6547ac feat(editor): WMO objects also try WOB open format first like M2->WOM does
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.
2026-05-06 03:56:52 -07:00
Kelsi
1d05bb0f13 fix(terrain): main game also propagates MODF scale to WMO instances
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.
2026-05-06 03:55:14 -07:00
Kelsi
cbf1d4638f fix(editor): WMO instance scale actually applied to renderer
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.
2026-05-06 03:52:40 -07:00
Kelsi
32ff80f177 feat(editor): texture panel shows total pool count + dir count 2026-05-06 03:48:37 -07:00
Kelsi
f4805b8e69 feat(editor): object panel shows total pool count for M2/WMO assets
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.
2026-05-06 03:47:54 -07:00