Skip to content

LoadSkillTemplate crashes Gw.exe (ArenaNet attribState assert) -- SetAttributes calls g_load_attributes_func with no target-state guard #4

Description

@Royel-Payne

issue-04-attribstate-crashdumps.zip

Summary

SkillBar.LoadSkillTemplate() deterministically crashes the Guild Wars client. The client dies on ArenaNet's own assertion, not an access violation:

Assertion: attribState
P:\Code\Gw\Char\Cli\ChCliAttrib.cpp(212)
exception_code: 0x80000003   (breakpoint / assert)
Build: 38734

This is not timing-dependent, not framework-dependent, and not related to the template's validity. The bundled Nightfall Leveler hits it at the "Equip Skill Bar" step inside Craft_First_Weapon (right after the first attribute-point quest reward grants new skills), but the leveler is incidental -- a bare call reproduces it on its own.

Minimal reproduction (100%, five lines)

from Py4GWCoreLib import *

def main():
    GLOBAL_CACHE.SkillBar.LoadSkillTemplate('OQBDAhITAoohAAAAAAAA')
    ActionQueueManager().ProcessQueue("ACTION")

Run on a level-2 Mesmer standing idle in an outpost (Chahbek Village). No bot, no FSM, no other scripts. Client aborts immediately.

The Python stack contains nothing but this script:

#00 ActionQueue.py:45  in ActionQueue.execute_next
#01 ActionQueue.py:133 in ActionQueueNode.execute_next
#02 ActionQueue.py:171 in ActionQueueNode.ProcessQueue
#03 ActionQueue.py:255 in ActionQueueManager.ProcessQueue
#04 <string>:36 in main

Root cause (source-traced)

All paths below are in the Py4GW_Reforged_Native repo (not this one) -- src/GW/skillbar/skillbar_methods.cpp:

LoadSkillTemplate(temp)                                   // :308
    if (map::GetInstanceType() != Outpost) return false;     // guard - ok
    DecodeSkillTemplate(...)                                 // succeeds
    profession_state = GetAgentProfessionState(agent_id)     // guard - ok, :322
    if (!profession_state) return false;
    LoadSkillbar(skill_template.skills, ...)                 // ok, :346
    SetAttributes(skill_template.attributes, ...)            // crash site, :347

SetAttributes(...)                                        // :407
    if (!g_load_attributes_func) return false;               // only guard: pointer non-null
    g_load_attributes_func(agent_id, count, ids, values);    // raw call - no target-state check

g_load_attributes_func is a pattern-resolved raw function pointer into ArenaNet's client code. It is checked for existence only. Nothing validates that the attribute state is actually ready to accept a write.

Naming confirmation -- Py4GW_Reforged_Native/include/GW/context/world.h:230:

GW::GWArray<ProfessionState> party_profession_states; // ... used in skill window. aka attribStates

The struct is literally annotated attribStates, matching the assertion text. And note that GetAgentProfessionState() -- the guard the function does have at :322 -- only confirms an entry exists in that array. That is a different precondition from "the client's attribute-loading internals are ready for a write," and only the former is checked.

Relevant context: PySkillbar.Skillbar was migrated from a legacy ctypes exposure to a full native pybind class on 2026-07-04 (Py4GW_Reforged_Native/docs/python-migration-progress.md:329), so this is recently-wired code rather than a long-exercised path.

The design observation

LoadSkillTemplate returns bool. It guards the instance type and returns false. It guards the profession state and returns false. SetAttributes also returns bool and returns false on a null pointer.

Every precondition in this function has a guard and a clean failure path -- except the attribute write, which aborts the process instead of returning false. The failure channel already exists in the signature; it simply isn't wired to this case.

Eliminated, each by a direct test

Suspect How it was ruled out
Timing / race Crash is byte-identical across a 2000 ms delay; the bare repro has no delay at all. 5+ runs, zero variance.
Botting framework / FSM Not present in the repro.
Nightfall Leveler Not present in the repro.
Attribute affordability Character has 5 unspent points; template needs 4.
Uninitialised attribute state Manually spent a point via the in-game panel first -- crashes identically.
Malformed template Decodes cleanly (Fast Casting 1 / Domination 2 / Inspiration 1; Ether Feast, Empathy, Resurrection Signet). Valid data.

Same structural shape as issue #3

Issue Raw function pointer Guard Failure
#3 g_get_graphics_renderer_value_func null-check only native AV (0xC0000005)
this issue g_load_attributes_func null-check only ArenaNet assert (0x80000003)

Both are pattern-resolved pointers into client code, checked for existence but not for target-state readiness. #3 faulted silently; here the game caught it itself.

What we could not determine

We have not identified the specific internal precondition g_load_attributes_func requires. That lives in ArenaNet's client code and is outside what we can read. This report establishes where the guard is missing and that the failure is deterministic -- not the exact runtime condition that would satisfy the game's assert. That likely needs your knowledge of the client internals.

Environment

  • Guild Wars Reforged, build 38734
  • Py4GW_Reforged, current main; Python 3.13.0 32-bit
  • Crash dumps attached: py4gw-20260713-1651-51840-23068 (bare-call repro) and py4gw-20260713-1659-5140-48728 (same repro, after manually spending an attribute point first -- identical crash)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions