# whist's 1.13c code-edit compilation — verified index **Source:** `E:\Diablo 2 - Esoteric Path\Diablo II - Resources\d2 catchall\whist 1.13c` — 97 `.asm` patch-note files, written before 2017 by whist (= kidpaddle94 on d2mods.info; D2MOO contributor, credited in BaseMod's readme, maintainer of `github.com/whistmxl/D2Template`). **Measured**, 2026-08-02, against: | role | path | |---|---| | LIVE — Doug's modified install | `C:\Diablo II\*.dll` (read-only; game never launched) | | STOCK — pristine 1.13c | `E:\Diablo 2 - Esoteric Path\Diablo II - Resources\VersionChanger\LoD\1.13c` | Method: PE-parse both copies, read the exact bytes at every address whist names, disassemble the neighbourhood with capstone, compare live ↔ stock ↔ note. `FileAlignment == SectionAlignment == 0x1000` on all seven modules, so **RVA == file offset** throughout this document. > **Full per-entry evidence** — every byte run, every disassembly listing — is preserved beside this > file as `WHIST-113c-COMPILATION-RAW-EVIDENCE.md`. This document is the usable distillation. --- ## 1. THE HEADLINE TALLY 97 files → **101 indexed entries**. Four files carry two independent edits and are split: `AncientsXPreward`, `CharSelectColors`, `CorpseMax`, `ExperienceBarColors`. | verdict | entries | meaning | |---|---:|---| | **EXACT** | **83** | the note's bytes are present verbatim in the LIVE binary | | **APPLIED** | **5** | live matches whist's *modified* bytes — Doug already has this edit | | **STOCK** | **3** | note's bytes in pristine only — Doug changed that site by another route | | **MIXED** | **4** | part exact, part already-spent, or a note-vs-binary artefact | | **UNPARSED** | **6** | reference lists / one unfinished note — no address to measure | | **MOVED** | **0** | — | | **MISMATCH** | **0** | — | **Zero mismatches. Zero moved. Not one address in 97 files pointed at the wrong instruction.** Of the 95 measurable entries, **91 resolve byte-for-byte**; the other 4 are MIXED for reasons that are themselves findings (Doug's own edits occupying whist's sites, and one runtime-mutated data field — §5.2). *File-level count, for reconciliation with the raw dump:* 79 files pure-EXACT, 5 carry an APPLIED site, 3 carry a STOCK site, 4 MIXED, 6 non-measurable. The raw dump's file-level tally (81/4/2/4/6) assigns each file a single dominant verdict, which folds `AncientsXPreward` and `CharSelectColors` into EXACT. Data verified dword-for-dword totals **3,229 bytes of raw tables** — SrvDoFunc 612 B, ShrineCllientTable 460 B, AnyaClassSpec 444 B, ObjOperatefn 292 B, ShrineTable 276 B, SrvStFunc 260 B, Hellforge 216 B, MonUModAura 160 B, pSpell 160 B, EventsTable 144 B, QuestIconPos 96 B, SpeedStat 60 B, PhysAbs 40 B, Automagics 9 B — **without a single wrong byte.** ### What that number means These notes were hand-transcribed out of OllyDbg by one person, at least nine years ago, against a binary nobody has recompiled since 2010. Twenty-three-row data tables with embedded absolute function pointers reproduce bit-perfect. **whist's compilation is not a lead to chase — it is a reference to cite.** Four defects exist across 97 files and all four are transcription-layer, not knowledge-layer: | file | defect | the correct value | |---|---|---| | `ChestAlwaysLocked.asm` | header offset says `21D1E` | real site RVA **0x2ED1E** — the note's own VA column `6FC4ED1E` is right | | `OpenWoundsDuration.asm` | header RVA `AD538` falls **mid-instruction** | real site RVA **0xAD53B** (imm at 0xAD53F); patching AD538 corrupts the preceding `MOV [ESP+18],1` | | `KickDamageDisplay.asm` | first "TO" block writes its NOPs **inside** the CMP | use the **second** block: RVA 0x94009, `75 13 → EB 13` | | `DenofEvilNameChange.asm` | site-B "TO" block is a copy-paste of site C's | the note never states site B's target; Doug solved it himself (`74 07 → EB 07`) | Plus two harmless column typos — `AutomapText` 5FE4C prints `6A 03` in hex where the mnemonic column says `PUSH 4` (binary has `6A 04`; the mnemonic is right), and `PoisonAbs` misprints one context dword — and one self-contradicting comment (`ClassOskillLimit` says "D2Game" where it means D2Common; no D2Game site exists). ### Base corrections this pass established **`D2Launch.dll` ImageBase is `0x6FA40000`, not `0x6FA10000`.** Verified this session from the PE header of *both* the live and the pristine copy. Any D2Launch VA computed against `0x6FA10000` is off by `0x30000`, and whist's D2Launch VAs (`6FA4E349`, `6FA4C913`, `6FA5E0CC`) only resolve at `0x6FA40000`. Confirmed a second way from the binary's own self-reference: RVA `0xC92F` reads `JMP DWORD [EDX*4+6FA4CAE8]`, and RVA `0xCAE8` holds `6FA4C936 / 6FA4C947` — self-consistent only at `0x6FA40000`. Any prior report of a D2Launch MISMATCH should be re-checked against this base before being blamed on the note. **D2Game relocation trap, confirmed from whist's side.** Storm occupies 0x6FBF0000..0x6FC50000 and swallows D2Game's preferred 0x6FC20000, so D2Game relocates every launch (observed 0x02870000). whist's D2Game notes were captured live-attached across **at least ten separate debugger sessions** at ten different relocated bases: 0x00C20000 HealersNPC 0x016C0000 QuestSkPointsRewards, ReanimatesLiveTime, 0x00CA0000 PhysAndMagPierce ExpandedPlayersCommand, TrapScimitar 0x02700000 Gamble 0x016B0000 DR%Cap 0x035D0000 ResetMaps, ClassicWhirlwind, 0x037F0000 EtherealSetsDrop StatsQuestRewards 0x03AB0000 DefenseWhileRun 0x03B00000 AbsorbCap 0x03B50000 ExperienceBeyondTwoScreen, OpenWounds, 0x00330000 DisableChat (D2Lang) FriendlyFire, OpenWoundsDuration **Every one of them reduces to the correct file RVA.** The rule for reading this compilation: the hex offset in the *header line* is the reliable field; the VA column is session noise. One exception only — `ChestAlwaysLocked`, where the reverse is true. --- ## 2. ALREADY APPLIED — edits Doug's binaries already carry Five sites where the LIVE binary matches whist's **modified** bytes. These are in the mod today, possibly without the memory that they came from here. ### 2.1 `MultiD2Window.asm` — D2gfx RVA 0x085BF — multiple game windows STOCK 6FA885B2 68 FC0CA96F FF15 84D1A86F 85C0 74 45 <- JE LIVE 6FA885B2 68 FC0CA96F FF15 84D1A86F 85C0 EB 45 <- JMP The `FindWindowA` single-instance guard, neutralised. **One of only 17 bytes separating Doug's D2gfx from pristine 1.13c** — a deliberate single-byte patch, not collateral. The other 16 are unrelated: eleven `D1 → 00`, plus 07FEA/07FEB/07FF0/07FF1 which read as a version/size field edit. ### 2.2 `ExperienceBeyondTwoScreen.asm` — D2Game RVA 0xDAFCA — XP past two screens STOCK 3D 00 19 00 00 77 53 cmp eax,0x1900 / ja LIVE 90 90 90 90 90 90 90 7 NOPs — whist's edit, byte for byte ### 2.3 `AncientsXPreward.asm` (one-level cap) — D2Game RVA 0x5BFF8 STOCK 3B DD 7E 04 cmp ebx,ebp / jle LIVE 3B DD EB 04 cmp ebx,ebp / jmp The Ancients quest can carry a character past one level. whist's minimal-diff style: the CMP is left in place, dead but harmless. The XP **constants** at 0x5BF88 are still vanilla — 0x155CC0 = 1,400,000 / 0x1312D00 = 20,000,000 / 0x2625A00 = 40,000,000. ### 2.4 `SummitEffect.asm` — D2Client RVA 0x8B1EC, cave at 0xCDE0D STOCK 6FB3B1EC 83 F8 78 75 09 cmp eax,0x78 / jne LIVE 6FB3B1EC E9 20 2C 04 00 jmp 6FB7DE11 LIVE 6FB7DE0D 90 90 90 90 (padding) 6FB7DE11 83 F8 78 74 0C cmp eax,0x78 / je -> do it 6FB7DE16 3D 94 00 00 00 74 05 cmp eax,0x94 / je -> do it 6FB7DE1D E9 DD D3 FB FF jmp 6FB3B1FF (clean skip) 6FB7DE22 E9 CA D3 FB FF jmp 6FB3B1F1 (shared "do it") STOCK 6FB7DE0D all 00 Arreat Summit visual effect extended to level IDs 0x78 (120) and 0x94 (148). **Doug's implementation is correct where whist's note is broken.** Both of the note's "CUSTOM CODE" examples have a bad fallthrough: example 1 ends `E9 C8D3FBFF JMP 6FB3B1EC`, which computes back to the hook site itself — an infinite loop; example 2 falls through to 6FB3B1F1 and applies the effect on *every* level. Doug's tail jumps to 6FB3B1FF for a clean skip and uses one shared "do it" target instead of a JE per level, so it scales without duplicating the call. The +4 delta on his hook (`6FB7DE11` vs the note's `6FB7DE0D`) is deliberate padding, not drift. **Anything scanning for that cave's entry point should expect RVA 0xCDE11, not 0xCDE0D.** ### 2.5 `DenofEvilNameChange.asm` — D2Client, all three sites Site A RVA 0xC12DE STOCK 85 C0 75 05 ... LIVE 85 C0 EB 05 ... (whist's TO, verbatim) Site B RVA 0xAB3D6 STOCK 85 C0 74 07 ... LIVE 85 C0 EB 07 ... (Doug's own — note defective here) Site C RVA 0xBE27B STOCK 85 C0 74 0A ... LIVE 90 90 90 90 ... (whist's TO, verbatim) Sites B and C land on **different** string ids — B pins to `0x13B7`, C to `0x13B8`. Whether that is deliberate (two surfaces: label vs automap) is **trusted, not verified** — the D2Lang string table was not resolved. ### 2.6 Bordering cases — Doug's edit at whist's exact address, different encoding Not APPLIED, but the site is spent. Anything planning to write there must know it is occupied. | site | stock | live | reading | |---|---|---|---| | `CorpseMax` D2Game **0x79237** | `76 13` JBE | `EB 08` JMP | Same outcome (no corpses), different route. whist's `90 90` falls through into the delete-oldest-corpse call at 0x6FC98FA0 then returns; Doug's jumps straight to the epilogue and **skips the cleanup**. Revisit if corpse-list growth ever appears. | | `CharSelectColors` D2Launch **0xB231** | `6A 02` | `6A 04` | Expansion-Character label colour 2→4. The parallel Ladder push at 0xB259 is `6A 04` in **both** — that is vanilla, not a matching edit. | | `ExperienceBarColors` D2Client **0x26AF2** | `57 03 DE 53 57` | `E9 A6 74 0A 00` → 6FB7DF9D | XP-bar **reposition** hook into a codecave. The cave restores the stolen instructions and adds offsets (`add edi,0x20 / add ebx,-0x4C / add esi,-0x4C`) then jumps back to 0x6FAD6AF7. It does not touch whist's four colour pushes, which all still read `68 FF 00 00 00`. | | `HireableIcons` D2Client **0x5AE9B** | `6A 37 83 C2 08` | `E9 33 D9 2C 00` → **6FDD87D3** | **Cross-module detour** — out of D2Client into a cave in D2Common's `.text` tail. Cave holds `68 B5 02 00 00 / 83 C2 08 / E9 C0 26 D3 FF`: pushes Y = 0x2B5 (693) instead of stock 0x37 (55), keeps whist's X edit `add edx,8` verbatim, returns to 6FB0AEA0. | | `HireableIcons` D2Client **0x5BA62** | `01` | `05` | Hireling icon draw mode set to whist's "desired mode" 5 (= Normal Draw). | | `SanctumArcanum` D2Client **0x8B1D6 / 0x8B1EC** | `83 F8 4A 75 05` / `83 F8 78 75 09` | two 5-byte JMPs | whist's multi-level technique applied at **both** consecutive gates. Cave A = D2Common RVA 0x887F6, cave B = D2Client RVA 0xCDE11. Levels enabled: 0x4A / 0x8A / 0xAB (first effect), 0x78 / 0x94 (second). | ### 2.7 The codecaves Doug has already carved — know these before writing free space All three are all-zero in pristine 1.13c and occupied in the live binaries. | cave | module / RVA | live VA | holds | |---|---|---|---| | **A** | D2Client `.text` tail, RVA **0xCDDFB..0xCDFFF** (~514 B) | 6FB7DDFB | Summit gate (0xCDE11), Sanctum gate B, XP-bar reposition (0xCDF9D), plus a shared resolution/layout block at 0xCDF90 and 0xCDFB0 with 0x2A2 / 0x2D0 bounds checks jumping to 6FB0BF66 and 6FB0C05E | | **B** | D2Common `.text` tail, RVA **0x887D3** | 6FDD87D3 | HireableIcons Y-coordinate rewrite | | **C** | D2Common `.text` tail, RVA **0x887F6** | 6FDD87F6 | Sanctum gate A: `cmp eax,0x4A / 0x8A / 0xAB` with JEs back into D2Client | Caves B and C only work because **D2Common holds its preferred base 0x6FD50000** on this install. D2Game does not. Anything that moves D2Common breaks two live patches. `RVA 0xCDF9D` is past D2Client `.text` vsize (0xCCDFB) but inside its raw size (0xCD000) — the alignment tail. **That is the free space, and it is already partly spent.** --- ## 3. THE INDEX — all 101 entries Sorted by usefulness, not alphabetically. `RVA` is the file offset in the named module. D2Game addresses are **file coordinates**; D2Game relocates at runtime. ### 3.A Already applied / already spent — read §2 before touching these | file | module | RVA | what it does | verdict | |---|---|---|---|---| | MultiD2Window | D2gfx | 0x085BF | multiple game windows | **APPLIED** | | ExperienceBeyondTwoScreen | D2Game | 0xDAFCA | XP credited past two screens | **APPLIED** | | AncientsXPreward *(cap)* | D2Game | 0x5BFF8 | Ancients may grant >1 level | **APPLIED** | | SummitEffect | D2Client | 0x8B1EC | Summit effect on arbitrary levels | **APPLIED** | | DenofEvilNameChange | D2Client | 0xC12DE / 0xAB3D6 / 0xBE27B | Den keeps its name after clearing | **APPLIED** | | SanctumArcanum | D2Client | 0x8B1D6 (+0x8B1EC) | Arcane star effect, multi-level | **MIXED** — Doug's form, whist's technique | | HireableIcons | D2Client | 0x5AE9B, 0x5BA62 (+4 EXACT) | merc / pet icon panel | **MIXED** | | CorpseMax *(jbe)* | D2Game | 0x79237 | player corpses suppressed | **STOCK** | | CharSelectColors *(expansion)* | D2Launch | 0xB231 | expansion-label colour | **STOCK** | | ExperienceBarColors *(geometry)* | D2Client | 0x26AF2 | XP-bar position hook | **STOCK** | ### 3.B Drawing, text, fonts, colours — the healthbar-adjacent set (see §4) | file | module | RVA | what it does | verdict | |---|---|---|---|---| | **MonLifeBar** | **D2Win** | **0x134AC, 0x134D3** | **monster life-bar colour + draw mode** | **EXACT** | | MainMenuButtonsText | D2Win | 0x0FC63, 0x0FC6C, 0x0FA43 | menu-button font ids + arg block | EXACT | | CharTitle | D2Win | 0x0BB84 code, 0x1CAF4 .rdata | 19 character-title strings | EXACT | | ScreenshotDirectory | D2Win | 0x1727B, 0x1C4A4 | screenshot path format string | EXACT | | ItemDescText | D2Client | 0x941D3 … 0x944E4 (10 sites) | 10 item-description line colours | EXACT | | QualityColors | D2Client | 0x94065, 0xBFEFA | item quality colours, two tables | EXACT | | CodeColoredItems | D2Client | 0x941F7..0x94256 | 11 item codes forced to colour 8 | EXACT | | ChatColors | D2Client | 0x7FA72, 0x7FAA8, 0x7F8EA, 0x7F991 | four chat text colours | EXACT | | MonsterNamesAndDescColors | D2Client | 0xC19E4, 0xC19FB | unique = gold(4), champion = blue(3) | EXACT | | ExperienceBarColors *(colours)* | D2Client | 0x26ADB, 0x26AE2, 0x26AFD, 0x26B02, 0x26A7B | XP-bar tint ×4 + hover popup | EXACT | | AutomapText | D2Client | 0x5FEF1 … 0x60ACA (12 sites) | automap overlay: strings, ids, colours | EXACT | | CharacterStatScreen | D2Client | 93 sites, 0x2C6AC … 0xF5370 | whole stat screen: fonts, colours, geometry, .data rows | MIXED | | HireableIcons | D2Client | 0x5AE86, 0x5AE99, 0x5AF7F, 0x5B85F, 0x5BA2A, 0x5BC10 | merc/pet font, colour, draw modes | MIXED | | ProgressiveStateDebug | D2Client | 0xC1639 | unused progressive-state overlay | EXACT | | RemoveShrineOverlay | D2Client | 0x25950 | stub the shrine state overlay | EXACT | | KickDamageDisplay | D2Client | **0x94009** | boots kick-damage line gate | EXACT | | SmiteDmgDisplay | D2Client | 0x93FA0 | shield smite-damage line gate | EXACT | | QuestIconPosTable | D2Client .data | 0xF8100 (96 B) | 6 quest-icon layout records | EXACT | | MainMenuVersionText | D2Launch | 0x180FF, 0x1811E, 0x18132 | version format strings + colour | EXACT | | CharSelectColors *(12 sites)* | D2Launch | 0xB0BD … 0x9EDC | char-select colours + class string ids | EXACT | | CharSelectSoftcorePos | D2Launch | 0xC913..0xC945 | softcore rendered in hardcore position | EXACT | | **DrawModesList** | — | — | **decoder for every draw-mode push, 0..7** | UNPARSED (reference) | | **ColorList** | — | — | **decoder for every colour push, 0..D** | UNPARSED (reference) | | **FontsList** | — | — | **decoder for every font-id push, 0..D** | UNPARSED (reference) | ### 3.C Crash fixes and engine limits — candidates on their own merits | file | module | RVA | what it does | verdict | |---|---|---|---|---| | CharmHasNoAffixError | D2Game | 0x1037B | **crash fix** — charm spawned with no affixes (`75 25 → EB 25`) | EXACT | | FilesizeCrashFix | D2Game | 0xCB0DB (join), 0x53484 (leave) | **crash fix** — save files ≥ 8192 B | EXACT | | BloodManaFix | D2Common 0x37D54 / D2Game 0xBE7E | | **crash fix** — blood_mana applied by item state | EXACT | | RandomBossPickFix | D2Game | 0xDF4BF | umon boss picks in NM/Hell, not just Normal | EXACT | | BetterDeathCalculation | D2Game | 0xDE255 | die at true 0 hp instead of 1 | EXACT | | CorpseMax *(cmp)* | D2Game | 0x79234 | corpse maximum 15 | EXACT | | ResistancesCap | D2Game 0xDB463…0xDB475 / D2Client 0xBD91C…0xBD95F | 12 sites | max res 75 / 95 / floor −100 — **both DLLs, or the screen lies** | EXACT | | BlockChanceCap | D2Common | 0x31E7F | block cap 75 | EXACT | | AbsorbCap | D2Game | 0xDAD1A, 0xDAD1F | absorb% ceiling 40 | EXACT | | DR%Cap | D2Game | 0xDB4AE | damage-reduction% ceiling 50 | EXACT | | PierceBreakImmunities | D2Game | 0xDB3E0 | pierce may break immunities | EXACT | | ClassOskillLimit | D2Common 0x4FD79 / D2Client 0x55984 | | oskill cap 3 on own-class skills | EXACT | | ExpandedPlayersCommand | D2Client 0x70A88, 0x70A8F / D2Game 0x37434 | | `/players` above 8 — **three** sites | EXACT | | FPScommand | D2Client | 0x5AB43 | verbose `/fps` diagnostic block | EXACT | | Hyperjoin | D2Client | 0x14D6D | TCP/IP load-bar delay 0xFA | EXACT | | ResetMaps | D2Game | 0xED3BC | SP map seeds reset every game | EXACT | | Gamble | D2Game | 0xBE611 | gamble screen item count 14 | EXACT | | DisableChat | D2Lang | 0x0B0E9 | kill chat input (`66 89 0F`) | EXACT | | DisableNonExpansion | D2Launch | 0x14629 | all classes count as expansion | EXACT | | ExternalDLL | D2Launch | 0x0E349 | LoadLibraryA up to 4 extra DLLs (0x34 B free at 0xE363) | EXACT | ### 3.D Data tables — highest reuse value, all verified dword-for-dword | file | module | RVA | size | what it is | verdict | |---|---|---|---|---|---| | SrvDoFuncTable | D2Game .data | 0x1074A8 | 612 B / 153 ptrs | skill server DoFunc dispatch | EXACT | | ShrineCllientTable | D2Client .rdata | 0xD2F60 | 460 B / 23×20 | client shrine defs: fn, overlay, sound | EXACT | | AnyaClassSpecReward | D2Game .data | 0x10F204 | 444 B | Anya class-reward item-code blob | EXACT | | ObjOperatefnTable | D2Game .data | 0x107BB8 | 292 B / 73 | object Operatefn dispatch | EXACT | | ShrineTable | D2Game .data | 0x0FA5F8 | 276 B / 23×12 | server shrine defs: fn, stat, state | EXACT | | SrvStFuncTable | D2Game .data | 0x10733C | 260 B / 65 | skill server StFunc dispatch | EXACT | | HellforgeRuneDrop | D2Game .data | 0x106F0C | 216 B / 54 codes | Hellforge drop table, ASCII | EXACT | | MonUModAura | D2Game .data | 0x10E4A0 | 160 B / 8×20 | monster aura mods — **Thorns off at 0x10E52C + 0x10E534** | EXACT | | pSpellTable | D2Game .data | 0x107240 | 160 B | pSpell (init, fn) pairs | EXACT | | EventsTable | D2Game .data | 0x1077AC | 144 B / 36 | ItemEventFuncs | EXACT | | SpeedStatTable | D2Common | 0x94608 | 60 B / 5×12 | IAS/FHR/FCR/FBR/FRW divisors | EXACT | | PhysAbs | D2Game | 0x102AAC | 40 B | physical damage-type record, 4 free slots | EXACT | | PhysAndMagPierce | D2Game | 0x102B6C, 0x102ABC | 56 B | magic / physical pierce free slots | EXACT | | PoisonAbs | D2Game | 0x102C20 | — | poison absorb free slots | MIXED — one context dword misprinted | | MonCurseCast | D2Game .rdata | 0x0FB454 | 5 dwords | curses monsters may cast | EXACT | | Automagics | D2Game | 0x11064 | 9 B | per-quality automagic on/off | EXACT | | QualKehkRuneReward | D2Game | 0x10BC38 (+0x5CB11, 0x5CB53) | 32 codes | Qual-Kehk rune table + count | EXACT | | QuestIconPosTable | D2Client .data | 0x0F8100 | 96 B | quest-screen icon layout | EXACT | | BossDeathMissiles | D2Client | 0x20FB3..0x21119 | 14 handlers | boss-death dispatch + per-boss missile id | EXACT | ### 3.E Gameplay tuning — single-immediate knobs, ready to use | file | module | RVA | what it does | verdict | |---|---|---|---|---| | MonUModResist | D2Game | 0x21D53 / 0x21D70 / 0x21D8D / 0x21DAE / 0x21DCB / 0x21E5E | 6 UMod resist grants, 1 byte each | EXACT | | MonUModNormal | D2Game | 0xEE2D3 / 0xEE2E3 / 0xEE2F3 | 3 normal-monster UMod ids | EXACT | | ItemDeletionDelay | D2Game | 0xE0C5 / 0xE0ED / 0xE103 / 0xE10F | despawn frames by item class | EXACT | | OpenWoundsDuration | D2Game | **0xAD53B** (imm 0xAD53F) | OW bleed 0xC8 frames — **header RVA is 3 B short** | EXACT | | OpenWounds | D2Game | 0xAD527..0xAD557 | OW duration / stat / state frame | EXACT | | ReanimatesLiveTime | D2Game | 0xAE416 | reanimate lifetime 0x5DC frames | EXACT | | QuestSkPointsRewards | D2Game | 0x653D2 / 0xD9E9A / 0xBA741 | Den / Radament / Izual skill points | EXACT | | StatsQuestRewards | D2Game | 0xE6495, 0xD9EE9 | Lam Esen stats (5), Golden Bird life (0x1400) | EXACT | | AncientsXPreward *(constants)* | D2Game | 0x5BF88 | 1.4M / 20M / 40M XP | EXACT | | QuestRingRewards | D2Game | 0x66E9B (Akara), 0xE97CA (Ormus) | ring code / quality / quantity | EXACT | | QuestSounds | D2Client | 11 sites, 0x1E205 … 0xB2551 | 11 quest-reward sound ids | EXACT | | DarkWandererSummons | D2Game | 0x36CED (+ **count** at 0x36CE9) | Act III summon HcIdx 0x12D | EXACT | | TrapScimitar | D2Game | 0x0B5AE | Act II flying-sword HcIdx 0xEA | EXACT | | HealersNPC | D2Game | 0xBD810, six compares | which NPCs heal on interaction | EXACT | | FireShrine | D2Game 0x6CB70 / 0x6CA39 / 0x6CA63, D2Client 0x26159 | | fire-shrine missile id + drained stat | EXACT | | CowLevelCheck | D2Game | 0x6750A | Cow King killed → still enter | EXACT | | CowLevelDest | D2Game | 0x6767B | cow portal destination (0x27) | EXACT | | CowKingPotionDrop | D2Game | 0x65B0C | Cow King `'vps '` ×8 | EXACT | | EtherealSetsDrop | D2Game | 0x0EBF7 | ethereal set items may drop | EXACT | | ChestAlwaysLocked | D2Game | **0x2ED1E**, 0x6F093 | all chests locked / Assassin gate | EXACT | | ClassicWhirlwind | D2Game | 0x26E7D | IAS no longer speeds WW | EXACT | | DefenseWhileRun | D2Game | 0xDE5F8 | defense while running | EXACT | | FriendlyFire | D2Game | 0xDE0DD | enable friendly fire | EXACT | | CharacterCreationItems | D2Game | 0x380B3 (quality), 0x380D8 (13 B) | starting-item quality / keep automagics | EXACT | | DiffManaAndEnergyColumn | D2Game | 0x0EB384 | starting mana from PercentInt column | EXACT | | UniqueColumnModification | D2Game | 0x0FCAE, 0x10B26 | `unique` column → quality specificator | EXACT | | RunewordsQuality | D2Common | 0x26BBF | runeword quality gate — **and the 9 three bytes later** | EXACT | | ImbueCode | D2Client | 0x45CFF | *note is unfinished: "File Not Finished!"* | UNPARSED | | ClassIdList | — | — | class ids 0..6 | UNPARSED (reference) | | QualityCodesList | — | — | quality codes 1..9 | UNPARSED (reference) | --- ## 4. DIRECTLY RELEVANT TO THE FLOATING-HEALTHBAR WORK Cross-reference: `research/FLOATING-HEALTHBARS-prior-art.md`. ### 4.1 The convergence — measured this session The prior-art document names, from Necrolis's 2023 answer to an explicitly-1.13c question, the two functions to replace. Re-verified here from D2Win's own export directory (207 exports, ordinals 10000..10206) and D2Client's import directory: | role | 1.13c ordinal | D2Win RVA | VA | |---|---|---|---| | set the params | **#10070** `CacheLifeBarParams` | 0x012730 | 0x6F8F2730 | | drain and draw | **#10110** `DrawCachedText` | 0x0133A0 | 0x6F8F33A0 | D2Client imports **38** D2Win ordinals. **#10124 and #10130 are not among them**; #10070 and #10110 are. That settles the 1.10-vs-1.13c ordinal question in the prior-art doc §3 from the binary. **whist's `MonLifeBar.asm` addresses land inside `#10110 DrawCachedText`.** Disassembled from the function entry at RVA 0x133A0 forward: **there is no RET anywhere between 0x133A0 and 0x134D5.** It is one function, ending at RET, RVA 0x1356B. So whist's note — written before 2017, with no knowledge of this project — points directly at the **drain-and-draw stage of the single-slot cache** that Necrolis names as the obstacle. Two independent sources, an OllyDbg transcription and a Discord answer six years apart, resolving to the same function in Doug's binary. ### 4.2 The measured anatomy of `#10110 DrawCachedText` Read out of `C:\Diablo II\D2Win.dll` this session. **D2Win RVA 0x133A0..0x13600 is byte-identical in live and stock** — nothing in Doug's nine-module mod stack has patched this on disk. 6F8F33A0 83EC1C sub esp,0x1C ; <- #10110 entry 6F8F33A3 A1 34FD8F6F mov eax,[6F8FFD34] ; current font id 6F8F33A8 B9 589E9A6F mov ecx,0x6F9A9E58 ; <-- THE GLOBAL TEXT BUFFER 6F8F33C7 BB 06000000 mov ebx,6 ; font 6 = Font6.dc6 6F8F33E9 833D 64A69A6F 01 cmp dword [6F9AA664],1 ; centre-align flag 6F8F33F2 A1 58A69A6F mov eax,[6F9AA658] ; X 6F8F3407 8B15 5CA69A6F mov edx,[6F9AA65C] ; Y 6F8F3437 E8 A4FBFFFF call 6F8F2FE0 ; = D2Win #10184 SetFont (ecx=0) 6F8F3451 E8 8AFBFFFF call 6F8F2FE0 ; = D2Win #10184 SetFont (ecx=6) 6F8F3495 8B0D 68A69A6F mov ecx,[6F9AA668] ; <-- LIFE PERCENT (0..128) 6F8F349D 0F84 C9000000 je 6F8F356C ; percent 0 -> empty-bar branch 6F8F34A3 A0 109D9A6F mov al,[6F9A9D10] ; "colour already set" latch 6F8F34AA 75 20 jne 6F8F34CC ---- whist's MonLifeBar site ---------------------------------------------- 6F8F34AC 6A 00 push 0 ; Blue 6F8F34AE 6A 00 push 0 ; Green 6F8F34B0 6A 50 push 0x50 ; Red 6F8F34B2 68 00010000 push 0x100 6F8F34B7 68 3823926F push 0x6F922338 6F8F34BC E8 A142FFFF call 6F8E7762 -> JMP D2CMP.dll #10049 --------------------------------------------------------------------------- 6F8F34C1 8B0D 68A69A6F mov ecx,[6F9AA668] 6F8F34D0 0FAFCF imul ecx,edi ; edi = bar width 6F8F34D3 6A 01 push 1 ; <-- whist's "transparency mode" 6F8F34D5 C1F907 sar ecx,7 ; percent/128 -> filled width 6F8F34EE E8 2F43FFFF call 6F8E7822 -> JMP D2gfx.dll #10014 ; DRAW RECT (filled) 6F8F34FD 6A 02 push 2 ; draw mode 2 for the remainder 6F8F3508 E8 1543FFFF call 6F8E7822 -> JMP D2gfx.dll #10014 ; DRAW RECT (empty) 6F8F3549 68 589E9A6F push 0x6F9A9E58 ; the text buffer again 6F8F3550 E8 EBF3FFFF call 6F8F2940 ; draw the cached text 6F8F356B C3 ret 6F8F356C ... (percent-0 branch: one rect, mode 2, rejoins at 6F8F3511) **`#10070 CacheLifeBarParams` is six instructions long**, and the entire life-bar-specific part of it is a single store: 6F8F2730 8B44240C mov eax,[esp+0xC] \ 6F8F2734 50 push eax | forward three args 6F8F2735 8B44240C mov eax,[esp+0xC] | to the generic 6F8F2739 50 push eax | text-cache setter 6F8F273A 8B44240C mov eax,[esp+0xC] | 6F8F273E 50 push eax / 6F8F273F E8 ACF1FFFF call 6F8F18F0 6F8F2744 8B4C2410 mov ecx,[esp+0x10] 6F8F2748 890D 68A69A6F mov [6F9AA668],ecx ; <-- the 4th arg IS the life percent 6F8F274E C21000 ret 0x10 `0x6F9AA668` is the whole life-bar half of the single-slot cache — the "one variable, last writer wins" Necrolis described, now **named and located**. ### 4.3 What this hands the healthbar work, concretely | what | measured value | |---|---| | the percent global — **the single slot** | **`0x6F9AA668`** — written by #10070 at RVA 0x12748, read by #10110 at 0x1349B and 0x134C1 | | the text buffer global | **`0x6F9A9E58`** — the "boxed text" cache Necrolis names | | position globals | X `0x6F9AA658`, Y `0x6F9AA65C`, `0x6F9AA660`, centre flag `0x6F9AA664` | | font globals | current font `0x6F8FFD34`, scale `0x6F8FFD40`; the bar uses **font 6 = `Font6.dc6`** via D2Win **#10184** (RVA 0x12FE0) | | colour latch | `0x6F9A9D10` — a byte that makes the palette-shift call at 0x134AC run **once**, not per frame | | **the draw-rect primitive** | **`D2gfx.dll #10014`**, via D2Win thunk `0x6F8E7822` — called three times in this function | | the colour / palette call | `D2CMP.dll #10049`, via thunk `0x6F8E7762` | | percent encoding | **0..128, not 0..100** — `imul ecx,width` then `sar ecx,7` | | bar colour | R `0x50`, G `0`, B `0` — the three bytes at RVA 0x134B0 / 0x134AE / 0x134AC | | draw modes | filled portion **1 = 50% opacity**; empty portion **2 = 75% opacity** | | text-measure neighbour | `0x6F8E7804` → `D2gfx.dll #10080`, called at 0x1341E to size the box | Necrolis's §8 recipe was *"a draw rect func, a D2Win func that measures the w+h of a text block, and normal text drawing."* **D2gfx #10014 is the draw-rect func and D2gfx #10080 is called for the measure**, both from inside the very function whist documented. The ALT-nameplate parallel-pass approach and whist's note point at the same primitives. ### 4.4 Three cross-file corroborations inside this compilation 1. **`DrawModesList.asm` decodes `MonLifeBar.asm`.** The note has no address and is unverifiable alone, but its table (0 = 25% opacity, 1 = 50%, 2 = 75%, 5 = Normal Draw, 7 = Highlight) exactly explains the two draw-mode pushes at 0x134D3 and 0x134FD — filled bar at 50%, empty portion at 75%. It also decodes the four stat-screen page pushes (D2Client 0xBCED4 / 0xBCF11 / 0xBCF48 / 0xBCF75, all `6A 05` = Normal Draw), the two popup boxes (0xBC8C3 / 0xBCA16, `6A 02` = 75%), and Doug's own hireling draw-mode edit (0x5BA62, `01 → 05` = Normal Draw). 2. **`FontsList.asm` decodes the bar's font.** `#10110` sets font 6 before drawing → `Font6.dc6`. The same ordinal (#10184) is what `HireableIcons.asm` documents at D2Client 0x5AE86 (`mov ecx,6` then the thunk at 0x6FABD3A8 → D2Win #10184), verified against D2Client's import directory. **The merc panel and the monster life bar use the same font-set call and the same font.** 3. **`ColorList.asm` decodes the colour operands** across `ItemDescText`, `ChatColors`, `QualityColors`, `MonsterNamesAndDescColors` and `CharSelectColors`. Its one in-binary corroboration: `MonsterNamesAndDescColors` pushes 4 for uniques and 3 for champions, which matches gold / blue in game. ### 4.5 Adjacent surfaces already mapped by this compilation - `MainMenuButtonsText.asm`'s target `0x6F8F2760` (RVA 0x12760) sits 0x30 bytes past #10070 and in the same D2Win text-draw neighbourhood as #10110's text call at `0x6F8F2940`. **Its lesson transfers:** the colour operand there is a *register* push (1 byte), so swapping in a 2-byte `PUSH imm8` shifts the frame — relocate the argument block instead. whist's advice on that is correct as written, not a hedge. - `ShrineCllientTable.asm` (D2Client `.rdata` 0xD2F60) and `MonCurseCast.asm` (D2Game `.rdata` 0xFB454) are in **.rdata** — read-only at runtime. A DLL-based patcher needs `VirtualProtect` there, unlike the `.text` edits. - `CharacterStatScreen.asm`'s data finding is directly relevant to any UI patching: **the WORD after each stat-row string ID is runtime-mutated** (§5.2). Disk patches to it are clobbered at load. - `BossDeathMissiles.asm` is a complete, currently-accurate map of the boss-death effect dispatcher — 14 handlers, 14 CALL targets, jump table at 6FAD1124, index table at 6FAD1140 — and the whole range 0x20FB3..0x21119 is untouched in Doug's install. If a floating bar ever needs a per-unit death hook, that is a worked example of D2Client's switch-dispatch shape. ### 4.6 Caution before hooking D2Client loads at its preferred base 0x6FAB0000 on this install, so `0x6FABxxxx` / `0x6FB7xxxx` are usable live addresses. **But the D2Client `.text` alignment tail at RVA 0xCDDFB..0xCDFFF is already occupied** by three of Doug's patches (§2.7) — any new codecave must start from a measured free run, not from an assumption that the tail is empty. And nine mod modules (`PlugY`, `SGD2FreeRes`, `d2fps`, `BaseMod`, `esoteric.dll`, `NewTxt`, `ExtLvl`, `Waypoints`, `D2Template`) patch D2Client in memory: verify the five `CacheLifeBarParams` call sites are still intact **after** every module initialises, not on the on-disk image. --- ## 5. WHAT DID NOT HOLD, AND WHY Nothing failed as a *1.13c claim*. The honest breakdown: ### 5.1 Not measurable — 6 entries (five reference lists, one unfinished note) | file | why | |---|---| | `DrawModesList.asm` | reference table — no module, no address, no bytes. whist credits Necrolis. | | `ColorList.asm` | reference table. Note: whist lists `C` as "dark green" a second time, duplicating `A` — that is in his file, not a transcription slip. | | `FontsList.asm` | reference table, 14 font-id → `.dc6` mappings | | `ClassIdList.asm` | reference table, class ids 0..6 | | `QualityCodesList.asm` | reference table, quality codes 1..9 | | `ImbueCode.asm` | **whist's own file says "File Not Finished!"** — names D2Client RVA 0x45CFF and gives no bytes | These are UNPARSED only because the verdict schema has no "no-address" value. **Four of the five reference tables are the decoder rings for the rest of the compilation** and are more useful than several of the patch notes. Two are corroborated in-binary: - `QualityCodesList`'s structure is **proven from the binary**, not trusted: D2Game RVA 0x10CC0 reads `call 0x6FC2A898 / dec eax / cmp eax,8 / ja …`. The `dec` before `cmp eax,8 / ja` means item quality ids are **1-based with exactly nine valid values**. `UniqueColumnModification` independently anchors 7 = unique. - `ClassIdList`'s Paladin = 3 is corroborated by `SmiteDmgDisplay`'s gate at D2Client RVA 0x93F9C (`cmp dword [eax+4],3`), verified live, and by `KickDamageDisplay`'s Assassin = 6 at 0x94005. Corroboration of two entries, not verification of the list. `ImbueCode.asm`'s address is *plausible* — the live disassembly at 0x45CFF shows two symmetric call-then-TEST-then-JE blocks calling into 0x6FABC5A0-ish — but with no stated bytes it **cannot be promoted to a 1.13c fact**. Doug has not modified that site. ### 5.2 The four MIXED entries — none of them is whist being wrong about 1.13c **`CharacterStatScreen.asm`** — 93 addresses. All code sites EXACT, including the long 28- and 33-byte blocks. 33 of 48 `.data` rows EXACT, **15 rows differ in exactly one WORD** — and those 15 are identical in live and stock, so this is note-vs-binary, not Doug-vs-stock. The divergence is coherent, not random. On disk the WORD is `strlen+1` of the English label (Level = 6, Experience = 11, Life = 5, Mana = 5). whist's values are `strlen` of the *rendered* label, and for the four resistances the strlen of the **full phrase**: `Fire Resistance` = 15 = 0x0F, `Cold Resistance` = 15, `Lightning Resistance` = 20 = 0x14, `Poison Resistance` = 17 = 0x11 — where the file holds `strlen('Fire')+1` = 5 and `strlen('Lightning')+1` = 10. **Reading: whist dumped these from OllyDbg *after* D2 had overwritten the WORD with the live string length.** A memory-vs-disk artefact — and a useful one: **that WORD is a runtime-mutated length cache, so anything patching it on disk is clobbered at load.** Two smaller note defects in the same file: the header for "Right skill attack rating value" says `F536C` but its own body line gives `6FBA5370` — **F5370 is correct** (`B8 00 00 00 36 01 00 00` = Y then X); F536C lands mid-record on the preceding entry. And neither of the file's two behavioural patches is applied in Doug's build — BC869 still reads `85 F6 50 74 22` (block chance hidden without a shield) and BD035 still reads `85 C0 0F 84 78 01 00 00` (stat-points box hidden when empty). Both are one-NOP-run edits if he wants them. **`HireableIcons.asm`** — 4 of 6 sites EXACT; 2 are Doug's (§2.6). whist is correct about what lives at 0x5AE99; Doug has simply already spent 0x5AE9B and 0x5BA62. **`SanctumArcanum.asm`** — whist's ORIGINAL bytes are present in pristine 1.13c and absent from live (STOCK by the letter), but what replaced them is whist's own edit *form* with Doug's addresses (APPLIED in substance). Exactly 10 bytes differ live-vs-stock across 0x8B000..0x8C000: the two 5-byte JMPs. Doug also hooked the **second** gate, which whist's note never mentions, and his fall-through preserves the original `push esi / call` re-fetch where whist's example skips it as redundant. Both are correct; Doug's is the literal-stock-flow choice. **`PoisonAbs.asm`** — every byte whist instructs you to change is verbatim correct. **One context dword in his listing is wrong**: RVA 0x102C1C reads `50 01 00 00` (stat id 0x150 = 336) where his listing prints `00 00 00 00`. It is a real field, not padding — the previous record at 0x102BF0 carries the same 0x150 in the same slot. The record layout was resolved by pointer stride (trailing fn pointers at 0x102BDC / 0x102C08 / 0x102C34 / 0x102C60 / 0x102C8C → **0x2C-byte records**), so whist's record spans 0x102C0C..0x102C37. **The patch is safe to apply as written.** ### 5.3 What is *not* here **No 1.10-era notes.** Every address in all 97 files resolved in 1.13c. That deserves stating explicitly, because the surrounding scrape material is riddled with 1.10 addresses presented without version tags — `FLOATING-HEALTHBARS-prior-art.md` §3 documents exactly that trap, where a 2026 forum post answers a 1.13c question with 1.10 ordinals #10124/#10130. **whist's compilation does not have that problem. It is version-clean.** **No wrong module attributions.** Where a note is ambiguous about module it is ambiguous by *omission*, not by error: `QuestIconPosTable.asm` gives a bare offset and no VA at all (it lands in D2Client `.data`, section start RVA 0xDB000, and the values are internally consistent as a 3×2 grid, which is what confirms the read). **No fabricated structure.** Where whist annotates an import ordinal, it checks out against the module's own import directory: `6FC2ABB6` → D2Common #11111 and `6FC2ABAA` → #11108 (CharacterCreationItems), `6FC2AB62` → #10176 (EtherealSetsDrop), D2Client `6FABD3A8` → D2Win #10184 (HireableIcons). All correct as written. --- ## 6. WHAT THIS SAYS ABOUT THE CORPUS **Verdict: whist's material is citable without re-derivation. Treat any remaining unindexed whist / kidpaddle94 / D2Template content in these trees as high-priority, high-yield, and index it.** The evidence, stated plainly: 1. **91 of 95 measurable entries are byte-exact nine-plus years later, and 0 are wrong.** Not "mostly right" — a zero-mismatch, zero-moved result across several hundred discrete addresses spanning seven DLLs. 2. **The data tables are the strongest signal.** `ShrineTable.asm` is 276 bytes of table including 23 absolute function pointers (`6FC89C00`, `6FC8ABB0`, `6FC8ACE0`, …) reproduced without one wrong byte; `SrvDoFuncTable` is 612 bytes / 153 pointers, likewise. Nobody transcribes 3,229 bytes of hex by eye at that accuracy — these were tool-assisted dumps from a live process, which is *why* they are trustworthy and also why they carry relocated bases. 3. **His structural claims survive checks he never anticipated.** `Automagics.asm` is not merely byte-matched: the live disassembly at D2Game RVA 0x10CC0 encodes `0x6FC31064` as an explicit `movzx` base, and the 2-slot jump table behind it (`CB 0F C3 6F`, `18 10 C3 6F`) sends value 0 to the apply path 0x6FC30FCB and value 1 to 0x6FC31018 — the same address as the out-of-range branch, i.e. skip. His "01 = no Automagic, 00 = applied" is **proven**. Likewise `DR%Cap`'s bare `MOV EAX,32` turns out to be dispatched on stat id 0x24 (damageresist) and consumed as the upper bound of a two-sided clamp with lower bound −100 — the one-line claim is structurally correct, not coincidentally byte-matching. And `SpeedStatTable`'s extent is **provable**: the 24 bytes immediately after it at D2Common RVA 0x94644 read `..\Source\D2Common\UNITS`, a leftover Blizzard source path, so there is no sixth row to find. 4. **Cross-note agreement.** `MonUModResist` and `BetterDeathCalculation`, written independently, both resolve `6FC2A472` to the D2Common #10887 SetStat thunk; `BetterDeathCalculation` also hits `6FC2A42A` = #10973 GetStat. `StatsQuestRewards`' two sites converge on one routine at `0x6FC2A4D2`. `SmiteDmgDisplay` corroborates `ClassIdList`; `Automagics` corroborates `QualityCodesList`; `DrawModesList` decodes `MonLifeBar`, `CharacterStatScreen` and `HireableIcons`; `ShrineTable` and `ShrineCllientTable` pair into a complete shrine definition. The compilation is **internally consistent** — what you expect from one person's accumulated working notes and would not expect from forum-copied hearsay. 5. **Doug's own binaries confirm the lineage.** Five of these edits are already in his install, and the `SanctumArcanum` codecave sits four bytes from whist's literally-suggested cave address (`6FB7DE11` vs `6FB7DE0D`, the gap being seventeen NOPs laid from `6FB7DE00`). **These notes, or their direct lineage, were on the desk.** That is a fingerprint, and it means the compilation is not merely accurate — it is already load-bearing in the Esoteric Path. 6. **Where whist is wrong, Doug was already right.** `SummitEffect`'s two sample codecaves both have broken fallthroughs — one loops infinitely back to the hook site, the other applies the effect on every level — and Doug's live implementation fixes both. `DenofEvilNameChange`'s site-B "TO" block is a copy-paste of site C's and Doug resolved it independently. So the corpus is worth citing but **not worth applying blind**: the four addressing defects in §1 are exactly the kind a careful reader catches and a copy-paste does not. 7. **What it says about the *rest* of the trees.** This sample sets a floor, not a ceiling, for authored-by-a-core-reverser material. It also sets the contrast: the Discord/forum scrape material in `research/` mixes 1.10 and 1.13c without version tags and needed a whole prior-art document to untangle four ordinals. whist's 97 files needed none. **When two sources disagree, weight this compilation above scrape material — then measure anyway.** ### The practical rule this pass earns > Cite whist's RVAs directly. Verify his *modified* bytes before applying them. Never trust the VA > column on a D2Game or D2Lang note — the header offset is the reliable field (except > `ChestAlwaysLocked`, where it is the reverse). And treat his five reference lists as the decoder > rings for every immediate operand in the rest of the compilation: they carry no addresses, but > they are what makes the addresses readable. --- *Compiled 2026-08-02 from eight verification batches against `C:\Diablo II` and pristine 1.13c. Load-bearing claims re-verified independently in this session: all seven module ImageBases; the `MonLifeBar` reference site and its transparency push; all five APPLIED sites; all three STOCK sites; all three codecaves; D2Win's export table and D2Client's 38 D2Win imports; the `#10110` function extent and every call target inside it; the `Automagics` table. "Verified" in this document means the bytes were read out of a binary during that work and the record says which; everything else is marked trusted.*