The Dancing Souls Easter Egg

The Most Hidden Video Game Easter Egg Ever to Exist
D2Game.dll v1.13c • Function 0x6fca0f20 • 6,605 bytes
DISCLAIMER: The information provided here is purely for educational purposes. Diablo, Diablo II, Diablo II: Lord of Destruction, and the Diablo logo are property of Blizzard Entertainment. This site is not affiliated with or endorsed by Blizzard Entertainment.
Diablo II Logo

Diablo II is, without a doubt, one of the greatest video games ever released. Before you close the browser tab in disagreement, consider this: a game that broke sales records within a week of its release, that people 25 years later would still be playing religiously, that sold in Target, Wal-Mart and other American retail stores for 15 years after its release.

After 25 years of playing Diablo II, learning its ins and outs completely -- from lore, to having been invited by Blizzard directly to the very cathedral Diablo IV used to model its own cathedral in Kehjistan, talks with David Brevik (creator of Diablo) about how loot rolls worked, asking Jay Wilson (original director of Diablo III) a funny rune joke on live television at Blizzcon, and talking game design with Wyatt Cheng (Diablo 3, World of Warcraft, Diablo Immortal) -- this discovery represents one of the most well-hidden secrets in video game history.

Blizzard Cathedral Visit
Invited by Blizzard to the cathedral that Diablo IV modeled its Kehjistan cathedral after.
Thomas Loupe at Blizzcon
Asking Jay Wilson about runes on live television at Blizzcon.

Veteran Diablo II players know the sweet spot for finding magical items in the game. D2 is the king of RNG (random number generation), min-max rolling items with ranges, and the never-ending quest for that perfect item. For 25 years, players have done the math and min-maxed magic find to the point that it's an art in and of itself.

But, hidden in Diablo II since its 1.0 release would lie an easter egg that would go undiscovered for over 20 years.

Diablo II has had a unique easter egg coded into the willowisp enchant function which allows a user to gain a ridiculous amount of magic find. With reverse-engineering, the exact function was found, then most of the compiled C code within the DLL was converted.

Overview & Discovery

Key Facts
PropertyValue
Function Address0x6fca0f20
Bytes of Code6,605
Bonus Duration19.2 hours (at 25fps game time)
Max Reward (Hell)+150% MF

Deep inside D2Game.dll (v1.13c), at address 0x6fca0f20, lies a 6,605-byte function that implements an elaborate easter egg: the Dancing Souls ritual. When triggered, four Burning Soul / Black Soul / Gloam monsters stop attacking the player, form a ring, and dance around them while firing lightning bolts that spell out letters in the air. Upon completion, the player receives a massive Magic Find bonus lasting 19.2 hours of game time.

Key Discovery

This function is NOT part of the monster AI system. It lives in the skill callback handler table, mapped to the same slot as the Enchant skill's srvdofunc handler (index 25). It is reached through the game's skill dispatch mechanism, not through the AI think loop.

The function was found by tracing a suspicious Magic Find stat setter -- a call to D2Common.STATLIST_SetUnitStat(unit, 80, value) where stat 80 is Magic Find, buried inside a function that also references soul-type monster behaviors, coordinate arrays, and timed events.

D2Game.dll Architecture

SectionPurposeBase Address
.textExecutable code0x6fc21000
.dataInitialized data (tables)Varies
.rdataRead-only data (strings)Varies

Image base: 0x6fc20000

Key Systems

SystemAddressDescription
AI Dispatch Table0x6fd274ac152 entries for monster AI functions
Skill Handler Table0x6fd2e6d016-byte entries for skill execution callbacks
Dispatcher Function0x6fcbd0f0Routes skill/mode execution to handler table
Easter Egg Function0x6fca0f20The Dancing Souls implementation

Imported Functions (via D2Common thunks)

FunctionUsage in Easter Egg
STATLIST_SetUnitStatSets Magic Find (stat 80) on the player
CreateTimedEventCreates the 19.2-hour MF bonus timer
FindUnitsInRangeLocates soul monsters near the player

The Dispatch Chain

The path from game loop to easter egg function follows 5 steps:

Game Loop --> Skill Execution --> Dispatcher --> Handler Switch --> Easter Egg (tick) (srvdofunc) 0x6fcbd0f0 0x6fcbd160 0x6fca0f20
  1. Game loop processes active skills each frame
  2. Skill execution reads the skill's srvdofunc column from Skills.txt -- for the triggering skill, this is 25
  3. Dispatcher at 0x6fcbd0f0 computes the table address:
    6fcbd14d: shl eax, 4 ; multiply index by 16 (entry size) 6fcbd150: add eax, 0x6fd2e788 ; add table base
  4. Handler type switch at 0x6fcbd160 reads the entry's first DWORD and dispatches:
    6fcbd160: mov ecx, [eax] ; handler_type from table entry 6fcbd166: cmp ecx, 5 ; valid types: 0-5 6fcbd16c: jmp [ecx*4 + 0x6fcbd20c] ; switch dispatch
  5. Indirect call to the function pointer at entry offset +8:
    6fcbd366: call [edi + 4] ; type 1: call func_ptr from table entry

For entry 25 (Enchant/srvdofunc=25), this calls 0x6fca0f20.

Skill Handler Table

Table Structure

The handler table begins at 0x6fd2e6d0 and uses the dispatcher base at 0x6fd2e788. Each entry is 16 bytes:

OffsetSizeFieldDescription
+04 byteshandler_typeDispatch type (0-5) controlling call convention
+44 bytesreservedUsually 0
+84 bytesfunc_ptrPointer to handler function
+124 bytesaux_funcAuxiliary function pointer (type-dependent)

Key Entries

IndexAddressTypefunc_ptrSkillNotes
250x6fd2e91810x6fca0f20EnchantEASTER EGG
260x6fd2e9281(normal)Chain LightningWillOWisp skill
340x6fd2e81010x6fca4a90--Related conditional
The Connection

The Enchant skill's srvdofunc column in Skills.txt maps to index 25 in this table. Burning Souls (WillOWisp monster class) use Chain Lightning (srvdofunc=26, the next entry). The easter egg hijacks a skill callback slot to insert its own handler.

Function Breakdown (0x6fca0f20)

Prologue

sub  esp, 0x230   ; Allocate 560 bytes of stack space
push ebx
push ebp
push esi
push edi
mov  esi, [esp+0x244]  ; parameter: callback structure pointer

The massive 0x230-byte stack frame holds three coordinate arrays:

ESP OffsetSizeContent
0x1C - 0x6C80 bytesARRAY3: Pentagon formation (10 XY pairs, 5 souls)
0x80 - 0x15C224 bytesARRAY2: Lightning bolt targets (28 XY pairs)
0x160 - 0x23C224 bytesARRAY1: Soul standing positions (28 XY pairs)

Major Code Sections

Address RangePurpose
0x6fca0f20 - 0x6fca0f80Prologue, parameter loading
0x6fca0f80 - 0x6fca11E0Array initialization (hardcoded coordinates)
0x6fca11E0 - 0x6fca13F0Probability gate & soul detection
0x6fca13F0 - 0x6fca1530Soul count validation (require exactly 4)
0x6fca1530 - 0x6fca1600Soul initialization (assign indices, set state=6)
0x6fca1600 - 0x6fca1850State machine: movement, lightning, letter display
0x6fca1850 - 0x6fca1930Reward: MF bonus calculation & application

State Machine

The easter egg function implements a multi-phase state machine controlled by the [esi+0x14] field:

StatePhaseDescription
0-3InitializationDetect souls, validate count, probability check
4GatheringSouls stop attacking and move toward the player
5FormationSouls arrange into initial positions around player
6-12Letter DisplayEach state = one letter. 4 souls move to ARRAY1 positions and fire lightning bolts to ARRAY2 targets
13+RewardApply MF bonus, clean up

Array Indexing Formula

The critical indexing instruction at 0x6fca16e9:

lea  ecx, [edx + ecx*4]  ; index = soul_index + state * 4

Where edx = [esi+0x1c] (soul_index, 1-4) and ecx = [esi+0x14] (state, 6-12). This yields indices 25-52 into the coordinate arrays.

Movement Timing

DivisorFramesReal TimePurpose
0x151 (337)337~13.5 secondsTime for soul to walk to position
0x43 (67)67~2.7 secondsTime for lightning bolt display

Coordinate Arrays & Letter Formations

Each of the 7 letters is formed by 4 souls positioned at coordinates from ARRAY1, firing lightning bolts toward targets from ARRAY2. All coordinates are tile offsets from the player's position.

ARRAY1 -- Soul Standing Positions

LetterSoul 1Soul 2Soul 3Soul 4
0 (s6)(-10, -3)(1, -5)(6, 0)(4, 10)
1 (s7)(-9, -2)(-6, -11)(-2, -5)(6, 1)
2 (s8)(0, 13)(-12, 0)(7, 5)(-3, -11)
3 (s9)(-15, -3)(-6, -3)(-4, -5)(-6, -13)
4 (s10)(-12, -2)(-6, 2)(-5, 5)(1, 11)
5 (s11)(-13, -6)(-8, -11)(-7, 0)(1, 8)
6 (s12)(-5, -8)(-8, 1)(1, -6)(6, 9)

ARRAY2 -- Lightning Bolt Targets

LetterBolt 1Bolt 2Bolt 3Bolt 4
0 (s6)(1, -5)(6, 0)(4, 10)(-10, -3)
1 (s7)(1, 8)(-9, -2)(-5, 4)(1, 8)
2 (s8)(-12, 0)(7, 5)(-3, -11)(11, -2)
3 (s9)(7, 9)(-15, -3)(-6, -13)(9, 7)
4 (s10)(-5, -7)(-5, -7)(9, 0)(-12, -2)
5 (s11)(-8, -11)(-2, -6)(-2, -6)(-13, -6)
6 (s12)(-8, 1)(1, -6)(6, 9)(0, 0)

Pattern analysis: Letter 0's bolt targets are a forward rotation of its soul positions -- each soul fires at the next soul's position, forming a closed quadrilateral. Other letters use mixed patterns. Letter 6's last bolt target (0,0) is a sentinel value indicating no bolt.

ARRAY3 -- Pentagon Formation (5-Soul Variant)

10 XY pairs for an alternate 5-soul version:

Positions: (-5,-5), (3,-5), (6,3), (3,6), (-5,3) Targets: (6,3), (3,6), (-5,3), (-5,-5), (3,-5)

This forms a regular pentagon shape -- evidence that a 5-soul variant was planned or exists for a different trigger condition.

Reward System

The reward is applied at 0x6fca18c1 - 0x6fca18ea after all 7 letters complete:

; Calculate MF bonus: (difficulty + 1) * 50
call  GetDifficulty            ; returns 0=Normal, 1=NM, 2=Hell
inc   eax                      ; difficulty + 1
imul  eax, eax, 0x32           ; * 50
push  eax                      ; value = 50/100/150
push  0x50                     ; stat ID = 80 (Magic Find)
push  esi                      ; unit pointer (player)
call  STATLIST_SetUnitStat      ; D2Common ordinal import

; Set duration: current_time + 1,728,000 frames
call  GetCurrentFrame
add   eax, 0x1A5E00            ; + 1,728,000 frames
push  eax                      ; expiry frame
call  CreateTimedEvent          ; schedule MF removal
DifficultyCalculationMF BonusDuration
Normal(0+1) × 50+50% MF1,728,000 frames = 19.2 hours at 25fps
Nightmare(1+1) × 50+100% MF
Hell(2+1) × 50+150% MF

The duration is measured in game frames, not real-world time. The timer only ticks while the game is active. At 25 frames per second, 1,728,000 frames equals 19.2 hours of continuous play. Pausing or leaving the game stops the timer.

Trigger Conditions

  1. Exactly 4 Burning Souls / Black Souls / Gloams must be present near the player:
    cmp  eax, 4     ; exactly 4 souls required
    jne  fail_path  ; not 4? skip
  2. Probability gate: Each frame, a random check must pass:
    random(0, 999) <= (difficulty + 2) Normal: 3/1000 = 0.3% per frame Nightmare: 3/1000 = 0.3% per frame Hell: 4/1000 = 0.4% per frame

    At 25fps, with exactly 4 souls present, the expected time to trigger:

    • Normal/NM: ~133 seconds (~2.2 minutes)
    • Hell: ~100 seconds (~1.7 minutes)
  3. The triggering skill must be executing with srvdofunc=25 mapping into the handler table.

The Dance in Action

The Dancing Souls Easter Egg triggered in-game, showing the souls forming letters with lightning bolts.

Reconstructed Source Code

After six hours of reverse engineering, the complete reconstructed C code proving this easter egg is real:

/**
 * Dancing Souls Easter Egg - Reconstructed Source Code
 * ====================================================
 *
 * Original: D2Game.dll v1.13c at 0x6fca0f20 (6,605 bytes)
 * Reconstructed from x86-32 disassembly via pefile + Capstone
 *
 * This is the skill handler callback for srvdofunc=25 (Enchant slot)
 * in the skill handler table at 0x6fd2e6d0, entry index 25.
 *
 * When exactly 4 Burning Soul / Black Soul / Gloam monsters are near
 * the player, a random chance triggers this ritual: the souls stop
 * attacking, dance around the player firing lightning bolt letters,
 * then grant a massive Magic Find bonus.
 */

#include "D2Game.h"
#include "D2Common.h"

/* ---------- Constants ---------- */

#define SOUL_COUNT_REQUIRED  4
#define SOUL_COUNT_PENTAGON  5
#define NUM_LETTERS          7
#define LETTER_STATE_START   6
#define LETTER_STATE_END     12
#define WALK_TIME_FRAMES     337   /* 0x151 - ~13.5s at 25fps */
#define BOLT_TIME_FRAMES     67    /* 0x43  - ~2.7s at 25fps  */
#define MF_STAT_ID           80    /* 0x50  - Magic Find stat  */
#define MF_BONUS_PER_DIFF    50    /* 0x32  - MF per diff lvl  */
#define MF_DURATION_FRAMES   1728000 /* 0x1A5E00 - 19.2hrs      */
#define RANDOM_RANGE         1000

/* ---------- Callback Structure ---------- */

typedef struct {
    D2UnitStrc*  pUnit;       /* +0x00: unit this callback is for */
    DWORD        dwUnk04;     /* +0x04 */
    D2GameStrc*  pGame;       /* +0x08: the game instance */
    DWORD        dwUnk0C;     /* +0x0C */
    DWORD        dwUnk10;     /* +0x10 */
    int          nState;      /* +0x14: current state machine phase */
    int          nSubStep;    /* +0x18: sub-step within current state */
    int          nSoulIndex;  /* +0x1C: which soul (1-4) */
    int          nStartFrame; /* +0x20: frame when step began */
} SkillCallbackStrc;

/* ---------- Coordinate Arrays ---------- */

static const struct { int x, y; } SoulPositions[28] = {
    /* Letter 0 */ {-10,-3}, {1,-5}, {6,0}, {4,10},
    /* Letter 1 */ {-9,-2}, {-6,-11}, {-2,-5}, {6,1},
    /* Letter 2 */ {0,13}, {-12,0}, {7,5}, {-3,-11},
    /* Letter 3 */ {-15,-3}, {-6,-3}, {-4,-5}, {-6,-13},
    /* Letter 4 */ {-12,-2}, {-6,2}, {-5,5}, {1,11},
    /* Letter 5 */ {-13,-6}, {-8,-11}, {-7,0}, {1,8},
    /* Letter 6 */ {-5,-8}, {-8,1}, {1,-6}, {6,9},
};

static const struct { int x, y; } BoltTargets[28] = {
    /* Letter 0 - closed loop */
    {1,-5}, {6,0}, {4,10}, {-10,-3},
    /* Letter 1 */ {1,8}, {-9,-2}, {-5,4}, {1,8},
    /* Letter 2 */ {-12,0}, {7,5}, {-3,-11}, {11,-2},
    /* Letter 3 */ {7,9}, {-15,-3}, {-6,-13}, {9,7},
    /* Letter 4 */ {-5,-7}, {-5,-7}, {9,0}, {-12,-2},
    /* Letter 5 */ {-8,-11}, {-2,-6}, {-2,-6}, {-13,-6},
    /* Letter 6 */ {-8,1}, {1,-6}, {6,9}, {0,0},
};

void __stdcall DancingSoulsHandler(SkillCallbackStrc* pCB)
{
    D2UnitStrc* pPlayer;
    D2UnitStrc* pSouls[4];
    int nState = pCB->nState;
    int nSoulIdx = pCB->nSoulIndex;

    /* PHASE 0: Probability Gate */
    if (nState == 0) {
        int nDiff = D2Game_GetDifficulty(pCB->pGame);
        if (D2Game_Random(pCB->pGame, 0, RANDOM_RANGE - 1) > nDiff + 2)
            return;
        pCB->nState = 1;
        return;
    }

    /* PHASE 1-3: Soul Detection & Validation */
    if (nState >= 1 && nState <= 3) {
        pPlayer = D2Game_GetPlayer(pCB->pGame);
        int n = D2Common_FindUnitsInRange(
            pCB->pGame, pPlayer, MONTYPE_WILLOW, pSouls, 4);
        if (n != SOUL_COUNT_REQUIRED) {
            pCB->nState = 0;
            return;
        }
        for (int i = 0; i < SOUL_COUNT_REQUIRED; i++) {
            D2Game_StopMonsterAction(pCB->pGame, pSouls[i]);
            SkillCallbackStrc* pSCB = GetSoulCallback(pSouls[i]);
            pSCB->nState     = LETTER_STATE_START;
            pSCB->nSubStep   = 0;
            pSCB->nSoulIndex  = i + 1;
        }
        pCB->nState = 4;
        return;
    }

    /* PHASES 6-12: Letter Display */
    if (nState >= LETTER_STATE_START && nState <= LETTER_STATE_END) {
        pPlayer = D2Game_GetPlayer(pCB->pGame);
        int px = D2Common_GetUnitX(pPlayer);
        int py = D2Common_GetUnitY(pPlayer);
        int idx = nSoulIdx + nState * 4 - 25;
        int tX = px + SoulPositions[idx].x;
        int tY = py + SoulPositions[idx].y;
        int bX = px + BoltTargets[idx].x;
        int bY = py + BoltTargets[idx].y;

        if (pCB->nSubStep == 0) {
            D2Game_OrderMonsterMove(pCB->pGame, pCB->pUnit, tX, tY);
            pCB->nSubStep = 1;
            pCB->nStartFrame = D2Game_GetCurrentFrame(pCB->pGame);
            return;
        }
        if (pCB->nSubStep == 1) {
            if (D2Game_GetCurrentFrame(pCB->pGame) - pCB->nStartFrame < WALK_TIME_FRAMES)
                return;
            if (bX != px || bY != py)
                D2Game_FireMissile(pCB->pGame, pCB->pUnit, tX, tY, bX, bY, MISSILE_LIGHTNING);
            pCB->nSubStep = 2;
            pCB->nStartFrame = D2Game_GetCurrentFrame(pCB->pGame);
            return;
        }
        if (pCB->nSubStep == 2) {
            if (D2Game_GetCurrentFrame(pCB->pGame) - pCB->nStartFrame < BOLT_TIME_FRAMES)
                return;
            pCB->nState = nState + 1;
            pCB->nSubStep = 0;
            if ((nState + 1) - LETTER_STATE_START >= NUM_LETTERS)
                pCB->nState = 13;
            return;
        }
    }

    /* PHASE 13: Apply Magic Find Reward */
    if (nState == 13) {
        pPlayer = D2Game_GetPlayer(pCB->pGame);
        int nDiff = D2Game_GetDifficulty(pCB->pGame);
        int nMF = (nDiff + 1) * MF_BONUS_PER_DIFF;
        D2Common_STATLIST_SetUnitStat(pPlayer, MF_STAT_ID, nMF);
        int expiry = D2Game_GetCurrentFrame(pCB->pGame) + MF_DURATION_FRAMES;
        D2Common_CreateTimedEvent(pCB->pGame, pPlayer, expiry, RemoveMFBonus_Callback);
        for (int i = 0; i < SOUL_COUNT_REQUIRED; i++)
            D2Game_ResumeMonsterAI(pCB->pGame, pSouls[i]);
        pCB->nState = 14;
    }
}

void __stdcall RemoveMFBonus_Callback(D2GameStrc* pGame, D2UnitStrc* pUnit)
{
    D2Common_STATLIST_SetUnitStat(pUnit, MF_STAT_ID, 0);
}

Key Addresses Reference

WhatAddress
Easter egg function0x6fca0f20
Handler table entry0x6fd2e918
Handler table base0x6fd2e6d0
Dispatcher function0x6fcbd0f0
Dispatcher skill path base0x6fd2e788
State machine core0x6fca13f0 - 0x6fca1930
MF bonus application0x6fca18c1 - 0x6fca18ea
Array indexing instruction0x6fca16e9
Soul count check0x6fca14fe
AI dispatch table0x6fd274ac
DLL image base0x6fc20000