Repository navigation
Replies: 1 comment
Update — resolved on our sideWe eventually found that our original conclusion was too strong. The issue was not evidence of an unexplained client-side "memory" layer inside Wow.exe. Our custom-spell pipeline had multiple independent problems which were discovered later while rebuilding the spell chain from scratch. 1. Our DBC build chain was not always cumulativeAt least one generator was cloning from the original/raw DBC instead of the previously generated cumulative candidate. That meant a newly generated spell could exist in one artifact while being absent from the next stage of the pipeline. We later reproduced this very clearly with another custom spell:
The spell then learned and worked correctly in-game. 2. SkillRaceClassInfo was another independent blockerFor a later custom spell ("Grace persistante"), the remaining problem after deployment/relog was traced to After correcting that data and restarting the server, the spell behaved correctly across reconnects. 3. Fresh custom spells do work with the stock 3.3.5a clientWe subsequently shipped and validated other custom spells end-to-end. For example, custom spell
So there is no general rule that "fresh spell IDs introduced during the current session are rejected by Wow.exe". What I would change in the original diagnosisThe ProcMon observation was real: Wow.exe did open and read the tested archive. However, that did not prove that the entire generated client/server data chain was coherent. Our mistake was treating: "the client read the Spell.dbc containing this record" as equivalent to: "all client and server data needed for this spell were generated from the same cumulative source and are mutually consistent." They are not equivalent. The later fixes showed that our build pipeline could produce internally inconsistent generations across Spell.dbc, server SQL, SkillRaceClassInfo and successive MPQ builds. Therefore the statement that the failure must occur inside Wow.exe after the DBC read should be considered withdrawn. Current statusResolved in our project. The practical rule we now follow is:
Thanks to anyone who spent time reading the original investigation, and apologies for the misleading final conclusion. |
Uh oh!
There was an error while loading. Please reload this page.
TL;DR
On a private, local-only WotLK 3.3.5a server (AzerothCore + mod-playerbots), custom spell IDs added tonight are 100% known/active server-side but completely invisible client-side (
GetSpellInfo(id)returnsnil, absent from spellbook), with no error anywhere. Custom spell IDs that existed before tonight work perfectly. The strongest finding, established last: a full Process Monitor trace of the client from launch to the login screen proves aSpell.dbccontaining the broken spell's record (hash-verified) is successfully opened and read by the client process — andGetSpellInfo()still returnsnilfor that ID. We have ruled out every physical file reachable from the client install, every MPQ-loading/priority explanation, add-ons (by source review and by fully disabling them), a real tooling bug found and fixed along the way, and confirmed the client executable is an unmodified stock retail build. The rejection is happening after a provably successful file read, inside the client itself — past what black-box testing from outside the process can diagnose. We'd appreciate a fresh pair of eyes, ideally with client-internals or reverse-engineering experience.Environment
Wow.exeMD545892BDEDD0AD70AED4CCD22D9FB5984— confirmed to match the publicly documented stock retail checksum for 3.3.5a (12340), i.e. not a repack, not binary-patchedSpell.dbcrecord, overwrite specific fields, generate matching SQL (spell_dbc,skill_line_ability), package into MPQ patchesSymptom
For a custom spell (e.g. ID 81030, "Grace Everlasting" — a public spell with a
SPELL_EFFECT_DUMMYthat triggers two internal spells via C++CastSpell, same architecture as other spells in the same range that work):character_spell(specMask=255)Player::SendInitialSpells:State=0 Active=true specMask=255 IsInSpec=true, thenspell 81030 SENT in SMSG_INITIAL_SPELLS).cast 81030actually triggers the effect server-side.learn 81030replies "you already know this spell"GetSpellInfo(81030)→nil, nil, nil. Not found by enumerating the spellbook (GetSpellName(i, BOOKTYPE_SPELL)).Custom spells that predate tonight's session (e.g. 81006, 81020) work flawlessly, including sharing the exact same
SpellIconIDas some of the broken ones.The single most decisive test: a spell survives its own deletion
Late in the investigation, in the same client process, without relaunching:
GetSpellInfo(81040)(an ID that has never once resolved tonight) correctly returnsnil.GetSpellInfo(81006)(a spell that has always worked) still returns its full name/rank/icon — after we removed its record from the onlySpell.dbcthat contained it, cleared the client cache, and fully restarted the process.This means the currently-loaded
Spell.dbcis internally consistent (81040'snilis correct for that file), but something other than that file is still supplying 81006's data. Every subsequent test in this document was aimed at finding that "something."The final proof: a confirmed-successful read still gets rejected
The remaining open question after every file-location hypothesis was: is the client even reading the file we think it's reading? This was settled with Process Monitor (Sysinternals), tracing every file operation from
Wow.exelaunch to the login screen — not a snapshot of open handles, the full historical sequence ofCreateFile/ReadFile/CloseFilecalls.Along the way this surfaced a real, separate bug in our own pipeline: our locale patch chain (
patch-frFR-ZZ.MPQ,patch-frFR-ZZZ.MPQ) was never being opened by the client at all — a naming issue to fix independently, but not the cause of the core symptom.Once deployed instead as a simple single-letter patch (
patch-frFR-X.MPQ, following the convention the client actually expects), Procmon confirmed:GetSpellInfo(81030)still returnednilThis is the second independent confirmation of the same result (after
patch-frFR-4.MPQearlier in the session) that container-loading and file-priority are not the issue. The rejection happens inside the client's own processing, after a proven-successful read of the correct bytes — which is past the point where black-box, outside-the-process testing can diagnose further without attaching a debugger to the running client.The byte-identical clone test
Separately, and just as decisive on its own: we took the complete record of a working spell (81006), copied it byte-for-byte into a brand-new slot, and changed only the ID field — nothing else, not the name string, not the icon, not the effects, literally nothing. Deployed in an isolated, freshly-built MPQ archive.
Result: still invisible.
This proves the spell's content is entirely irrelevant to the bug. Whatever is wrong is tied exclusively to the ID / the moment it was introduced — not to any field inside the DBC record. This test was later rebuilt and rerun end-to-end after fixing a tooling bug (see "Tooling integrity" below), with an MD5-verified deployment and a single confirmed client process — same result.
The pattern
What's been ruled out (30 distinct, controlled tests)
Spell.dbc content & structure
SpellClassSet/SpellClassMask(inherited from clone, then reset to generic) — no effectSpellVisualID_1(was 0, corrected to a known-good value) — no effectskill_line_ability/SkillRaceClassInfo.dbc— structurally identicalrecord_countvs. actual file size) — verified exact via independent recountSpellIconID/ActiveIconIDreferencing an invalid row inSpellIcon.dbc— icon is valid (index well withinSpellIcon.dbc's 3226 rows), and shared by a working spell, which alone rules this outName_Lang_Mask(distinct fromAuraDescription_Lang_Mask, which was also checked) — identical value on both, correct locale bit set in bothMPQ container & patch chain
12. A separate, higher-priority patch (or locale-specific patch) silently overriding
Spell.dbc— everyData/**/patch*.MPQinventoried, none else containsSpell.dbc13. Patch naming convention (custom letter scheme
W/X/Y/Z/ZZ/ZZZvs. the community-documented numericpatch-N) — tested by physically removing all letter-named patches; the working spell still resolves through the numeric patch, the new spell in that same file still doesn't14. MPQ container-level corruption from ~6 incremental re-saves tonight (stale hash/block table entries) — rebuilt a completely fresh archive from scratch, isolated, no other
Spell.dbc-bearing archive present — same result15. Total record count acting as a hard cap — reproduced the exact record count that worked before (49852) by substituting a record instead of appending — still invisible
16. Spell ID value acting as a cap/threshold — tested a low ID (80000) well below the custom range — still invisible
Client-side persistent state
17.
Cache/WDB— folder entirely renamed/emptied — no effect18.
WTFfolder (account/realm/character config, distinct fromCache) — entirely renamed to force a fresh regeneration, full client restart, retested, restored afterward — no effect19. Stray/loose
Spell.dbcfile elsewhere in the client tree overriding the MPQ archives — recursive search of the entire client install directory — nothing found20. File modification/creation timestamp as the differentiator, tested in both directions with content held byte-identical (verified via MD5 at each step): a working spell's file re-dated to today still works; a broken spell's file re-dated to before the project started still fails — no effect either way
Third-party validation
21. Independent generation via the community-standard third-party tool stoneharry/WoW-Spell-Editor: reviewed its DBC-writing source directly (structurally equivalent to ours), then replicated its exact method (full rebuild with a fresh, deduplicated string block) on our own file — same result: the working spell resolves, the new one doesn't
Launch path
22. The custom launcher's "Update" action was found to silently revert our test deployments — identified and confirmed as a real, separate issue, now bypassed by always launching
Wow.exedirectly. All subsequent tests below were run this way.Every physical file on the client install
23.
Data\frFR\(entire folder) removed outright — a stock spell and the working custom spell (81006) still resolve, meaning frFR is not the source either24.
Data\enUS\— confirmed to contain the broken spell (81030), which nonetheless never resolves25. Loose/unpacked DBCs under
04_Client\dbc\— inspected directly, contain no custom content at all, cannot explain 81006 resolving26. Files actually held open by the running client process, observed directly via Windows Resource Monitor (handle list, not inferred from folder inventories) — nothing outside the expected, already-inspected archives
Add-ons
27. Static review of all ~15 loaded add-ons (WarLike, WarLike_GM, WarLike_CraftBank, and third-party): no reassignment of
GetSpellInfoor related globals, only harmless local captures28. Dynamic test: all add-ons fully disabled at runtime and 81006 deleted from the only file containing it —
GetSpellInfo(81006)still returns full dataTooling integrity
29. Found and fixed a real bug: MPQEditor invoked via PowerShell's
&operator was failing silently and intermittently, meaning some deployments earlier in the session may not have actually applied. Fixed by switching toStart-Process -Wait. The single most load-bearing test (the byte-identical clone, see above) was rebuilt and rerun with the fixed tooling, a single confirmed client process, and an MD5-verified deployment — same result (still invisible).Direct observation of file reads (not inference)
30. Full Process Monitor trace of the client from launch to login screen: uncovered a real, independent pipeline bug (our locale patch naming chain
patch-frFR-ZZ/ZZZ.MPQwas never actually opened by the client — fixed by switching to a simple single-letter patch name). Once corrected, the trace proved the client does open and read aSpell.dbccontaining the broken spell's hash-verified record — andGetSpellInfo()still returnsnilfor it. Second independent confirmation of this exact result, afterpatch-frFR-4.MPQearlier in the session.Where this leaves us
Every hypothesis reachable from outside the running process — file content, file structure, container, cache, saved state, timestamps, add-ons, tooling, and now the read operation itself, observed directly — has been tested and ruled out. The client provably opens the correct file, provably reads the correct bytes, and still returns
nilfor that spell ID while correctly resolving the same ID's absence for genuinely-nonexistent spells and correctly resolving IDs that predate tonight's session. Whatever decides this must live entirely in the client's in-memory handling of the record after the read — something we cannot observe without attaching a debugger to the live process, which is beyond what we can do here.What we're asking
Where in the client's internal processing of a freshly-read
Spell.dbcrecord — after the file read itself, which we've now proven succeeds — could a specific spell ID be silently dropped, while an ID that predates the current session (in some cases the exact same record content, only the ID differing) resolves correctly? We're specifically looking for anyone with insight into the in-memory data structures the client builds from Spell.dbc at load time (as opposed to the file format itself), or anyone willing to help us look at this with a debugger attached to a liveWow.exeprocess.All reactions