# The Grimoire — Diablo II 1.13c reverse-engineering knowledge base > Canonical machine-readable source: `grimoire/symbols.jsonl` (724 records). This markdown is a > generated, **READ-ONLY** rendering of it — do not edit this file; edits are lost on regeneration. ## How to use this (for an RE agent) - This is the accumulated RE record for **D2Client.dll 1.13c** (and the surrounding mod stack) for Doug's *Esoteric Path* project. Every function/global/struct below carries its **absolute VA** and its module. - **HARD invariant — the live binary is the ground truth, not this file and not the on-disk DLL.** D2Client loads at base **0x6FAB0000** (RVA = VA − 0x6FAB0000). The LIVE process is patched in memory by SGD2FreeRes, PlugY, d2fps and D2DX, so **on-disk bytes ≠ live bytes** for patched sites. Verify against the live process (`C:\Diablo II\D2Client.dll`, same-user RPM — UAC is off) before asserting a fact. - **Trust-but-verify.** These records are point-in-time findings; a few carry retracted/superseded notes. When a record names a byte/offset, re-check it in the binary before building on it. - **Navigate by grepping** `symbols.jsonl` or this file for an address (`0x6FAF4243`), an id, or a term. - **Current state:** the flagship feature — render the world full-width behind the side panels — is **SOLVED** (see `solution.world_behind_panels_ARCHITECTURE` / `milestone.world_behind_panels_DONE_v118`). New RE work extends from there. ## How to record your findings (aggregation protocol) - Do **NOT** modify `symbols.jsonl`, this `GRIMOIRE.md`, or anything under `src/` — all read-only to you. - Append each new finding as ONE JSON object per line to **`grimoire/codex/findings.jsonl`**, schema: `{"id":"codex.", "kind":"function|global|struct|finding|note|openquestion", "date":"YYYY-MM-DD", "title":"...", "body":"...", "module":"D2Client.dll", "rva":"0x44243", "refs":[""]}` — `module`/`rva` optional (only for addressed symbols); prefix every id with **`codex.`**. - Cite the evidence in `body` (what you disassembled / read live, and the exact bytes/offsets). Hostile-reader rigor: no fabrication, mark uncertainty explicitly. Claude periodically merges your findings into the canon. ## Contents 1. [Open questions (the RE agenda)](#open-questions) 2. [Findings, solutions & notes](#findings-solutions--notes) 3. [Symbol reference by module](#symbol-reference-by-module) --- ## Open questions - **Panel open: is the D2Glide floor submit clamped at 640(=DispW/2) by the [esp+0x38] per-subtile CULL (NOP-able @0x856889) or by a VERTEX-X CLAMP elsewhere in 0x856840's projection? v1.11.2 gated probe (GlideUnclamp=1) settles it: submit max ~1280 => cull is the lever; ~640 => clamp is elsewhere. ==> ANSWER: It's the CULL. v1.11.2 gated probe (mode=1): GlideUnclamp=1 -> panel-open D2Glide floor submit max 1264-1296 with 338 verts IN [960,1280); GlideUnclamp=0 was 640. The NOP defeats the cull (640->1264). The remaining black is a DROP at/after D2DX -- geometry reaches the dead zone, D2DX doesn't paint it.** (`OQ.panelopen_640_cull_vs_clamp`) - **Why did v1.9.1/v1.11.0 GlideUnclamp (NOP the cull narrowing 0x856889/0x85689E) show NO visual change if 640 is the cull? Either the NOP widens the submit but a downstream layer hides it, or 640 comes from a clamp the NOP doesn't touch. Depends on OQ.panelopen_640_cull_vs_clamp. ==> ANSWER: GlideUnclamp DOES widen the submit (640->1264, 338 verts into the dead zone) -- never 'no effect'. The visual didn't change because the widened geometry is dropped downstream at/after D2DX.** (`OQ.glideunclamp_no_change`) - **DISPUTES finding.d2dx_gamesize_960. What is D2DX's ACTUAL runtime gameSize in-game (panel open)? Workflow inferred 960 (narrow). But forcing 1280 via SetCustomResolution (D2DXWiden) SHRANK the menu (black margin) -> implies native was LARGER than 1280, contradicting 960. Need a runtime read (probe gameSize / grSstWinOpen args), not source inference.** (`OQ.d2dx_runtime_gamesize`) - **Units/NPCs shift LEFT on inventory-open (they must NOT shift). Source unknown: the ctx+0x0C widen, D2DXWiden (now reverted), or the double-motion-smoothing (d2fps+D2DX both on). Re-check with D2DXWiden off. ==> PROGRESS 2026-07-12: terrain shift CONFIRMED = -320 (1x) via offX(0x6FBCC418)->ctx+0x04; scrollX untouched. Shared tile projection is 1x for all layers, so the objects' extra -320 is downstream/unit-specific. STILL OPEN: unit-layer re-read of offX (theory a) vs motion-smoothing (theory b). NEXT: xref offX reads -- if the unit/PASS2 path reads 0x6FBCC418 too, theory (a). ==> 2026-07-12 xref: offX read 173x, 1 write (0x6FB5AF7C). Attempt A (offX=0) un-shifted floor+units but NOT walls (separate source) + broke clicks. Shift is MULTI-SOURCE + hit-test-coupled; units lean theory (a). Naive offX-kill is OUT.** (`OQ.units_shift_left`) - **ONE clip (D2Glide 640) or TWO (D2Glide 640 + a D2DX target)? Panel-open probe=640, no-panel probe=1296 => the D2Glide clip is panel-mode-dependent. Whether D2DX ALSO clips once geometry passes 640 is blocked by OQ.d2dx_runtime_gamesize.** (`OQ.one_clip_or_two`) - **D2DXWiden(1280) regressed the menu resolution. Is forcing 1280 wrong for the low-res menu, or is the game's native size >1280? Ties to OQ.d2dx_runtime_gamesize. If D2DXWiden is re-applied, scope it IN-GAME only.** (`OQ.menu_res_regression`) - **DISAMBIGUATOR (council chat+clau demanded, never run): what is D2DX's ACTUAL D3D11 world render-target width + viewport, panel-open vs closed? Pairs with the panel-gated D2Glide submit probe to triangulate the stop point: (submit 640 + RT 1280)=>upstream D2Glide; (submit 1296 + RT 960)=>D2DX extent; (submit 1296 + RT 1280)=>wide-but-retagged (inspect MRTs). Build: hook ID3D11DeviceContext::Draw or read D2DX gameSize; or log grSstWinOpen incoming res arg (feature_d2dx already hooks it).** (`OQ.d3d11_rt_width_unmeasured`) - **BLOCKER (council wave-5 m1+m4): walls/roofs shift via a SEPARATE source, NOT offX (attempt A: offX=0 un-shifted floor+units but not walls). The authored no-panel view-state is INCOMPLETE until this source is named. Find it: read-breakpoint on the wall projection input during PASS2 (0x6FB3B110), or resolution-differential (vary DisplayWidth, scan for the value that tracks it AND encodes panel-open). Last unknown before the view-state-authorship patch. ==> TESTING (v1.12.0 attempt.I_noshift_lens): authoring ctxL=0 will show if walls read the ctx (un-shift) or a PASS2 recompute (stay shifted). ==> CLOSED (v1.12.0): the walls UN-SHIFTED with our ctxL=0 authoring -> the walls read the render context, NOT a separate source. Authoring the no-panel lens covers them.** (`OQ.walls_shift_source`) - **v1.12.0: after authoring the no-panel lens (offX=0/ctxL=0/ctxR=DispW each frame), the WORLD, WALLS and UNITS un-shift correctly -- but 1-2 FLOOR sub-layers still sit at the shifted position. So a floor sub-layer projects through a source our per-frame ctx authoring does NOT cover (a cached projection, a pre-PASS1 draw, or an offX re-read in the tilesub/floor-sublayer path 0x6FAF4307). Minor visual residual. Find via which draw path emits the lagging layer. Polish -- lower priority than the black. ==> IDENTIFIED (v1.12.2): it's the play-area rect playAreaW (0x6FBA9E14) -- the floor draws into it AND the hit-test reads it; narrowed to ~640 on panel-open (EP's 2022 HOM/4-tile-buffer notes). Authoring playAreaW=DispW/frame is the test. ==> v1.12.3: nViewXOffset was ALREADY 0 at our pre-PASS1 hook (no-op) -> the floor shift is COMPUTED INSIDE PASS1, not a pre-authorable global. See [[finding.floor_shift_internal_to_pass1]]. Council consult queued. ==> v1.12.6 [PROBE glidevtx count=0 @0xBEFC]: 0xBEFC is NOT the floor's live submit path. The floor's actual grDraw site is unidentified. See [[finding.floor_shift_ledger]]. Next = unfiltered return-addr logger.** (`OQ.floor_sublayer_residual`) - **THE LAST BARRIER TO ZERO BLACK: with the world un-shifted, floor geometry reaches x>=640 (probe band[960,1280)=66-104) and D2DX renders it BLACK. Confirmed m2's prediction: D2DX SurfaceIdTracker retags world batches with minx>=gameSize/2(=640) as USER_INTERFACE while ScreenOpenMode&1 -> the UI projection drops the world verts. Fix (council m2): runtime hot-patch the minx>=640 retag compare in glide3x during the world draw, restore after. We CANNOT rebuild D2DX. First step: read the D2DX source (bolrog/d2dx SurfaceIdTracker.cpp) to nail whether the retag drops COLOR or only writes metadata (m5 disputed) -> then locate + hot-patch it. ==> CORRECTED 2026-07-13 (read D2DX source): the surfaceId retag is METADATA ONLY (SetSurfaceId on vertices, for AA/motion) -- it does NOT relocate color (m5/chat was right, m2 wrong). The real clip is D2DX RenderContext::UpdateViewport, which sets the game VIEWPORT + SCISSOR to _gameSize.width (=960 for our desktop). World pixels x in [960,1280] are scissored -> black. FIX = widen _gameSize to 1280 via SetCustomResolution (our feature_d2dx.c / D2DXWiden). It failed alone earlier ONLY because the world was still SHIFTED (geometry never reached [960,1280]); with v1.12.0 no-shift the world fills [0,1280], so no-shift + D2DXWiden TOGETHER should fill the dead zone. RT is >=1024 (max(1024,w)); if a thin [1024,1280] band stays black, the RT alloc is the next tweak.** (`OQ.d2dx_retag_black`) --- ## Findings, solutions & notes ### [solution] CRYSTALLIZED SOLUTION -- world-behind-panels = THREE INDEPENDENT levers (there is NO single shift global). scr-guard (black/width) + offX-hold (units) + drawWin-hold (walls/roofs). All in esoteric.dll PanelWorld. *`solution.world_behind_panels_ARCHITECTURE` · 2026-07-20* GOAL: at 1280x720, render the D2 LoD 1.13c world FULL-WIDTH and UN-SHIFTED behind the opaque side panels (inventory=right/scr1, character=left/scr2), instead of vanilla's DispW/4 (=320) play-area recenter that hides the world and leaves a black dead-zone. KEY INSIGHT (why it took so long): the panel shift is injected in THREE STRUCTURALLY INDEPENDENT places, each read by a different render layer -- there is no one global to zero. Every single-lever attempt fixed one layer and exposed the next. THE THREE LEVERS (all live, all in PanelWorld, esoteric.dll): 1. scr-GUARD (kills BLACK / width). Transactional virtualization of scr=ScreenOpenMode(0x6FBCC414)=0 for the ENTIRE DrawGame world pass: E9-detour the gate @0x6FAF4243 (save scr, zero for scr in {1,2}, depth-count, reproduce the JNE) + the epilogue @0x6FAF43BE (restore). Makes the scr-gated draw-window + tile-cull compute the full no-panel width -> nothing scissored to black. ini PanelWorldScrGuard=1. Verified CONFIRMED_SAFE (blob byte-correct, single-entry/single-exit, epilogue-restore scope). Makes the old consumer patches (0x6FB1027D je->jmp, 0x6FB10D12 sub->nop) REDUNDANT. 2. offX-HOLD (kills UNIT/NPC/OBJECT recenter). Async thread pins offX=GeneralPlayAreaCameraShiftX(0x6FBCC418)=0 while a panel is open. offX is written once at panel-open and read only by the 5 unit/object/overhead-text projections; holding 0 un-recenters them. Race-free (written-once). ini PanelWorldNoShift=1 (arms pw_hold_offx_noshift). 3. drawWin-HOLD (kills WALL/ROOF shift). Same thread also pins the render-context draw-window drawWinL(ctx+0x04)=0 and drawWinR(ctx+0x0C)=DispW (ctx=*[0x6FBCC3C4]). The panel-toggle setter 0x6FB3A4D0 bakes +/-DispW/4 into that RECT at open time; walls/roofs (PASS2 drawers 0x6FB3AE00/6C0/140) project through it RAW. The floor is immune (DrawGame re-derives scrollX FROM drawWinL each frame so it cancels) and units ride offX -- only walls needed this. Race-free (drawWinL written once at toggle, DrawGame only reads). ini PanelWorldHoldDrawWin=1. LIVE-CONFIRMED: drawWinL 0(closed)->-320(right panel); WPM poke to 0 snapped the walls into alignment. Supporting: SGD2FreeRes sets DispW=1280 live; D2DXWiden forces D2DX gameSize=1280 so x>=960 isn't scissored (ini D2DXWiden=1). nViewXOffset(0x6FB9A9CC) is DEAD CODE in 1.13c (0 refs) -- ignore it. WINNING ini [Features]: PanelWorldScrGuard=1, PanelWorldNoShift=1, PanelWorldHoldDrawWin=1, D2DXWiden=1, PanelWorld=1, PanelWorldSecondPass=1 (forces the old A-E shift-hacks off), FloorPanelShiftFix=0 (redundant under the guard). PanelWorldWallFix (the 3 wall/roof view-setup patches) applied but did NOTHING -- retire it. METHOD that cracked it: read the actual live binary (not assume vanilla), static disasm (capstone) + ChatGPT frontier analysis + a 4-agent workflow to fan out the wall trace, then LIVE same-user RPM/WPM to confirm each lever before baking (esoteric-dll ools + scratchpad live_ctx_read.py / live_drawwin_poke.py). Related: `finding.wall_shift_SOLVED_drawwinL_ctx04`, `impl.scrguard_v117_built_verified_deployed`, `finding.scrguard_LIVE_black_resolved_offx_recenter_remains`, `finding.floor_fix_CONVERGENT_scr_virtualize` ### [milestone] DONE (v1.18 CONFIRMED LIVE): the world renders WHOLE behind the side panels -- black gone, floor+units+objects+walls+roofs all static, full-width, coherent. The hard part is over. *`milestone.world_behind_panels_DONE_v118` · 2026-07-20* Doug confirmed the v1.18 relaunch: with the inventory/character panels open, the ENTIRE world (ground tiles, NPCs, objects, AND walls/roofs) holds static and full-width behind the opaque panels -- no black dead-zone, no shift, no shear, world coherent, panel drawn on top. This is the goal the esoteric.dll PanelWorld feature was built for. Shipped as C:\Diablo II\esoteric.dll 1.18.0-worldwhole. Achieved by three composed levers (see [[solution.world_behind_panels_ARCHITECTURE]]). Remaining: Doug's cleanup list (config tidy, retire the dead shift-hacks + the ineffective wall/roof patches) -- polish, not the hard part. Related: `solution.world_behind_panels_ARCHITECTURE`, `finding.wall_shift_SOLVED_drawwinL_ctx04` ### [impl] v1.17 TRANSACTIONAL scr=0 world-pass guard: implemented in PanelWorld, CONFIRMED_SAFE on 3 adversarial angles, built + deployed *`impl.scrguard_v117_built_verified_deployed` · 2026-07-20* Implemented ChatGPT's transactional guard as pw_install_scrguard() in feature_panelworld.c, gated by [Features] PanelWorldScrGuard. Mechanism: E9-detour the GATE @0x6FAF4243 (6 bytes 0F857501 0000 = jne 0x6FAF43BE) -> 0x80-byte cave entry stub (pushfd/pushad; if depth==0 save scr@0x6FBCC414; depth++; if saved scr in {1,2} set scr=0; popad/popfd; REPRODUCE the original JNE via preserved EFLAGS -> push 0x6FAF4249 fall-through / push 0x6FAF43BE early-out; ret). E9-detour the EPILOGUE @0x6FAF43BE (5 bytes 83C420 C3 CC = add esp,0x20;ret) -> exit stub (pushfd/pushad; depth-- underflow-guarded; if depth==0 restore scr; popad/popfd; add esp,0x20; ret). Hooking the GATE (not body 0x6FAF4249) puts scr=0 BEFORE the prologue tile-collection reads it; every invocation incl. early-out balances depth. Only scr in {1,2} virtualized (0 and special-3 left alone). Blob is PIC for savedScr(+0x78)/depth(+0x7C) via CALL/POP; 5 absolute D2Client refs relocated to live cb (ASLR-safe). Uses eso_codecave_alloc(0x80) + eso_detour (verify-then-write, auto-refuses double-hook vs PanelWorld2P:reseed-ret which shares 0x6FAF43BE). VERIFICATION: my capstone disasm + byte-compare vs live binary PASSED (gate/epi bytes match, JNE target 0x6FAF43BE, 5 reloc fields correct, push targets correct). Then a 3-agent adversarial workflow ALL returned CONFIRMED_SAFE: (1) blob stack-balanced/EFLAGS-correct/depth-safe/PIC-correct; (2) DrawGame single-entry/single-exit, nothing branches into either E9, ALL exits funnel through 0x6FAF43BE, nothing enters 0x6FAF4249; (3) epilogue-restore scope safe -- scr is a D2Client-PRIVATE global, the 6 post-PASS2 tail calls are world/scene finalization + cross-DLL thunks (D2Win/D2gfx can't read scr), no UI/panel/hit-test runs inside DrawGame's tail. Only caveat (informational): a callee longjmp/throw would strand the guard, but D2's draw path uses neither. BUILT 1.17.0-scrguard (Zig cc x86, LoadLibrary OK), DEPLOYED C:\Diablo II\esoteric.dll (266240 bytes, sha 67728A57). INI clean-test config: PanelWorldScrGuard=1, FloorPanelShiftFix=0 (consumer patches reverted -> should compute 0 under the guard), PanelWorldNoShift=0, SecondPass=1 (A-E off), D2DXWiden=1. AWAITING Doug's live test: does the floor un-shift full-width behind the panel? does the panel still render + clicks stay coherent? log line to confirm install: '[feat] ScrGuard: transactional scr=0 world-pass guard ARMED'. Related: `finding.floor_fix_CONVERGENT_scr_virtualize`, `finding.floor_collection_shift_cullblock`, `finding.floor_patch_v115_CONFIRMED_shear_gone` ### [finding] @12 floor de-dup v1.14 FAILED live (still shears); count=12000 is an AGGREGATE not per-call; root cause unconfirmed *`finding.floor_dedup_v1_FAILED` · 2026-07-20* Doug tested attempt.floor_at12_dedup_v1 (v1.14, per-call seen[256] pointer de-dup) LIVE: the ground tiles STILL show significant distortion/smear -- the de-dup did NOT fix the shear. CORRECTION: the probe log '[PROBE glidefloor] count=12000' is p->cnt, an ACCUMULATED counter summed across many floor calls between dumps (probe.c:254), NOT one call; probe.c:91 bails on any call >4096 verts. So per-call vertex count is UNMEASURED (prior single capture ~12 verts). TWO candidate causes, NOT yet distinguished -- do NOT patch on a guess: (a) seen[256] cap too small -> a call with >256 unique verts leaves the tail double-comped -> shear; (b) the >4096-vert bail -> big floor calls skip the comp entirely (render shifted) while small calls render comped -> a MIX that reads as tearing. Need a per-call count measurement (add per-call log, relaunch). Source read confirmed D2DX reads each source pointer ONCE, so the in-place de-dup MODEL is right; the shear is our comp double-hitting shared pointers within the source array. LESSON: my read of Doug's screenshot as 'no shear' was WRONG -- pixels lie, Doug's eyes are ground truth (he caught it). Related: `attempt.floor_at12_dedup_v1`, `finding.floor_at12_CONFIRMED_shear` ### [finding] Floor -320 = native D2Client width/4 on SGD2FreeRes's 1280, DECOUPLED from offX; D2DX exonerated; scr@0x11C414 + width@0x6FB8BC48 *`finding.floor_shift_source_and_disasm` · 2026-07-20* SOURCE READ (4-agent workflow over refsrc/ SGD2FreeRes + d2dx-jarcho + D2MOO): (1) D2DX EXONERATED -- OnDrawVertexArray reads each floor GrVertex.x ONCE, maps linearly to NDC, no X offset; the -320 is baked in BEFORE D2DX -> the de-dup model is correct. (2) SGD2FreeRes has NO literal width/4 or panel-conditional floor shift; it only sets GeneralDisplayWidth=1280 (native width/4 = 320 vs vanilla 640->160) and ScreenShiftX = width/2-320 = 320. So the floor -320 is NATIVE D2Client re-deriving from width at draw time, AMPLIFIED (1280) + DECOUPLED from units by SGD2FreeRes. (3) D2MOO: vanilla shifts floor+units TOGETHER via one camera origin -> a floor-ONLY divergence is a MOD artifact. LIVE+DISASM (same-user ctypes RPM + capstone on on-disk D2Client, base 0x6FAB0000): scr @ D2Client+0x11C414 (abs 0x6FBCC414) = panel flag, LIVE-CONFIRMED 0->1 on inventory open (2=left). Resolution-WIDTH global = [0x6FB8BC48] (800/640 on disk; SGD2FreeRes patches to 1280 live). Vanilla panel handler @ 0x6FACE8B3 / 0x6FACEA3F: mov eax,[0x6FB8BC48]; cdq; and edx,3; add eax,edx; sar eax,2; neg eax (= -(width/4)) -> mov [0x6FBCC418],eax (offX) ; mov [0x6FBCC414],1 (scr). BUT offX (0x6FBCC418) = UNITS/WALLS shift: live offX read 0 with inventory open yet floor still -320 -> FLOOR IS DECOUPLED from offX (matches ledger_v2). Broad open-vs-closed D2Client memory diff found NO clean stored -320 global (camera drift = noise) -> floor shift is INLINE / a SGD2FreeRes hook. D2FloorTileParamsStrc (code-editing KB, Necrolis): nScreenPanels+20, pView+24, nViewXOffset+3C; instance addr 0x6FB9A990 is a 1.10f addr (0 refs in our 1.13c -> confirms ledger_v2 (c)). VERIFIED STACK: Jarcho d2dx 0.103.4 (glide3x, NOT bolrog -- that's the .bak) writes+loads bundled d2dx_sgd2freeres.dll + d2fps 1.0.2; PlugY 14.03. Related: `finding.floor_dedup_v1_FAILED`, `finding.floor_shift_ledger_v2`, `finding.floor_shift_measured_320` ### [finding] Floor -320 site still unpinned (D2Client inline vs SGD2FreeRes hook); RE offloaded to ChatGPT via FLOOR_RE_PACKAGE *`finding.floor_re_offload_chatgpt` · 2026-07-20* The floor's exact -320 site is unpinned: either a separate inline -width/4 in the D2Client floor-tile draw loop (gated by scr/nScreenPanels) OR a SGD2FreeRes live patch/hook of that draw. On-disk D2Client search: scr (0x6FBCC414) = 210 refs; filtered to 60 sites reading width AND scr AND computing -(x/4) -- too many to eyeball, and the on-disk floor draw may be vanilla (SGD2FreeRes patches it live). Doug has FREE ChatGPT frontier compute, so the RE is OFFLOADED: assembled FLOOR_RE_PACKAGE_for_ChatGPT.md (esoteric-dll root, ~231KB/56k tok) = findings + DATA A (the 60 D2Client candidate disasm sites, offX handler flagged) + DATA B (SGD2FreeRes D2Client render patches: set_screen_shift / set_tile_culling_bound / set_general_display_width_and_height) + DATA C (D2FloorTileParamsStrc + PATH_WorldToScreen). Asks for the exact site + a minimal version-guarded byte patch (esoteric.dll applies via WPM at load) un-shifting ONLY the floor. AWAITING ChatGPT result -> verify vs live binary -> implement. Toolkit: scratchpad/{live_floor_read,scan_disk,scan_scr_shift,gen_disasm,build_package}.py. OPEN FIX OPTIONS: A=fix the comp (measure per-call count -> hash-set de-dup + handle >4096 calls); B=upstream neutralize (this package). Related: `finding.floor_shift_source_and_disasm`, `finding.floor_dedup_v1_FAILED` ### [finding] FLOOR -320 SITE FOUND + byte-verified: D2Client 0x6FB10275 (scr->EBX=+/-width/4); 2-byte patch 0x6027D 74 15->EB 27 *`finding.floor_shift_site_FOUND_verified` · 2026-07-20* ChatGPT (frontier, fed FLOOR_RE_PACKAGE) located the floor shift; I VERIFIED it byte-for-byte vs on-disk D2Client. The recursive ground-tile traversal at D2Client 0x6FB10250 (RVA 0x60250) holds the floor-local panel offset: 0x6FB10275 mov eax,[0x6FBCC414](scr); 0x6FB1027A xor ebx,ebx (EBX=0 default); dec eax; 0x6FB1027D je 0x6FB10294 (scr==1 right); dec eax; jne 0x6FB102A6 (scr!=2); scr==2 LEFT -> EBX=+width/4; scr==1 RIGHT @0x6FB10294 -> mov eax,[0x6FB8BC48](width); sar eax,2; mov ebx,eax; neg ebx = -width/4. At 1280 (SGD2FreeRes-patched width) EBX=-320 right/+320 left; applied to tile coords at 0x6FB102A6+ (movsx eax,[ecx+6]; idiv [0x6FBA16B0]; sub edi,[0x6FBCC1F8]). VERIFIED FIX: patch 0x6FB1027D 74 15 (je 0x6FB10294) -> EB 27 (jmp 0x6FB102A6); EBX pre-cleared by the xor, so ALL panel states reach 0x6FB102A6 with EBX=0 -> NO floor shift, no shear, single site. SIGNATURE @RVA 0x6027C = 48 74 15 48 75 24 A1 48 BC B8 6F, confirmed EXACT on the live binary. Untouched: offX(0x6FBCC418 walls/units), panel state, and the culling block @0x6FB10C52/0x6FB10D12 (commits view window to 0x6FBCC1F0/1F4/1F8/1FC -- DO NOT patch that). NOT a SGD2FreeRes hook (it only sets width=1280, magnifying this vanilla /4). Implement via eso_patch_bytes (verify-then-write). Before test: turn OFF the competing FloorCompX comp (=0) + the PanelWorldNoShift/D2DXWiden shift-features so the clean upstream patch is tested alone. PENDING Doug live test. Related: `finding.floor_re_offload_chatgpt`, `finding.floor_shift_source_and_disasm` ### [finding] FloorPanelShiftFix v1.15 CONFIRMED LIVE: floor shear/divergence GONE (world coherent; whole-world vanilla shift remains, PanelWorld's job) *`finding.floor_patch_v115_CONFIRMED_shear_gone` · 2026-07-20* esoteric.dll v1.15 applied the verified 2-byte patch (esoteric.log: '[patch] OK floor-panel-shift @ 6FB1027C'). Doug live-tested in ISOLATION (FloorPanelShiftFix=1, PanelWorld=0, D2DXWiden=0, FloorCompX=0): the FLOOR SHEAR IS GONE -- the world renders COHERENT (waypoint diamond sharp, ground moves as one piece); Doug: 'everything is shifted now, as if back to vanilla'. INTERPRETATION: the D2Client 0x6FB10275 EBX=+/-(width/4) branch was the floor's EXTRA/DIVERGENT shift ON TOP of the whole-world camera recenter, NOT the world shift itself. Zeroing EBX makes the floor shift the SAME amount as units/walls -> coherent, no divergence, no shear. The multi-day floor shear/divergence bug is SOLVED. What remains is the whole-world vanilla camera shift (a SEPARATE mechanism -- exactly what PanelWorld's NoShift suppresses). Why PanelWorld sheared before: it held units at 0 while the floor's EBX branch kept diverging; with EBX=0 the floor no longer fights the no-shift lens. NEXT (ini only, no rebuild): re-enabled PanelWorld=1 + D2DXWiden=1 alongside FloorPanelShiftFix=1 -> the no-shift + widen stack should now hold the whole world coherently AND fill behind the panel. Pending Doug relaunch + test. Related: `finding.floor_shift_site_FOUND_verified`, `finding.floor_shift_measured_320` ### [finding] Floor+roof RESIDUAL shift = tile COLLECTION/cull shift @ D2Client 0x6FB10C52 (EBX=+/-width/4 -> sub ecx,ebx @0x6FB10D12 -> view window 0x6FBCC1F0) *`finding.floor_collection_shift_cullblock` · 2026-07-20* After the floor DRAW patch (0x6FB1027D, shear gone) + PanelWorld NoShift+Widen+D2DXWiden ALL applying (log 04:37: [NOSHIFT] offX0/ctxL-320->0, [WIDEN] ctx0C 960->1280, D2DXWiden 1280 asserted), Doug: floor AND roof STILL shift. FOUND the source -- a SECOND width/4 shift in the tile VIEW/CULL setup @ D2Client 0x6FB10C52: mov eax,[0x6FBCC414](scr); xor ebx,ebx; dec eax; je 0x6FB10CBC(right); dec eax; je 0x6FB10C91(left); each branch (gated also by ebp=[0x6FBCC1B0]==1) computes EBX=+/-(width/4) from [0x6FB8BC48], then 0x6FB10D12 'sub ecx,ebx' commits to the VIEW WINDOW 0x6FBCC1F0/1F4/1F8/1FC. The ground-tile traversal (0x6FB10250) READS 0x6FBCC1F8 for bounds -> shifts ALL collected tiles (floor+roof) at COLLECTION, BEFORE PanelWorld's NoShift zeroes the camera = the multi-session 'hook too late', now pinned. ChatGPT flagged 0x6FB10C52/0x6FB10D12 as risky (drives tile CULLING/selection) -> un-shifting may clip margins (mitigated by D2DXWiden=1280). This is the DRAW-shift (patched) vs COLLECTION-shift (this) split. Doug has ~unlimited free ChatGPT frontier compute (promo) -> chose to OFFLOAD: package E:\...\esoteric-dll\FLOOR_RE_PACKAGE2_collection_for_ChatGPT.md (context + cull-block disasm + traversal read + question: un-shift collection WITHOUT breaking culling). AWAITING ChatGPT -> verify vs binary -> patch. Related: `finding.floor_patch_v115_CONFIRMED_shear_gone`, `finding.floor_shift_site_FOUND_verified` ### [finding] CONVERGENT ANSWER (4 ChatGPT prompts): virtualize scr(0x11C414)=0 TRANSACTIONALLY around the world pass (PASS1@0x6FAF4302..PASS2@0x6FAF436A); scr is the shared SELECTOR, no single derived shift global *`finding.floor_fix_CONVERGENT_scr_virtualize` · 2026-07-20* Ran 4 frontier prompts (forward-RE, architecture-rethink, adversarial-audit, prior-art-research) + 3 KB-extracts. They CONVERGED: there is NO shared derived panelShiftX global; scr @0x6FBCC414 (ScreenOpenMode) is the shared SELECTOR that MULTIPLE INDEPENDENT consumers each read to recompute +/-(width/4) (floor EBX @0x6FB10275, view/cull @0x6FB10C52, others). Clean fix is NOT per-consumer whack-a-mole -- VIRTUALIZE scr=0 for the WORLD RENDER PASS, RESTORE it for the UI/panel draw (D2R does exactly this scene/UI separation). offX(0x11C418) is NOT the root (separate walls/units offset). Prior-art: SGD2FreeRes is the closest legacy prior art (patches CullingSpec draw_window_rect+tile_culling_window); no known LoD mod does full world-under-panel; a wrapper +320 is too late (can't recover un-collected tiles). Audit MISREAD our draw patch as asymmetric -- it's EB=unconditional jmp so ALL scr reach 0x6FB102A6 with EBX=0 (symmetric, re-verified). KEY: PanelWorld ALREADY has a scr=0 hold (g_hold_mode / ini PanelWorldHoldMode, DEFAULT OFF) but as an ASYNC thread holding scr=0 continuously incl. during UI -> 'diamond'/cull artifacts. FIX = TRANSACTIONAL: DrawGame @0x6FAF4249 loads ctx *[0x6FBCC3C4], calls PASS1(0x6FB3B0C0)@0x6FAF4302 then PASS2(0x6FB3B110)@0x6FAF436A(walls/roofs/units); PanelWorld already hooks the PASS1 call (PW_RVA_PASS1CALL 0x44302). Set scr=0 before PASS1, restore after PASS2. QUICK TEST first (ini PanelWorldHoldMode=1, no rebuild): does async scr=0 un-shift the floor (artifacts OK)? If yes -> implement transactional; then ChatGPT's check: reverting 0x6027D/0x6FB10D12 should still compute 0 (scr=0). Related: `finding.floor_collection_shift_cullblock`, `finding.floor_shift_persists_render_origin_drawL`, `finding.floor_patch_v115_CONFIRMED_shear_gone` ### [finding] v1.17 scr-guard LIVE TEST: BLACK DEAD-ZONE RESOLVED -- world now renders FULL-WIDTH BEHIND both panels (the ultimate goal's hard part). Remaining = DispW/4 camera RECENTER (offX), a separate lever. *`finding.scrguard_LIVE_black_resolved_offx_recenter_remains` · 2026-07-20* Doug's live test of the transactional scr=0 world-pass guard (v1.17): the black dead-zone is GONE -- the world renders full-width behind BOTH the inventory (right) and character (left) panels (grass/walls/NPCs continue behind the panel art). This is the 'world behind panels' goal's hardest part, achieved. Mechanism confirmed: guard holds scr=0 across DrawGame -> the native no-panel draw-window computes full width -> nothing scissored to black. NOTE this also proved the widen worker (eso_pw_widen @0x6FAF4302) is NEUTERED by the guard (it early-returns on mode!=1&&!=2, and the guard makes mode=0 inside the pass) -- so the full-width comes from the GUARD's scr=0 (native no-panel draw window), not the widen worker. REMAINING BUG: the world still SHIFTS when a panel opens (right panel -> world slides LEFT; left panel -> world slides RIGHT) = the vanilla DispW/4 play-area RECENTER. This is a SEPARATE lever from the scr-gated width: it rides in offX = GeneralPlayAreaCameraShiftX @0x6FBCC418, a STORED camera offset the panel handler writes ONCE at panel-open, OUTSIDE the world pass -- so the guard's scr=0 never resets it. offX is written-once and is NOT a detection flag (unlike scr), so pinning it 0 is safe + keeps hit-test coherent (screen<->world both use offX=0). NEXT: re-armed the async offX-hold (ini PanelWorldNoShift=1, pw_hold_offx_noshift) to test whether offX is the sole remaining lever. If the world centers -> fold offX into the guard transactionally (entry: save+zero offX@0x6FBCC418 alongside scr; exit: restore) for a clean race-free fix. If still shifted -> the recenter is in scrollX (scroll origin) and hold that instead. Related: `impl.scrguard_v117_built_verified_deployed`, `finding.floor_fix_CONVERGENT_scr_virtualize` ### [finding] WALL/ROOF SHIFT SOLVED: carrier = render-ctx drawWinL(ctx+0x04), baked -/+DispW/4 at panel-toggle by setter 0x6FB3A4D0; pin ctx+0x04=0/ctx+0x0C=DispW -> world whole behind panels. Live-confirmed. *`finding.wall_shift_SOLVED_drawwinL_ctx04` · 2026-07-20* The last lever. After scr-guard (scr=0 world pass) + offX-hold locked floor/units/objects, WALLS+ROOFS still slid +/-320. A 4-agent workflow (find-wall-lever) traced it: the panel-toggle handlers (0x6FAEB0A0 inventory/skill; the 0x6FB2F0C3/3A9/648 family) compute DispW/4 and do TWO things: (1) write offX(0x6FBCC418)+scr(0x6FBCC414) [already neutralized], AND (2) fold +/-DispW/4 into a viewport CENTER (ecx=[0x6FBA9E14]+/-DispW/4) passed to the generic draw-window setter 0x6FB3A4D0, which BAKES it into the render-context RECT: drawWinL @ ctx+0x04 and drawWinR @ ctx+0x0C (ctx=*[0x6FBCC3C4]). This bake happens at TOGGLE time (not per-frame; 0x6FB3A4D0 has ~110 callers, none per-frame), so no per-frame guard reaches it. FLOOR is immune because DrawGame re-derives scrollX(ctx+0x24) FROM drawWinL each frame -> drawWinL cancels in screenX=drawWinL-scrollX+tileAbsX; UNITS ride offX (held 0). But the wall/roof drawers (0x6FB3AE00/6C0/140) project through the baked drawWinL RAW with no cancelling term -> the +/-320 survives. nViewXOffset(0x6FB9A9CC) is DEAD CODE (0 refs whole-file) -- confirmed why holding it did nothing. VERIFICATION (couldn't be proven on-disk: wall accessors are D2gfx ordinal imports): LIVE same-user RPM read confirmed drawWinL(ctx+0x04): 0 panel-CLOSED -> -320 with RIGHT panel open (drawWinR 1280->960). LIVE WPM poke: forcing drawWinL=0/drawWinR=DispW while the inventory was open SNAPPED the walls into alignment with the floor (Doug confirmed y). FIX v1.18: pw_hold_offx_noshift (the offX-hold thread) now also pins ctx+0x04=0 and ctx+0x0C=DispW when a panel is open (ini PanelWorldHoldDrawWin=1). Race-free: drawWinL is written once at toggle, DrawGame only READS it. This completes 'world whole behind panels': scr-guard (width/black) + offX-hold (units) + drawWin-hold (walls/roofs/floor coherence). Live-read toolkit: scratchpad live_ctx_read.py / live_drawwin_poke.py. Related: `finding.scrguard_LIVE_black_resolved_offx_recenter_remains`, `impl.scrguard_v117_built_verified_deployed`, `finding.floor_fix_CONVERGENT_scr_virtualize` ### [finding] BOTH-PANELS (scr=3) all-black FIXED (v1.18.1): D2 SKIPS the world passes when ScreenOpenMode=3 (cmp[scr],3;je); scr-guard now virtualizes ANY non-zero scr {1,2,3}. *`finding.both_panels_scr3_black_FIXED` · 2026-07-20* With BOTH side panels open (inventory right + character left), ScreenOpenMode(scr @0x6FBCC414)=3 and the whole world went BLACK. LIVE read (both panels open): scr=3, offX=0, drawWinL(ctx+0x04)=0, drawWinR(ctx+0x0C)=1280 -- i.e. the both-panels draw window is ALREADY full+unshifted, so this was NOT a shift; D2 simply SKIPS the world render passes when scr==3 (the DrawGame `cmp [scr],3; je ` gates before the passes). The scr-guard originally virtualized only scr in {1,2} (ChatGPT deliberately left 3 alone), so scr=3 fell through -> passes skipped -> black. FIX: changed the guard's entry-stub condition from `cmp eax,1;je / cmp eax,2;jne` to `test eax,eax; je` (same 10 bytes, no relocation drift) so it virtualizes scr=0 for ANY non-zero scr {1,2,3}; with scr=0 the cmp/je no longer skips -> passes run -> world renders behind both panels. offX/drawWin hold thread condition also widened `mode==1||2` -> `mode!=0` (idempotent for scr=3 since its window is already correct). Shipped v1.18.1-bothpanels, Doug confirmed fixed. Verified the modified blob via capstone before deploy. Related: `solution.world_behind_panels_ARCHITECTURE`, `milestone.world_behind_panels_DONE_v118`, `finding.wall_shift_SOLVED_drawwinL_ctx04` ### [attempt] @12 floor comp de-dup: comp each UNIQUE vertex pointer once per call (shear fix, perspective OFF confirmed) *`attempt.floor_at12_dedup_v1` · 2026-07-19* Doug confirmed he does NOT play with D2 'Perspective' video mode on -> the floor is orthographic, so a FLAT +320 comp is geometrically valid (no per-Y scaling), which makes the shared-vertex de-dup the correct fix for the shear (finding.floor_at12_CONFIRMED_shear). CHANGE (src/probe.c, PK_VTX12 branch): added a per-call seen[256] pointer set; when comp!=0 each vertex POINTER is comped exactly ONCE (linear-scan de-dup, skip duplicates) so shared strip/fan verts get +320 not +640. Crash-safety unchanged (null-ptr skip + integer NaN/Inf exponent check before any x87 fld). Built with portable Zig (tools/build.ps1) + deployed to C:\Diablo II\esoteric.dll (264704 bytes, mtime 2026-07-19 20:50). ini armed: FloorCompX=320, glidefloor=vtx12 D2Glide 0x11250 0xBC00, Survey=0. PREDICTION: floor now translates a clean +320 (fills the panel-open dead-zone) WITHOUT the horizontal smear. PENDING: Doug relaunch (PlugY, non-elevated via explorer) + open right panel + report. If it still shears -> either >256 distinct verts/call (raise cap) or the shear is in D2DX's fan expansion downstream (go upstream: write-BP the SGD2FreeRes floor-shift global). CAVEAT: seen cap 256 -> a single @12 call with >256 distinct verts would leave the tail un-de-duped (observed calls ~12 verts, so unlikely). Related: `finding.floor_at12_CONFIRMED_shear`, `finding.floor_draw_path_DEFINITIVE` ### [finding] SURVEY/phase-corr min-max envelope is BLIND to a sub-tile-period floor shift *`finding.survey_minmax_aliasing_blind` · 2026-07-19* The [SURVEY] probe (grDrawVertexArrayContiguous vertex X min/max, closed vs open) reported dMinX=0 for the floor caller 0x6A55 (x=-32..1296 both states), which I nearly read as 'floor reaches the GPU unshifted'. WRONG: the floor is a periodic full-screen tile grid; a -320 (=DispW/4) shift REFILLS the same min/max envelope, so the envelope is invariant under the exact shift Doug sees. Same aliasing trap that made diff_shift.py phase-correlation return dx=0 on the floor grid. RULE: never use a min/max envelope or a periodic-signal correlation to decide whether a tiled floor shifted; use a NON-periodic landmark (Doug's waypoint template-match gave the true -320) or read a specific tile's abs coord. Doug's direct visual observation (floor+waypoint slide -320, units/walls hold) stands; the probe simply cannot see it. Related: `finding.floor_shift_measured_320`, `probe.glidevtx`, `diag.diff_shift_aliasing` ### [finding] Floor -320 is baked into tile coords in the DrawGame PROLOGUE via native ctxL=-320, before the lens's PASS1-hook correction *`finding.floor_shift_root_ctxl_prologue` · 2026-07-19* Live [NOSHIFT] log (session PID17412, right panel mode=1): ctxL -320->0 every frame -- the native game sets ctx+0x04 (draw-window left) = -DispW/4 = -320 on right-panel-open, and the NoShift lens zeroes it at the PASS1-call hook 0x6FAF4302. But the floor room-collection reads ctxL a beat EARLIER (DrawGame prologue, before the hook; council-wave-6 note feature_panelworld.c:590), so the -320 is baked into the collected tile abs coords (tileAbsX); the flush 0x6FB4ED50 (screenX=ctxL-scrollX+tileAbsX) then faithfully carries it with ctxL=0. offX held 0 (units/walls safe); ctxL NOT held early enough for the floor. [FLOORPARAMS]/[SCRATCH] channels empty => nViewXOffset & scratch-scroll are NOT the carrier; ctxL-baked-in-prologue is. Source of the -320: view-setup fn 0x6FB29140 computes -DispW/4 and writes it to ctx+0x04 via cull-bound setter 0x6FB3A4D0 (pass-through). TEST IN FLIGHT: esoteric.ini FloorCompX=320 + glidevtx floor-filter 0x6840 -> compensate the floor's vertex X +320 at the D2Glide submit; if the floor snaps into alignment, upstream-baked confirmed and the root fix = author ctxL=0 before the prologue collection (earlier hook) or NOP the 0x6FB29140 -320 write. Related: `finding.survey_minmax_aliasing_blind`, `finding.floor_shift_measured_320`, `sym.cullbound_setter_6FB3A4D0`, `sym.viewsetup_6FB29140`, `feature.floorcompx` ### [finding] DEFINITIVE floor draw path: D2Glide 0xB890 via grDrawVertexArray@12; -320 baked in vertex screen-X upstream *`finding.floor_draw_path_DEFINITIVE` · 2026-07-19* Settled by LIVE execution counters (safe codecave, no guessing): (1) The floor is drawn by D2Glide fn 0x6F85B890 (an FPU polygon rasterizer) via grDrawVertexArray@12 (thunk 0x6F856026 -> IAT 0x6F861250). @12 fires ~54k calls/2s, ONLY from 0xB890 (call sites 0xBC57/0xBEF7). (2) The workflow's 0x6F856290 AND 0x6F856AA0 (both have the pretty DispW/4+neg -320 logic and call @12) are DEAD -- entry execution counters = 0 while walls_6840 counter = 310969. Patching either's mode-1 cmp (0x62B8 / 0x6AC9 : 01->7f) did NOTHING (confirms dead; corroborates the old v1.9.1 KB 'dead 0x6290'). The live dispatch calls 0x6AA0 from 0x8572B2 but 0x6AA0 still never executes -- 'called-from' != 'executed'. (3) Walls are @16 grDrawVertexArrayContiguous (IAT 0x6F8611D0) via 0x6840; the 4 probed callers 0x6A55/0xA344/0x9AF4/0xA555 are ALL @16 (walls) -- which is why FloorCompX on 0x6A55 moved walls not floor (wrong primitive). (4) The @12 vertices are an array of POINTERS (arg3=0x6F866308 -> ptrs 0x6F865F18..) into 0xB890's OWN static scratch buffer at D2Glide 0x6F865F18 (0xB890 opens with 'mov edi,0x6f865f18'); that buffer is READWRITE and holds REAL screen coords (captured GrVertex X=720.0 Y=164.0, 7 floats/28B stride, panel open). So the floor's -320 is baked into the vertex SCREEN-X and rides from D2Client through D2Glide unchanged; 0xB890 and its callers 0xD1A0<-0xDF4F compute NO DispW/4 (the DispW touches there are RGB565 unpack + res-config writes 0x280/0x320). CONCLUSION: the -320 is baked ONCE upstream in D2Client's floor tile abs-coord (tileAbsX at flush 0x6FB4ED50), in a value the NoShift lens does NOT touch (not ctxL/offX/nViewXOffset -- holding all three 0 never moved the floor). The aliasing-blind [SHIFT]/[SURVEY] probes (min/max envelope on a periodic tile grid) could not see it. FIX MUST BE UPSTREAM (static patch), NOT a live per-vertex comp: 3 ACCESS_VIOLATION crashes came from hand-writing the shared 450x/frame vertex buffer live (FPU/accumulation/downstream-D2DX-fragility). Live toolkit built this session: log_drawers.py (entry exec counter+mode histogram, codecave), iat_count.py (draw-primitive call counter), safe_at12_log.py (READ-ONLY arg capture), patch_dg_byte.py (1-byte D2Glide patch). Codecave lesson: stub must NOT overlap its own data buffers; rel8/rel32 = target-(end of jump); verify-disasm-before-fire catches bugs. Related: `finding.survey_minmax_aliasing_blind`, `finding.floor_shift_root_ctxl_prologue`, `finding.floor_shift_measured_320`, `sym.d2glide_floor_drawer_B890`, `sym.d2glide_wall_drawer_6840` ### [finding] D2Client tile flush 0x6FB4ED50 is DEAD -- the floor's live screen-X exists ONLY in the D2Glide @12 vertex buffer *`finding.d2client_flush_6FB4ED50_DEAD` · 2026-07-19* Read-only exec probe at the flush 0x6FB4ED6E (cmp bl,0xff): with the game IN-GAME (worldCtx 0x21FD39D4) and the RIGHT panel open (ScreenOpenMode=1) and the hook confirmed installed (byte 0xE9), the flush counter stayed idx=0 -- it NEVER FIRES. So the tile flush screenX=ctxL-scrollX+tileAbsX @0x6FB4ED50 (the projection the NoShift lens PW_RVA_FLUSHHOOK and the floor-path workflow both analyzed) is DEAD CODE. This is why holding ctxL/offX/nViewXOffset=0 never moved the floor: they feed a dead projection. SAME dead-code trap as the D2Glide twins 0x6290/0x6AA0. CONSEQUENCE: there is NO live D2Client static-patch point of the classic form; the floor's finalized screen coordinate exists ONLY in the D2Glide @12 vertex buffer (0xB890 output at D2Glide 0x6F865F18, GrVertex X=720 panel-open, READWRITE). Likely the vanilla D2Client tile path was redirected by SGD2FreeRes (d2dx_sgd2freeres.dll, the res+sidebar mod that also owns SetTileCullingBound 0x739D221C LEFT-=320/RIGHT+=320) into the D2Glide 0xB890/@12 path, and the -320 sidebar shift is applied on that live path -- NOT in vanilla D2Client. PRAGMATIC FIX: a +320 comp on the @12 (grDrawVertexArray, IAT 0x6F861250) floor vertices, done in crash-tested C in esoteric.dll (masked FPU, skip NaN/inf, gate ScreenOpenMode==1) -- targets the floor primitive only (walls=@16). Live hand-rolled per-vertex comp crashed (ACCESS_VIOLATION) 3x: read-only capture PROVED the vertices are pointer-array into a READWRITE static buffer refilled per-call (no accumulation) with real coords, so the crash was FPU-on-special-value or a downstream D2DX effect from the shifted X -- both handled by the framework's C path. ROOT (deeper): trace 0xB890 coord source or SGD2FreeRes's floor redirect. Related: `finding.floor_draw_path_DEFINITIVE`, `finding.survey_minmax_aliasing_blind` ### [finding] @12 CONFIRMED as the live floor path (Doug visual); +320 vertex comp SHEARS the floor (shared-vertex mesh) *`finding.floor_at12_CONFIRMED_shear` · 2026-07-19* BREAKTHROUGH: v1.13 comps the floor's grDrawVertexArray@12 vertex X (+320 right panel, in crash-safe C, retaddr-filtered to 0xB890's submits [0xBC00,0xBF00)). Doug's DIRECT visual confirmation: the comp VISIBLY affects the FLOOR ('you are ABSOLUTELY touching the right place now') -- units/walls unaffected. So @12 / D2Glide 0xB890 IS the live floor draw, settled beyond doubt after a session of dead-code traps (0x6290/0x6AA0 @12 twins dead, D2Client flush 0x6FB4ED50 dead). NEW SYMPTOM: instead of a clean +320 translate, the floor renders DISTORTED/SHEARED (horizontal smear of the ground plane; panel-closed baseline is clean; units undistorted). This matches the safety-review's #1 predicted correctness risk: the floor is a SHARED-VERTEX mesh (grDrawVertexArray@12 = array of POINTERS to GrVertex; D2's isometric floor tiles share edge/corner vertices), so blindly comping every pointer-array entry double-comps shared vertices (+640 on shared, +320 on unique) -> shear. Also possible: the two @12 submit sites in 0xB890 (0xBC57/0xBEF7) reuse the static buffer 0x6F865F18, or per-frame accumulation across the ~450 calls/frame. FIX DIRECTION: comp each UNIQUE vertex exactly once (per-call or per-frame de-dup of vertex pointers), OR shift at a stage upstream of the shared-vertex expansion, OR find where SGD2FreeRes applies the sidebar shift and neutralize it there. Crash-safety now solid (integer NaN/Inf exponent check before any x87 fld -- the review caught that the earlier fld-on-SNaN was the same bug as the 3 asm crashes). Next: research the D2 floor mesh structure + SGD2FreeRes sidebar shift + D2DX grDrawVertexArray, then a shear-free comp. Related: `finding.floor_draw_path_DEFINITIVE`, `finding.d2client_flush_6FB4ED50_DEAD` ### [finding] 2026-07-12 (v1.10.0 probe): the panel-world dead-zone clip is IN D2DX. Floor geometry crosses grDrawVertexArrayContiguous at max 1296 (138 verts in [960,1280)) yet the dead zone is BLACK. NOT D2Client (issues 1236), NOT D2Glide (submits past 960). The [640,960] tiles render via the SAME path, so the [960,1280] verts are valid, not degenerate -> D2DX clips them. Fix belongs at D2DX via a runtime glide3x hook from esoteric.dll (rebuild not available here). *`finding.panelworld_clip_in_d2dx`* ### [finding] 2026-07-12 (workflow d2dx-panel-clip-fix, source+bytes): D2DX renders the world into its NARROW _gameSize={960,720} (Metrics.cpp desktop 1280x720). Clip cutoff == 960 == _gameSize.width: framebuffer sized to gameSize (SetFramebufferSize), UpdateViewport sets RSSetViewports+RSSetScissorRects to that rect, GameVS.hlsl NDC.x/960 -> x>=960 off-RT+out-of-scissor+NDC>1, rasterizer-clipped. The SurfaceIdTracker retag (minx>=gameSize/2) is a RED HERRING (surfaceId only feeds FXAA, color write unconditional). Fix = force gameSizeWide via SetCustomResolution(1280,720). *`finding.d2dx_gamesize_960_is_the_clip`* ### [finding] [SUPERSEDED 2026-07-12: the 640 was the CULL with GlideUnclamp=0. Gated probe with GlideUnclamp=1 -> submit 1264, 338 verts in the dead zone, still black => the binding clip is a D2DX-downstream DROP, not D2Glide 640.] CORRECTION 2026-07-12 (v1.10.0 probe, PANEL-OPEN frames): with a panel open, the D2Glide live drawer 0x6F856840 submits floor geometry to grDrawVertexArrayContiguous at max screenX ONLY 640 (=DisplayWidth/2, the mode-1 right bound), band[960,1280)=0. D2Client still ISSUES to 1280 (TILEBAND). So the binding panel-open clip is D2Glide 0x856840 at 640, NOT D2DX. My earlier 'crosses at 1296 -> D2DX' read used NO-PANEL frames (before [WIDEN] mode=1) by mistake -- the harness was right, I read the wrong frames. finding.panelworld_clip_in_d2dx and finding.d2dx_gamesize_960 describe the NO-PANEL/second clip; the panel-open lever is here. Likely TWO clips: D2Glide 640 (panel) + D2DX 960 target; both need fixing. v1.9.1 cull-NOP showed no visual change -> re-testing GlideUnclamp WITH the probe to see if it widens the submit past 640 (cull-skip) or the 640 is a vertex-X clamp elsewhere in 0x856840. *`finding.panelopen_clip_is_d2glide_640`* ### [finding] Council wave-4 (chat m5 + clau m6 converge): the panel-open dead zone is the 960-WIDE RENDER-TARGET EXTENT, not a surfaceId routing/split. D2Glide announces 960 gameSize to D2DX at grSstWinOpen; SGD2FreeRes widened D2 to 1280 without updating that; D2DX allocates a 960 world RT and clips the 1280-coord floor. Fix they gave = D2DXGetConfigurator->SetCustomResolution(1280,720) before grSstWinOpen. STATUS: we BUILT this (feature_d2dx.c / D2DXWiden) -> 122 successful calls but it REGRESSED the menu res and did NOT fill. So their consensus fix is tested-and-insufficient. *`council.wave4_consensus`* Related: `hook.D2DXWiden`, `OQ.d2dx_runtime_gamesize` ### [finding] Council wave-4 dissent (gem m4): the drop is the UI-RETAG. D2DX SurfaceIdTracker:88 retags world batches with minx>=640 as USER_INTERFACE when a panel is open; retagged verts get the UI orthographic projection (wrong for world coords) and render into the void = black. Fix he gave = hot-patch the minx>=640 Jcc in glide3x to fail ONLY during the floor draw call, then restore. NOTE: clau m6 REFUTES this - [640,960) is retagged too yet renders fine, so a rule that fires on visible geometry can't be the blackout rule. *`council.wave4_gem_retag`* Related: `council.wave4_consensus` ### [finding] Council wave-4: BOTH chat m5 and clau m6 demanded a PRE-PATCH measurement we never ran -- read D2DX's ACTUAL D3D11 world render-target width + viewport (RSGetViewports width; OMGetRenderTargets->GetDesc width/height) at the first Game-pass Draw, plus grSstWinOpen incoming args. chat's decision tree last branch: 'viewport 1280, RTV 1280+, constants 1280, still cutoff at 960 => your binary differs; next hook is ID3D11DeviceContext::Draw, inspect bound VS/PS and MRTs on the first missing floor batch' -- NOT SurfaceIdTracker. *`council.wave4_mandatory_measurement`* Related: `OQ.d3d11_rt_width_unmeasured` ### [finding] Doug reframe 2026-07-12: the dead zone, the panel-open camera SHIFT, and the world-vs-units DESYNC are ONE cause -- opening a panel CHANGES THE WORLD VIEW-STATE (offX=-320 + play-area narrowing). Clean target: render the world in the NO-PANEL view-state (no shift, full width) and composite the panel on top = ONE view-state (matches the ledger META-LESSON). Neutralize the change AT ITS SOURCE (the panel-open handler 0x6FB5AEC0 writing offX @0x6FB5AF7C), leaving the mode flag 0x6FBCC414 intact for culling (attempt A proved mode is load-bearing). *`reframe.one_cause_view_state`* Related: `reframe.keep_shift_retired`, `finding.shift_desync_is_half`, `OQ.units_shift_left` ### [finding] RETIRED CONSTRAINT: 'keep the native -320 left-shift' (offX=0x6FBCC418, written @0x6FB5AF7C). Preserved through attempts A-H. Doug 2026-07-12 REVERSES it: wants ZERO shift on panel-open (world stays put, panel composites on top). 'both moves are inappropriate when opening the inventory if we have the world behind as we want.' *`reframe.keep_shift_retired`* Related: `reframe.one_cause_view_state` ### [finding] Doug confirms 2026-07-12: panel-open, the WORLD shifts HALF as much as characters/items (units = ~2x world). Ledger theories (both unconfirmed): (a) multi-layer shift (floor vs wall/roof/units = separate draw fns), (b) double motion-smoothing (d2fps + D2DX both interpolate the camera move -> 2x on moving units, 1x on static terrain). Exactly-2x favors (b). MEASURE before patch: log offX(0x6FBCC418) + ctx+0x24 world-scroll + one unit screenpos, panel-open vs closed. *`finding.shift_desync_is_half`* Related: `OQ.units_shift_left`, `reframe.one_cause_view_state` ### [finding] CONFIRMED 2026-07-12 (v1.11.2 panel-gated probe, mode=1, GlideUnclamp=1): floor geometry REACHES the dead zone at the D2Glide->D2DX seam -- submit max 1264-1296, 338 verts in [960,1280) -- yet renders BLACK. The dead-zone black is a DROP at/after D2DX, NOT a D2Glide clip (GlideUnclamp already carries the submit to 1264). Independently reproduces council wave-4 (geometry crosses into D2DX in the dead zone, black). Next diagnostic = the council's demanded D2DX world-RT-width read (extent 960 vs UI-retag). SEPARATE from the world/objects SHIFT desync. *`finding.clip_is_d2dx_drop_confirmed`* Related: `council.wave4_consensus`, `council.wave4_gem_retag`, `OQ.d3d11_rt_width_unmeasured`, `finding.shift_desync_is_half` ### [finding] CONFIRMED 2026-07-12 (v1.11.3 [SHIFT] snapshot, standing still): opening a side panel shifts the FLOOR/terrain by exactly -320 (1x = -DispW/4), and the WHOLE shift is offX(0x6FBCC418) 0->-320 driving ctx+0x04 0->-320. scrollX(ctx+0x24) is NOT touched by the panel (log changes there = the player walking). Terrain pan (ctxL - scrollX) moves -320. So the terrain-shift LEVER is offX->ctx+0x04. The objects-2x is a SEPARATE source: the shared tile projection is only 1x, so units (PASS2, a separate draw fn) pick up an EXTRA -320 downstream, NOT the camera/ctx. Which source (unit re-read of offX vs motion-smoothing) is unmeasured. *`finding.terrain_shift_is_offx_ctxL`* Related: `finding.shift_desync_is_half`, `reframe.one_cause_view_state`, `OQ.units_shift_left` ### [finding] CONFIRMED 2026-07-12 (d2re xref 0x6FBCC418): offX is READ at 173 sites across D2Client, but WRITTEN once (0x6FB5AF7C, =-DispW/4=-320 on panel-open). So the surgical lever is the single write, not the 173 reads (blanket-patching reads = ledger attempt A = infeasible). CROSS-REF attempt A (held offX=0): un-shifted FLOOR + UNITS but NOT walls/roofs (a SEPARATE shift source) AND broke click-to-move (the hit-test reads offX). CONCLUSION: the panel shift is MULTI-SOURCE (offX for floor/units; a different global for walls/roofs) and HIT-TEST-COUPLED. Units read offX twice -> the 2x (theory a favored; not proven vs motion-smoothing). Naive offX=0 is OUT (already failed). The reframe goal (render world in no-panel view-state) needs an architecture decision, not a single patch. *`finding.offx_read_173_one_write`* Related: `finding.terrain_shift_is_offx_ctxL`, `reframe.one_cause_view_state`, `OQ.units_shift_left` ### [finding] Council wave-5 (all 3 CONVERGE): kill the panel shift by AUTHORING a coherent NO-PANEL WORLD VIEW-STATE for the whole world-draw -- NOT per-value patching, NOT a mid-frame ctx swap (that's the C-G scheduling trap: 173 readers read at different times, no single safe instant). Two flavors: m1(chat)=post-hook the panel-transition routine (holds offX write 0x6FB5AF7C); after it runs, canonicalize the world-camera outputs (offX=0, wall/roof offset=0, ctx+0x04=0, ctx+0x0C=DispW) and HOLD persistent; keep ScreenOpenMode=1/2 (UI+input stay live). m2(gem)+m4(clau)=STATE-SPOOF: hook the world-draw, temporarily set the panel-state flags to 0 (lie 'no panel open'), call original (D2 naturally zeroes offX + wall offsets + hit-test), restore flags before the panel compositor 0x6FB739E0 -> ONE hook, hit-test solves itself, UI intact. Hit-test coherent BY CONSTRUCTION (render + mouse ray read the same authored state). *`council.wave5_view_state_authorship`* Related: `reframe.one_cause_view_state`, `OQ.units_shift_left`, `OQ.walls_shift_source`, `council.wave5_d2dx_retag_is_the_black` ### [finding] Council wave-5 CRITICAL LINK (m2 gem): the SHIFT fix and the dead-zone BLACK are the SAME downstream issue. Unshifting the world pushes valid geometry into x>=640; D2DX SurfaceIdTracker:88 retags world batches minx>=640 as USER_INTERFACE while a panel is open -> the UI orthographic projection drops those world verts -> black/corrupt right half. So authoring the no-panel view in D2Client is NECESSARY BUT NOT SUFFICIENT: we must ALSO defeat the D2DX minx>=640 retag during the world draw (runtime hot-patch the compare in glide3x, restore after). Defeating that retag IS the fix for the black. *`council.wave5_d2dx_retag_is_the_black`* Related: `finding.clip_is_d2dx_drop_confirmed`, `council.wave4_gem_retag`, `council.wave5_view_state_authorship` ### [finding] ATTEMPT I (v1.12.0, council wave-5 recipe): author the NO-PANEL world lens each frame before PASS1 -- in the existing widen hook (0x6FAF4302), when a panel is open set offX(0x6FBCC418)=0, ctx+0x04(draw_left)=0, ctx+0x0C(draw_right)=DispW, dirty|=1; ScreenOpenMode left TRUE (culling+UI live). Grounded: the panel handler 0x6FB5AEC0 writes ONLY offX+mode from DisplayWidth (no separate wall global), so this run also DIAGNOSES OQ.walls_shift_source. OUTCOMES: (a) floor+walls+units all un-shift (character/waypoint bisected by panel) => walls' source IS the ctx, blocker closed; (b) floor+units un-shift but walls stay => walls recompute in PASS2 (0x6FB3B110) => name that next; (c) clicks coherent? (offX=0 persistent should self-solve per m1/m2); (d) x>=640 goes black => D2DX SurfaceIdTracker retag confirmed = the 2nd knob (m2). ==> RESULT (v1.12.0, WIN): [NOSHIFT] fired once (offX -320->0, ctxL -320->0, ctxR->1280) and HELD every frame (offX 0->0). The WORLD UN-SHIFTED -- character/waypoint bisected by the panel, walls came with it, clicks fine. Geometry reaches the dead zone (probe band[960,1280)=66-104, TILEBAND max=1277). TWO residuals remain: (1) the dead zone is still BLACK = the D2DX retag (m2's 2nd knob); (2) 1-2 FLOOR sub-layers still shift (a source our ctx authoring doesn't cover). The multi-source shift is otherwise SOLVED by authoring the no-panel lens each frame. *`attempt.I_noshift_lens`* Related: `reframe.one_cause_view_state`, `OQ.walls_shift_source`, `finding.offx_read_173_one_write`, `council.wave5_view_state_authorship`, `council.wave5_d2dx_retag_is_the_black`, `struct.D2Client_RenderContext` ### [finding] THE FLOOR + THE MOUSE HIT-TEST are governed by the SAME play-area rect: playAreaW (0x6FBA9E14, RVA 0xF9E14) / playAreaH (0x6FBA9E18), narrowed to ~640 on panel-open. This is the '4-tile buffer'/Hall-of-Mirrors region: the world isn't rendered there by default. SOURCES (all trusted): EP's own d2-re-reference v0.8 note (playAreaW view struct, 'same rect governs mouse hit-test -> the non-clickable black') + our diag.c RD(0xF9E14); phrozen_code Discord 2022-11 -- Necrolis: 'd2dx might be doing it via a clipping rect' (= the RenderContext viewport/scissor we re-found); Mir Drualga (SGD2FreeRes author): 'those black rects are hardcoded in SGD2FreeRes to hide the HOM from not adding assets'; EP: 'a 4 tile buffer... also appears on the left if you open the character stats pane'. FIX (v1.12.2): author playAreaW=DispW each frame -- the bottom-void fix on the horizontal axis. Should make the floor cover the dead zone AND make click-to-move work standing still (currently only works while moving). *`finding.playarea_governs_floor_and_hittest`* Related: `OQ.floor_sublayer_residual`, `attempt.I_noshift_lens`, `OQ.d2dx_retag_black`, `struct.D2Client_RenderContext` ### [finding] THE FLOOR SHIFT IS INTERNAL TO PASS1 (0x6FB3B0C0), not a pre-authorable global. Authoring the no-panel lens each frame BEFORE PASS1 (offX=0, ctxL=0, ctxR=DispW) un-shifts WALLS + UNITS perfectly (they read ctxL) but the FLOOR still shifts. Ruled out, each a NO-OP at our pre-PASS1 hook: playAreaW(0x6FBA9E14)=already 1280; nViewXOffset(D2FloorTileParamsStrc 0x6FB9A9CC, EP's 'terrain shift')=already 0 ([FLOORPARAMS] never fired). Yet the floor is ISSUED full-width (TILEBAND tiles reach screenX 1278, 637 in [960,1280)) -- so it's drawn wide but at the SHIFTED position. The floor-flush projection we RE'd (screenX=ctx+0x04 - ctx+0x24 + tileAbsX) SHOULD use our ctxL=0; since the floor is shifted anyway, the FLOOR uses a different scroll/offset OR its collected tileAbsX carry the shift. Candidate: the DrawGame scratch scroll (0x6FBA9F60/5C) the passes overwrite. Need to hook INSIDE PASS1 or find the floor's internal horizontal source. *`finding.floor_shift_internal_to_pass1`* Related: `attempt.I_noshift_lens`, `finding.playarea_governs_floor_and_hittest`, `OQ.floor_sublayer_residual`, `struct.D2Client_RenderContext` ### [finding] CONVERGENCE: the floor's shift is applied at a point our PASS1-call hook (0x6FAF4302) MISSES -- we zero the globals AFTER the floor grabbed the shifted value. Candidates: (m4/clau) the floor SNAPSHOTS the scratch scroll at the DrawGame prologue (0x6FAF42E0 mov[0x6FBA9F60],ecx / 0x6FAF42E6 mov[0x6FBA9F5C],edx from ctx, at the TOP of DrawGame, before our hook) OR bakes the shift into tileAbsX at room COLLECTION (tilesub pre-projects); (m2/gem) the floor's D2Gfx draw-offset (ctx+0x04) is set early via D2Gfx_SetDrawOffset before our hook -> hook HIGHER; (m1/chat) the floor submits the CORRECT unshifted X and D2Glide applies -DispW/4 INTERNALLY via its nScreenPanels arg in the live drawer 0x6F856840 -> force nScreenPanels=0 for the floor path. DECISIVE delta (m1): track one floor tile's submittedX (D2Client) vs vertexX (D2Glide) panel-open vs closed -- clientDelta=-320 => upstream (D2Client); glideDelta=-320 => D2Glide. v1.12.4 tests the scratch-scroll hypothesis (author 0x6FBA9F60/5C = ctx + log the delta). UNIFYING PIN (m4): both the floor AND the click are a value = DispW-320 that our hook doesn't reach; one resolution-differential scan collects EVERY DispW-320 address. *`council.wave6_floor_hook_too_late`* Related: `finding.floor_shift_internal_to_pass1`, `OQ.floor_sublayer_residual`, `OQ.d2dx_retag_black` ### [finding] TWO side-answers for later. (1) MENU shrink from SetCustomResolution(1280): no native menu-exclusion; scope it DYNAMICALLY by D2 client state -- hook the game-enter/exit (state 0x01 menu / 0x03 in-game) and toggle SetCustomResolution(800 menu / 1280 in-game), or gate it in the top-level render dispatch. (2) CLICK-into-black-only-while-moving (m4): it's the click-to-move path-VALIDITY / acceptance rect, a separate rect derived from DispW-320, refreshed only by motion (stale when standing still). Fix: widen the acceptance rect (find via the resolution-differential DispW-320 scan); if clicks then register but won't path, force the walkable-region refresh independent of motion. *`council.wave6_menu_and_click_answers`* Related: `council.wave6_floor_hook_too_late`, `OQ.floor_sublayer_residual` ### [finding] NEW LEADING THEORY (m10, corroborated by m12): the floor/roof shift is a D2DX POST-SUBMIT bug, not a D2Client origin. (1) D2DX SurfaceIdTracker retags world batches with minx>=gameSize.w/2(=640) as USER_INTERFACE when a panel is open. (2) In OnBufferSwap, D2DX applies the player MOTION-PREDICTION offset ONLY to surfaceId!=UI && cat!=Player -- so the UI-mislabeled floor/roof batches SKIP the camera offset while the walls get it -> they slide apart AS the prediction changes with click-to-move = 'follows the mouse'. Isolated batches (the palace doorway) most susceptible. Ruled out upstream: offX held 0, floor still shifts (D2Client camera not the source). m12: the mouse-tracking PROVES live-read (not baked at collection); the black split MIRRORS sides with the panel = a compositing/retag split, not a clip. DEFINITIVE TEST (m10): d2dx.cfg [optouts] nomotionprediction=true -> if the slide stops, confirmed. FIX (m10, keeps motion pred): in the grDrawVertexArrayContiguous hook, temporarily set ScreenOpenMode(0x6FBCC414)=0 for WORLD-TILE calls only (filter by return addr: D2Glide+0xBEFC DrawFloor, +0x67AB/+0x67B9 DrawWall1/2), restore after -> D2DX won't retag the floor as UI. Start with +0xBEFC. *`council.wave6b_d2dx_motion_prediction`* Related: `finding.floor_shift_internal_to_pass1`, `OQ.floor_sublayer_residual`, `finding.clip_is_d2dx_drop_confirmed`, `OQ.d2dx_retag_black` ### [finding] THE FLOOR AND WALLS SUBMIT FROM DIFFERENT D2Glide CALL SITES (verified via disasm of the call to the grDraw wrapper 0x6F856026): DrawFloor -> 0x6F85BEF7 call (return 0x6F85BEFC = D2Glide+0xBEFC); DrawWall1 -> 0x6F8567A6 (return 0x67AB); DrawWall2 -> 0x6F8567B4 (return 0x67B9). Our glidevtx probe was filtering return-addrs [0x6840..0x6B40] = the WALL calls, so all our floor-drawer RE + the FloorCompX seam-compensation were hitting the WALLS, not the floor -- which is exactly what Doug saw (the correctly-placed tiles moving by +320). CORRECT floor filter = 0xBEFC. chat's public-1.13c-table mapping (DrawWall1/2=0x67AB/0x67B9, DrawFloor=0xBEFC) is CONFIRMED for our build. v1.12.6 + INI: glidevtx arg=0xBEFC, FloorCompX=320 -> compensate the FLOOR at its seam. *`finding.floor_vs_wall_grdraw_sites`* Related: `finding.floor_shift_internal_to_pass1`, `council.wave6b_d2dx_motion_prediction`, `OQ.floor_sublayer_residual` ### [finding] FLOOR+ROOF SHIFT LEDGER (evidence-backed; walls+units are FIXED, floor+roof still shift when a panel opens). Each source RULED OUT with version+log: * WALLS/UNITS fixed: v1.12.0 authoring no-panel lens (offX=0/ctxL=0/ctxR=DispW) pre-PASS1. PROVEN: [NOSHIFT] log fires each frame + visual (walls/units snap into place). * offX (0x6FBCC418): RULED OUT v1.12.5. PROVEN: [SHIFT] 'offX 0->0' held every frame, floor STILL shifted. * playAreaW (0x6FBA9E14): RULED OUT v1.12.2. PROVEN: [NOSHIFT] 'playAreaW 1280->1280' (already full, no-op). * nViewXOffset (D2FloorTileParamsStrc 0x6FB9A9CC): RULED OUT v1.12.3. PROVEN: [FLOORPARAMS] NEVER logged (condition fvx!=0 false -> already 0 at our hook). * scratch scroll (0x6FBA9F60/5C): RULED OUT v1.12.4. PROVEN: [SCRATCH] NEVER logged (already == ctx scroll). * D2Glide drawer 0x856840 vertex math: RULED OUT by DISASM -- vertexX = baseX + subtileX; the 640 midpoint feeds ONLY the cull bounds ([esp+0x30]/[esp+0x38], already NOP'd), no -DispW/4 on the vertex. * D2DX motion prediction: RULED OUT -- d2dx.cfg nomotionprediction=true -> no change (Doug); + the shift is STATIC not dynamic (Doug correction), which motion-prediction wouldn't produce. * grDraw site 0xBEFC (chat's 'DrawFloor'): RULED OUT v1.12.6. PROVEN: [PROBE glidevtx] arg=0xBEFC -> count=0 (0 hits). 0x6F85BEF7 IS a call site on disk but is NOT on the live floor path in our build. VERIFIED-live: WALLS submit via grDraw returns 0x67AB/0x67B9 (disasm: 0x6F8567A6/B4 call the wrapper 0x856026; probe with arg=0x6840 fired in v1.11-1.12.5). 0x6840==walls is INFERRED (Doug: compensating that filter moved the CORRECTLY-placed tiles) + chat labels them DrawWall1/2. OPEN: the FLOOR's actual LIVE grDraw return address is UNIDENTIFIED (not 0x6840=walls, not 0xBEFC=count0). RIGOROUS NEXT: an UNFILTERED return-address logger (probe arg=0) that dumps every distinct grDraw return addr + its vertex-X range + count -> identify the floor by its signature, THEN compensate/fix that exact site. *`finding.floor_shift_ledger`* Related: `finding.floor_vs_wall_grdraw_sites`, `finding.floor_shift_internal_to_pass1`, `OQ.floor_sublayer_residual`, `protocol.evidence_rule` ### [finding] CONVERGENCE: measure the floor batch's vertex X at the grDraw seam, panel-CLOSED vs panel-OPEN. dMinX=-320 => D2Client submits the floor shifted (UPSTREAM); dMinX=0 => D2DX shifts it (DOWNSTREAM). Also capture the caller return-addr to NAME the floor's live site (our 0xBEFC guess fired 0x). chat: D2DX's 0xBEFC 'DrawFloor' is only an IdentifyGameAddress label, not the live path; forward is unchanged (OnDrawVertexArrayContiguous copies x/y verbatim). gem: if downstream, read D2DX's VS projection matrix via a D3D11 VSSetConstantBuffers vtable hook (matrix[12]=X translation). clau: DISSENT-then-agree -- 'mouse kills it' argument uses the RETRACTED mouse-tracking premise (Doug corrected to STATIC), so its mouse-sweep isn't our discriminator; BUT clau's mouse-INDEPENDENT argument is strong: walls render correct through the SAME D2DX pipeline, so if D2DX shifted world batches walls would shift too -> predicts dMinX=-320 (client-side). clau CAUTION taken: do NOT do the ScreenOpenMode=0 mask (attempt-A anti-pattern: fixes readers who look, misses others, desyncs hit-test). IMPLEMENTED v1.12.7 [Probes] Survey=1 + glidevtx arg=0 -> [SURVEY] rows per caller, closed vs open. *`council.wave7_grdraw_seam_delta`* Related: `finding.floor_shift_ledger`, `finding.floor_vs_wall_grdraw_sites`, `protocol.evidence_rule` ### [finding] THE LIVE grDraw CALLERS (v1.12.8 Survey, panel-CLOSED only so far): D2Glide+0x6A55 (n=87710, x=-32..1296, the dense one), 0xA344 (n=16767, x=0..1151), 0xA555 (n=5340, x=303..1152), 0x704A (n=895, x=1216..1296), 0x9AF4 (n=293, x=11..788). CORRECTION (rigor): finding.floor_vs_wall_grdraw_sites claimed walls=0x67AB/0x67B9 (from disasm of call sites 0x6F8567A6/B4) -- but those return-addrs DO NOT APPEAR in the survey, so they do NOT fire on the live path. The disasm found call sites that aren't reached. The REAL callers are the 5 above. Still OPEN: which is the FLOOR (need panel-OPEN data -> the caller with dMinX~-320). v1.12.8's run was the menu run (open[n=0] everywhere); a park+toggle run is needed. NOTE the survey only counts IN-GAME frames (eso_probe_frame runs from the DrawGame hook, not at the menu). *`finding.live_grdraw_callers`* Related: `finding.floor_shift_ledger`, `finding.floor_vs_wall_grdraw_sites`, `council.wave7_grdraw_seam_delta`, `protocol.evidence_rule` ### [finding] MENU FIX. v1.12.9 grSstWinOpen log: the IN-GAME open = res=4099(0x1003) with worldCtx=0x214439AC (NON-NULL); the MENU opens = res=7 and res=4097 with worldCtx=NULL. The world render ctx (*0x6FBCC3C4) is set at the in-game open and null at the menu -> gate is worldCtx!=null. FIX v1.13.0: eso_d2dx_set_wide calls SetCustomResolution(1280) only when worldCtx!=null -> menu keeps native size, in-game world stays widened. (grSstWinOpen arg1=window handle; the resolution is arg2=[esp+0x2C].) *`finding.menu_worldctx_gate`* Related: `OQ.d2dx_retag_black` ### [finding] SURPRISE: the floor shift is likely DOWNSTREAM in D2DX, not at the D2Client->grDraw submit. Survey open-vs-closed, 5 live callers: 0x6A55 (floor, n=828558, closed x=-32..1311 / open x=-32..1296 => dMinX=0, dMaxX~-15: STABLE, NOT shifted at submit); 0x09AF4 (dead-zone FILL, open x=640..1280 n=22082 = the world we enabled behind the panel, WORKING); 0xA555 (dMinX=-84); 0xA344 (dMinX=33); 0x704A (dMinX=16). NO CALLER submits shifted by -320. So the floor is submitted un-shifted and D2DX shifts it downstream => chat/gem's branch, AGAINST clau's client-side prediction (reconciled: D2DX shifts only floor-category batches, not walls). CAVEAT: aggregate min/max is confounded by the changing tile set on open, but the ABSENCE of any -320 across all 5 callers is robust. nomotionprediction=true gave no change, so it's a D2DX transform OTHER than motion-prediction. NEXT: specific-tile track OR gem's D3D11 VSSetConstantBuffers matrix read. *`finding.survey_floor_is_downstream`* Related: `council.wave7_grdraw_seam_delta`, `finding.live_grdraw_callers`, `finding.floor_shift_ledger`, `protocol.evidence_rule` ### [finding] BREAKTHROUGH: the -320 FLOOR/ROOF shift source is ScreenShiftX = D2Client+0x11B9A0 (mir names it 'ScreenShiftX'; planqi names 0x11B9A0 'PanelOffsetX -- horizontal shift applied when side panels are open'). SGD2FreeRes REPLACES vanilla SetScreenShift with Sgd2fr_D2Client_SetScreenShift (set_screen_shift/replacement.cpp L54-63; hook D2Client+0xC39F6 in 1.13c, found via write-BP on ScreenShiftX/Y): screen_shift_x = display_width/2 - 640/2 => at 1280 wide = 640-320 = the centering shift. The floor/tile projection reads ScreenShiftX. Our no-shift lens zeroed offX (GeneralPlayAreaCameraShiftX 0x11C418) + ctx draw_left -- fixing WALLS+UNITS -- but NEVER touched 0x11B9A0, so FLOOR/ROOF stayed shifted. That is the exact residual. NEXT: author 0x11B9A0=0 each frame alongside offX (or neutralize the SetScreenShift hook @0xC39F6); x32dbg live-read of 0x11B9A0 panel-open expects the -320-class value. *`finding.screenshiftx_is_floor_shift`* Related: `finding.floor_shift_ledger`, `finding.survey_floor_is_downstream`, `reframe.d2dx_no_per_category_transform` ### [finding] OVERTURNS finding.survey_floor_is_downstream. Full source read of D2DX (bolrog + Necrolis fork): D2DX applies NO per-category floor-vs-wall positional transform -- TextureCategory::Floor(4)/Wall(6) are never used to offset geometry (GameHelper::RefineTextureCategoryFromGameAddress only TAGS batches for AA). The ONLY post-capture world shift is UnitMotionPrediction (D2DXContext.cpp OnBufferSwap ~408-436): UNIFORM across all world geometry (floor AND wall), skipped only for surfaceId==USER_INTERFACE(16383). We already set nomotionprediction=true -> no change. RenderContext::UpdateViewport sets ONE uniform viewport+scissor; Constants CB carries only screenSize/invScreenSize, no translate. => the -320 floor shift is NOT downstream in D2DX; it is upstream (ScreenShiftX). The survey's dMinX 'downstream' read was an artifact of the confounded aggregate + not having neutralized 0x11B9A0. *`reframe.d2dx_no_per_category_transform`* Related: `finding.survey_floor_is_downstream`, `finding.screenshiftx_is_floor_shift` ### [finding] The panel/dead-zone architecture SGD2FreeRes imposes (clean-room from its develop-branch source): (1) SetGeneralDisplayWidthAndHeight -> GeneralDisplayWidth/Height (0xDBC48/4C). (2) SetScreenShift (hook D2Client+0xC39F6) -> ScreenShiftX/Y (0x11B9A0/4) centering = the -320 floor shift. (3) SetTileCullingBound (hook D2Client+0x8A4D0; CullingSpec{flags@0x0, draw_window_rect RECT@0x4}) -> tile draw + cull windows. (4) DrawLeft/RightScreenBackground (hook D2Client+0xC3AA6) -> paints the BLACK dead-zone: d2gfx::DrawRectangle(0,0,width/2,height) 'to stop transparent-DC6 cheaters' + tiles 800BorderFrame around the 640 play area; stock bg draws NOP'd at +0x271ED / +0x270F1. => black perimeter = DrawScreenBackground; floor shift = SetScreenShift. To reveal the world behind the panel: neutralize DrawScreenBackground and zero ScreenShiftX. *`finding.sgd2freeres_deadzone_architecture`* Related: `finding.screenshiftx_is_floor_shift`, `finding.clip_is_d2dx_drop_confirmed`, `OQ.d2dx_retag_black` ### [finding] LIVE against the running 1.13c binary (esoteric.dll mod ON), via elevated ReadProcessMemory/WriteProcessMemory. (1) worldCtx (D2Client+0x11C3C4) CONFIRMED null@menu, non-null in-game (0x214739D4) -> verifies finding.menu_worldctx_gate vs the binary. (2) FALSIFIES finding.screenshiftx_is_floor_shift: ScreenShiftX (0x11B9A0) = +320 CONSTANT in-game (SGD2FreeRes centering, re-written per-frame), identical in mode 0/1/2; a live freeze of 0x11B9A0=0 did NOT move the floor (only a border element flickered) -> the FLOOR does NOT read ScreenShiftX. (3) PANEL-DELTA SCAN (12 globals + render-ctx struct fields, panel-closed vs inventory vs character): opening a panel changes NO horizontal shift global -- ScreenShiftX, offX(0x11C418; our lens holds 0), OffsetPtX(0x11C1F8=0), ctxL(0), ctxR(1280), playAreaW(0xF9E14=1280) ALL constant across mode 0/1/2. Only ctxScrollX(=global 0x119960) and mouse offsets vary, and that is CAMERA MOVEMENT (character walking), not the panel. (4) CONCLUSION: the floor residual shift is STATIC (matches Doug: 'always shifted, not sliding') and is driven by NO authorable global on panel-open -> it is INTERNAL to the floor pass (PASS1 / FloorFlush 0x9ED50), a constant floor-vs-wall X-base difference. Re-confirms finding.floor_shift_internal_to_pass1; global-poke approach exhausted. NEXT: x32dbg breakpoint inside FloorFlush 0x9ED50 with a panel open -> read the X base the floor computes and compare to the wall pass (PASS2 0x8B110). RPM cannot observe internal computed values. *`finding.live_rpm_2026_07_18`* Related: `finding.screenshiftx_is_floor_shift`, `finding.floor_shift_internal_to_pass1`, `finding.menu_worldctx_gate`, `reframe.d2dx_no_per_category_transform` ### [finding] CORPUS-CONFIRMED floor-shift mechanism -- converges with finding.floor_shift_internal_to_pass1. The FLOOR is a SEPARATE drawer from walls/roofs (Necrolis: MULTIRES_DrawGroundTiles vs DrawLowerWallTiles/DrawUpperWallTiles/DrawRoofTiles; SwineFlu confirms floor is a distinct recursive fn per Necrolis's reversed code at d2mods.info t=59615). Floor horizontal placement = D2FloorTileParamsStrc @ D2Client+0xEA990 (0x6FB9A990, sizeof 0x44): nScreenPanels(+0x20)=which panels open, pView(+0x24), clip nXMin/Max nYMin/Max(+0x2C..0x38), nViewXOffset(+0x3C = D2Client+0xEA9CC)/nViewYOffset(+0x40). Necrolis: 'anyone chasing the D2DX floor -320 shift should hook the FILL of this struct and watch nScreenPanels -> nViewXOffset.' Struct filled INSIDE the floor path (1.10f fill 0x6FAA3330 -- rebase to 1.13c by xref of writes to +0xEA990). EXPLAINS why our lens fails on the floor: pre-PASS1 hook authors the render ctx (walls read it -> fixed) but nViewXOffset is set LATER internally from nScreenPanels; our ledger's 'nViewXOffset already 0' was a PRE-fill timing error. FIX: hook the struct fill / DrawGroundTiles and zero nViewXOffset (or force nScreenPanels=0 for the floor pass only) so floor matches the unshifted walls. Cull rect = D2RoomCullArgsStrc{pView,pCullingRect,pClientRoom,bTilesOnly} (Necrolis). *`finding.d2floortileparams_floor_shift`* Related: `finding.floor_shift_internal_to_pass1`, `finding.live_rpm_2026_07_18`, `finding.floor_shift_ledger`, `finding.screenshiftx_is_floor_shift` ### [finding] ROOT CAUSE PINNED. The floor+roof static -320 shift is ctx[+0x24] (scrollX), NOT ctx[+0x04] and NOT any global we ruled out. TWO PROJECTION FAMILIES: (1) FLOOR+ROOF flush @0x6FB4ED50 (D2Client+0x9ED50): screenX = ctx[+0x04](drawL) - ctx[+0x24](scrollX) + tileAbsX -- NO offX term. (2) WALLS+UNITS project via offX=ScreenXShift 0x6FBCC418 (RVA 0x11C418) which our lens zeroes -> they re-center. ctx = *0x6FBCC3C4 (RVA 0x11C3C4) = the SAME struct the lens writes (confirmed: both callers of tile drawer 0x6FB4EC10 -- 0x6FB3B224 in PASS2 + 0x6FB4EE7A -- push edi=ctx; PASS1 0x6FB3B0C0->DrawRooms 0x6FB2A9F0 on same ctx). So ctx[+0x04]=0 DOES reach the floor -> not the residual. THE 320 ENTERS ctx[+0x24] at DrawGame 0x6FAF4230: scrollX = scrollCenterX(thunk 0x6FABC1D2 on view *0x6FBCBBFC=playerBase) - (R-L)/2 (0x6FAF4270-79), written 0x6FAF42F8 'mov [esi+0x24],ecx' (esi=*0x6FBCC3C4) right before PASS1 call 0x6FAF4302. Lens moves ctx[+0x04]/[+0x0C] SYMMETRICALLY (-320,960->0,1280) so R-L stays 1280, (R-L)/2 stays 640, scrollX UNAFFECTED -> panel camera re-center +DispW/4=320 survives in ctx[+0x24]. Floor pan = 0-(scrollX_nopanel+320) = static -320. DispW/4=1280/4=320. Roof same path. SUPERSEDES finding.screenshiftx_is_floor_shift, finding.survey_floor_is_downstream, finding.d2floortileparams_floor_shift (that struct was 1.10f). FIX: correct ctx[+0x24] to hold no-panel pan before PASS1 (extend lens; ctx[+0x24]-=DispW/4 for open-panel side, sign per ScreenOpenMode 0x6FBCC414). Floor+roof only. Confirm sign/magnitude via one live RPM of ctx[+0x24] panel-open vs closed standing still. *`finding.floor_shift_is_ctx_scrollx`* Related: `finding.floor_shift_internal_to_pass1`, `finding.live_rpm_2026_07_18`, `finding.d2floortileparams_floor_shift`, `struct.D2Client_RenderContext` ### [finding] DEFINITIVE ROOT CAUSE of the floor/roof panel-shift. It is in SGD2FreeRes.dll (loaded @0x739C0000), NOT D2Client -- D2Client's whole floor path is 100% VANILLA (verified by live-vs-disk patch map: DrawGame 0x44230, scroll write 0x442F8, floor flush 0x9ED50, floor drawer 0x9EC10, DrawRooms 0x7A9F0, PASS1 0x8B0C0, PASS2 0x8B110 all byte-identical to disk). SGD2FreeRes hijacks SetTileCullingBound: D2Client+0x8A4D0 is patched 'jmp 0x739C16B0' (trampoline) -> real fn 0x739D21D0. Inside 0x739D21D0 a PANEL-CONDITIONAL block: 0x739D2212 call 0x739D64A0 (nonzero when a side panel open); 0x739D221A je 0x739D2230 (skip if none); 0x739D221C add [ebx+4],0xFFFFFF60 (top-=160); 0x739D2223 add [ebx],0xFFFFFEC0 (LEFT-=320=DispW/4=0x140); 0x739D2229 add [ebx+8],0x140 (RIGHT+=320). ebx = the tile-cull/draw window (arg+0x14). => on panel-open SGD2 moves the floor's tile window by +/-320. The vanilla flush (screenX = ctx+0x04 - ctx+0x24 + tileAbsX) then faithfully draws the shifted tiles; walls read offX (our lens holds 0) so they don't move -> floor vs walls split by 320. RECONCILES the paradox that render ctx (drawL/scrollX) measured FLAT open-vs-closed (floor_scan same-position: both drawL=0 scrollX=-208, delta 0): the shift rides in tileAbsX collected from the shifted cull window, NOT in the ctx. Other SGD2 hooks confirmed live: SetScreenShift 0xC39F6 -> 0x739D1F80; DrawScreenBg 0xC3AA6 -> 0x739C10B0; stock left/right bg NOP'd @0x271ED/0x270F1. FIX target: neutralize/adjust the panel block at 0x739D221C or its gate je@0x739D221A (NOP-test flips je->jmp). CONFIRMED SHIFT (Doug's 3 screenshots: no-panel centered, inventory=world pushed left, character=pushed right). *`finding.floor_shift_sgd2_tileculling`* Related: `finding.floor_shift_is_ctx_scrollx`, `finding.d2floortileparams_floor_shift`, `finding.live_patch_map_method`, `finding.sgd2freeres_deadzone_architecture` ### [finding] FLOOR-SHIFT HYPOTHESIS LEDGER (chain of superseded theories, each with why it fell): (a) ScreenShiftX D2Client+0x11B9A0 -- FALSIFIED live (WriteProcessMemory froze it to 0, floor did not move; only a border flickered). (b) 'D2DX shifts floor-category downstream' -- FALSIFIED by reading bolrog+Necrolis D2DX source (NO per-category transform; only uniform UnitMotionPrediction, and nomotionprediction=true gave no change). (c) D2FloorTileParamsStrc.nViewXOffset @0x6FB9A990/0x6FB9A9CC -- FALSIFIED: that instance address is 1.10f, ZERO refs in our 1.13c DLL (xref + immediate byte-search); the struct LAYOUT transfers but the 1.13c instance differs. (d) 'ctx+0x24 scrollX carries +320' (5-front on-disk disasm assault) -- FALSIFIED live: floor_scan same-position closed-vs-open showed drawL=0/scrollX=-208 in BOTH (delta 0). (e) CONFIRMED: SGD2FreeRes SetTileCullingBound live patch shifts the tile-cull window +/-320 on panel-open (finding.floor_shift_sgd2_tileculling). META-LESSON: (a)-(d) all came from analyzing the ON-DISK vanilla DLL. The shift is in a MOD's live patch. Doug flagged the layered-mod interaction; the live-vs-disk patch map resolved it. Also: a same-position live read (stand still) is required -- 20-min-apart readings are confounded by camera movement. *`finding.floor_shift_ledger_v2`* Related: `finding.floor_shift_sgd2_tileculling`, `finding.live_patch_map_method`, `finding.floor_shift_ledger`, `protocol.evidence_rule` ### [finding] FREEZE-scrollX TEST (hammered ctx+0x24 = *(D2Client+0x11C3C4)+0x24 from -240 to -600, 3439 writes/30s): BOTH floor AND walls flickered/slid (Doug: 'a lot of flickering on the walls and floors'). CONCLUSIONS: (1) the D2Client flush 0x6FB4ED50 / render-ctx path IS the LIVE world render -- SGD2FreeRes does NOT draw the floor via its own renderer; the vanilla screenX = ctx+0x04(drawL) - ctx+0x24(scrollX) + tileAbsX is what shows. Rules IN the D2Client path. (2) CORRECTS finding.floor_shift_is_ctx_scrollx: walls do NOT use offX-not-scrollX -- BOTH floor and walls read ctx+0x24. The world renders as a unit through this ctx. (3) By elimination: drawL+scrollX are FLAT panel-open-vs-closed (floor_scan same-position delta 0), the flush is live, so the panel shift can only ride in tileAbsX (the COLLECTED tile coords). Leading theory: our no-shift lens forces drawL=0 AFTER DrawRooms already collected tiles using the game's shifted drawL (the council-wave-6 'hook too late'). ALSO: NOP of SGD2 SetTileCullingBound panel-block (0x739D221C) had ZERO impact -> that cull-widen is not the shift. NEXT: read tileAbsX/screenX at the flush (codecave probe or hardware BP) panel-open vs closed; and re-check WHEN our lens sets drawL vs when DrawRooms reads it. *`finding.freeze_scrollx_flush_live`* Related: `finding.floor_shift_is_ctx_scrollx`, `finding.floor_shift_sgd2_tileculling`, `finding.floor_shift_internal_to_pass1`, `finding.floor_shift_ledger_v2` ### [finding] OBJECTIVE PIXEL-PRECISE MEASUREMENT of the floor shift. Setup (Doug): Barbarian standing on the Act1 encampment waypoint, no merc, no effects. The waypoint = co-located FLOOR marker (brown cross-diamond) + UNIT markers (blue swirls). Captured game frames closed vs open, template-matched each marker: waypoint brown-cross (FLOOR tile) moved LEFT 480 screen-px (dy 0, conf 0.56); blue swirl (UNIT) 0px (conf 0.82); campfire embers (OBJECT) 0px (conf 0.68); walls 0 (prior overlay). Screen is 1920w upscaled by D2DX from game-render 1280 (DispW) => factor 1.5 => 480 screen-px = 320 GAME-px = exactly DispW/4. DEFINITIVE: the FLOOR-TILE LAYER shifts exactly DispW/4 = 320 game-px LEFT on panel-open; units/objects/walls hold at 0 (offX layer, our lens already fixes them). Confirms Doug's exact claim to the pixel. RECONCILES the whole investigation: the render ctx (drawL/scrollX) reads FLAT open-vs-closed because the 320 rides in the collected floor tileAbsX, NOT the camera scalars; the earlier full-floor overlay aliased the 320 (=~5 tiles) but the waypoint's non-periodic cross revealed it. Method note: capture the game window via PIL ImageGrab (D2DX/D3D content grabs fine); measure in GAME-space = screen/1.5. FIX TARGET: un-shift the floor tile collection (tileAbsX) by DispW/4. Ruled out as the source: ScreenShiftX 0x11B9A0 (poke null), SGD2 SetTileCullingBound cull-block 0x739D221C (NOP null), the render ctx (flat). It is in the floor tile-coordinate computation (DrawRooms / the floor pass tileAbsX). *`finding.floor_shift_measured_320`* Related: `finding.freeze_scrollx_flush_live`, `finding.floor_shift_ledger_v2`, `finding.floor_shift_internal_to_pass1`, `finding.floor_shift_sgd2_tileculling` ### [note] D2DX Metrics.cpp: desktop 1280x720 -> gameSize={960,720} (narrow), gameSizeWide={1280,720}. 960 = the narrow render target. Chosen by GetSuggestedGameSize(desktop, wide=!NoWide) (D2DXContext.cpp:1170); d2dx.cfg nowide=false + [game]size=[1280,720] should force 1280 -- CONFIRM at runtime. *`glide3x.gameSize` · glide3x* ### [note] SurfaceIdTracker.cpp:88: ScreenOpenMode&1 (panel open) && minx>=gameSize.width/2 (=640) RETAGS a world batch surfaceId=USER_INTERFACE. Prior audit said 'only tags, no cull' -- but the visible world clips ~640 with a panel open, so re-examine whether the UI-tag changes world compositing/clipping. Prime suspect. *`glide3x.SurfaceIdTracker_retag` · glide3x* ### [note] # The Esoteric Synthesis — D2 is a graph projected through one lens *Our own synthesis, fusing the ingested canon with what we verified against the binary. This is the "make it ours" layer: the basic facts assembled into the single architecture that explains our whole hunt.* ## 1. The graph (what exists) — screen-independent, seed-deterministic `Game` owns everything. The spine is `Game -> Act -> Level -> Room2 -> Room1 -> UnitAny`. - **Room2** is the static, seed-deterministic *description* of a room; **Room1** is the live *instance*, hydrated only near players. World state is data, not pixels. - **UnitAny** is the universal entity: a tagged union with three arms -- **A** `pUnitData` (*what it is*: Player/Monster/Object/Item), **B** `pPath -> Room1 -> ... -> Act` (*where it is*), **C** `pInfo`/skills (*what it can do*). Stats (`pStats -> StatListEx -> Stat[]`) and inventory (`pInventory -> item UnitAny list`) are a **parallel graph off the same root** -- same UnitAny, disjoint bodies, meeting only at the screen. - Nothing in this graph stores a screen coordinate. Ever. ## 2. The lens (how it reaches the screen) — one render context per frame Every tile and unit is projected through ONE struct we verified, [[struct.D2Client_RenderContext]] (`*0x6FBCC3C4`): `screenX = draw_left(+0x04) - scroll_x(+0x24) + worldX`. The renderer walks the Arm-B spine (`Act -> Level -> Room2 -> Room1 -> unit list`), turns each entity's room-space Path position into a `CellContext.dwPosX`, and blits its `GfxCell` through `D2Client -> D2gfx -> D2Glide -> D2DX`. The lens is the *only* place world->screen happens. ## 3. The phenomenon (the theory that explains everything we've chased) Opening a panel **does not touch the graph** -- it mutates the **lens**: - `offX -> draw_left(+0x04) = -320` -> the **shift** ([[finding.terrain_shift_is_offx_ctxL]]) - `draw_right(+0x0C)` narrowed to ~960 -> the **issue-clip** So the shift, the clip, and the world/units desync are **one cause**: a view-state mutation ([[reframe.one_cause_view_state]]). The graph is innocent; the lens moved. Units take an *extra* -320 in PASS2 (a second lens-read) -- that is the 2x desync. And the residual black is a **second lens downstream**: even when we widen `draw_right` and D2Glide submits the far tiles (338 verts into [960,1280)), **D2DX drops them** ([[finding.clip_is_d2dx_drop_confirmed]]). Two lenses, one graph. ## 4. Why this makes the fix tractable Because the graph is screen-independent (the canon *proves* it), we can render the whole Arm-B walk in a **no-panel lens** (offX=0, draw_right=DispW) and composite the panel on top -- safely, without touching the 173 offX readers or any world struct. The canon tells us it's safe; our RE tells us the exact two knobs (the D2Client lens + the D2DX drop). That is the entire path to ZERO black with the world behind the panel. **One line:** *D2 is a screen-independent object graph drawn through a single per-frame lens; every symptom we've chased is the lens being rewritten when a panel opens -- never the graph.* *`note.esoteric_synthesis`* Related: `struct.D2Client_RenderContext`, `finding.terrain_shift_is_offx_ctxL`, `reframe.one_cause_view_state`, `finding.clip_is_d2dx_drop_confirmed`, `struct.UnitAny` ### [note] # The units struct web (D2 1.13c) and how it reaches our render path Grounding: UnitAny field offsets are from the in-repo 1.11b Structure Reference (`research/kb/corpus/forum/highvalue/t47809.json`); the render-context side is from `grimoire/symbols.jsonl` (`struct.D2Client_RenderContext`, `D2Client.DrawRooms`, `D2Client.FloorFlush`). Offsets are stable UnitAny-relative through 1.13c. Room1/Room2/Level/Act are named by their canonical fields; the repo confirms the *roots* of that chain (see the lifecycle section) but not every byte offset inside it, so those inner offsets are named, not asserted numerically. ## 1. UnitAny is the hub — a tagged union with three "arms" Every player, monster, object, item, missile and tile is one `UnitAny`. It carries a type/id header and then hands off to per-type payloads through three pointer arms: - **Identity header**: `+0x00` dwType, `+0x04` dwTxtFileNo, `+0x0C` GUID (links a server unit to its client twin), `+0x10` eMode (anim/action mode), `+0x18` actNo, `+0x28` dwInitSeed. - **Arm A — what it *is*** (`+0x14 pUnitData`, a union of 5 classes per the repo): `pPlayerData` / `pMonsterData` / `pObjectData` / `pMissileData` / `pItemData`. Which member is live is selected by dwType. "Note, there is no pTileData." - **Arm B — where it *is*** (`+0x2C pPath`, a union of 2 classes per the repo): `pStaticPath` (Objects, VisTiles, Items) vs `pDynamicPath` = **Path** (Players, Monsters, Missiles). - **Arm C — what it can *do*** (`+0xA8 pSkills`): the skill controller = **Info**, "a list of all skills the unit has (pointers to pSkill)." Other hub fields that matter for lifecycle/draw: `+0x5C pStatListEx`, `+0x60 pInventory`, `+0xE4 pPrevUnit` (prev in the unit-type list), `+0xE8 pPrevUnitInRoom` ("previous unit in the current room"). ## 2. Arm A — the payload structs - **PlayerData**: name + quest/waypoint blocks; the human-facing identity. It's the union member picked when dwType == player. - **MonsterData** (sizeof 0x60): `+0x00 pMonStats` = the monstats.txt record; `+0x04 Components[16]` = the component ids (HD/TR/LG/...) that drive which cels get drawn. - **ObjectData**: points to its objects.txt/ObjectMode record — the static-world furniture arm. - **ItemData** (sizeof 0x74): quality/flags/fingerprint. Items also thread through `pInventory` (Inventory: `+0x08 pOwnerUnit`, `+0x0C pFirstItem`, `+0x14 pInvInfo` equipped-gear list) — i.e. an item's UnitAny is reachable both as a standalone unit and as a node inside another unit's inventory. These are pure data payloads: they never point back up to rooms. Position lives entirely in Arm B. ## 3. Arm B — Path → Room1 → Room2 → Level → Act (the spatial spine) This is the chain that matters for rendering, because a unit's *screen* position is derived from its *Path* position projected through the room it sits in. - **Path** (dynamic, for moving units) carries the fine sub-tile position (dwPosX/dwPosY plus target/offset fields) and a back-pointer to its **Room1**. **StaticPath** (objects/tiles/items) is the lean version: a room pointer + a fixed tile position (no velocity, no targets), because static things never move. - **Room1** = the collision/active room: its unit list is what the draw walks (units chained via `UnitAny+0xE8 pPrevUnitInRoom`), it holds the CollMap, near-room pointers, and a link up to **Room2**. - **Room2** = the DRLG/preset room: geometry, presets, near-room graph, and the link up to **Level**. - **Level** = the area (its levelno indexes Levels.txt), owning its Room2 list and linking up to **Act**. - **Act** = `pDrlgAct`, reachable directly off the unit at `UnitAny+0x1C` and rooted in the game at `pGame->pDrlgAct[5]` / `pGame->pDrlgRoomList[5]`. So `UnitAny → pPath → (Room1 → Room2 → Level → Act)` is how any unit answers "which act/level am I in, and where," and the reverse (`Act → Level → Room2 → Room1 → unit list`) is exactly the order the world renderer iterates. ## 4. Arm C — Info → Skill → SkillInfo `UnitAny+0xA8 pSkills` → **Info** (the per-unit skill controller) → a linked list of **Skill** nodes (level, charges, left/right assignment) → each Skill's **SkillInfo** = the Skills.txt/skilldesc record. This arm is orthogonal to rendering (it drives casting/sequence via `pSeqMode` at `UnitAny+0x30`), included here to complete the hub. ## 5. Ownership & lifecycle - **Owner**: `pGame` owns everything — the per-act DRLG (`pDrlgAct[5]`), the room lists (`pDrlgRoomList[5]`), and the unit lists. Units do not own their Path/Data payloads' *world*; they borrow into the game-owned room/level/act graph via Arm B. A unit's own `pUnitData`, `pPath`, `pInventory`, `pStatListEx`, and `Info` are allocated with it. - **Membership**: a unit lives in two intrusive lists simultaneously — the global type/GUID bucket (`pPrevUnit`, hashed by GUID&127) and its current room (`pPrevUnitInRoom`). When a unit crosses a room boundary its Path's Room1 pointer is re-linked and it moves room lists; that re-link is the seam the renderer sees. - **Determinism**: `dwInitSeed` (unit) + `GameSeed` (game) make spawns reproducible. - **Client/server twins**: GUID ties a client `UnitAny` to its server one; several hub fields (`+0x64`, `+0x80`, `+0xD8`) are overloaded client-vs-server. ## 6. How this all lands in OUR render work The world draw is a walk of the Arm-B spine, and each unit it reaches is **projected through the render context** — the one struct we've been fighting. - **The projection**: for any tile/unit, `screenX = ctx+0x04 − ctx+0x24 + tileAbsX` and `screenY = tileAbsY − ctx+0x28 + ctx+0x08` (`struct.D2Client_RenderContext`; matches `D2Client.FloorFlush`). `tileAbsX` comes from the unit's Path/StaticPath room-space position; the two ctx terms are the draw-window origin (`+0x04` draw_left) and the camera scroll (`+0x24` scroll_x). - **The room iteration is clipped by the ctx draw-window**: `D2Client.DrawRooms` (0x7A9F0) builds its room-iteration RECT from `ctx` draw-window + scroll (`band = [scrollX, scrollX + (R−L)]`) and culls any tile with `tileWorldX > RECT.R`. So `ctx+0x0C` (draw_right) is literally the bound on how far along the Room1/Room2 walk we draw. That is the tile/room draw path the units hang off of. - **The shift = the projection's origin term** (`finding.terrain_shift_is_offx_ctxL`): opening a side panel writes `offX (0x6FBCC418) = −320` into `ctx+0x04`, so every tile *and* unit derived from the same Path→Room projection slides −320 (1×). scroll_x (`ctx+0x24`) is untouched — the pan is entirely the draw-window origin, not the camera. - **Why units desync 2×**: floor tiles and units read the *same* 1× projection, yet units land at −640. The extra −320 is applied in the unit pass (PASS2), not in the shared ctx term — consistent with offX being read at 173 sites while the units path re-reads it. The Arm-A/Arm-B split is why: terrain tiles are projected once off the room; unit sprites are re-projected on top with a second offX application. - **`reframe.one_cause_view_state`**: the shift, the play-area narrowing, and the world-vs-units desync are one thing — opening a panel mutates the world **view-state** held in this render context (offX + draw_right). The clean fix is to render the whole Arm-B walk in the no-panel view-state (offX=0, draw_right=DispW) and composite the panel on top, so terrain and units share one coherent projection again. - **The remaining black is downstream of all of this** (`finding.clip_is_d2dx_drop_confirmed`): even when we widen `ctx+0x0C` so `DrawRooms` iterates the full room band and D2Glide submits the far tiles (verts reach x≈1264, 338 in [960,1280)), D2DX drops the dead-zone geometry. So the units-struct → render-context chain correctly *produces* the far-right world geometry; the loss is past the D2Glide→D2DX seam, not in the unit/room walk. **One-line takeaway**: `UnitAny` is a tagged union — Arm A says *what*, Arm B (`pPath → Room1 → Room2 → Level → Act`) says *where*, Arm C says *what it can do*. The renderer walks Arm B (Act→Level→Room2→Room1→unit list) and turns each unit's room-space Path position into pixels via the render-context origin/scroll/draw-window; the panel-open "shift" and "clip" are just that render context's origin (`+0x04`=offX) and right-bound (`+0x0C`) being rewritten, with the residual black living downstream in D2DX. *`note.units_synthesis`* Related: `struct.D2Client_RenderContext`, `finding.terrain_shift_is_offx_ctxL`, `reframe.one_cause_view_state`, `finding.clip_is_d2dx_drop_confirmed`, `struct.UnitAny`, `struct.Path`, `struct.Info` ### [note] # The "world" struct graph — synthesis note Source of truth for every field/offset below: `D:\_SERVER\esoteric-path\tools\dll-work\plugy-src\chaos\Commons\D2UnitStruct.h`. That header uses Yohann's older names; our own patch logs use the canonical names (`reference/EP-assembly-notes/townCheck.asm`: `CheckIfRoomIsTown(Room1* pRoom)`, `GetTownFromAct(Act* pDrlgAct)`, `GetActFromLevel`). Name map: **Room1 = `Room`, Room2 = `RoomEx`, Act = `ActMap`, ActMisc = `ActData`**; Level, PresetUnit unchanged; RoomTile is the list hung at Room2's warp-tile pointer. ## The core insight: two graphs, not one The world is a **static description graph** (Room2 → Level → ActMisc, seed-deterministic, screen-independent) with a **live instance graph** (Room1) lazily allocated on top of it. UnitAny rides the live graph; the renderer is a third, per-frame layer that never touches either — which is the whole reason the terrain/click bugs are view-state, not world-state. ## The pointer chain (UnitAny → … → Act) Grounded in real fields: - **`Unit.path` (+2C, `Path*`)** and `Unit.ptPos` (+38). `Path.ptRoom` (+1C) → the **Room1** the unit occupies. Path also caches the unit's `x`/`y` (+0C/+10) and `mapx`/`mapy` (+02/+06). Unit also caches `ptAct` (+1C) and `ptGame` (+80) directly. - **Room1 (`Room`)** `ptRoomEx` (+10, `RoomEx*`) → its **Room2**. (Reverse of Room2→Room1.) - **Room2 (`RoomEx`)** `ptLevel` (+58, `Level*`) → its **Level**. - **Level** `ptActData` (+1B4, `ActData*`) → **ActMisc**. The canonical "→ Act" edge closes via **`ActData.ptActMap` (+46C)** → the small **Act (`ActMap`)**; ActMisc↔Act is mutual (`ActMap.ptActData` +48). So the full walk is: `Unit.path → Path.ptRoom(Room1) → Room1.ptRoomEx(Room2) → Room2.ptLevel(Level) → Level.ptActData(ActMisc) → ActMisc.ptActMap(Act)`. ## Ownership & lifecycle - **Roots live on Game.** `Game.mapAct[5]` (+BC, `ActMap*`) — one Act per act. `ActData.ptGame` (+9C) points back up; `ActMap.ptFirstRoom` (+10) and `ActMap.ptActData` (+48) fan down. `ActData` owns the DRLG seeds (`seed1`/`seed2` +0/+4), difficulty (+450), room count (`nbRooms` +8), and the special tomb-level fields (`nTalRashaTombLevel` +94, `nBossMummyTombLevel` +484). - **Level is the per-area container**, chained by `ptNextLevel` (+1AC) into the act's level list; it carries `levelNo` (+1D0, the levels.txt Id), `levelType` (+1C0), its own `seed1/2` (+1C4/+1C8), and bounds `posX/Y/sizeX/Y` (+1C…+28). `Level.ptFirstRoomEx` (+10) heads the level's **Room2** list. - **Room2 = persistent description.** It exists for the level's whole lifetime, chained by `ptNextRoomEx` (+24); near-graph in `roomExNear` (+08) / `roomsNearCount` (+2C); layout in `posX/Y/sizeX/Y` (+34…+40, room/tile units); `presetType` (+48); `roomFlags` (+28). - **Room1 = transient instance.** `Room2.ptRoom` (+30) is **NULL until the room is activated**; when a room comes into play a Room1 is allocated and back-linked (`Room1.ptRoomEx`). Room1 holds the runtime-only state: `coll` (+20, collision map), the room's unit list head `ptFirstUnit` (+74, walked via `Unit.ptNextUnitInRoom` +E8), the active near-set `ptNearRooms`/`nbNearRooms` (+00/+24), collision-space bounds `startX/Y/sizeX/Y` (+4C…+58), and `ptNextRoom` (+7C) chaining the currently-active Room1s. Room1s are created/destroyed as units move; Room2s are not. That Room2-is-data / Room1-is-live split is the lifecycle in one sentence. ## What hangs off Room2: PresetUnit and RoomTile - **PresetUnit** — the fixed seeded contents of a room (chests, waypoints, superuniques, preset monsters). Each carries `unitType` (+0), `txtFileNo` (+4), room-local `xPos`/`yPos` (+C/+10), and chains via `pNext` (+1C). The list hangs off Room2 (canonical `Room2.pPreset`; note this repo's `RoomEx` header truncates at `ptLevel` +58 and does not expand the preset field — the PresetUnit struct itself is defined and verified). - **RoomTile** — the per-Room2 warp-tile list at **`RoomEx.ptWarpTiles` (+4C)** (typed `void*` in this header). Each RoomTile links a spot in the room to a level transition; this is the structural basis of the warp/adjacency graph that `Vis/Warp` in levels.txt feeds (see `kb/08-levels-maps.md`). ## Connection to our render work The renderer consumes the **live** graph only and lays a view-state on top: - **Tile/room draw path.** D2Client walks the active **Room1** list and, per room, emits its terrain tiles plus its unit list (`ptFirstUnit`). World positions are Room1 subtile coords (`startX/Y`, `sizeX/Y`); nothing screen-relative is stored on any world struct. - **The render context** (`struct.D2Client_RenderContext`) is where world→screen happens: it holds `offX` and the draw-window/clip rect. The concrete knobs we've located are the resolution globals in `kb/15-EP-assembly-notes-index.md` (`GeneralDisplayWidth [6FB8BC48]`, `GeneralDisplayHeight [6FB8BC4C]`) and the tile-extent edits (`!Definite.asm` `ADD ECX,-2F → 50`, `[[6F865B04]] = screen height`; `TileRendering.asm`, `viewmatrix110.asm`). - **Terrain shift = offX, not world.** Because Room1 tile coords are fixed and seed-deterministic, any apparent terrain movement is the render context's `offX`/`ctxL` draw-window left edge changing (`finding.terrain_shift_is_offx_ctxL`) — i.e. the view-state, not the room graph. - **One cause = view-state** (`reframe.one_cause_view_state`). The terrain-shift and the bottom-bar click-miss are the same defect: the world struct graph is intact and screen-independent, while the per-frame view-state (render context draw-window + D2Client's panel/resolution geometry) is what moved after the SGD2FreeRes provider swap. The click side is the input twin of the draw side (`clickThrough.txt`: `6FAD81F5` JNZ/JMP gate). - **Clip is a d2dx drop, not engine culling** (`finding.clip_is_d2dx_drop_confirmed`). The engine's room enumeration (Room1 near-list) correctly yields the edge rooms/tiles; the missing band is dropped downstream by the d2dx/SGD2FreeRes tile culling at the wrapper, confirmed in `kb/14-rendering-stack.md` (the 47px void region + motion-prediction tile culling). So edge loss is renderer-side clip, cleanly separable from the world structs above it. ## Punchline Room2/Level/ActMisc/Act are a seed-deterministic, screen-agnostic description; Room1 is the live overlay the renderer and units actually touch; and every "the world moved" symptom we've chased lives one layer higher still — in the render context's view-state — never in these structs. *`note.world_synthesis`* Related: `struct.D2Client_RenderContext`, `finding.terrain_shift_is_offx_ctxL`, `reframe.one_cause_view_state`, `finding.clip_is_d2dx_drop_confirmed`, `struct.Room1`, `struct.Room2`, `struct.Level`, `struct.Act` ### [note] # stats_inv — synthesis note (1.13c) Everything here hangs off one root: the player `UnitAny`. Two independent pointer graphs grow from it, and they only reconverge at the screen: - **WHERE the unit is** → `pPath → Room1 → Room2 → Level → Act` (feeds the world tile/room draw). - **WHAT the unit is** → `pStats → StatListEx → Stat[]` and `pInventory → Inventory → item UnitAny list` (feeds the inventory/character *panel* draw). The six `stats_inv` structs are the second graph. `InventoryLayout` is the odd one out — it is not in the unit graph at all; it is the static screen-space stencil the panel draw borrows. --- ## 1. The stat chain ``` UnitAny +0x5C pStats ── StatListEx (the unit's ONE aggregated/"full" list) Stats(StatVector) : pStat @+0x24 → Stat[ ] , wCount @+0x28 fpStatExpires @+0x38 (contributor-removed callback) pNext / pMyLastList ── chain of contributor StatList nodes ``` - **`Stat`** — one record, 8 bytes: `wSubIndex`(+0x00, WORD) · `wStatIndex`(+0x02, WORD = the ItemStatCost id) · `dwStatValue`(+0x04, DWORD). The corpus lists this verbatim: `iStatIndex[0]`/`iStatID[0]`/`iStatValue[0]` at 0/2/4, array packed 8-byte stride, kept sorted by id. `wSubIndex` is why one id can appear twice (e.g. per-skill / per-class stats). - **`StatEx`** — the same `{wSubIndex, wStatIndex, dwStatValue}` triple as it lives in the *extended* vector, carrying the extra bookkeeping the "Ex" (full) list needs to know **which contributor list a value came from** so it can be re-folded or expired. It is the element type of the aggregated list; `Stat` is the element type of a plain modifier list. - **`StatList`** — the base node/header: its `Stats` StatVector (`pStat`@+0x24, `wCount`@+0x28 — corpus-grounded) points at the `Stat[]` this node contributes, plus a `pNext` link. A single state (aura, curse, buff) or a single equipped item owns exactly one of these. - **`StatListEx`** — the unit-owned superset that `UnitAny.pStats` actually points at. It embeds the base `StatList` and adds the multi-layer value vectors, the `pUnit` back-pointer, and `fpStatExpires`(+0x38, corpus-grounded) — the callback invoked when a contributor list is torn off. The `+0x58` field the corpus flags "inside StatListEx" belongs to this extra region. **Fold/expire lifecycle (the ownership story):** each equipped item and each active state produces its *own* `StatList` of raw modifiers, links it into the owner's `StatListEx` chain via `pNext`, and its values get summed into the aggregated `StatEx[]`. When the item is removed or the state ends, the node is unlinked and `fpStatExpires` recomputes the full list. `ITEMS_UpdateTransferredProperties` (corpus, `D2Game.0x6FC424E0`, args `bUpdateStatList`) is the server-side driver of exactly this. So: the unit **owns** one `StatListEx`; every contributor is **owned by its source** and has list-scoped lifetime, not unit-scoped. --- ## 2. The inventory chain ``` UnitAny +0x60 pInventory ── Inventory dwSignature @+0x00 (0x1020304 sanity tag) pOwner ── back to this UnitAny (cycle) pFirstItem ─► item UnitAny ─► ... ─► pLastItem (equipped/stored items) pCursorItem (item on the mouse) ``` - **`Inventory`** — the runtime container. `pOwner` closes the cycle back to the unit; `pFirstItem…pLastItem` is the linked list of item `UnitAny`s. Each item is a full `UnitAny` with **its own `pStats` (StatListEx)** — that is the physical link between the two structs: an item's stat list is a contributor that folds into the wearer's aggregated list (section 1). Corpus D2Common sigs (`D2Common_FindStackTarget(Inventory*, ...)`, `InventoryHasRoomForItem(D2InventoryStrc*, ...)`) operate on exactly this node. - **`InventoryLayout`** — **not reached by any pointer from `Inventory`.** It is the static per-panel/per-class grid descriptor: pixel bounds `Left/Right/Top/Bottom` + `SlotWidth/SlotHeight` (cells) + per-cell pixel size. Lifetime = process (a data table), not the unit. The inventory-panel draw (`D2Client.0x3EAC0` in the ref; `UI_INVENTORY_DrawItemBackground(D2InventoryGridInfoStrc*, nX, nY, nItemWidth, nItemHeight, …)` in the corpus) *reads* it to place each `Inventory` item's cell. So `Inventory` says *which items and where in grid-cells*; `InventoryLayout` says *where those cells land in screen pixels*. --- ## 3. Why this sits parallel to the position chain The map chain `UnitAny.pPath → Path.pRoom1 → Room1.pRoom2 → Room2.pLevel → Level → Act` is the *other* graph. It is what `DrawRooms` (0x7A9F0) walks: it iterates the `Room1` linked list (`Room1.pNext` @+0x74, per-room units via `UnitAny` room-next @+0xE8 — both corpus-grounded) and projects each tile through the render context. The stat/inv graph never touches tiles; it drives the panel. **Same root, disjoint bodies, they only collide in screen space.** --- ## 4. Where it meets our render work The collision point is the render context — [[struct.D2Client_RenderContext]] (`*0x6FBCC3C4`), which *is* the world view-state. Tile projection is `screenX = ctxL(+0x04) − scrollX(+0x24) + tileAbsX`. - **Opening the inventory is the trigger for both graphs at once.** The panel it opens is drawn from graph #2 (`pInventory` items placed via `InventoryLayout.Left/Right/Top/Bottom`). That panel occupies the right band — and to make room, the panel-open handler rewrites graph-#1's view-state: `offX → ctx+0x04 = −320` and narrows `ctx+0x0C` toward 960. - The −320 world shift is [[finding.terrain_shift_is_offx_ctxL]]: the *whole* floor pan is `offX(0x6FBCC418) → ctx+0x04`, `scrollX` untouched. So `InventoryLayout`'s `Left` edge (where the panel starts, screen-space) is conceptually the same X-coordinate the world view is being shoved away from — the panel's static geometry and the world's dynamic `ctxL`/`draw_right` are fighting over one screen column. - This is the whole point of [[reframe.one_cause_view_state]]: the dead zone, the camera shift, and the world/units desync are **one cause** — opening the panel changes the world view-state. The clean target is to render graph #1 in the *no-panel* view-state (offX = 0, full `ctx+0x0C`) and composite the graph-#2 panel (its `InventoryLayout` region) on top, so the two graphs stop colliding. - Caveat we proved: neutralizing the ctx window is necessary but not sufficient for the black band — [[finding.clip_is_d2dx_drop_confirmed]]. Once the world is un-shifted, valid floor geometry reaches `x≥640/960` at the D2Glide→D2DX seam (submit max 1264–1296, 338 verts in the dead zone) and is still dropped **downstream in D2DX**, not by `ctx+0x0C`. So the `stats_inv`/panel side dictates *where* the world must not draw; the D2DX target extent dictates *whether* it can. **One-line takeaway:** `pStats`/`pInventory` and `pPath` are twin graphs off `UnitAny`; the inventory panel is drawn from the former via the static `InventoryLayout` stencil, and the act of opening it is precisely what rewrites the render-context view-state that our terrain-shift and dead-zone work is chasing. --- ### Offsets: sourcing - Corpus/repo-grounded: `UnitAny.pStats` +0x5C; `StatList.Stats` pStat +0x24 / wCount +0x28; `StatListEx.fpStatExpires` +0x38; `Room1.pNext` +0x74; unit room-next +0xE8; `Inventory` sigs via `D2InventoryStrc`. - Canonical D2Structs.h (not re-measured vs the 1.13c binary here): `UnitAny.pInventory` +0x60; `Inventory.dwSignature/pOwner/pFirstItem/pLastItem/pCursorItem`; `InventoryLayout.Left/Right/Top/Bottom + SlotWidth/SlotHeight`. Verify against the live binary before hooking. *`note.stats_inv_synthesis`* Related: `struct.D2Client_RenderContext`, `finding.terrain_shift_is_offx_ctxL`, `reframe.one_cause_view_state`, `finding.clip_is_d2dx_drop_confirmed`, `struct.StatListEx`, `struct.Inventory`, `struct.InventoryLayout` ### [note] # gfx_ui structs — how they connect (1.13c RE reference) **One-line thesis:** the eight gfx_ui structs are *content* (what a sprite/tile/overlay looks like and where it wants to sit in world space). None of them owns the world→screen transform — that lives entirely in the render context. So a panel-open never changes a single field in these structs; it changes `D2Client_RenderContext`, and every one of these structs re-projects through the new view-state. That's why the shift/clip fix belongs at the view-state, not here. ## Confidence tiers (hostile-reader honesty) - **Project-verified (exact offsets, from our grimoire/1.13c binary):** `D2Client_RenderContext` layout, the FloorFlush/DrawRooms projection, the automap fns (`NewAutomapCell` 0x703C0, `AddAutomapCell` 0x71EA0, `InitAutomapLayer_I` 0x733D0, `DrawAutomapCell` D2GFX 0x7C80, `AutomapLayer*` var 0x11CF28), and the "Lit" cel path. - **Canonical D2Structs.h field names (community-standard, and our own d2-re-reference §3 states "layouts are stable across versions"):** the sprite triad, automap pair, `Control`, `Waypoint`, `Light`. Field *names* below are dependable; where a byte offset for these isn't in our verified set I name the field and flag it, rather than invent an offset. --- ## 1. The sprite triad — GfxInfo ⊃ GfxCell, driven by CellContext Ownership is a strict containment + a transient pointer-in: ``` GfxInfo (loaded DC6/DCC file) CellContext (per-draw state) dwVersion / eFormat nCellNo ── frame index ──┐ numdirs, numcells pCellFile ─────────────────┼──► GfxInfo cells[] ──► GfxCell* (per dir × frame) dwPosX, dwPosY │ │ │ ▼ GfxInfo.cells[nCellNo]│ GfxCell (one frame) ◄───────────────────────────────────┘ flags, width, height xoffs, yoffs (sprite-local anchor, NOT screen) lpParent ──► back to owning GfxInfo length, cols[] (RLE pixel data) ``` - **GfxInfo owns GfxCell.** `GfxInfo.numcells` sizes the `cells[]` table; each `GfxCell.lpParent` points back to its `GfxInfo`. GfxCells have **no independent lifecycle** — they live and die with the file. Lifetime = file load → cache eviction. - **CellContext owns nothing; it *selects*.** `pCellFile` → a `GfxInfo`; `nCellNo` indexes `GfxInfo.cells[]` to pick the `GfxCell`; `dwPosX/dwPosY` say where to blit it. It is the per-draw cursor, rebuilt every frame. - **Key point for us:** `GfxCell.xoffs/yoffs` are *sprite-local* anchors, and `CellContext.dwPosX/dwPosY` are already *screen* coords. The world→screen step that produced those screen coords happened earlier, in the render context (§4). The sprite structs are position-agnostic — hand the same `GfxCell` a different `dwPosX` and it draws shifted, unchanged. ## 2. Where the tile/floor GfxCells come from — the world hierarchy The classic chain feeds the tile draw path. Named by field: ``` UnitAny.pPath ─► Path.pRoom1 ─► Room1.pRoom2 ─► Room2.pLevel ─► Level.pMisc ─► ActMisc.pAct ─► Act │ │ Room1.pRoomNext (active-room list) Level.pRoom2First (all rooms in level) Room1.pUnitFirst (units in room) Room2.pRoomTiles (tile/gfx data) Room1.Coll (collision) ``` - **DrawRooms** seeds off the player's room (`GetRoomFromUnit` #10331 → the `Room1` the player's `Path.pRoom1` points at), then iterates the `Room1` list. Per room it runs the per-tile dispatch (tile struct 0x30 bytes; flags `[t+0x14]` bit-test `0x1C000` = DRAW_GROUND|FLOOR|ROOF; orientation `[t+0x1c]`). Each drawn tile resolves to a `GfxCell` and is handed to the cel-blit thunks (§4). - **Room1 vs Room2 = lifecycle boundary.** `Room2` is the persistent level-geometry node (always present via `Level.pRoom2First`); `Room1` is the *hydrated/active* instance (`Room2.pRoom1` non-null only near players) that actually carries collision + live units + the tiles that get drawn. Panel-open changes *which* Room1s survive culling, never the Room1/Room2 structs themselves. ## 3. The three overlay structs — parallel projections, NOT the world path These are drawn *outside* the render-context tile path, which is exactly why they don't shift/clip with the world: - **AutoMapLayer / AutoMapCell** — one `AutoMapLayer` per `Level` (`AutoMapLayer.nLayerNo` == level no; global head at var **0x11CF28**). Cells hang off the layer's four buckets `pFloors / pWalls / pObjects / pExtras`, each a sorted tree via `AutoMapCell.pLess / pMore`. Each `AutoMapCell` carries `nCellNo` (frame in the automap sprite) + `xPixel/yPixel`. Built by `RevealAutomapRoom` (0x73160) walking a Room's tiles → `NewAutomapCell` (0x703C0) → `AddAutomapCell` (0x71EA0); drawn by **`DrawAutomapCell` (D2GFX 0x7C80, takes a RECT\* cliprect)**. So the automap is a *second, independent* projection of the same Room geometry — its own coord space, its own clip. It stays whole with a panel open because it never touches `D2Client_RenderContext`. - **Light** — dynamic light entries associated with the active `Room1`/`Act` environment. The connection worth grounding: our live cel handler is **`pfTileDrawLit`** (D2Glide dispatch `+0x9c`, `LiveCelHandler` 0x72F0) — the *lit* tile blit. Lights modulate the same `GfxCell` pixels as they cross that handler. (Light's finer field layout is the thinnest of the eight in our verified set — treat its role, not specific offsets, as load-bearing until read off the binary.) - **Control** — the UI-widget chain (`Control.pNext` linked list; `dwType`, `dwPosX/dwPosY`, `dwSizeX/dwSizeY`, plus text/edit fields). Controls are the panel's buttons/boxes, drawn by the **compositor/present** stage of `DrawGame` (body: PASS1 0x44302 → tiles → PASS2 0x4436F → compositor 0x4437B → present #10040), i.e. *after and over* the world pass — they sit on top of the dead zone, not in it. - **Waypoint** — `Waypoint.flags` (one byte per waypoint, arrayed). Pure UI/save state surfaced by the **waypoint side panel**, which is one of the panels whose *opening* is the trigger event: `PanelOpenArray` waypoint slot `+0x50`. Opening it fires the exact view-state write that shifts/clips the world (§4). So `Waypoint` is upstream cause-by-association, not a rendered-world participant. --- ## 4. Tie-in to our render work — the whole point Everything above pours through **one** transform, the render context. **`D2Client_RenderContext`** (`struct.D2Client_RenderContext`, instance `*0x6FBCC3C4`) IS the world view-state: - `+0x04 draw_left` == **offX** (0 no-panel, **−320** panel-open) — the shift - `+0x0C draw_right` — iteration bound (1280 no-panel, **960** panel-open) — the issue-side clip - `+0x24/+0x28 scroll_x/scroll_y` — camera origin (**not** touched by panels) The tile projection (FloorFlush / DrawRooms band) is literally: ``` screenX = ctx.draw_left(+0x04) − ctx.scroll_x(+0x24) + tileAbsX ``` That `screenX` is what becomes a tile's `CellContext.dwPosX`. So: - **`finding.terrain_shift_is_offx_ctxL`** — panel-open writes `offX 0→−320` → `ctx+0x04 0→−320`; `scroll_x` untouched. The floor/terrain GfxCells didn't change; their **CellContext.dwPosX moved −320** because the shared `ctx+0x04` term moved. Confirmed exactly −320 (=−DispW/4), standing still. - **`reframe.one_cause_view_state`** — shift (`+0x04`), clip (`+0x0C` narrow), and the world/units desync are ONE cause: the panel rewrites the view-state. The gfx_ui structs are all downstream of it, so the clean fix authors a coherent no-panel view-state for the whole world draw — you do **not** patch per-struct or per-sprite, and you keep `ScreenOpenMode` live for UI/culling. (Units pick up an *extra* −320 beyond the shared 1x tile projection — a separate PASS2/unit-layer source, not the ctx.) - **`finding.clip_is_d2dx_drop_confirmed`** — this exonerates the gfx-struct layer as the blackout cause. With the view-state widened, the tile GfxCells *do* reach the D2Glide→D2DX seam in the dead zone (submit max 1264–1296, 338 verts in [960,1280)) and still render **black**. The geometry — valid GfxCell frames, same path as the [640,960] tiles that paint fine — is dropped at/after D2DX. So: `ctx+0x0C` governs which GfxCells get **issued/projected**; **D2DX** governs whether the issued verts get **painted**. Two distinct clip layers; neither is a property of the eight structs. ### The stack, one line ``` GfxInfo.cells[nCellNo] ─► GfxCell (content, position-agnostic) │ positioned by ▼ CellContext.dwPosX/Y = D2Client_RenderContext (+0x04 offX, −0x24 scroll, +tileAbsX) ← the shift & issue-clip live HERE │ blitted via ▼ CelBlitThunk → D2gfx #10001 → D2Glide pfTileDrawLit (Light modulates) → grDrawVertexArrayContiguous → D2DX ← the terminal paint-clip lives HERE ``` Automap (`AutoMapLayer/Cell` → `DrawAutomapCell`), `Control` (compositor), and `Waypoint` (panel state) all sit *off* this spine — which is exactly why they never shift or clip when the world does. **Bottom line for the fix:** don't touch the content structs. Author the no-panel view-state in `D2Client_RenderContext` (neutralize `offX`→`+0x04`, restore `+0x0C`→DispW) AND defeat the D2DX dead-zone drop. Both are view/pipeline layers; the eight gfx_ui structs are innocent bystanders that simply render wherever the view-state tells them to. Source files: `E:\Diablo 2 - Esoteric Path\esoteric-dll\grimoire\symbols.jsonl` (render ctx, cel path, D2DX findings), `...\research\kb\reference\d2-re-reference-1.13c.md` (automap fns, struct-stability note, tile params), `...\research\kb\reference\panelworld-mods-and-hooks.md` (room/tile draw chain). *`note.gfx_ui_synthesis`* Related: `struct.D2Client_RenderContext`, `finding.terrain_shift_is_offx_ctxL`, `reframe.one_cause_view_state`, `finding.clip_is_d2dx_drop_confirmed`, `struct.GfxInfo`, `struct.GfxCell`, `struct.CellContext` ### [note] Hack replaces 640x480 mode with 1066x600 true 16:9. 0x280=640, 0x1E0=480, 0x42A=1066, 0x258=600. Note width 1066=0x42A but render uses 1064=0x428 in one spot (see D2gdi Part1). | src: replacing 640x480 mode; height wouldn't change, width would, true 16:9 *`ext.(global).Target_resolution` · (global)* ### [note] Author's reasoning verbatim-ish: reg->mem needs fewer bytes than DWORD-const->mem, so HD mode needs 8 extra bytes; instead of call+NOP he reorders. Because height is unchanged 800x600<->1066x600, the 800x600 y-offset setup code is reused, freeing 6 bytes and reducing writes from 8 to 4 bytes. | src: 'the height remains the same when switching from 800x600 mode to 1066x600 mode... By reordering the code, I can reuse the code that sets up the y-offset positioning for 800x600 mod *`ext.D2client.Author_conclusion_panel_byte_budget_600_height` · D2client.dll+0xC39F9* ### [note] Author flags glide3x as hardest because Cheat Engine relative instruction addresses differ from hex-editor addresses: 0xC0D5-0xC0EC (hex editor) == 0xCAD5-0xCAEE (Cheat Engine). | src: 'relative instruction addresses that show up on Cheat Engine are not the same relative addresses that are shown in a hex editor' *`ext.glide3x.glide3x_CE_vs_hex_editor_address_divergence` · glide3x.dll* ### [note] Tiling loop: half_num_frames = GetNumFrames()/2; rows alternate row_start_frame = (row%2)*half_num_frames; columns drawn until width_covered reaches (width/2)-1. Left screen anchors DrawPositionXBehavior::kRight and draws from (width/2 - width_covered); right screen anchors kLeft and draws from (width/2 + width_covered). Both use position_y_behavior kTop. Row loop condition compares height_covered against resolution.width (note: width, not height — as written in source). *`ext.draw_screen_background.background_tiling_loop` · draw_screen_background* ### [note] Centering math for the original 800-wide border frame around the 640-wide play area. left = (width-640-(800-640))/2 = (width-800)/2; top = ((height-480-(600-480))/2)-3 = ((height-600)/2)-3. This is the (width-640)/2 family of centering offsets the task references, adjusted for the 800x600 frame art. | src: const int left = (resolution.width - 640 - (800 - 640)) / 2; const int top = ((resolution.height - 480 - (600 - 480)) / 2) - 3; *`ext.DrawOriginalLeftScreenBorderFrame.OriginalLeftBorderFrame_centering` · DrawOriginalLeftScreenBorderFrame* ### [note] Right-side original border frame anchor. right = (width+640+(800-640))/2 = (width+800)/2; top same as left ((height-600)/2)-3. | src: const int right = (resolution.width + 640 + (800 - 640)) / 2; const int top = ((resolution.height - 480 - (600 - 480)) / 2) - 3; *`ext.DrawOriginalRightScreenBorderFrame.OriginalRightBorderFrame_centering` · DrawOriginalRightScreenBorderFrame* ### [note] Custom border frames center the 640x480 UI: screen_left = (width-640)/2, screen_top = (height-480)/2, screen_right = (width+640)/2. Vertical centering of the side borders uses (256+176)=432 as the reference inner-panel height: border_top = screen_top + ((432 - border_height)/2). | src: const int screen_left = (resolution.width - 640) / 2; const int screen_top = (resolution.height - 480) / 2; *`ext.DrawCustomLeft/RightScreenBorderFrame.CustomBorder_screen_left_top` · DrawCustomLeft/RightScreenBorderFrame* ### [note] DrawLeftScreenBackgroundRibbon / DrawRightScreenBackgroundRibbon tile horizontal and vertical 'ribbon' DC6s across the perimeter beyond the border frame, using inner-panel reference height (256+176)=432 for vertical centering; border_left = screen_left - firstFrameWidth; loops fill until width_covered/height_covered exceed borders. Config-gated by IsScreenBorderFrameEnabled(); original-vs-custom chosen by IsUseOriginalScreenBorderFrame(). *`ext.draw_screen_background.draw_screen_background_border_ribbon_fns` · draw_screen_background* ### [note] CD3D11_RECT ctor is (left,top,right,bottom) but D2DX passes { offset.x, offset.y, size.width, size.height } -> right=size.width, bottom=size.height rather than offset.x+width / offset.y+height. Harmless when offset==0 (offscreen render at {0,0,gameSize}) but with a centered _renderRect (non-zero offset in Present) the scissor's right/bottom fall short by offset.x/offset.y. Viewport still places content correctly; only the scissor is mismatched. Load-bearing if chasing edge-cl | src: CD3D11_RECT scissorRect{ rect.offset.x, rect.offset.y, rect.size.width, rect.size.height }; *`ext.d2dx.Scissor_rect_construction_quirk_right_bottom_s` · d2dx* ### [note] Grep + full read of D2DXContext.cpp, RenderContext.cpp, GameHelper.cpp: TextureCategory::Floor(4) and Wall(6) are never used to apply any positional offset/transform/scissor/viewport difference. They drive only texture categorization/cache selection and motion-prediction exclusion. Floor and wall are treated identically for geometry. *`ext.d2dx.NO_per_batch_floor_vs_wall_transform_exists` · d2dx* ### [note] TextMotionPredictor.GetOffset(textId,pos) offsets text; WeatherMotionPredictor.GetOffset offsets weather lines. Both additive screen offsets, not floor/wall specific. Feature flags: Unit+Weather MoP for Lod109d/112/113c/113d/114d; Text MoP only 113c/113d/114d. *`ext.d2dx.Text_Weather_MoP_also_uniform_category_gated` · d2dx* ### [note] All FUNCPTR/VARPTR/ASMPTR offsets in this file are RELATIVE to the containing DLL's load base, not absolute VAs. d2bs adds the runtime module base. For 1.13c D2Client.dll the canonical base is 0x6FAB0000, so e.g. GetUnitX 0x1630 => ~0x6FAB1630. Negative numbers (e.g. -10010) are export ORDINALS resolved via GetProcAddress(MAKEINTRESOURCE(10010)), used for D2GFX/D2WIN/D2COMMON. | src: FUNCPTR/VARPTR/ASMPTR(MODULE, name, type, args, offset-or-ordinal) *`ext.(all).Offset_convention_base_relative` · (all)* ### [note] The file uses three macros, each ending with a DUAL offset pair: the 1.13c value FIRST, then the 1.13d value. VARPTR(DLL, Name, Type, off_113c, off_113d) for globals; FUNCPTR(DLL, Name, RetType/callconv, (args), val_113c, val_113d) and ASMPTR(DLL, Name, ..., val_113c, val_113d) for functions. NEGATIVE values are export ORDINALS (e.g. -10079); POSITIVE hex values are direct in-DLL OFFSETS (e.g. 0x18750). This c-then-d ordering is the rebasing key. | src: VARPTR(D2CLIENT, ScreenSizeX, DWORD, 0xDBC48, 0xF7034) *`ext.macro_format`* ### [note] CRITICAL: Level, Room2, CollMap, PresetUnit, RoomTile, RosterUnit, BnetData, PlayerData, Waypoint, QuestInfo are inside a #pragma pack(push)/pack(1) region — their true in-memory byte spacing is packed (no natural alignment padding); trust the explicit hex offset comments, not sizeof-based math. GfxCell, CellFile, CellContext, AutomapCell/Layer, UnitAny, Room1, Act, ActMisc, Control are OUTSIDE pack(1) (default alignment). | src: #pragma pack(push) #pragma pack(1) ... #pragma pack(pop) *`ext.pragma_pack_context_for_world_structs`* ### [note] The 256-pixel horizontal correction: subtract 256 from adjusted X when the RIGHT panel is up, add 256 when the LEFT panel is up. Applied only in perspective mode (isometric offsets already bake in the shift via 6FBC9960/6FBCC418). *`ext.D2Client.256px_panel_offset` · D2Client* ### [note] Correction/caveat on the render math: D2 default is ISOMETRIC projection; 'perspective' is a depth distortion applied only in D3D mode. Objects and missiles have special (file-derived, NOT image) coordinate offsets you must adjust for depending on where you read coords from; supporting perspective mode requires the 'order aware adjustment' for objects. | src: this is isometric projection, not perspective projection (which is a depth distortion you can apply in D3D mode)... objects and missiles have special offsets you need to adjust for *`ext.D2Client/D2Common.Necrolis_isometric_not_perspective_special_off` · D2Client/D2Common* ### [note] THE core screen-space transform for drawing a unit into the play area. Takes unit world X/Y, adds GFX position (Z folded into Y), then SUBTRACTS UNITDRAW_GetWorldXOffset()/YOffset() (the camera/scroll offset), and IF perspective on, ADDS UNITDRAW_GetPanelAdjust() and runs D2AdjustPerspectiveCoords. This is exactly the screen-shift / panel-offset math behind the play-area vs side-panel positioning. | nXpos -= UNITDRAW_GetWorldXOffset(); nYpos -= UNITDRAW_GetWorldYOffset(); if(GFXUTIL_CheckPerspective()){ nXpos += UNITDRAW_GetPanelAdjust(); D2Adjust *`corpus.World_offset_panel_adjust_perspective_coord_` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] To let a unit/missile pass where it normally collides, find the flag the collision fns test and override it with an unused tile flag; likely a D2Common ordinal (points to COLLISION_CheckMask and the COLLIDE_* mask set). | you basically need to look what the fn's use to determine if they collide and override it with an unused tile flag; its likely a D2Common ordinal *`corpus.Collision_override_for_pass_through_units` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Store panel draw path: when sgnInventoryMode in [INVMODE_TRADE..INVMODE_09], checks globals D2CLIENT_6FBCBC4C (tick gate), 6FBCBC54, 6FBCBC30, gdwActiveNpcGUID, gnInvPage, then calls STOREINVENTORYSCREEN_Main. 6FB3F710 is the key sub (likely click-spam/latency guard) | It's the part that draws the merchant store panel ... STOREINVENTORYSCREEN_Main(); *`corpus.Merchant_store_panel_draw_D2Client` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] 1.10 equivalent of store-panel draw: iterates D2CLIENT_dword_6FBB37AC..37FC stepping 20 bytes, sets gnInventoryMode=VENDORMODE_TRADE, calls 6FB575B0 with cursor item | *D2CLIENT_gnInventoryMode = VENDORMODE_TRADE; *`corpus.Store_panel_draw_1_10_equiv` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] WriteCall(target,source,length): writes 0xE8 call + NOP pad with VirtualProtect. Example hooks the in-game loop at D2Client.dll+0x45EC4 (24-byte patch) to run per-frame code | WriteCall(DWORD(GetModuleHandle("D2Client.dll"))+0x45EC4, DWORD(&InGameLoop), 24); *`corpus.WriteCall_hook_helper_D2Client_ingame_loop_h` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] SetWindowLongPtr(D2GFX_GetHwnd(), GWL_STYLE, WS_POPUP|WS_VISIBLE) removes blank/border pixels around the render window | SetWindowLongPtr(D2GFX_GetHwnd(), GWL_STYLE, WS_POPUP | WS_VISIBLE); removes blank pixels *`corpus.Window_border_blank_pixel_fix` · D2Gfx* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Necrolis' worklist for a custom OpenGL D2 driver. KEY render-pipeline facts: (1) the driver internally switches EACH draw function between perspective and flat mode - see D2Glide.0x6F814480 - render coords must be fixed and #10068's impact investigated; (2) an 'underlay shader' causes the screen to darken watching fights outside Act V town and breaks GetHit-mode transparent backgrounds on some Act 5 monsters; (3) Mod | internally the driver switchs each draw function between perspective and flat, see D2Glide.0x6F814480, we need to fix the render coords *`corpus.Custom_GFX_driver_render_TODO_known_bugs` · D2Glide* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] DC6 frame decoder. Pixel byte semantics: 0x80 = end of row (x=0, y--); (pix & 0x80) with other bits = skip/advance x by (pix & 0x7F) transparent pixels; else pix = run length of literal pixels to copy. Necrolis also gives a DWORD-vectorized variant (copy pix/4 dwords then switch on pix&3). Writes into gpDecodeBuffer at p[(y*pw)+x]. | if(pix == 0x80){ x = 0; y--; } else if(pix & 0x80) x += pix & 0x7F; else { while(pix--){ p[(y * pw) + x++] = pFrame->pPixels[++i]; } } *`corpus.DC6_frame_RLE_decode` · D2CMP* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Disasm that packs an R5G5B5 pixel for the Smacker (video) manual-decode path. Necrolis notes the last shift (SHL EDX,2 before final OR) is WRONG - a math error in the original code producing a slightly-off blue channel. | this code is meant to create an R5G5B5 pixel for the smacker (in manual decode), the last shift is wrong because someone can't math good *`corpus.Smacker_R5G5B5_pixel_pack_bug` · D2Glide* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] NOP the JL/JG hover-bounds branches (6FAF8B3A/43/4E/59 and 6FAF8C19/24/2F/3A) so orb hover text always renders. To reach the mana-text block, also NOP the epilogue at 6FAFF1E6-6FAFF1F0 (pop edi/esi/ebp/ebx; add esp,25C; retn) or JMP to 6FAFF1F1, so execution falls through from life-text to mana-text rendering. | From 6FAFF1E6 to 6FAFF1F0, you should nop those instructions as well, it'll allow it to continue to the mana text rendering *`corpus.Force_render_life_mana_text_always_show_hove` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] 1.10 mana-render block calls D2Win.#10117 (LEA ECX,[ESP+20]; SUB EDX,EAX; CALL JMP.&D2Win.#10117) then the standard epilogue; this call is absent in the 1.09 layout. | 6FAFF1E1 |. E8 D2CD0600 CALL *`corpus.D2Win_10117_text_draw_call_mana_render_path` · D2Client/D2Win* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] NOPping CMP DWORD PTR DS:[6FBD3490],EBX / JNE at 6FAF4E51 raises SP framerate to 64 FPS; 6FBD3490 is the frame-skip gate global in 1.13 D2Client. Also gdwFrameRenderSkip test at 6FAAA2C9 can be NOPped. | nopping this out, increases the framerate to 64 FPS in SP. 6FAF4E51 CMP DWORD PTR DS:[6FBD3490],EBX *`corpus.Uncap_FPS_by_nopping_frame_skip_check_1_13` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Recommended render throttle: hook the render fn with a counter and skip every X render; canonical form 'if(current-last>=target) render();' rather than Sleep-based CPU fix. | void RenderFN() { if(current - last >= target) render(); } *`corpus.Frame_rate_limiter_pattern_render_gate` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Unit-selection scans up to 16 (0x10) components: sub_6FB28D90 fetches component AABB, IsComponentSelectable_sub_860F7F0 filters, then point-in-box test (pt.x vs xMin/xMax, y vs yMax/yMin). Note inverted Y (yMax is top). Used for clicking units on the play field. | || pt.x < pAABB.xMin || pt.x > pAABB.xMax || a3 < pAABB.yMax || a3 > pAABB.yMin *`corpus.Component_hit_test_loop_unit_selection_AABB` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Unit composite draw special-cases COF component letters: S8 layer applies green-blood palshift (GetGreenBloodPalshift) when monStatsEx->nLocalBlood and a client flag (sub_7B8C020); S7 layer resolves a linked unit (sub_7C06AD0) and if missile or in state 143 recurses into UNITDRAW_ProcessUnit_6FB4DB40. Component enum check NUM_COMPONENTS asserts in Composit.cpp:613. | else if ( compByte == S7 ) ... D2CLIENT_UNITDRAW_ProcessUnit_6FB4DB40(v45, dwGamma, 1); *`corpus.Composite_palette_shift_green_blood_S7_S8_la` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Necrolis's custom renderer notes: D2 makes 8 palette copies for any unit with a palshift.dat; PL2/randshift are created ad hoc; DCC/DC6 must stay resident for reshifting. His approach decodes the DCC/DC6, builds an L8 or HW-compressed GPU texture, frees the source, and on palshift just changes the index (offset) into the palette LUT (or a shader switch for shadow/HW-compressed textures). Notes D2 poorly caches D2GetN | the way D2 handles monsters with a palshift.dat means that if its present, "8 copies" are made *`corpus.Palette_shift_texture_rendering_monster_pals` · D2gfx/D2CMP* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Full flag/enum sets for tile libraries, sub-tile walkability/LoS/light collision, tile materials, and tile-component encoding (run-length, gamesquare). Governs floor tile behavior and decoding. | TILE_NOWALK=0x01, TILE_NOLOSLIGHT=0x02, TILE_NOJUMPTELEPORT=0x04, TILE_NOPLAYERWALK=0x08, TILE_NOLIGHT=0x20 *`corpus.Tile_SubTile_GFX_material_flag_enums` · D2CMP* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] 1.07 references a resolution-config string 's1280x800(1).left' at 6FD447C8 - suggests table-driven screen-region layout keys. | MOV ECX,DWORD PTR DS:[6FD447C8] ; ASCII "s1280x800(1).left" *`corpus.1_07_resolution_string_s1280x800` · D2Game* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Decompiled render code showing D2GFX_10010_CheckPerspective gating a block that reads player X/Y (D2COMMON #10327 GetXPos / #10330 GetYPos), calls D2Common_10108 (ScreenToWorld) on shifted coords, then D2GFX_10064_SetPerspectiveScale(playerX,playerY). Names the perspective-mode camera-scale ordinals: D2GFX #10010 CheckPerspective, #10064 SetPerspectiveScale; GetPlayerUnit at D2Client.6FB283D0. | if ( D2GFX_10010_CheckPerspective() ) { ... D2GFX_10064_SetPerspectiveScale(nPlayerPosX, nPlayerPosY); } *`corpus.Perspective_scale_SetPerspectiveScale_usage` · D2gfx/D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Client UI setup reads mpq file '800CtrlPnl7' if a GFX function returns 2, otherwise 'CtrlPnl7' (the actual control-panel UI DC6). The gating GFX function is ordinal for 0x6FAD7290 in 1.13c. 800-prefix appears to be a legacy/alt-resolution variant. | code tries to read file from mpq called 800CtrlPnl7 if some GFX function will return 2. If it returns something else then CtrlPnl7 is readed ... Ordin *`corpus.CtrlPnl7_800CtrlPnl7_control_panel_gfx_load` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Necrolis patch around D2Client 6FAF8AE0..6FAF8C40 that redirects control flow (JMP/CALL Ordinal10117, then MOV [ESP+20],0) so both items draw in a menu control. Ordinal10117 is a D2Client draw helper; region is a UI/menu-control draw routine. | 6FAF8B38 | CALL 6FAF8B3D | JMP d2client.6FAF8C40 ... MOV DWORD PTR SS:[ESP+20],0 *`corpus.Draw_both_weapons_UI_patch_menu_panel_draw_f` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Unit flags at +0xC4: 0x01 = tells game to update the unit, 0x20 = whether to draw a shadow (client-only). FlagsEx +0xC8: 0x02000000 = expansion unit, 0x00000004 = spawned in vendor shop, 0x10 (server) = seed initialized (signals sync packet). Necrolis confirms 0x20 is a client-only draw flag. | 0x00000020 - whenever to draw a shadow or not ( client only ) *`corpus.Unit_flags_0xC4_0xC8_semantics` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Lectem posts the start of a full D2Common 1.13c ordinal->address->name table: #10000 GetLevelType @6fd75260, #10001 GetActNoFromLevelId @6fd75300, #10002 GetLOSDraw @6fd75350, #10005 GetLevel @6fd749a0, #10006 InitLevel @6fd74c10, #10007 GetRoomExFromRoom @6fd8d0c0, #10010 GetLevelDefsRecord @6fd60d90, #10011 GetAutomapCell @6fd62d30, #10012 FreeDrlgAndLevels @6fd743b0, etc. | // List of D2Common.dll exports (base address 6fd40000) ... D2COMMON_10000_GetLevelType ... @address 6fd75260 *`corpus.D2Common_dll_export_ordinal_table_base_6fd40` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] D2GetRoomList = D2Common.10035 @0x4BC10; D2PlaySound = D2Client.6FB55820 @0xB5820. Room-list retrieval feeds the render room iteration. | static D2Common2733 D2GetRoomList = (D2Common2733)(0x4BC10 + CommonOffset); // D2Common.10035 *`corpus.D2GetRoomList_D2PlaySound_offsets` · D2Common/D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Ground tiles are drawn in draw-order steps 1..2 selected by ((dwFlags>>14)&7); DrawOrderMask = 0x7<<14 (0x4000|0x8000|0x10000). Each tile drawn via D2Client_Draw_dFloor_6FAA2E70 with position = ptTileDrawParams->nViewXOffset + tile width and nnViewYOffset + tile height, plus pos*5. Lectem also notes 0x10000 can mean 'do not draw'. Shows the exact viewport-offset math applied to floor tiles. | D2Client_Draw_dFloor_6FAA2E70(ptTileDrawParams, pFloorTileData, v18, ptTileDrawParams->nViewXOffset + pFloorTileData->nWidth, ptTileDrawParams->nnView *`corpus.floor_tile_draw_order_stepping` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] CRITICAL PANEL FACT: In the D2DDraw screen-clear/blit, the constant 0x2F (47) is the adjustment for the bottom panel -- the game deliberately never clears the last 47px of the screen. 6F8D01C8 is the screen front buffer being cleared; if your custom back buffer is smaller than the sizes passed here you get a buffer overflow / HOM. Directly explains uncleared dead-zones tied to panel geometry. | if you see 0x2F you now its the adjustment for the bottom panel, as the game never clears the last 47px... 6F8D01C8 is the screen front buffer thats b *`corpus.BOTTOM_PANEL_47px_clear_adjustment_0x2F` · D2DDraw* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Patch to colour an inventory item cell's background red when a charm is not in a valid charm-zone, blue when its requirements are met. Hooks the item-cell draw path in the inventory panel; original code calls D2Common.#10244 then TEST/JE at 6FB45790. This is inventory-side-panel item rendering. | I'm trying to get charms to have a red background when not in a charm zone ... 6FB45790 E9 CB860300 JMP D2Client.6FB7DE60 *`corpus.Charm_inventory_cell_red_blue_background_hac` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Ordinals used by the charm inventory-cell colouring code in 1.13c; Firehawk gives the resolved D2Client jump-stub addresses for #10744 and #11057. | #10744 - 6FABC1CC / #11057 - 6FABCA30 *`corpus.Charm_check_D2Common_ordinals_resolved_to_ad` · D2Common/D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] In the charm inventory-cell draw hook the player unit pointer is at [esp+0x28] on 1.13c rather than [esp+0x20] as in 1.10 — a porting gotcha for the panel item-render stack frame. | it also seems that [esp+0x28] holds the player on 1.13c rather than [esp+0x20] *`corpus.Inventory_cell_hook_stack_slot_player_ptr_sh` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Server->client packet 0x58 opens a user-interface panel (UnitInteract); nUIType selects which UI/panel. | S>C 0x58 - Opens User Interface on Client - UnitInteract *`corpus.S_C_0x58_UnitInteract_opens_UI_panel` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Necrolis' recipe for D2 2D rendering: glViewport(0,0,w,h); glOrtho(0,w,h,0,-1,1) with identity modelview -- top-left origin ortho matching D2 screen space. | glOrtho(0,nWidth,nHeight,0,-1,1); *`corpus.2D_projection_matrix_setup_OpenGL_glrenderer` · D2Glide/gl* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Belt/mouse text draw gate: computes (ScreenWidth/2)-146 and (ScreenWidth/2)-23 as x-bounds, ScreenHeight-43/-34 as y-bounds. Shows the play-area is centered via ScreenWidth/2 -- key idiom for panel/viewport offset work. | lea edx, [eax-146] ; edx = (screenwidth/2) - 146 *`corpus.Screen_center_text_bounds_math` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Necrolis: to color item background rects you must patch many D2DrawRect call sites -- belt, inv slots, equipment slots, merc, vendors -- because coloring wasn't done in the item draw function. Enumerates the 1.10 panel item-slot draw calls. | belt drawing, inv slots, equipment slots, merc, vendors etc, this amounts to quite a few calls to D2DrawRect *`corpus.Item_slot_color_rect_draw_sites_D2DrawRect` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] UI widget structs (editbox etc.) come from D2Win and are cleanly done; the arg-1 object at D2Client 6FAD7540 (1.10f) is the ad-hoc bank dialog code which is messy and reused by other adhoc menus - advise side-stepping. | The widgets come from D2Win, these are actually properly done... What you are refering to in that code is the incredible piece of shit that is the ban *`corpus.D2Win_widgets_vs_bank_dialog` · D2Win* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] KEY RENDER FACT: world<->screen conversion done by D2Common ordinals in the #1011x range (choose tile vs subtile variant), result then offset by the CURRENT SCREEN WORLD CENTER (camera). This is the camera/scroll offset applied to drawn units/tiles. | Conversion is done via a few funcs in the D2Common.#1011x range depending on if you want tile or subtile coords, and then offset by the current screen *`corpus.world_screen_coordinate_conversion` · D2Common/D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Global dword_6FBCBE5C (1.13c) / dword_6FBB5B14 (1.10) used as a loop counter inside the mercenary-items draw function; observed always 0. | I found that inside merc items draw functions (compare loop counter with that but this is always 0 ) *`corpus.merc_items_draw_global_dword_6FBCBE5C` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Practical note: a draw-text hook and a reveal-minimap hook each work alone in DLL_PROCESS_ATTACH, but running back-to-back the minimap reveal fails — timing/ordering caveat when hooking D2 draw functions at attach. | when executed back-to-back like here, the minimap is not revealed *`corpus.HelloWorld_RevealMinimap_hook_interaction_bu` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Mini-guide to inject a custom UI: hook client-side item right-click handler (find barkscroll item code in D2Client), set a global, open barkscroll UI; hook the barkscroll draw function to load/draw a replacement DC6 when the global is set. A concrete example of hooking a D2Client panel draw routine. | Hook barkscroll draw function, if that global is set, load (and unload the old DC6) and then draw the new DC6 *`corpus.Barkscroll_UI_custom_draw_hook` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Cross-version ordinal mapping in D2Cmp (graphics/cell decompression module): #10044 in 1.10 == #10067 in 1.13c | D2Cmp.10044 (1.10) D2Cmp.10067(1.13c) *`corpus.D2Cmp_ordinal_10044_1_10_10067_1_13c` · D2Cmp* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Silvermane's on-screen text overlay: calls level-name printout at D2Client.6FACF0D0, fetches Y coord from [6FBB19FC] and X coord from [6FB9A704], SUB EDX,30 to shift X, then D2Win #10171 display text. Shows the screen coord globals used for HUD text placement. | MOV EAX,DWORD PTR DS:[6FBB19FC] ; retrieve Y coordinate ... MOV EDX,DWORD PTR DS:[6FB9A704] ; retrieve X coordinate SUB EDX,30 *`corpus.Area_Level_print_asm_level_name_overlay` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Patch point in inventory placement: CheckCharmInvLocation(pItem) validates a charm's inventory location (ItemLocation, IsMatchingType 0x0D, pItemPath->dwPosY>3). Directly touches inventory-panel item placement logic. | // Charm Inventory check {D2DLL_D2COMMON, 0x27064, PATCH_JMP...} ... if (pItemPath->dwPosY > 3) return TRUE; *`corpus.Charm_inventory_location_check_1_13c` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Hookable draw-line export; params (nXStart,nYStart,nXEnd,nYEnd,nPaletteIndex,nAlpha); width = end-start. Direct hook point for line rendering | void __stdcall ExpDrawLine(int nXStart, int nYStart, int nXEnd, int nYEnd, uint8_t nPaletteIndex, uint8_t nAlpha) *`corpus.ExpDrawLine_via_D2GFX_10057` · D2GFX* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] The 'Panel\Level' DC6 (character/level side panel gfx) string is at D2Client.6FB79AB0; loaded via DC6 load function D2Client.6FB20100 and the loaded handle stored at global [6FBB5BB4]; corresponding unload function at D2Client.6FB20140. Load MOV ECX,6FB79AB0 at 6FAEEBA0; store MOV [6FBB5BB4],EAX at 6FAEEBAF | 6FAEEBA0 MOV ECX,D2Client.6FB79AB0 ; ASCII "Panel\Level" ... so DC6/DCC loading function is D2Client.6FB20100? *`corpus.Panel_Level_DC6_side_panel_graphics` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Correct sequence to drop from inventory without leaving blank slot: ChangeAnimMode(IMODE_ONGROUND), RemoveItemFromInventory, GetRoom, D2DropItem(pGame,NULL,pItem,pRoom,x,y), then server UpdateInventoryItems + RefreshInventory | D2COMMON_INVENTORY_RemoveItemFromInventory(pUnit->pInventory, pItem); D2GAME_D2DropItem(pGame, NULL, pItem, pRoom, pCoords.nX, pCoords.nY); *`corpus.Ctrl_click_drop_item_refresh_chain` · D2Common/D2Game* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Automap uses hardcoded ASCII level-type table in D2Common .rdata; table start at 6FDDB9E0 (offset 0xB9E0), sole reference/call at offset 0x6B30D (live 6FDBC30D) which loads table into EDI. To extend past lvltype 35 relocate table and repoint the call | copies the table from 6FDDB9E0 into the EDI register ... the call to that address at 0x6B30D *`corpus.Automap_level_type_table_extension_1_13c` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] whist's extension: rebuilt automap.txt compile to link LevelName to lvltypes.txt (TXTLINKER_LVLTYPES) so table can be arbitrary size (256+ pairs). AutomapBin record layout: TileName@0x10, Style@0x18, Start/EndSequence@0x19/0x1A, Cel1-4@0x1C/0x20/0x24/0x28. Patch stubs at D2Common 0x6CEF4 (GetLvlTypeFromAutomapRecord), 0x6CE9B (CompileAutomapTables), plus range-table repoints at 0x6CFF3/0x6FAD4/0x6CFB0/0x6D013/0x6FADF | {"TileName", TXTFIELD_ASCII, 7, 0x10, 0} ... {"Cel1", TXTFIELD_DWORD, 0, 0x1C, 0} *`corpus.Automap_txt_lvltypes_txt_dynamic_link_Automa` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] To get game render width: D2Win ordinal 10121 (1.09D/1.10), 10132 (1.12A), 10028 (1.13C), 10150 (1.13D); Game.exe+0xFF010 (1.14C), Game.exe+0x101820 (1.14D) | ordinal 10028 for 1.13C ... offset Game.exe+0xFF010 for LoD 1.14C *`corpus.Screen_window_WIDTH_accessor_per_version` · D2Win* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Player is always at center of screen; getting window rect and halving mis-places by ~15-20%. Correct approach converts unit world coords to screen via a world_to_screen routine (diablo2::utils::screen::world_to_screen); unit x/y from get_unit_x/y(player->path) | diablo2::utils::screen::world_to_screen(label->x, label->y, mx, my); Does the trick. *`corpus.Draw_label_above_player_head_world_to_screen` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Kingpin: the easy way to add new buttons is to hook the code that draws the bottom UI panel. Points at the panel-draw routine as the injection site. | add new buttons is simple, easiest way is hook part where it draw the bottom ui panel *`corpus.Bottom_UI_panel_button_hook` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Elmegaard working through a broken gfx-frame bug: proposal to split pUnit->dwGfxFrame from a new dwGfxGameFrame = pGame->dwGameFrame << 8, and where the frame bonus (D2Common_10031_GetFrameBonus << 8) is applied. Animation-frame timing for unit rendering. | pUnit->dwGfxGameFrame = D2Common_10031_GetFrameBonus(pUnit) << 8; pUnit->dwGfxFrame = pGame->dwGameFrame << 8; *`corpus.gfx_frame_vs_game_frame_dwGfxFrame_dwGfxGame` · D2Common / D2Game* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Skill-description decimal precision fix; hook points in D2Client at 0x73438 (capture value), 0x73495 (fix), 0x73458 (exit-if-zero), with returns 0x7343D/0x734A1/0x73472. This is on the skill-description UI text render path. | { D2DLL_D2CLIENT, 0x73438, (DWORD)PATCH_JMP, FALSE, 0x1 }, { D2DLL_D2CLIENT, 0x73439, (DWORD)SkillDescCaptureValue, TRUE, 0x0 } *`corpus.D2Client_SkillDesc_formatting_hooks` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] v1.10 automap.txt loader compares level names against a hardcoded 36-entry table at D2Common offset 0x8E2D0 (from lvltypes.txt), which breaks level types past 36; fix substitutes a lvltypes record-index field. Relevant to floor/tile/automap level handling. | The existing loader function in v1.10 for automap.txt compares the level names against a hardcoded table at d2common offset 8E2D0 ... has only 36 entr *`corpus.Automap_level_name_hardcoded_table_8E2D0` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] D2Client.0x6FAA8870 was thought to control rain but actually just reflects the result of D2COMMON_10632_GetRainFromLevelsTxt (weather from levels.txt) - a client display value. | D2Client.0x6FAA8870 ... it's actually just updated to reflect the results of D2COMMON_10632_GetRainFromLevelsTxt *`corpus.Rain_render_tied_to_GetRainFromLevelsTxt` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] D2Common_10177 (returns 32-bit) is consumed in D2Client.0x6FB49D70; lack of sign-extension when moving from D2PathPointStrc implies D2PathPointStrc::X is unsigned. Path point coords feed positional/camera math. | looking at the usage of D2Common_10177 in D2Client.0x6FB49D70 ... D2PathPointStrc::X is unsigned *`corpus.D2Common_10177_usage_D2PathPointStrc_X_unsig` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] A patch that intercepts panel positioning at 0xC39F7 (one past the 0xC39E0 UI loop region) | { D2DLL_D2CLIENT, 0xC39F7, 38, (int)HD::PanelPosition_Interception, TRUE } *`corpus.HD_PanelPosition_Interception` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Panel button hit-test computes coords from screen width/height minus WindowStartX/Y plus fixed offsets (e.g. -0x12e..-0x10e X, -0x60..-0x40 Y); Conqueror advises deriving positions from screen-width/window-width constants and half-screen center + cursor offset for dragging | (BLSS_CLIENT_GetScreenWidth() - BLSS_CLIENT_WindowStartX()) + -0x12e <= mouse_x *`corpus.Resolution_adaptive_panel_coordinate_math` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] To fill missing tiles at higher resolution: iterate nearby rooms excluding those the client already drew, and do additional drawing for rooms visible from client's perspective. FLOOR tiles are drawn by a SEPARATE function from wall/roof tiles and units — two distinct functions, both called recursively. Directly relevant to filling the black play-area/dead-zone with world tiles | Floor tiles are handled separately from wall/roof tiles and units - hence why there are two distinct functions ... These two functions are also called *`corpus.Multires_additional_room_floor_drawing_Slugg` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] LAZ: to add a NEW screen/UI shift without disturbing existing behavior, you must extend the shift struct | if you want a new shift that doesn't affect other stuff, you might have to extend that struct *`corpus.Screen_shift_struct_must_be_extended` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Patch sites (NOPBLOCK, 0x15 bytes) that disable the bottom-panel input handlers; input handlers are separate from the draw code. | Disable bottom panel input handlers *`corpus.Bottom_panel_input_handler_NOP_sites` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Patch sites (NOPBLOCK 0x15) that disable the mini-panel input handlers. | Disable mini panel input handlers *`corpus.Mini_panel_input_handler_NOP_sites` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Control panel + exp bar draw from one top-level handler; NOP the no-arg calls to see what draws what. Click/input handlers are NOT part of draw code and can be found via storm ordinal #511. | You can NOP out the calls with no args to quickly see what draws what ... find any input handler via that ordinal *`corpus.Find_panel_draw_vs_input_code` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] To locate draw code for an asset, find READS of the asset's data pointer, not the load code. Example pointer 6FBCBC80. | to find what draws it you need to look for reads of 6FBCBC80 *`corpus.Find_what_draws_a_loaded_asset` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Unnamed RoomEx flag bit; room-tile relevant, asked but unnamed. | anybody has a name for RoomExFlag 0x80000000 ? *`corpus.RoomExFlag_0x80000000` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] When charm reqs are met, jump gives the charm a blue background (Firehawk 2019 note). Cited by our own Doug. | reqs met, give charm a blue background ... JMP D2Client.6FB45803 *`corpus.Charm_blue_background_jump` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Plan to decompile d2expres in Ghidra to load a high-res DC6 for a new stat page and make its button work. | decompile d2expres in ghidra, and see if anything there seems like it may let us load a high res dc6 for new stat page and make the button work *`corpus.New_high_res_stat_page_panel_decompile_d2exp` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Custom stats interface: loads a DC6 and blits frames via D2PrintImage at panel coordinates (0,256),(256,256),(0,512),(256,512),(0,552) then fillRect(0,0,400,552). Extra frames 6/7 don't render. Uses setImage/setFrame/D2PrintImage/fillRect helpers; palette index 5. | NewInterface_Stats.cpp is containing this code, where it loads the dc6 ... D2PrintImage(&data, 0, 256, -1, 5, 0); *`corpus.NewInterface_Stats_DC6_panel_draw` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Doug (Esoteric Path/Sophia) notes porting functions/hooks from 1.10 to 1.13c requires shifting all offsets and re-hooking. | It would require too much work to shift offsets from 1.10, hook those functions. Etc. *`corpus.Porting_1_10_hooks_to_1_13c_requires_offset_` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Item-on-ground nameplate rendering. When Alt held, MOV DWORD PTR [EBP+118] sets draw mode: 6FB0933F sets 1 (regular items), 6FB09306 sets 5 (hovered item). Can hook 6FB0946E CALL D2Win.#10013 to override palette/draw mode. Single nameplate (alt not held) at 6F8F356C PUSH 2. | else you can just hook: 6FB0946E CALL and replace the values for palette and draw mode yourself *`corpus.Alt_item_ground_item_name_drawing_palette_dr` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] 1.13c: forced a conditional jump to unconditional JMP at D2Client 6FB28092 -> 6FB281BF to eliminate the 'Skill Tab 1' hover text plus its black background box. | 6FB28092 E9 28010000 JMP D2Client.6FB281BF ... eliminated it *`corpus.Remove_skill_tab_hover_text_black_background` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Whilst's edit controlling the level-name DC6 shown on level change. At D2Client BF33F (6FB6F337): reads [6FB8BC4C], CDQ/SUB/SAR, PUSH 1 (palshift), PUSH 5 (draw mode), SUB EAX,8C (vertical position), PUSH 1 (horizontal position). Routine begins at 6FB6F0D0 (reads [6FBCBBFC]); called from 6FB73AA1. | 6FB6F345 2D 8C000000 SUB EAX,8C ;vertical position 6FB6F34A 6A 01 PUSH 1 ;horizontal position *`corpus.Level_entry_level_name_DC6_display_routine` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] CALL D2Client.6FB6F0D0 at 6FB73AA1 is the sole call site of the level-entry display routine; nearby CMP [6FBCC414],3 and CALL 6FB12680 / 6FB0C5C0 / CMP [6FBAADCC]. | 6FB73AA1 |> E8 2AB6FFFF CALL D2Client.6FB6F0D0 <--- here *`corpus.Level_entry_DC6_caller_site` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Client monster-description block (immunities/minion text) around 6FB2100D. NOP the TEST EAX/JNZ at 6FB2100D to append monstats to minions/uniques; also adjust MOV EAX,13 at 6FB22167 and ADD EAX,31 at 6FB221D0. Underlying draw call at 6FB20600. D2Client based at 6FAA0000. | 6FB2100D ... 90 NOP ... 6FB22167 B8 13000000 MOV EAX,13 ... 6FB221D0 83C0 31 ADD EAX,31 *`corpus.Monster_desc_immunities_drawing_edits` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] 1.13c D2Client attack/cast speed bars for left/right click. Draw routine at 6FAEE7F0 (SUB ESP,124; MOV EAX,[6FB8BC4C]; ADD EAX,-56 vertical position). Horizontal position set by PUSH 50 (Arg2) at 6FAEEC24 — that section holds left bar, right bar, and mouseover icon values. | 6FAEEC24 |. 6A 50 PUSH 50 ; |Arg2 = 00000050 ... values for the icons for the left skill bar, right skill bar *`corpus.Skill_speed_bars_attack_speed_positioning` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] HP/mana/stamina sent as 15-bit fields in one packet; change the 15 to 16 at three server + three client sites to raise the ~65k limit (1.13c) | just gotta change the 15 to 16 at these three locations (1.13c) D2Game.0x8B81B D2Game.0x8B82D D2Game.0x8B83F *`corpus.Health_mana_globe_15_16_bit_widening` · D2Game/D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Hover-object hook draws custom DC6 frames (hp/res frames) at hardcoded screen coords via D2GFX_DrawShiftedImage when hovered unit is an enemy monster matching a txt id | D2GFX_DrawShiftedImage(&ChesRes, 90, 17, -1, 5, 0);// hp frame *`corpus.HoverObjectPatch_custom_monster_bar_draw` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Walks Room2->pPreset PresetUnit chain; sets cellNo into Maximap.dc6 to draw custom automap icons for preset units/shrines (shrine list by dwTxtFileNo, Type 0x0F) | void Miniicons::RevealRoom(Room2* room) { for (PresetUnit* preset = room->pPreset; ...) ... cellNo = 1499; *`corpus.Automap_custom_icon_via_Miniicons_RevealRoom` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] D2Tweaks hooks two D2Client call sites: +0x1641B = handle dropped items, +0x81BF5 = draw dropped item names (the ground-label render call). Useful anchors for the ground-item name draw in the play area. | m_draw_dropped_items_names_original = ... get_call(diablo2::d2_client::get_base() + 0x81BF5)); ... set_call(...get_base() + 0x1641B, handle_dropped_it *`corpus.Loot_filter_dropped_item_render_handler_hook` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] For windowed/background running: NOP D2GFX 0x4D11 and 0x4932 (VA 6FA74932/6FA74D11) to prevent minimizing; NOP D2Win 0xDC9E (6F8ADC9E) to keep the game running while unfocused. D2Gfx multiclient fix at d2Gfx+0x446A (write 0xEB). | //D2GFX.6FA74932/6FA74D11 prevent minimizing { D2DLL_D2GFX, 0x4D11, 0x90, FALSE, 11 }, { D2DLL_D2GFX, 0x4932, 0x90, FALSE, 11 }, //D2Win.6F8ADC9E Stay *`corpus.D2GFX_prevent_minimize_stay_running_unfocuse` · D2GFX/D2Win* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Reported intermittent access-violation in D2Common when entering one particular level -- unresolved in this chunk; possibly level/room load related. No fix given. | c0000005 at 6F622D97 01:00021D97 in D2Common.dll ... when entering one level *`corpus.crash_c0000005_at_6F622D97_on_level_entry` · D2Common* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Multibox patch nops D2GFX window-focus check; 1.13c range 0x21-0x66 | { D2Dll_kD2GFX, 0x21, kPatchNop, 0, 0x66 - 0x21 } *`corpus.D2GFX_multibox_nop_patch` · D2GFX* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Lighting interception patch; user reports it fails in Act 5 for 1.13c (may need separate act5 patch) | Patch* lightingPatch = new Patch(Call, D2CLIENT, { 0xA9A37, 0x233A7 }, (int)Lighting_Interception, 6) *`corpus.Lighting_Interception_patch` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Act5 snow/rain: per-level switch sets weather DAT_007a89d8/dc/9c/c0 params, RNG FUN_00472280, draw via FUN_004f6300(x,0,y,...); seeds from pPlayer->pSeed | i assume the snow is here ... switch(DVar3) { case LEVEL_HARROGATH ... _DAT_007a89dc = 0x2a *`corpus.snow_weather_render_globals_act5` · D2 exe (1.14d)* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] Store-tab loop MOV ECX,D2Client.6FB91186 then indexed access [ESP+EAX*4+10] | MOV ECX,D2Client.6FB91186 ... MOV EBP,DWORD PTR SS:[ESP+EAX*4+10] *`corpus.vendor_tab_access_asm` · D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [note] D2Win.#10175 uses pUnit->nGlobalPaletteShift(+0x6C) for the unit's current PL2 shift, taking precedence over D2Client.0x6FAAE3C0; overwrite those calls for custom PL2 shift | D2Win.#10175 which uses pUnit->nGlobalPaletteShift (+6C) ... takes precedence over D2Client.0x6FAAE3C0 *`corpus.unit_palette_PL2_shift` · D2Win / D2Client* corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### [reference] THE DISCORD CORPUS: research/kb/corpus/discord/phrozen_code_channel.json = 52,604 messages from PhrozenKeep's #phrozen_code. Top authors (all TRUSTED-tier candidates): Necrolis(6737), Lectem(2883), whist(1930), Kingpin(1495), Firehawk, Silvermane, Ogodei, eezstreet, Mir Drualga(SGD2FreeRes author). DOUG IS IN IT as 'Esoteric Path' (47 msgs, 2022-06 to 2025-06). His 2022-11 thread IS this exact problem: he asked how to fill the 47px bottom void; Necrolis: 'd2dx might be doing it via a clipping rect' (= the RenderContext viewport we re-found); Mir: 'those black rects are hardcoded in SGD2FreeRes to hide the HOM from not adding assets'; EP: 'a 4 tile buffer... also appears on the left if you open the character stats pane'. The bottom void + the side-panel void are the same phenomenon, documented by Doug in 2022. whist = Doug's 'whilst'. *`ref.discord_corpus`* Related: `finding.playarea_governs_floor_and_hittest`, `finding.clip_is_d2dx_drop_confirmed` ### [reference] DOCUMENTATION RIGOR RULE: every recorded observation MUST carry a valid way to confirm it -- the DLL VERSION it was seen in + the exact LOG line (or disasm) that proves it. Mark PROVEN (log/disasm) vs INFERRED (visual). No narrative claims without a verification handle. Applies to all esoteric.dll findings henceforth. *`protocol.evidence_rule`* ### [reference] NOVELTY vs public prior art (10 findings audited): 8 NOVEL (no public source states the technique/conclusion): the DrawGame 1.13c pipeline map; offX single-write@0x6FB5AF7C read-173x; the no-panel view-state lens; the floor-un-shifted-at-seam experiment; the worldCtx(0x11C3C4) menu gate; playAreaW-governs-floor-AND-hittest; the whole dead-zone-fill endeavor; the render-context struct at *(0x11C3C4). 2 CORROBORATED (attribute, do not claim as discovery): (a) D2DXWiden = bolrog's SANCTIONED public Configurator API SetCustomResolution -- attribute bolrog; (b) GlideUnclamp is adjacent to IAmTrial's documented hardcoded-640 D2Glide clamp @+0x15A68 (a DIFFERENT site than our 0x6F856840 cull -- ours is distinct). SUBSTRATE (named globals ScreenShiftX 0x11B9A0, GeneralPlayAreaCameraShiftX 0x11C418, GeneralDisplayWidth 0xDBC48) is PUBLIC (mir) -- attribute; the behavioral CONCLUSIONS + techniques on top are ours. Attribution: mir, bolrog, IAmTrial, Necrolis, kidpaddle94/JayTee. *`finding.novelty_audit_2026_07_18`* Related: `finding.screenshiftx_is_floor_shift`, `reframe.d2dx_no_per_category_transform` ### [reference] CROSS-VERIFY of all Grimoire addresses vs the on-disk 1.13c DLLs (D2Client 0x6FAB0000-0x6FBE5000, D2gfx ..0x6FAA1000, D2Win 0x6F8E0000, D2Glide 0x6F850000, D2Common 0x6FD50000, D2Game 0x6FC20000, D2CMP 0x6FE10000). 476 addresses / 698 records: 419 (88%) fall in a valid 1.13c module range (222 D2Client code + 117 D2Client data + D2Glide/D2Common/D2gfx/D2Win/D2Game); 57 FLAGGED as outside any 1.13c module = 1.10f-era/wrong. Flagged cluster = corpus.* 0x6FAAxxxx addresses sitting in the GAP between D2gfx (ends 0x6FAA1000) and D2Client (0x6FAB0000) -> they are 1.10f D2Client (lower base). NOTABLY the floor-drawer MULTIRES_DrawGroundTiles 0x6FAA2E60 and tile-setup 0x6FAA3330 ARE FLAGGED 1.10f -> do NOT use for 1.13c; rebase via struct layout + a 1.13c xref/disasm. RULE: any corpus 6FAAxxxx (below 0x6FAB0000) or cross-module-gap address is 1.10f, rebase before use. Assimilation-verification baseline. *`finding.crossverify_2026_07_19`* Related: `finding.d2floortileparams_floor_shift`, `protocol.evidence_rule` ### [reference] METHOD (Doug's key correction 2026-07-19): the ON-DISK DLL is NOT what runs. PlugY / SGD2FreeRes / d2fps patch D2Client in memory at load; at least 3 mods control frame/UI. Analyzing on-disk VANILLA code produced FOUR dead-end floor-shift hypotheses (see finding.floor_shift_ledger). Correct approach: (1) LIVE-vs-DISK byte diff of the running process reveals every mod patch; (2) disassemble the LIVE (patched) code, following mod redirects; (3) NOP-test via WriteProcessMemory (reversible, elevated) to isolate which mod's patch drives a symptom. D2Client live map: 117 patched regions / 1419 bytes; core floor draw all vanilla; render-controlling mods = SGD2FreeRes (@0x739C0000: SetScreenShift/SetTileCullingBound/DrawScreenBg/disable-stock-bg), PlugY (many), esoteric (ours). Redirect target bases seen: 0x739Cxxxx=SGD2FreeRes; 0x72Exxxx/0x73Dxxxx = mod code; 0x032xxxxx=codecaves(ours); 0x10000000=another module. TOOLKIT built tonight (scratchpad, all elevated RPM/WPM, no x32dbg needed for read/write/NOP): rpm_poll.py, freeze_poke.py, panel_delta.py, floor_scan.py, live_patch_map.py, disasm_sgd2.py, patch_toggle.py. *`finding.live_patch_map_method`* Related: `finding.floor_shift_sgd2_tileculling`, `finding.floor_shift_ledger` --- ## Symbol reference by module ### (19 symbols) - **function** `ext.DisableScrollOfInifussRectPatch_1_07_NOP` — NOPs the D2GFX.DrawRectangle call that draws the black rectangle on the Scroll-of-Inifuss (Cain quest) screen, which otherwise mis-renders at high res. Located via the '%s\UI\MENU\scroin' string then scrolling up to the DrawRectangle call. Single NOP patch per version. 1.13C at D2Client +0x7C38D (size 0x2D = 0x7C3BA-0x7C38D); 1.09D 0x8330C, 1.12A 0x70FBD, 1.13D 0x7516D, LoD1.14C 0x9C20D, LoD1.14D 0x9FA20 (all size 0x2D). Applies to essentially all versions 1.07-1.14D. - **struct** `struct.RosterUnit` — Party roster entry. szName[16] 0x00; dwUnitId 0x10; dwClassId 0x1C; wLevel 0x20; wPartyId 0x22; dwLevelId 0x24; Xpos 0x28; Ypos 0x2C; dwPartyFlags 0x30; szName2[16] 0x66; pNext 0x80. Xpos/Ypos are party-member map coords (minimap/render). | src: DWORD Xpos;0x28 | DWORD Ypos;0x2C | DWORD dwLevelId;0x24 - **function** `ext.GET_VERSION_D2Client` — D2Client.dll version probe: DWORD at base+0x17F. Render-critical module (client-side draw/UI); its detected version selects the offset table used for all client render hacks. | src: GET_VERSION(D2Client, 17F, 00000000, 14500000, 12500000, 0D814800, 0D812800, 0DA01000, 0DA03000); - **function** `ext.GET_VERSION_D2gfx` — D2gfx.dll (D2Gfx) version probe: DWORD at base+0x10D. Graphics/present module; version selects render offsets. Note col7 (V113c) sig is 00000000 (same literal as D2Client col1) — value copied verbatim as read. | src: GET_VERSION(D2gfx, 10D, EB000000, 006FA700, 00000010, 2A000401, 19000401, 0B210E00, 00000000); - **function** `ext.GET_VERSION_D2Win` — D2Win.dll version probe: DWORD at base+0x1699. UI/window/panel module; version selects offsets used by the panel (new-interfaces / stash) render hacks. | src: GET_VERSION(D2Win, 1699, 88686F8C, 84686F8C, D094686F, F0030000, 001435E8, 8B088F44, 0013F5E8); - **function** `ext.Install_NewInterfaces_entry_point_call_site` — Panel installer entry point: the new-interfaces (large stash / shared-stash panels + reworked inventory UI) hooks are installed only when the active_newInterfaces config flag is set. The Install_NewInterfaces() DEFINITION is NOT in D2wrapper.cpp (it lives in the interface/panel source, referenced via #include); D2wrapper.cpp holds only the gated call site. | src: if (active_newInterfaces) Install_NewInterfaces(); - **function** `ext.Install_MultiPageStash_entry_point_call_site` — Panel installer entry point: the multi-page (paged) stash hooks are installed only when active_multiPageStash is set. Definition not in D2wrapper.cpp (external source via #include); D2wrapper.cpp holds only the gated call site. | src: if (active_multiPageStash) Install_MultiPageStash(); - **struct** `struct.UnitAny_1_13c_layout_gfx_render_members` — UnitAny: dwType 0x00, dwTxtFileNo 0x04, pMemPool 0x08, dwUnitId 0x0C, dwMode 0x10, union pPlayerData/pItemData/... 0x14, dwAct 0x18, pAct 0x1C, seeds 0x20/24/28, pPath 0x2C, dwGfxFrame 0x44, dwFrameRemain 0x48, wFrameRate 0x4C, pAnimData 0x50, pGfxInfo(D2GfxDataStrc*) 0x54, pGfxInfo2 0x58, pStatsEx 0x5C, pInventory 0x60, pLight 0x64, dwLightRadius 0x68, nPalShiftIdx 0x6C, wX 0x8C, wY 0x8E, dwFlags 0xC4, dwFlagsEx 0xC | D2GfxDataStrc* pGfxInfo; //0x54 ... Light * pLight; //0x64 ... WORD nPalShiftIdx; //0x6C corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.UnitAny_0x6C_union` — pUnit+0x6C is a union: bool bInteracting / char nPL2Shift (palette-shift index). Confirmed by disasm MOV BL,[ESI+6C] as pPlayer->nInteract | union { bool bInteracting; char nPL2Shift; }; //+6C corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.D2GFXDataStrc_gfx_anim_descriptor_sizeof_0x4` — Necrolis authoritative gfx data struct: pCellInit +00, pCellFile +04, nFrame +08, nDirection +0C, nMaxDirections +10, nMaxFrames +14, fFlags +18, fState +1C, nComponent/fItemFlags +1D, nUnitType +20, nUnitIndex +24, nMode +28, nOverlay +2C, union{dwToken +30, dwComponent +34, dwArmorType +38, dwMode +3C, dwHitClass +40} , pName +44. This is UnitAny->pGfxInfo (+0x54) | struct D2GFXDataStrc //sizeof 0x48 { D2GFXCellStrc* pCellInit; //+00 ... const char* pName; //+44 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.AnimToken_COF_animation` — AnimToken{ D2TokenType tokenType; union{ cof_handle plrCof[PLRMODE_MAX][WC_MAX]; monCof[MONMODE_MAX]; objCof[OBJMODE_MAX]; } }. COFs load with token; .dcc directions loaded lazily per direction needed | struct AnimToken { D2TokenType tokenType; union { cof_handle plrCof[PLRMODE_MAX][WC_MAX]; ... } }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.D2InventoryGridInfoStrc` — Layout of an inventory grid panel: grid cell counts (X/Y), the pixel bounds of the grid (left/right/top/bottom) and per-cell box width/height. This is the geometry of a side panel's item grid. | int nGridLeft;//0x04 ... int nGridBottom;//0x10 BYTE nGridBoxWidth;//0x14 BYTE nGridBoxHeight;//0x15 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.D2_Tile_TileHeader_DT1` — Layout of the DT1 floor/wall tile header used by the map renderer, including per-tile collision bitmap (25 bytes), runtime cache index, block offset, light direction, roof height, dimensions, type/style/sequence, and 24-bit transparent color. Direct floor/tile rendering data. | uint8_t fCollisionInfo[25];//+28 ... uint16_t wCacheIndex;//+42 - runtime only ... uint32_t dwBlockOffset;//+48 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.D2SubTileStrc` — Sub-tile (tile component) descriptor for DT1 rendering: screen X/Y, grid X/Y, pixel format, encoded length, and offset/pointer to the pixel data block. | struct D2SubTileStrc //sizeof 0x14 { short nXpos;//+00 short nYpos;//+02 ... uint16_t wFormat;//+08 int nLength;//+0A corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.DT1_file_Header` — On-disk DT1 tile-library header layout for both current (v7) and older (v4) formats; v4's skipColor is the RGB skipped in D2CMP CelIteratePixels. Floor/tile file format. | uint32_t version; ... if(version == 7){ char libraryName[260]; uint32_t numTiles; uint32_t firstTile; } corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.Rooms_Act_active_room_fields_GetActiveRooms` — Level/room bookkeeping offsets; nRooms increments on room init while nActiveRooms tracks active count. GetActiveRooms(Act* pAct, int dwLevelNo). Relevant to map streaming around the play area. | int nRooms;//0x004 ... int nActiveRooms;//0x00C ... GetActiveRooms ... -10662 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.D2ObjectsTxt_frame_fields` — Objects.txt runtime record has a frame-count array around offset 0xD8-0xD9 (pad0xD8 then dwFrameCnt[7]); relevant to object animation frame rendering. | BYTE pad0xD8;//0xD8 DWORD dwFrameCnt[7];//0xD9 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.Collision_Attributes` — Full COLLISION_* bitflag table 0x0001-0x8000 (BlockWalk, BlockLOS, Wall, BlockPlayer, Missile, Player, DeadBody etc.) used in path/tile collision map | #define COLLISION_BLOCKWALK 0x0001 ... #define COLLISION_DEADBODY 0x8000 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `struct.AutomapCellTypeInfo` — { CelFile* pCelFile; RECT tRect; DWORD nOffsetX; DWORD nOffsetY; } - describes an automap cell graphic | struct AutomapCellTypeInfo { CelFile *pCelFile; RECT tRect; DWORD nOffsetX; DWORD nOffsetY; } corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### (no module) (31 symbols) - **struct** `struct.UnitAny` — The universal client-side game-entity struct; every player, monster, item, and object shares this layout, with a union selecting the type-specific data block. fields: [{"off": "0x00", "name": "dwType", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "dwClassId", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "pMemPool", "type": "void*", "note": ""}, {"off": "0x0C", "name": "dwUnitId", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "dwMode", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "pPlayerData", "type": "PlayerData*", "note": "union @0x14"}, {"off": "0x14", "name": "pItemData", "type": "ItemData*", "note": "union @0x14"}, {"off": "0x14", "name": "pMonsterData", "type": "MonsterData*", "note": "union @0x14"}, {"off": "0x14", "name": "pObjectData", "type": "ObjectData*", "note": "union @0x14"}, {"off": "0x18", "name": "dwAct", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "pAct", "type": "Act*", "note": ""}, {"off": "0x20", "name": "dwSeed[2]", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "dwInitSeed", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "pPath", "type": "Path*", "note": "union @0x2C"}, {"off": "0x2C", "name": "pStaticPath", "type": "StaticPath*", "note": "union @0x2C"}, {"off": "0x30", "name": "_3[5]", "type": "DWORD", "note": ""}, {"off": "0x44", "name": "dwGfxFrame", "type": "DWORD", "note": ""}, {"off": "0x48", "name": "dwFrameRemain", "type": "DWORD", "note": ""}, {"off": "0x4C", "name": "wFrameRate", "type": "WORD", "note": ""}, {"off": "0x4E", "name": "_4", "type": "WORD", "note": ""}, {"off": "0x50", "name": "pAnimData", "type": "DWORD*", "note": ""}, {"off": "0x54", "name": "pGfxInfo", "type": "GfxInfo*", "note": ""}, {"off": "0x58", "name": "pGfxInfo2", "type": "GfxInfo*", "note": ""}, {"off": "0x5C", "name": "pStatsEx", "type": "StatListEx*", "note": ""}, {"off": "0x60", "name": "pInventory", "type": "Inventory*", "note": ""}, {"off": "0x64", "name": "pLight", "type": "Light*", "note": ""}, {"off": "0x68", "name": "dwLightRadious", "type": "DWORD", "note": ""}, {"off": "0x6C", "name": "nPalShiftIdx", "type": "WORD", "note": ""}, {"off": "0x6E", "name": "UpdateType", "type": "WORD", "note": ""}, {"off": "0x70", "name": "pUpdateUnit", "type": "UnitAny*", "note": ""}, {"off": "0x74", "name": "pQuestRecord", "type": "DWORD*", "note": ""}, {"off": "0x78", "name": "bSparkyChest", "type": "BYTE", "note": ""}, {"off": "0x79", "name": "_6a[3]", "type": "BYTE", "note": ""}, {"off": "0x7C", "name": "pTimerArgs", "type": "DWORD", "note": ""}, {"off": "0x80", "name": "dwSoundSync", "type": "DWORD", "note": ""}, {"off": "0x84", "name": "_7[2]", "type": "DWORD", "note": ""}, {"off": "0x8C", "name": "wX", "type": "WORD", "note": ""}, {"off": "0x8E", "name": "wY", "type": "WORD", "note": ""}, {"off": "0x90", "name": "pEvent", "type": "DWORD", "note": ""}, {"off": "0x94", "name": "dwOwnerType", "type": "DWORD", "note": ""}, {"off": "0x98", "name": "dwOwnerId", "type": "DWORD", "note": ""}, {"off": "0x9C", "name": "_8[2]", "type": "DWORD", "note": ""}, {"off": "0xA4", "name": "pHoverText", "type": "HoverText*", "note": ""}, {"off": "0xA8", "name": "pInfo", "type": "Info*", "note": ""}, {"off": "0xAC", "name": "ptCombat", "type": "void*", "note": ""}, {"off": "0xB0", "name": "dwLastHitClass", "type": "DWORD", "note": ""}, {"off": "0xB4", "name": "_9[4]", "type": "DWORD", "note": ""}, {"off": "0xC4", "name": "dwFlags", "type": "DWORD", "note": ""}, {"off": "0xC8", "name": "dwFlags2", "type": "DWORD", "note": ""}, {"off": "0xCC", "name": "_10[5]", "type": "DWORD", "note": ""}, {"off": "0xE0", "name": "pChangedNext", "type": "UnitAny*", "note": ""}, {"off": "0xE4", "name": "pRoomNext", "type": "UnitAny*", "note": "changed 14.12.11"}, {"off": "0xE8", "name": "pListNext", "type": "UnitAny*", "note": ""}, {"off": "0xEC", "name": "pMsgFirst", "type": "pMsg*", "note": ""}, {"off": "0xF0", "name": "pMsgLast", "type": "pMsg*", "note": ""}] - **struct** `struct.PlayerData` — Per-player data block (name, quest flags, waypoints, assigned/switch skills, trade state) hung off UnitAny.pPlayerData; copied from the serverside layout. fields: [{"off": "0x00", "name": "szName[16]", "type": "char", "note": ""}, {"off": "0x10", "name": "QuestsFlags[3]", "type": "QuestFlags*", "note": ""}, {"off": "0x1C", "name": "pNormalWaypoint[3]", "type": "Waypoint*", "note": ""}, {"off": "0x28", "name": "ptAiGeneral", "type": "void*", "note": ""}, {"off": "0x2C", "name": "_1[2]", "type": "DWORD", "note": ""}, {"off": "0x34", "name": "KillCounter", "type": "WORD*", "note": "Monster"}, {"off": "0x38", "name": "_1b", "type": "DWORD", "note": ""}, {"off": "0x3C", "name": "_2[4]", "type": "DWORD", "note": "2[2] is merc related"}, {"off": "0x4C", "name": "dwTradeTick", "type": "DWORD", "note": "mby InactivityTime"}, {"off": "0x50", "name": "nTradeState", "type": "DWORD", "note": ""}, {"off": "0x54", "name": "_3", "type": "DWORD", "note": ""}, {"off": "0x58", "name": "dwAcceptTradeTick", "type": "DWORD", "note": ""}, {"off": "0x5C", "name": "pTrade", "type": "void*", "note": ""}, {"off": "0x60", "name": "_4[3]", "type": "DWORD", "note": ""}, {"off": "0x6C", "name": "dwBoughtItemId", "type": "DWORD", "note": ""}, {"off": "0x70", "name": "dwRightSkill", "type": "DWORD", "note": ""}, {"off": "0x74", "name": "dwLeftSkill", "type": "DWORD", "note": ""}, {"off": "0x78", "name": "dwRightSkillFlags", "type": "DWORD", "note": ""}, {"off": "0x7C", "name": "dwLeftSkillFlags", "type": "DWORD", "note": ""}, {"off": "0x80", "name": "dwSwitchRightSkill", "type": "DWORD", "note": ""}, {"off": "0x84", "name": "dwSwitchLeftSkill", "type": "DWORD", "note": ""}, {"off": "0x88", "name": "dwSwitchRightSkillFlags", "type": "DWORD", "note": ""}, {"off": "0x8C", "name": "dwSwitchLeftSkillFlags", "type": "DWORD", "note": ""}, {"off": "0x90", "name": "_5[3]", "type": "DWORD", "note": ""}, {"off": "0x9C", "name": "pClientData", "type": "ClientData*", "note": ""}, {"off": "0x100", "name": "_6[50]", "type": "DWORD", "note": ""}, {"off": "0x168", "name": "GameFrame", "type": "DWORD", "note": ""}] - **struct** `struct.MonsterData` — Per-monster data block (txt record, gfx components, name seed, unique/champion/minion bit-flags, enchantments, AI pointer, name) hung off UnitAny.pMonsterData. fields: [{"off": "0x00", "name": "pMonStatsTxt", "type": "MonStatsTxt*", "note": ""}, {"off": "0x04", "name": "Components[16]", "type": "BYTE", "note": ""}, {"off": "0x14", "name": "NameSeed", "type": "WORD", "note": ""}, {"off": "0x16", "name": "(anonymous bitfield)", "type": "BYTE : 1 (x8)", "note": "fOther, fSuperUniq, fChampion, fUnique, fMinion, fPossesed, fGhostly, fMultishot (each 1 bit)"}, {"off": "0x17", "name": "dwLastMode", "type": "BYTE", "note": ""}, {"off": "0x18", "name": "dwDuriel", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "anEnchants[9]", "type": "BYTE", "note": ""}, {"off": "0x25", "name": "_1", "type": "BYTE", "note": ""}, {"off": "0x26", "name": "wUniqueNo", "type": "WORD", "note": ""}, {"off": "0x28", "name": "pAiGeneral", "type": "AiGeneral*", "note": ""}, {"off": "0x2C", "name": "wName[28]", "type": "wchar_t*", "note": ""}] - **struct** `struct.ObjectData` — Per-object data block (object txt record, interaction type, owner name) hung off UnitAny.pObjectData. fields: [{"off": "0x00", "name": "pObjectTxt", "type": "ObjectTxt*", "note": ""}, {"off": "0x04", "name": "InteractType", "type": "BYTE", "note": ""}, {"off": "0x05", "name": "_1[3]", "type": "BYTE", "note": ""}, {"off": "0x08", "name": "_2[8]", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "szOwner[16]", "type": "char", "note": ""}] - **struct** `struct.ItemData` — Per-item data block (quality, seeds, owner, flags, prefixes/suffixes, body & inventory location, socket linkage) hung off UnitAny.pItemData. fields: [{"off": "0x00", "name": "QualityNo", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "LowSeed", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "HighSeed", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "OwnerID", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "InitSeed", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "CommandFlags", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "ItemFlags", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "_1[2]", "type": "DWORD", "note": ""}, {"off": "0x24", "name": "ActionStamp", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "FileIndex", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "ItemLevel", "type": "DWORD", "note": ""}, {"off": "0x30", "name": "ItemFormat", "type": "WORD", "note": ""}, {"off": "0x32", "name": "RarePrefix", "type": "WORD", "note": ""}, {"off": "0x34", "name": "RareSutfix", "type": "WORD", "note": ""}, {"off": "0x36", "name": "AutoPrefix", "type": "WORD", "note": ""}, {"off": "0x38", "name": "MagicPrefix[3]", "type": "WORD", "note": ""}, {"off": "0x3E", "name": "MagicSutfix[3]", "type": "WORD", "note": ""}, {"off": "0x44", "name": "BodyLoc", "type": "BYTE", "note": "location on body = xpos of item (only if on body) (spread)"}, {"off": "0x45", "name": "InvPage", "type": "BYTE", "note": "0 = inventory, 3 = cube, 4 = stash (spread)"}, {"off": "0x46", "name": "_2[2]", "type": "BYTE", "note": ""}, {"off": "0x48", "name": "EarLvl", "type": "BYTE", "note": ""}, {"off": "0x49", "name": "InvGfxIdx", "type": "BYTE", "note": ""}, {"off": "0x4A", "name": "szPlayerName[16]", "type": "char", "note": ""}, {"off": "0x5A", "name": "_3[2]", "type": "BYTE", "note": ""}, {"off": "0x5C", "name": "pNodeInv", "type": "Inventory*", "note": ""}, {"off": "0x60", "name": "_4", "type": "DWORD", "note": ""}, {"off": "0x64", "name": "pNextItem", "type": "UnitAny*", "note": ""}, {"off": "0x68", "name": "NodePos", "type": "BYTE", "note": ""}, {"off": "0x69", "name": "NodePosOther", "type": "BYTE", "note": ""}] - **struct** `struct.Path` — Movement/position path for an active (moving) unit: current and target coordinates, owning rooms, collision flags, path type, target unit, and facing direction. fields: [{"off": "0x00", "name": "xOffset", "type": "short", "note": ""}, {"off": "0x02", "name": "xPos", "type": "short", "note": ""}, {"off": "0x04", "name": "yOffset", "type": "short", "note": ""}, {"off": "0x06", "name": "yPos", "type": "short", "note": ""}, {"off": "0x08", "name": "xUnknown", "type": "DWORD", "note": "16 * (wInitX - wInitY) <- Mby AutomapX"}, {"off": "0x0C", "name": "yUnknown", "type": "DWORD", "note": "8 * (wInitX + wInitY + 1) <- Mby AutoampY"}, {"off": "0x10", "name": "xTarget", "type": "short", "note": ""}, {"off": "0x12", "name": "yTarget", "type": "short", "note": ""}, {"off": "0x14", "name": "_2[2]", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "pRoom1", "type": "Room1*", "note": ""}, {"off": "0x20", "name": "pRoomUnk", "type": "Room1*", "note": ""}, {"off": "0x24", "name": "_3[3]", "type": "DWORD", "note": ""}, {"off": "0x30", "name": "pUnit", "type": "UnitAny*", "note": ""}, {"off": "0x34", "name": "dwFlags", "type": "DWORD", "note": "0x40000 -> PATH_MISSILE_MASK"}, {"off": "0x38", "name": "_4", "type": "DWORD", "note": ""}, {"off": "0x3C", "name": "dwPathType", "type": "DWORD", "note": ""}, {"off": "0x40", "name": "dwPrevPathType", "type": "DWORD", "note": ""}, {"off": "0x44", "name": "dwUnitSize", "type": "DWORD", "note": ""}, {"off": "0x48", "name": "_5[2]", "type": "DWORD", "note": ""}, {"off": "0x50", "name": "dwCollisionFlag", "type": "DWORD", "note": "0x1804 <- bFlying, 0x3401 <- bOpenDoors, 0x3C01 <- Cannot Open Doors, 0x804 <- Ghost, 0x1C09 <- Player"}, {"off": "0x54", "name": "_5d", "type": "DWORD", "note": ""}, {"off": "0x58", "name": "pTargetUnit", "type": "UnitAny*", "note": ""}, {"off": "0x5C", "name": "dwTargetType", "type": "DWORD", "note": ""}, {"off": "0x60", "name": "dwTargetId", "type": "DWORD", "note": ""}, {"off": "0x64", "name": "bDirection", "type": "BYTE", "note": ""}] - **struct** `struct.StaticPath` — Simplified position record (size 0x20) for units that do not move (items/objects): owning room plus offset and grid position. fields: [{"off": "0x00", "name": "pRoom1", "type": "Room1*", "note": ""}, {"off": "0x04", "name": "xOffset", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "yOffset", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "xPos", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "yPos", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "_1[2]", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "dwFlags", "type": "DWORD", "note": ""}] - **struct** `struct.Info` — Skill container hung off UnitAny.pInfo: mem pool plus pointers to the first, left-hand, and right-hand active skills. fields: [{"off": "0x00", "name": "pMemPool", "type": "DWORD*", "note": ""}, {"off": "0x04", "name": "pFirstSkill", "type": "Skill*", "note": ""}, {"off": "0x08", "name": "pLeftSkill", "type": "Skill*", "note": ""}, {"off": "0x0C", "name": "pRightSkill", "type": "Skill*", "note": ""}] - **struct** `struct.Skill` — A single skill entry in a unit's skill linked list: txt record, next-link, mode, level, and flags. fields: [{"off": "0x00", "name": "pSkillsTxt", "type": "SkillsTxt*", "note": ""}, {"off": "0x04", "name": "pNextSkill", "type": "Skill*", "note": ""}, {"off": "0x08", "name": "SkillMode", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "_1[7]", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "dwSkillLevel", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "_2[2]", "type": "DWORD", "note": ""}, {"off": "0x30", "name": "dwFlags", "type": "DWORD", "note": ""}] - **struct** `struct.SkillInfo` — Skill identifier record referenced by Skill.pSkillInfo; holds the skill id. NOTE: absent from D2Structs_113D.h (which uses SkillsTxt* directly) — this definition comes only from the d2bs D2Structs.h. fields: [{"off": "0x00", "name": "wSkillId", "type": "WORD", "note": ""}] - **struct** `struct.Room1` — Active/in-memory room node holding collision map, units, and nearby-room links for a level. fields: [{"off": "0x00", "name": "pRoomsNear", "type": "Room1**", "note": "pptVisibleRooms"}, {"off": "0x04", "name": "_1", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "_1s", "type": "void*", "note": ""}, {"off": "0x0C", "name": "_1b", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "pRoom2", "type": "Room2*", "note": ""}, {"off": "0x14", "name": "_2[2]", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "pUnitChanged", "type": "UnitAny**", "note": ""}, {"off": "0x20", "name": "Coll", "type": "CollMap*", "note": ""}, {"off": "0x24", "name": "dwRoomsNear", "type": "DWORD", "note": "dwVisibleRooms"}, {"off": "0x28", "name": "nPlayerUnits", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "pAct", "type": "Act*", "note": ""}, {"off": "0x30", "name": "_4", "type": "DWORD", "note": ""}, {"off": "0x34", "name": "nUnknown", "type": "DWORD", "note": ""}, {"off": "0x38", "name": "_5[4]", "type": "DWORD", "note": ""}, {"off": "0x48", "name": "pClients", "type": "ClientData**", "note": ""}, {"off": "0x4C", "name": "hCoords", "type": "RoomCoords", "note": ""}, {"off": "0x6C", "name": "hSeed", "type": "D2Seed", "note": ""}, {"off": "0x74", "name": "pUnitFirst", "type": "UnitAny*", "note": ""}, {"off": "0x78", "name": "nNumClients", "type": "DWORD", "note": ""}, {"off": "0x7C", "name": "pRoomNext", "type": "Room1*", "note": ""}] - **struct** `struct.Room2` — Static/preset room descriptor (DRLG layout) linking to its Room1, level, tiles, and preset units. fields: [{"off": "0x00", "name": "_1[2]", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "pRoom2Near", "type": "Room2**", "note": ""}, {"off": "0x0C", "name": "_2[2]", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "hSeed", "type": "D2Seed", "note": ""}, {"off": "0x1C", "name": "_2b", "type": "DWORD", "note": ""}, {"off": "0x20", "name": "pType2Info", "type": "struct { DWORD dwRoomNumber; DWORD* _1; DWORD* pdwSubNumber; }*", "note": "inline anon struct: dwRoomNumber //0x00, _1 //0x04, pdwSubNumber //0x08. points to 0x70 struct if dwPresetType == 1, 0xF8 struct if == 2"}, {"off": "0x24", "name": "pRoom2Next", "type": "Room2*", "note": ""}, {"off": "0x28", "name": "dwRoomFlags", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "dwRoomsNear", "type": "DWORD", "note": ""}, {"off": "0x30", "name": "pRoom1", "type": "Room1*", "note": ""}, {"off": "0x34", "name": "dwPosX", "type": "DWORD", "note": ""}, {"off": "0x38", "name": "dwPosY", "type": "DWORD", "note": ""}, {"off": "0x3C", "name": "dwSizeX", "type": "DWORD", "note": ""}, {"off": "0x40", "name": "dwSizeY", "type": "DWORD", "note": ""}, {"off": "0x44", "name": "_3", "type": "DWORD", "note": ""}, {"off": "0x48", "name": "dwPresetType", "type": "DWORD", "note": ""}, {"off": "0x4C", "name": "pRoomTiles", "type": "RoomTile*", "note": ""}, {"off": "0x50", "name": "_4[2]", "type": "DWORD", "note": ""}, {"off": "0x58", "name": "pLevel", "type": "Level*", "note": ""}, {"off": "0x5C", "name": "pPreset", "type": "PresetUnit*", "note": ""}] - **struct** `struct.Level` — A single DRLG level (map area) with its rooms, size/position, seed, presets, and warp/room-center coordinates. sizeof = 0x230. fields: [{"off": "0x00", "name": "dwDrlgType", "type": "DWORD", "note": "1 - maze, 2 - preset, 3 - wilderness"}, {"off": "0x04", "name": "dwLevelFlags", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "_1[2]", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "pRoom2First", "type": "Room2*", "note": ""}, {"off": "0x14", "name": "(anonymous union)", "type": "union { LvlMazeTxt* pMazeTxt; PresetData* pPreset; WildernessData* pWilderness; }", "note": "pMazeTxt for dwDrlgType==1 (RANDOM MAZE); pPreset for dwDrlgType==2 (PRESET MAP); pWilderness for dwDrlgType==3 (RANDOM AREA WITH PRESET SIZE)"}, {"off": "0x18", "name": "_2", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "dwPosX", "type": "DWORD", "note": ""}, {"off": "0x20", "name": "dwPosY", "type": "DWORD", "note": ""}, {"off": "0x24", "name": "dwSizeX", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "dwSizeY", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "_3[96]", "type": "DWORD", "note": ""}, {"off": "0x1AC", "name": "pNextLevel", "type": "Level*", "note": ""}, {"off": "0x1B0", "name": "_4", "type": "DWORD", "note": ""}, {"off": "0x1B4", "name": "pMisc", "type": "ActMisc*", "note": ""}, {"off": "0x1B8", "name": "_5[2]", "type": "DWORD", "note": ""}, {"off": "0x1C0", "name": "dwLevelType", "type": "DWORD", "note": ""}, {"off": "0x1C4", "name": "hSeed", "type": "D2Seed", "note": ""}, {"off": "0x1CC", "name": "pLevelPresets", "type": "LevelPreset*", "note": ""}, {"off": "0x1D0", "name": "dwLevelNo", "type": "DWORD", "note": ""}, {"off": "0x1D4", "name": "_6[3]", "type": "DWORD", "note": ""}, {"off": "0x1E0", "name": "(anonymous union)", "type": "union { DWORD RoomCenterX[9]; DWORD WarpX[9]; }", "note": ""}, {"off": "0x204", "name": "(anonymous union)", "type": "union { DWORD RoomCenterY[9]; DWORD WarpY[9]; }", "note": ""}, {"off": "0x228", "name": "dwRoomEntries", "type": "DWORD", "note": ""}, {"off": "0x22C", "name": "_7", "type": "DWORD", "note": ""}] - **struct** `struct.Act` — Per-act runtime container holding the act's environment, town level, map seed, root room, and DRLG misc pointer. fields: [{"off": "0x00", "name": "_1a", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "pEnviroment", "type": "ActEnvironment*", "note": ""}, {"off": "0x08", "name": "dwTownLvl", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "dwMapSeed", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "pRoom1", "type": "Room1*", "note": ""}, {"off": "0x14", "name": "dwAct", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "hTile[12]", "type": "DWORD", "note": "not sure, some inline struct of 0x30 size seems fit"}, {"off": "0x48", "name": "pMisc", "type": "ActMisc*", "note": ""}, {"off": "0x4C", "name": "_4", "type": "DWORD", "note": ""}, {"off": "0x50", "name": "_5", "type": "DWORD", "note": ""}, {"off": "0x54", "name": "_6", "type": "DWORD", "note": ""}, {"off": "0x58", "name": "_7", "type": "DWORD", "note": ""}, {"off": "0x5C", "name": "pMemPool", "type": "void*", "note": ""}] - **struct** `struct.ActMisc` — DRLG-level misc/generation state for an act (aka Drlg): seeds, tile pointers, callbacks, level list, and back-pointer to Act. sizeof(0x48C). fields: [{"off": "0x00", "name": "hSeed", "type": "D2Seed", "note": ""}, {"off": "0x08", "name": "_1", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "pTile", "type": "DWORD*", "note": ""}, {"off": "0x10", "name": "_1g[15]", "type": "DWORD", "note": ""}, {"off": "0x4c", "name": "pfnCallBack", "type": "DWORD", "note": ""}, {"off": "0x50", "name": "_1a[16]", "type": "DWORD", "note": ""}, {"off": "0x90", "name": "pVis", "type": "DrlgVis*", "note": ""}, {"off": "0x94", "name": "dwStaffTombLevel", "type": "DWORD", "note": ""}, {"off": "0x98", "name": "_2a", "type": "DWORD", "note": ""}, {"off": "0x9C", "name": "pGame", "type": "Game*", "note": ""}, {"off": "0xA0", "name": "_2[236]", "type": "DWORD", "note": ""}, {"off": "0x450", "name": "nDiffLvl", "type": "DWORD", "note": ""}, {"off": "0x454", "name": "fnCallback", "type": "DWORD*", "note": ""}, {"off": "0x458", "name": "InitSeed", "type": "DWORD", "note": ""}, {"off": "0x45C", "name": "_2d[4]", "type": "DWORD", "note": ""}, {"off": "0x46C", "name": "pAct", "type": "Act*", "note": ""}, {"off": "0x470", "name": "_3[2]", "type": "DWORD", "note": ""}, {"off": "0x478", "name": "pMemPool", "type": "void*", "note": "D2PoolManager*"}, {"off": "0x47C", "name": "pLevelFirst", "type": "Level*", "note": ""}, {"off": "0x480", "name": "nAct", "type": "DWORD", "note": ""}, {"off": "0x484", "name": "_5", "type": "DWORD", "note": "Tomb Levels Related 66 + rand(7)"}, {"off": "0x488", "name": "fnCallback2", "type": "DWORD*", "note": ""}] - **struct** `struct.PresetUnit` — A preplaced (preset) unit within a Room2 — class/type id and tile position, linked as a list. fields: [{"off": "0x00", "name": "_1", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "dwClassId", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "dwPosX", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "pPresetNext", "type": "PresetUnit*", "note": ""}, {"off": "0x10", "name": "_3", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "dwType", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "dwPosY", "type": "DWORD", "note": ""}] - **struct** `struct.RoomTile` — A tile linking a Room2 to a neighboring room (warp/connection), stored as a linked list. fields: [{"off": "0x00", "name": "pRoom2", "type": "Room2*", "note": ""}, {"off": "0x04", "name": "pNext", "type": "RoomTile*", "note": ""}, {"off": "0x08", "name": "_2[2]", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "nNum", "type": "DWORD*", "note": ""}] - **struct** `struct.Stat` — A single stat entry: sub-index, stat index, and value. fields: [{"off": "0x00", "name": "wSubIndex", "type": "WORD", "note": ""}, {"off": "0x02", "name": "wStatIndex", "type": "WORD", "note": ""}, {"off": "0x04", "name": "dwStatValue", "type": "DWORD", "note": ""}] - **struct** `struct.StatEx` — A stat vector/array container: pointer to a Stat array plus counts (size 0x8). fields: [{"off": "0x00", "name": "pStat", "type": "Stat*", "note": "An Array[wStatCount]"}, {"off": "0x04", "name": "wStatCount", "type": "WORD", "note": ""}, {"off": "0x06", "name": "wnBitSize", "type": "WORD", "note": ""}] - **struct** `struct.StatList` — A node in a unit's linked list of stats (size 0x40). fields: [{"off": "0x00", "name": "pMemPool", "type": "void*", "note": ""}, {"off": "0x04", "name": "pUnit", "type": "UnitAny*", "note": ""}, {"off": "0x08", "name": "dwOwnerType", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "dwOwnerId", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "dwFlags", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "dwStateNo", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "ExpireFrame", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "SkillNo", "type": "DWORD", "note": ""}, {"off": "0x20", "name": "sLvl", "type": "DWORD", "note": ""}, {"off": "0x24", "name": "Stats", "type": "StatEx", "note": ""}, {"off": "0x2C", "name": "pPrevLink", "type": "StatList*", "note": ""}, {"off": "0x30", "name": "pNextLink", "type": "StatList*", "note": ""}, {"off": "0x34", "name": "pPrev", "type": "StatList*", "note": ""}, {"off": "0x38", "name": "fpStatRemove", "type": "void*", "note": ""}, {"off": "0x3C", "name": "pNext", "type": "StatList*", "note": ""}] - **struct** `struct.StatListEx` — Extended stat list container tracking base/full/mod stats for a unit or item (size 0x64). fields: [{"off": "0x00", "name": "pMemPool", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "_1", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "dwOwnerType", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "dwOwnerId", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "dwListFlags", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "_2[4]", "type": "DWORD", "note": ""}, {"off": "0x24", "name": "BaseStats", "type": "StatEx", "note": ""}, {"off": "0x2C", "name": "pLastList", "type": "StatList*", "note": "pointer to the last pStatList of the StatListEx owner (aka item owner in case list ex belongs to item)"}, {"off": "0x30", "name": "_3", "type": "DWORD", "note": ""}, {"off": "0x34", "name": "pListEx", "type": "StatListEx*", "note": "pStatListEx - pointer to owner StatListEx (if this one is owned by a item, this points to the item owners list)"}, {"off": "0x38", "name": "pNextListEx", "type": "StatListEx*", "note": ""}, {"off": "0x3C", "name": "pMyLastList", "type": "StatList*", "note": ""}, {"off": "0x40", "name": "pMyStats", "type": "StatList*", "note": ""}, {"off": "0x44", "name": "pOwner", "type": "UnitAny*", "note": ""}, {"off": "0x48", "name": "FullStats", "type": "StatEx", "note": ""}, {"off": "0x50", "name": "ModStats", "type": "StatEx", "note": ""}, {"off": "0x58", "name": "StatFlags", "type": "BYTE*", "note": "pt to an array"}, {"off": "0x5C", "name": "fpCallBack", "type": "void*", "note": ""}, {"off": "0x60", "name": "ptGame", "type": "Game*", "note": ""}] - **struct** `struct.Inventory` — A unit's inventory: item linked list plus cursor/owner/count data. fields: [{"off": "0x00", "name": "dwSignature", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "pMemPool", "type": "BYTE*", "note": ""}, {"off": "0x08", "name": "pOwner", "type": "UnitAny*", "note": ""}, {"off": "0x0C", "name": "pFirstItem", "type": "UnitAny*", "note": ""}, {"off": "0x10", "name": "pLastItem", "type": "UnitAny*", "note": ""}, {"off": "0x14", "name": "_1[2]", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "dwLeftItemUid", "type": "DWORD", "note": ""}, {"off": "0x20", "name": "pCursorItem", "type": "UnitAny*", "note": ""}, {"off": "0x24", "name": "dwOwnerId", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "dwItemCount", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "_2[4]", "type": "DWORD", "note": ""}, {"off": "0x3C", "name": "CorpseNumber", "type": "DWORD", "note": ""}] - **struct** `struct.InventoryLayout` — Layout/geometry of an inventory grid panel (slot sizes and screen bounds). Not present in D2Structs_113D.h; taken from d2bs D2Structs.h, which has no offset comments. fields: [{"off": "", "name": "SlotWidth", "type": "BYTE", "note": ""}, {"off": "", "name": "SlotHeight", "type": "BYTE", "note": ""}, {"off": "", "name": "unk1", "type": "BYTE", "note": ""}, {"off": "", "name": "unk2", "type": "BYTE", "note": ""}, {"off": "", "name": "Left", "type": "DWORD", "note": ""}, {"off": "", "name": "Right", "type": "DWORD", "note": ""}, {"off": "", "name": "Top", "type": "DWORD", "note": ""}, {"off": "", "name": "Bottom", "type": "DWORD", "note": ""}, {"off": "", "name": "SlotPixelWidth", "type": "BYTE", "note": ""}, {"off": "", "name": "SlotPixelHeight", "type": "BYTE", "note": ""}] - **struct** `struct.GfxCell` — A single frame/cell of a DC6/DCC sprite graphic (dimensions, draw offsets, pixel data length). fields: [{"off": "0x00", "name": "flags", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "width", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "height", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "xoffs", "type": "int", "note": ""}, {"off": "0x10", "name": "yoffs", "type": "int", "note": ""}, {"off": "0x14", "name": "_2", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "lpParent", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "length", "type": "DWORD", "note": ""}, {"off": "0x20", "name": "cols", "type": "BYTE", "note": ""}] - **struct** `struct.CellContext` — Rendering context binding a unit/component to its cell file and current graphics cell and facing direction (size 0x48). fields: [{"off": "0x00", "name": "nCellNo", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "_0a", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "dwUnit", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "dwClass", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "dwMode", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "_3", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "_4", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "_5", "type": "BYTE", "note": ""}, {"off": "0x1D", "name": "_5a", "type": "BYTE", "note": ""}, {"off": "0x1E", "name": "_6", "type": "WORD", "note": ""}, {"off": "0x20", "name": "_7", "type": "DWORD", "note": ""}, {"off": "0x24", "name": "_8", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "_9", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "szName", "type": "char*", "note": ""}, {"off": "0x30", "name": "_11", "type": "DWORD", "note": ""}, {"off": "0x34", "name": "pCellFile", "type": "CellFile*", "note": "also pCellInit"}, {"off": "0x38", "name": "_12", "type": "DWORD", "note": ""}, {"off": "0x3C", "name": "pGfxCells", "type": "GfxCell*", "note": ""}, {"off": "0x40", "name": "direction", "type": "DWORD", "note": ""}, {"off": "0x44", "name": "_14", "type": "DWORD", "note": ""}] - **struct** `struct.GfxInfo` — Graphics draw-info block holding the draw-offset table pointer and palette-shift index for a sprite. fields: [{"off": "0x00", "name": "_1", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "_2", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "_3", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "_4", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "_5", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "_6", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "_7", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "_8", "type": "DWORD", "note": ""}, {"off": "0x20", "name": "_9", "type": "DWORD", "note": ""}, {"off": "0x24", "name": "_10", "type": "DWORD", "note": ""}, {"off": "0x28", "name": "_11", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "_12", "type": "DWORD", "note": ""}, {"off": "0x30", "name": "pGfxDrawOffsets", "type": "GfxDrawOffset*", "note": ""}, {"off": "0x34", "name": "_13", "type": "DWORD", "note": ""}, {"off": "0x38", "name": "nPalShiftIdx", "type": "DWORD", "note": ""}] - **struct** `struct.Light` — A dynamic light source in the game world (type, static lightmap validity flag and map pointer). Only a forward declaration exists in 113D, so the d2bs body is used. fields: [{"off": "0x00", "name": "_1[3]", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "dwType", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "_2[7]", "type": "DWORD", "note": ""}, {"off": "0x2C", "name": "dwStaticValid", "type": "DWORD", "note": ""}, {"off": "0x30", "name": "pnStaticMap", "type": "int*", "note": ""}] - **struct** `struct.AutoMapLayer` — A layer of the automap holding linked lists of floor/wall/object/extra cells (sizeof = 0x1C). Verbatim source spelling is AutoMapLayer. fields: [{"off": "0x00", "name": "nLayerNo", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "fSaved", "type": "DWORD", "note": ""}, {"off": "0x08", "name": "pFloors", "type": "AutoMapCell*", "note": ""}, {"off": "0x0C", "name": "pWalls", "type": "AutoMapCell*", "note": ""}, {"off": "0x10", "name": "pObjects", "type": "AutoMapCell*", "note": ""}, {"off": "0x14", "name": "pExtras", "type": "AutoMapCell*", "note": ""}, {"off": "0x18", "name": "pNextLayer", "type": "AutoMapLayer*", "note": ""}] - **struct** `struct.AutoMapCell` — A single automap tile node (cell number, pixel position, weight) linked as a binary tree via pLess/pMore. Verbatim source spelling is AutoMapCell. fields: [{"off": "0x00", "name": "fSaved", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "nCellNo", "type": "WORD", "note": ""}, {"off": "0x06", "name": "xPixel", "type": "WORD", "note": ""}, {"off": "0x08", "name": "yPixel", "type": "WORD", "note": ""}, {"off": "0x0A", "name": "wWeight", "type": "WORD", "note": ""}, {"off": "0x0C", "name": "pLess", "type": "AutoMapCell*", "note": ""}, {"off": "0x10", "name": "pMore", "type": "AutoMapCell*", "note": ""}] - **struct** `struct.Control` — A UI control/widget (button, textbox, etc.) with position, size, handler pointers, linked text and edit state. Only a forward declaration exists in 113D, so the d2bs body is used. fields: [{"off": "0x00", "name": "dwType", "type": "DWORD", "note": ""}, {"off": "0x04", "name": "_1", "type": "DWORD*", "note": "unsure? definitely a ptr but not obvious, usually points to 6 when dwType is 6 I think"}, {"off": "0x08", "name": "dwDisabled", "type": "DWORD", "note": ""}, {"off": "0x0C", "name": "dwPosX", "type": "DWORD", "note": ""}, {"off": "0x10", "name": "dwPosY", "type": "DWORD", "note": ""}, {"off": "0x14", "name": "dwSizeX", "type": "DWORD", "note": ""}, {"off": "0x18", "name": "dwSizeY", "type": "DWORD", "note": ""}, {"off": "0x1C", "name": "_2", "type": "DWORD*", "note": "some sort of function (maybe click?); preceding source comment: I think _2 thru _9 are a handler table of some sort"}, {"off": "0x20", "name": "_3", "type": "DWORD", "note": ""}, {"off": "0x24", "name": "_4", "type": "DWORD*", "note": "some sort of function"}, {"off": "0x28", "name": "_5", "type": "DWORD*", "note": ""}, {"off": "0x2C", "name": "_6", "type": "DWORD", "note": ""}, {"off": "0x30", "name": "_7", "type": "DWORD*", "note": "ptr to something..."}, {"off": "0x34", "name": "_8", "type": "DWORD*", "note": "another random ptr... mostly dead ends when I examined them"}, {"off": "0x38", "name": "_9", "type": "DWORD", "note": ""}, {"off": "0x3C", "name": "pNext", "type": "Control*", "note": ""}, {"off": "0x40", "name": "_10", "type": "DWORD", "note": ""}, {"off": "0x44", "name": "unkState", "type": "DWORD", "note": "_11 0 when button avail to be clicked 1 when greyed - still need to look at this more"}, {"off": "0x48", "name": "pFirstText", "type": "ControlText*", "note": ""}, {"off": "0x4C", "name": "pLastText", "type": "ControlText*", "note": ""}, {"off": "0x50", "name": "pSelectedText", "type": "ControlText*", "note": ""}, {"off": "0x54", "name": "dwSelectEnd", "type": "DWORD", "note": ""}, {"off": "0x58", "name": "dwSelectStart", "type": "DWORD", "note": ""}, {"off": "0x5C", "name": "wText[256]", "type": "wchar_t", "note": "union member, anonymous struct: Textboxes"}, {"off": "", "name": "dwCursorPos", "type": "DWORD", "note": "union member, anonymous struct: Textboxes (no offset comment in source)"}, {"off": "", "name": "dwIsCloaked", "type": "DWORD", "note": "union member, anonymous struct: Textboxes (no offset comment in source)"}, {"off": "0x5C", "name": "_12[2]", "type": "DWORD", "note": "union member, anonymous struct: Buttons (overlaps textbox members at 0x5C)"}, {"off": "0x6C", "name": "wText2[256]", "type": "wchar_t", "note": "union member, anonymous struct: Buttons"}] - **struct** `struct.Waypoint` — A single waypoint activation record (flags bitfield). Only a forward declaration exists in 113D, so the d2bs body is used. fields: [{"off": "0x00", "name": "flags", "type": "BYTE", "note": ""}] ### 1.13c ADDRESSES (23 symbols) - **global** `1.13c ADDRESSES+0x11BF60` `ext.113c ADDRESSES.env_effects` — environment effects global (weather/env array) - **global** `1.13c ADDRESSES+0x10A608` `ext.113c ADDRESSES.entity_table` — primary entity table - **global** `1.13c ADDRESSES+0x109A08` `ext.113c ADDRESSES.entity_table2` — secondary entity table - **global** `1.13c ADDRESSES+0x1197E0` `ext.113c ADDRESSES.client_loop_globals` — client loop globals block - **function** `1.13c ADDRESSES+0xDA40` `ext.113c ADDRESSES.apply_pos_change` — Applies a DyPos position change. Signature per comment: __stdcall(DyPos* @ esi, FixedU16, FixedU16, Room*). DyPos pointer passed in ESI (register/thiscall-ish stdcall). | src: // Signature: `__stdcall(DyPos* @ esi, FixedU16, FixedU16, Room*)` apply_pos_change: 0xda40, - **function** `1.13c ADDRESSES` `ext.113c ADDRESSES.in_perspective` — perspective-mode check (draw/render path) - **function** `1.13c ADDRESSES` `ext.113c ADDRESSES.hwnd` — main window handle accessor - **function** `1.13c ADDRESSES` `ext.113c ADDRESSES.draw_menu` — draw menu function - **global** `1.13c ADDRESSES+0xD8558` `ext.113c ADDRESSES.cursor_table` — cursor table - **global** `1.13c ADDRESSES+0xFB834` `ext.113c ADDRESSES.game_cursor` — game cursor global - **global** `1.13c ADDRESSES` `ext.113c ADDRESSES.summit_cloud_x_pos` — summit cloud X position (0 = not present/unused in 1.13c) - **function** `1.13c ADDRESSES` `ext.113c ADDRESSES.draw_line` — draw line primitive - **function** `1.13c ADDRESSES` `ext.113c ADDRESSES.find_closest_color` — palette find-closest-color - **global** `1.13c ADDRESSES+0xF9E14` `ext.113c ADDRESSES.viewport_width` — viewport width global - **global** `1.13c ADDRESSES+0xF9E18` `ext.113c ADDRESSES.viewport_height` — viewport height global (immediately after width, +4) - **global** `1.13c ADDRESSES+0x11C418` `ext.113c ADDRESSES.viewport_shift` — viewport shift global (the render offset knob) - **global** `1.13c ADDRESSES+0x113934` `ext.113c ADDRESSES.max_weather_particles` — max weather particles - **global** `1.13c ADDRESSES+0x113918` `ext.113c ADDRESSES.weather_angle` — weather angle - **global** `1.13c ADDRESSES+0x1138F4` `ext.113c ADDRESSES.rain_speed` — rain speed - **global** `1.13c ADDRESSES+0x11BF78` `ext.113c ADDRESSES.is_snowing` — is-snowing flag - **global** `1.13c ADDRESSES+0x2F470` `ext.113c ADDRESSES.sine_table` — sine lookup table - **function** `1.13c ADDRESSES+0x80700` `ext.113c ADDRESSES.gen_weather_particle` — generate one weather particle. Signature: stdcall(&mut Rng). | src: // Signature: stdcall(&mut Rng) gen_weather_particle: 0x80700, - **function** `1.13c ADDRESSES` `ext.113c ADDRESSES.env_array_remove` — remove entry from environment array ### BaseAddresses (3 symbols) - **global** `BaseAddresses+0x6FC20000` `ext.BaseAddresses.BASE_ADDRESSES_game` — D2Game.dll default image base - **global** `BaseAddresses+0x6FA80000` `ext.BaseAddresses.BASE_ADDRESSES_gfx` — D2gfx.dll default image base (the module whose ordinals draw_menu/draw_line/in_perspective/find_closest_color/hwnd/env_array_remove are exports of) - **global** `BaseAddresses+0x6F8E0000` `ext.BaseAddresses.BASE_ADDRESSES_win` — D2Win.dll default image base ### BaseAddresses (from v111a, re-exported through v112 into v113c) (1 symbols) - **global** `BaseAddresses (from v111a, re-exported through v112 into v113c)+0x6FAB0000` `ext.BaseAddresses (from v111a, re-exported through v112 into v113c).BASE_ADDRESSES_client` — D2Client.dll default image base ### D2 (custom mod DLL, runtime 0x04D7xxxx) (1 symbols) - **function** `D2 (custom mod DLL, runtime 0x04D7xxxx)` `corpus.background_dialog_draw_via_D2gfx_10072` — kain_abel_666 located where a background is drawn: a D2gfx.#10072 draw call at 04D72AE5 that takes an X (EAX from [EBP-18C]) and a Y value (EDX from [EBP-190]); he identifies the Y push at 04D72AD0 as the value that is 'messing up' his positioning. Directly a render-pipeline / draw-position problem analogous to ours. | so i figured out where it draws the background ... CALL DWORD PTR DS:[4D76AA4] ; D2gfx.#10072 ... is the Y Value that is messing up corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2 client (2 symbols) - **struct** `D2 client` `struct.ItemPath` — Item position path head: _1[3] 0x00; dwPosX 0x0C; dwPosY 0x10 (DWORD tile coords). 'Use Path for the rest.' | src: DWORD _1[3]; //0x00 | DWORD dwPosX; //0x0C | DWORD dwPosY; //0x10 - **struct** `D2 client` `struct.ObjectPath` — Object position path head: pRoom1 0x00; _1[2] 0x04; dwPosX 0x0C; dwPosY 0x10. 'Leaving rest undefined, use Path.' | src: Room1* pRoom1; //0x00 | DWORD _1[2]; //0x04 | DWORD dwPosX; //0x0C | DWORD dwPosY; //0x10 ### D2 client (DC6/sprite file) (1 symbols) - **struct** `D2 client (DC6/sprite file)` `struct.CellFile` — Sprite/cell file container. dwVersion 0x00; an anonymous struct at 0x04 { WORD dwFlags; BYTE mylastcol; BYTE mytabno:1 }; eFormat 0x08; termination 0x0C; numdirs 0x10; numcells 0x14; GfxCell* cells[255] at 0x18 (array of pointers to decoded GfxCell frames). | src: DWORD dwVersion; //0x00 | struct{WORD dwFlags;BYTE mylastcol;BYTE mytabno:1;}; //0x04 | DWORD eFormat; //0x08 | DWORD termination; //0x0C | DWORD numdirs; //0x10 | DWORD numcells; ### D2 client (TXT) (1 symbols) - **struct** `D2 client (TXT)` `struct.ObjectTxt` — Object type record. Render-relevant: nOrientation at 0x14C (draw orientation) and nAutoMap at 0x1BB (automap icon index). Also szName 0x00, wszName 0x40, nSelectable0 0xC4, nSubClass 0x166, nParm0 0x178, nPopulateFn 0x1B2, nOperateFn 0x1B3. | src: BYTE nOrientation; //0x14C | DWORD nAutoMap; //0x1BB ### D2 client (automap) (1 symbols) - **struct** `D2 client (automap)` `struct.AutomapLayer2` — Small automap layer variant: _1[2] 0x00; nLayerNo at 0x08. | src: DWORD _1[2]; //0x00 | DWORD nLayerNo; //0x08 ### D2 client (collision/map) (1 symbols) - **struct** `D2 client (collision/map)` `struct.CollMap` — Collision map grid. dwPosGameX 0x00; dwPosGameY 0x04; dwSizeGameX 0x08; dwSizeGameY 0x0C; dwPosRoomX 0x10; dwPosRoomY 0x14; dwSizeRoomX 0x18; dwSizeRoomY 0x1C; pMapStart 0x20 (WORD*); pMapEnd 0x22 (WORD*). Game vs Room coordinate spaces both present; pMapStart is the walkability grid used for map rendering/pathing. | src: DWORD dwPosGameX;0x00 | DWORD dwPosGameY;0x04 | DWORD dwSizeGameX;0x08 | DWORD dwSizeGameY;0x0C | DWORD dwPosRoomX;0x10 | DWORD dwPosRoomY;0x14 | DWORD dwSizeRoomX;0x18 | DWORD dwS ### D2 exe (1.14d) (1 symbols) - **struct** `D2 exe (1.14d)` `struct.weather_draw_update_globals` — Weather tick decrements timer, re-rolls via FUN_00472280, and draws overlay via FUN_004f6300 with screen-relative offsets DAT_0071146c/711470 | FUN_004f6300(iVar7,0,iVar5,iVar6,0xff,5); return; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2 exe / D2GFX (1 symbols) - **global** `D2 exe / D2GFX` `corpus.D2GFX_DrawLine` — D2GFX_DrawLine at exe base + offset 0xF6380; pointer-arith cast (uintptr_t/DWORD) needed, hooks confirmed working | D2GFX_DrawLine = ...(hBaseExe) + 0xF6380 ... confirmed my hooks work now corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2CLIENT (44 symbols) - **function** `D2CLIENT+0x1630` `ext.D2CLIENT.D2CLIENT_GetUnitX` — Returns a unit's world X coordinate. Feeds world->screen projection. | src: FUNCPTR(D2CLIENT, GetUnitX, int __fastcall, (UnitAny* pUnit), 0x1630) - **function** `D2CLIENT+0x1660` `ext.D2CLIENT.D2CLIENT_GetUnitY` — Returns a unit's world Y coordinate. | src: FUNCPTR(D2CLIENT, GetUnitY, int __fastcall, (UnitAny* pUnit), 0x1660) - **function** `D2CLIENT+0x48350` `ext.D2CLIENT.D2CLIENT_ClearScreen` — Clears the screen/framebuffer. | src: FUNCPTR(D2CLIENT, ClearScreen, void __fastcall, (void), 0x48350) - **function** `D2CLIENT+0x5F080` `ext.D2CLIENT.D2CLIENT_GetAutomapSize` — Returns automap size metric used for automap draw scaling. | src: FUNCPTR(D2CLIENT, GetAutomapSize, DWORD __stdcall, (void), 0x5F080) - **function** `D2CLIENT+0x5F6B0` `ext.D2CLIENT.D2CLIENT_NewAutomapCell` — Allocates a new AutomapCell node. | src: FUNCPTR(D2CLIENT, NewAutomapCell, AutomapCell * __fastcall, (void), 0x5F6B0) - **function** `D2CLIENT+0x61320` `ext.D2CLIENT.D2CLIENT_AddAutomapCell` — Links an AutomapCell into an automap layer node list. | src: FUNCPTR(D2CLIENT, AddAutomapCell, void __fastcall, (AutomapCell *aCell, AutomapCell **node), 0x61320) - **function** `D2CLIENT+0x62580` `ext.D2CLIENT.D2CLIENT_RevealAutomapRoom` — Reveals a Room1's automap cells onto an AutomapLayer (with clip flag). Core map-reveal. | src: FUNCPTR(D2CLIENT, RevealAutomapRoom, void __stdcall, (Room1 *pRoom1, DWORD dwClipFlag, AutomapLayer *aLayer), 0x62580) - **function** `D2CLIENT+0x62710` `ext.D2CLIENT.D2CLIENT_InitAutomapLayer_I` — Initializes/returns the AutomapLayer for a given layer number. | src: FUNCPTR(D2CLIENT, InitAutomapLayer_I, AutomapLayer* __fastcall, (DWORD nLayerNo), 0x62710) - **function** `D2CLIENT+0x1BF20` `ext.D2CLIENT.D2CLIENT_ClickMap` — Handles a click at screen x,y (translates screen->world action). Coordinate-relevant. | src: FUNCPTR(D2CLIENT, ClickMap, void __stdcall, (DWORD MouseFlag, DWORD x, DWORD y, DWORD Type), 0x1BF20) - **function** `D2CLIENT+0x3F6C0` `ext.D2CLIENT.D2CLIENT_GetMouseXOffset` — Returns the mouse X offset used in screen<->world coordinate math. | src: FUNCPTR(D2CLIENT, GetMouseXOffset, DWORD __fastcall, (void), 0x3F6C0) - **function** `D2CLIENT+0x3F6D0` `ext.D2CLIENT.D2CLIENT_GetMouseYOffset` — Returns the mouse Y offset used in screen<->world coordinate math. | src: FUNCPTR(D2CLIENT, GetMouseYOffset, DWORD __fastcall, (void), 0x3F6D0) - **function** `D2CLIENT+0x7D850` `ext.D2CLIENT.D2CLIENT_PrintGameString` — Prints a message to the on-screen game message area. | src: FUNCPTR(D2CLIENT, PrintGameString, void __stdcall, (wchar_t *wMessage, int nColor), 0x7D850) - **function** `D2CLIENT+0x7D610` `ext.D2CLIENT.D2CLIENT_PrintPartyString` — Prints a party message to screen. | src: FUNCPTR(D2CLIENT, PrintPartyString, void __stdcall, (wchar_t *wMessage, int nColor), 0x7D610) - **function** `D2CLIENT+0xBE4C0` `ext.D2CLIENT.D2CLIENT_DrawRectFrame` — Draws a rectangular frame on screen. | src: FUNCPTR(D2CLIENT, DrawRectFrame, void __fastcall, (DWORD Rect), 0xBE4C0) - **global** `D2CLIENT+0xDBC4C` `ext.D2CLIENT.D2CLIENT_ScreenSizeY` — Current viewport height in pixels. | src: VARPTR(D2CLIENT, ScreenSizeY, DWORD, 0xDBC4C) - **global** `D2CLIENT+0xE0EB8` `ext.D2CLIENT.D2CLIENT_CursorHoverX` — Screen X of the hovered cell/cursor target. | src: VARPTR(D2CLIENT, CursorHoverX, DWORD, 0xE0EB8) - **global** `D2CLIENT+0xE0EBC` `ext.D2CLIENT.D2CLIENT_CursorHoverY` — Screen Y of the hovered cell/cursor target. | src: VARPTR(D2CLIENT, CursorHoverY, DWORD, 0xE0EBC) - **global** `D2CLIENT+0x11B828` `ext.D2CLIENT.D2CLIENT_MouseX` — Current mouse cursor X in screen pixels. | src: VARPTR(D2CLIENT, MouseX, DWORD, 0x11B828) - **global** `D2CLIENT+0x11B824` `ext.D2CLIENT.D2CLIENT_MouseY` — Current mouse cursor Y in screen pixels. | src: VARPTR(D2CLIENT, MouseY, DWORD, 0x11B824) - **global** `D2CLIENT+0x119960` `ext.D2CLIENT.D2CLIENT_ViewportX` — Viewport/gamewindow X extent (int). Used with ViewportY for world->screen shift. | src: VARPTR(D2CLIENT, ViewportX, int, 0x119960) - **global** `D2CLIENT+0x11995C` `ext.D2CLIENT.D2CLIENT_ViewportY` — Viewport/gamewindow Y extent (int). | src: VARPTR(D2CLIENT, ViewportY, int, 0x11995C) - **global** `D2CLIENT+0xFADA8` `ext.D2CLIENT.D2CLIENT_AutomapOn` — Nonzero when the automap is displayed. | src: VARPTR(D2CLIENT, AutomapOn, DWORD, 0xFADA8) - **global** `D2CLIENT+0xF16B0` `ext.D2CLIENT.D2CLIENT_AutomapMode` — Automap display mode (full/mini/etc). | src: VARPTR(D2CLIENT, AutomapMode, int, 0xF16B0) - **global** `D2CLIENT+0x11C1F8` `ext.D2CLIENT.D2CLIENT_Offset` — POINT screen offset applied when drawing (render/scroll origin). Coordinate-shift global. | src: VARPTR(D2CLIENT, Offset, POINT, 0x11C1F8) - **global** `D2CLIENT+0x11C1C4` `ext.D2CLIENT.D2CLIENT_AutomapLayer` — Pointer to the active AutomapLayer being drawn. | src: VARPTR(D2CLIENT, AutomapLayer, AutomapLayer*, 0x11C1C4) - **function** `D2CLIENT+0xE0EC4` `ext.D2CLIENT.D2CLIENT_BodyClickTable_ASM` — ASM patch anchor near the cursor/hover click-target table (adjacent to CursorHoverX/Y). | src: ASMPTR(D2CLIENT, BodyClickTable, 0xE0EC4) - **function** `D2CLIENT+0xFADA4` `ext.D2CLIENT.D2CLIENT_ClickMap_I_ASM` — ASM patch anchor for click-to-move handling (adjacent to AutomapOn 0xFADA8). | src: ASMPTR(D2CLIENT, ClickMap_I, 0xFADA4) - **function** `D2CLIENT+0x11C4D8` `ext.D2CLIENT.D2CLIENT_ClickMap_II_ASM` — ASM patch anchor for click-map coordinate handling. | src: ASMPTR(D2CLIENT, ClickMap_II, 0x11C4D8) - **function** `D2CLIENT+0x3F5F0` `ext.D2CLIENT.D2CLIENT_ClickMap_III_ASM` — ASM patch anchor for click-map (near GetMouseX/YOffset 0x3F6C0/0x3F6D0). | src: ASMPTR(D2CLIENT, ClickMap_III, 0x3F5F0) - **function** `D2CLIENT+0x1BF99` `ext.D2CLIENT.D2CLIENT_ClickMap_IV_ASM` — ASM patch anchor inside ClickMap (0x1BF20 + 0x79). | src: ASMPTR(D2CLIENT, ClickMap_IV, 0x1BF99) - **global** `D2CLIENT+0xDBC4C` `ext.D2CLIENT.ScreenSizeY` — Screen height global (DWORD). | src: VARPTR(D2CLIENT, ScreenSizeY, DWORD, 0xDBC4C, 0xF7038) - **global** `D2CLIENT+0xE0EB8` `ext.D2CLIENT.CursorHoverX` — Cursor hover X coordinate (DWORD). | src: VARPTR(D2CLIENT, CursorHoverX, DWORD, 0xE0EB8, 0xEE4AC) - **global** `D2CLIENT+0xE0EBC` `ext.D2CLIENT.CursorHoverY` — Cursor hover Y coordinate (DWORD). | src: VARPTR(D2CLIENT, CursorHoverY, DWORD, 0xE0EBC, 0xEE4B0) - **global** `D2CLIENT+0x11B828` `ext.D2CLIENT.MouseX` — Mouse X screen position (DWORD). | src: VARPTR(D2CLIENT, MouseX, DWORD, 0x11B828, 0x11C950) - **global** `D2CLIENT+0x11B824` `ext.D2CLIENT.MouseY` — Mouse Y screen position (DWORD). | src: VARPTR(D2CLIENT, MouseY, DWORD, 0x11B824, 0x11C94C) - **global** `D2CLIENT+0x119960` `ext.D2CLIENT.MouseOffsetX` — Mouse offset X (int) — screen-to-world/viewport offset. | src: VARPTR(D2CLIENT, MouseOffsetX, int, 0x119960, 0x106844) - **global** `D2CLIENT+0x11995C` `ext.D2CLIENT.MouseOffsetY` — Mouse offset Y (int) — screen-to-world/viewport offset. | src: VARPTR(D2CLIENT, MouseOffsetY, int, 0x11995C, 0x106840) - **global** `D2CLIENT+0x11C1F8` `ext.D2CLIENT.Offset` — Screen render Offset (POINT struct) — the world-to-screen scroll/camera offset used when drawing units and automap. | src: VARPTR(D2CLIENT, Offset, POINT, 0x11C1F8, 0x11CF5C) - **global** `D2CLIENT+0x11B9A0` `ext.D2CLIENT.PanelOffsetX` — Panel offset X (int) — horizontal shift applied when side panels (inventory/skill) are open. Directly relevant to viewport shift. | src: VARPTR(D2CLIENT, PanelOffsetX, int, 0x11B9A0, 0x11D354) - **global** `D2CLIENT+0x11BF00` `ext.D2CLIENT.xShake` — Screen-shake X offset (int) applied to render position. | src: VARPTR(D2CLIENT, xShake, int, 0x11BF00, 0x11CA6C) - **global** `D2CLIENT+0x10B9DC` `ext.D2CLIENT.yShake` — Screen-shake Y offset (int) applied to render position. | src: VARPTR(D2CLIENT, yShake, int, 0x10B9DC, 0xFC3DC) - **global** `D2CLIENT+0xFADA8` `ext.D2CLIENT.AutomapOn` — Automap-visible flag (DWORD). | src: VARPTR(D2CLIENT, AutomapOn, DWORD, 0xFADA8, 0x11C8B8) - **global** `D2CLIENT+0xF16B0` `ext.D2CLIENT.AutomapMode` — Automap display mode (int). | src: VARPTR(D2CLIENT, AutomapMode, int, 0xF16B0, 0xF34F8) - **global** `D2CLIENT+0x11C1C4` `ext.D2CLIENT.AutomapLayer` — Pointer to current AutomapLayer* — root of the automap cell list that gets drawn. | src: VARPTR(D2CLIENT, AutomapLayer, AutomapLayer*, 0x11C1C4, 0x11CF28) ### D2CMP (6 symbols) - **function** `D2CMP` `corpus.D2CreatePL2` — Builds a PL2 (palette+shift-tables) from a 256-color palette; entry point for changing D2's color scheme. D2PL2Strc* __stdcall D2CreatePL2(BYTE pPalette[256]). | D2CMP.#10000 D2PL2Strc* __stdcall D2CreatePL2(BYTE pPalette[256]) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2CMP` `struct.DC6_frame_decode_DC6_LoadImage` — DC6 image load layout: frames indexed dir*dwFrames+frame, each DC6Frame has dwWidth/dwHeight and dwNextBlock set to running pixel offset; the file offset/pointer table lives outside the dc6 body. Relevant to cel/graphics decoding. | memcpy(&pImage->pFrames[dwFramePos], gpDecodeBuffer + pFramePointers[dwFramePos], sizeof(DC6Frame)); pImage->pFrames[dwFramePos].dwNextBlock = dwTotal corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2CMP` `corpus.D2CMP_crash_address` — Access-violation fault address inside D2CMP.dll (cross-referenced to d2mods thread) | Fault address: 6FE21614 01:00010614 D:\Spiele\Diablo II\D2CMP.dll corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2CMP` `struct.D2CellFileStrc_DC6_cell_file_header` — Cell-file (sprite) header, sizeof 0x18 + frame data: dwVersion(+00), fCellFlags(+04), nEncodeType(+08), dwPaletteOffset(+0C), nDirections(+10), nFrames(+14), D2GFXCellStrc* pFrames[](+18). Necrolis clarifies 24-bit DC6's aren't really 24-bit - they carry their own PCX-style palette, and the 'zeros' field is the offset to that internal palette. He states he has mapped out most of D2CMP's ordinals. | struct D2CellFileStrc ... DWORD dwPaletteOffset; //+0C ... 24bit DC6's aren't 24bit, they just have their own palette corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2CMP` `corpus.D2CMP_10025_ReadDC6Header` — signed int __stdcall ReadDC6Header(CelFile* pFile, DC6Header** a2, int a3, int a4). Asserts pFile->dwFlags & CELFILE_24BIT; allocates 4*framesPerDir*directions + sizeof(DC6Header); sets version=6, subVersion = dwFlags & 0xF8, copies directions/framesPerDir; dispatches via off_6FE091C0 table indexed by pFile->gap8. Related build-DC6 code sets subVersion 5 if 24bit else 1. | signed int __stdcall D2CMP_10025_ReadDC6Header(CelFile *pFile, DC6Header **a2, int a3, int a4) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2CMP` `corpus.D2Parse24BitTileLibrary_D2Load24BitTileLibra` — Ordinal exports that parse/load a 24-bit tile library, taking a per-pixel RGB mapping callback. Part of the DT1 tile-loading pipeline for the floor renderer. | 10074 void __stdcall D2Parse24BitTileLibrary(D2TileLibraryStrc* pLibrary, void (__stdcall* pfMap)(DWORD dwRGB)) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2CMP.dll (2 symbols) - **function** `D2CMP.dll` `ext.D2CMP.GetCelFromCelContext` — Retrieves a CEL frame from a CEL context (sprite frame access). | src: D2CMP.dll GetCelFromCelContext Ordinal 10021 - **function** `D2CMP.dll` `ext.D2CMP.CloseCelFile` — Closes a CEL file (sprite resource cleanup). | src: D2CMP.dll CloseCelFile Ordinal 10065 ### D2Client (156 symbols) - **function** `0x6FABD0D2 D2Client+0xD0D2` `D2Client.CelBlitThunkOpaque` — jmp [IAT 0x6FB7EC3C] -> D2gfx #10001. TILE-DEDICATED; the TILEBAND/GROUND-COLLECT counter hook site expect `FF 25 3C EC B7 6F` - **function** `0x6FABD0D8 D2Client+0xD0D8` `D2Client.CelBlitThunkAlpha` — jmp [IAT 0x6FB7EC40] -> D2gfx #10058 (alpha) expect `FF 25 40 EC B7 6F` - **function** `0x6FAF4230 D2Client+0x44230` `D2Client.DrawGame` — main world render entry. body: PASS1(0x44302) -> tilesub(0x44307) -> PASS2(0x4436F, gated mode!=3) -> compositor(0x4437B) -> present #10040(0x443B9) -> RET(0x443BE) - **hook** `0x6FAF4302 D2Client+0x44302` `hook.PanelWorldWiden` — detour the base `call PASS1` to widen ctx+0x0C->DispW before PASS1 (issues floor tiles to screenX ~1236). NECESSARY but not sufficient (strip still black -- downstream clip) expect `E8 B9 6D 04 00` - **function** `0x6FB2A9F0 D2Client+0x7A9F0` `D2Client.DrawRooms` — builds room-iteration RECT from ctx draw-window+scroll (band=[scrollX,scrollX+(R-L)]); per-tile cull skip if tileWorldX>RECT.R; grid helper 0x6FB2A0D0 sizes ctx+0xEAA8 (HEAP UAF trap on save/restore) - **function** `0x6FB3B0C0 D2Client+0x8B0C0` `D2Client.Pass1Floor` — floor pass wrapper; esi=ctx, [esp+4]=bTilesOnly, ret 4; calls DrawRooms - **function** `0x6FB3B110 D2Client+0x8B110` `D2Client.Pass2WallsUnits` — wall/roof + unit pass; eax=CullingSpec; gated cmp[ScreenOpenMode],3;je - **function** `0x6FB4ED50 D2Client+0x9ED50` `D2Client.FloorFlush` — floor tile flush; screenX=ctx+0x04-ctx+0x24+tileAbsX; NO inline clip; hands each tile to the cel blit thunks - **function** `0x6FB70D30 D2Client+0xC0D30` `ext.D2Client.UNITHOVER_Main` — Unit-hover main handler. Comment notes it 'originally had pUnit arg'; reconstructed as void __fastcall pulling the unit from D2CLIENT_GetSelectedUnit(). Writes gnHoverX/gnHoverY then dispatches by pUnit->dwType. | src: /* Function: UNITHOVER_Main Address: D2Client.0xC0D30 Notes: originally had pUnit arg */ void __fastcall UNITHOVER_Main() - **function** `0x6FB70D30 D2Client+0xC0D30` `ext.D2Client.UNITHOVER_Main_non_perspective_hover_math` — Non-perspective hover coordinate math. IDENTICAL X formula to UNITS_GetDrawingPosition, but Y subtracts an extra 8: (*D2CLIENT_6FBC995C - 8). This is the key difference from the drawing-position formula. | src: *D2CLIENT_gnHoverX = D2COMMON_GetUnitOffsetX(pUnit) - (*D2CLIENT_6FBC9960 - *D2CLIENT_6FBCC418); *D2CLIENT_gnHoverY = D2COMMON_GetUnitOffsetY(pUnit) - (*D2CLIENT_6FBC995C - 8); - **function** `0x6FB70D30 D2Client+0xC0D30` `ext.D2Client.UNITHOVER_Main_perspective_hover_math` — Perspective hover math. Same CheckPerspectivePosition guard + AdjustPerspectivePosition + UISTATE +/-256 X shift as the drawing-position function, but the final Y adds 16: gnHoverY = nAdjustY + 16. | src: if (!D2GFX_CheckPerspectivePosition(nX, nY)) return; D2GFX_AdjustPerspectivePosition(nX, nY, 0, &nAdjustX, &nAdjustY); switch (*D2CLIENT_gnGlobalUiState) { case UISTATE_RIGHT: - **global** `0x6FB70D30 D2Client+0xC0D30` `ext.D2Client.D2CLIENT_gnHoverX_D2CLIENT_gnHoverY` — Destination globals for the hover cursor/box position. Non-perspective: Y carries an extra -8 vs the draw formula. Perspective: Y carries an extra +16. | src: *D2CLIENT_gnHoverX = ...; *D2CLIENT_gnHoverY = nAdjustY + 16; - **function** `0x6FB70D30 D2Client+0xC0D30` `ext.D2Client.UNITHOVER_Main_dwType_dispatch` — After computing hover coords, dispatches to per-type handlers: UNIT_PLAYER->UNITHOVER_PlayerUnits, UNIT_MONSTER->UNITHOVER_MonsterUnits, UNIT_OBJECT->UNITHOVER_ObjectUnits, UNIT_ITEM->UNITHOVER_ItemUnits, UNIT_TILE->UNITHOVER_TileUnits; default returns. | src: switch (pUnit->dwType) { case UNIT_PLAYER: UNITHOVER_PlayerUnits(pUnit); case UNIT_MONSTER: UNITHOVER_MonsterUnits(pUnit); case UNIT_OBJECT: UNITHOVER_ObjectUnits(pUnit); case UNIT - **global** `0x6FB8BC48 D2Client+0xDBC48` `D2Client.GeneralDisplayWidth` — DispW=1280 (SGD2FreeRes). +4=height, +8=w-copy, +0xC=centerX, +0x10=centerY - **global** `0x6FBAAD80 D2Client+0xFAD80` `D2Client.PanelOpenArray` — per-panel open flags: inventory +4, char +8, skill +0x10, waypoint +0x50 - **global** `0x6FBCC3C4 D2Client+0x11C3C4` `D2Client.RenderContextPtr` — *[this] = the render ctx (ONE struct): draw-window +0x04(L)/+0x08(T)/+0x0C(R)/+0x10(B), scroll +0x24/+0x28, dirty +0x00, visited-room grid +0xEA9C - **global** `0x6FBCC414 D2Client+0x11C414` `D2Client.ScreenOpenMode` — which side panel(s) open: 0=none,1/2/3=side/both; drives the L/R world-view shift; == nScreenPanels passed to the driver tile-draw fns - **global** `0x6FBCC418 D2Client+0x11C418` `D2Client.GeneralPlayAreaCameraShiftX` — offX: 0 with no panel, +/-DispW/4 (=320) with one; the native left-shift the mod KEEPS - **struct** `D2Client` `struct.D2Client_RenderContext` — The world render context. Instance pointer = *0x6FBCC3C4. It IS the world view-state: the tile projection reads screenX = (+0x04) - (+0x24) + tileAbsX, screenY = tileAbsY - (+0x28) + (+0x08). A side panel writes +0x04 = offX = -320 (the shift) and narrows +0x0C to the play-area edge (the clip). See [[finding.terrain_shift_is_offx_ctxL]] and [[reframe.one_cause_view_state]]. fields: [{"off": "+0x00", "name": "dirty", "type": "u32", "note": "geometry-dirty bit; |=1 forces mirror/cull rebuild for a wider window"}, {"off": "+0x04", "name": "draw_left", "type": "i32", "note": "draw-window L; equals offX (0 no-panel, -320 panel-open) -> the SHIFT"}, {"off": "+0x08", "name": "draw_top", "type": "i32", "note": "draw-window T"}, {"off": "+0x0C", "name": "draw_right", "type": "i32", "note": "draw-window R; iteration bound = 1280 no-panel, 960 panel-open -> the CLIP"}, {"off": "+0x10", "name": "draw_bottom", "type": "i32", "note": "draw-window B"}, {"off": "+0x24", "name": "scroll_x", "type": "i32", "note": "scroll origin X (camera); NOT touched by the panel"}, {"off": "+0x28", "name": "scroll_y", "type": "i32", "note": "scroll origin Y"}] - **function** `D2Client` `ext.D2Client.UNITS_GetDrawingPosition_full_signature` — Signature and null-guard of the reconstructed C function that kidpaddle94 describes as a slightly modified version of what the unit-hover handler uses. Returns FALSE on null args or failed perspective-position check. | src: BOOL __fastcall UNITS_GetDrawingPosition(D2UnitStrc* pUnit, D2CoordStrc* pPos) { if (!pUnit || !pPos) return FALSE; - **global** `D2Client` `ext.D2Client.D2CLIENT_6FBC9960` — Camera/viewport X reference. Used as the minuend inside the X screen-offset term (*D2CLIENT_6FBC9960 - *D2CLIENT_6FBCC418) in BOTH UNITS_GetDrawingPosition and UNITHOVER_Main non-perspective branches. Dereferenced pointer to an int. | src: (*D2CLIENT_6FBC9960 - *D2CLIENT_6FBCC418) - **global** `D2Client` `ext.D2Client.D2CLIENT_6FBCC418` — Secondary X offset. Subtrahend in the X term (*D2CLIENT_6FBC9960 - *D2CLIENT_6FBCC418). Present in both non-perspective X formulas (drawing-position and hover). | src: (*D2CLIENT_6FBC9960 - *D2CLIENT_6FBCC418) - **global** `D2Client` `ext.D2Client.D2CLIENT_6FBC995C` — Camera/viewport Y reference. In drawing-position: nY = OffsetY - *D2CLIENT_6FBC995C. In hover: gnHoverY = OffsetY - (*D2CLIENT_6FBC995C - 8) (extra -8). | src: pPos->nY = D2COMMON_GetUnitOffsetY(pUnit) - *D2CLIENT_6FBC995C; - **global** `D2Client` `ext.D2Client.D2CLIENT_gnGlobalUiState` — Current UI panel state, switched on in the perspective branch. UISTATE_RIGHT => nAdjustX -= 256; UISTATE_LEFT => nAdjustX += 256. The 256 is the side-panel width shift applied when a left/right panel is open. | src: switch (*D2CLIENT_gnGlobalUiState) { case UISTATE_RIGHT: nAdjustX -= 256; case UISTATE_LEFT: nAdjustX += 256; } - **global** `D2Client` `corpus.Unit_Display_globals` — Two D2Client globals referenced in the Unit_Display function; 6FBBA4A0 is believed to be the monster description string (resists, montype, etc.) built for the hover panel. | I have a feeling its used to build 6FBBA4A0, which is the monster desc string (resists, montype etc) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDRAW_DrawOutline` — Draws the selection/aura outline around a unit. BOOL __fastcall UNITDRAW_DrawOutline(D2UnitStrc* pUnit). Part of the client unit-render path. | Function: UNITDRAW_DrawOutline Address: D2Client.0x6FB59F20 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDRAW_TickOutline` — Decrements a unit's outline tick and returns whether to draw it (unless STATE_ATTACHED). Reads pUnit->dwOutlineTick. Part of unit-render tick. | Function: UNITDRAW_TickOutline Address: D2Client.0x6FB29A40 ... DWORD dwTick = pUnit->dwOutlineTick; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Unit_visibility_check_is_unit_shown` — Under 1.10 determines whether a unit is drawn/shown; forcing its dead-check to return false keeps dead units visible. A gate in the client draw path. | D2Client.0x6FACE8F0 under 1.10 determines if a unit is shown corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Life_orb_draw` — Draws the (life/mana) orb on the HUD panel. Direct panel/HUD render entry. | D2Client.0x6FAFF9D0 draws the orb corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Gouraud_view_render_setup_D2CreateView_D2Bui` — Client-side view/render init trio: D2CreateView (called at 6FAA9501), D2BuildGouraudTables (6FAAB3C7), D2FreeGuaraudTables (6FAABB40). Gouraud tables drive the lighting/shading of the play-area render. | CALL D2Client.6FAB6FF0 ; D2CreateView() ... CALL D2Client.6FAB6AC0 ; D2BuildGouraudTables() ... CALL D2Client.6FAB6EB0 ; D2FreeGuaraudTables() corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDRAW_DrawOverlays` — Draws a unit's overlays; repeats the same world-offset/panel-adjust/perspective transform (subtract WorldX/YOffset, else-branch; or D2AdjustPerspectiveCoords + GetPanelAdjust + 16 in perspective). Second confirmed site of the panel-adjust screen-shift logic. void __fastcall(pUnit, dwColorTint, bBack, nXpos, nYpos, bPerspectiveDisabled). | Function: UNITDRAW_DrawOverlays Address: D2Client.0x6FB5A490 ... nXpos += UNITDRAW_GetPanelAdjust(); nXpos += 16; ... nXpos = pCoord.nXpos - UNITDRAW corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Trade_panel_draw_function_1_13c` — Function where the trade panel is drawn in 1.13c | function where trade panel are drawn 0x99440 (1.13c) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.GetItemGfxData_I` — Internal item gfx data fetch: (D2GfxDataStrc*, UnitAny*, CellFile*, int nDirection, int nFrame, BYTE fFlags, DWORD dwColorTint, int* pIndex). 1.13c offset 0x6E2C0 confirmed. Various naked __fastcall/__stdcall wrappers posted | D2PTR(D2CLIENT, GetItemGfxData_I,0x6E2C0) //0x6FB1E2C0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Item_quality_color_jumptable_1_13c_client` — Item gfx setup writes 0xFFFFFFFF to pnUnused via jumptable cases 0-2 (lea esi,[esp+pGfxData]) | jumptable 6FB5CFC7 cases 0-2 ... mov dword ptr [eax], 0FFFFFFFFh corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Item_quality_color_select_1_10_client` — Item name color selection: calls D2Common.#10695 to get quality, jumptable at 6FB20A14 maps quality-4 to color: rare=9,crafted=8,tempered=0A,magic=3,set=2,unique=4. Also 1.10 variant at 6FAE365B (item-list colors) | CALL ; get item quality ... MOV EBX,9 ; D2_Yellow_Light (rare) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Inventory_panel_draw_function` — The function that handles inventory drawing. IMPORTANT for panel work: it ALSO draws the Stash, Cube, and Trade panels, so it's non-trivial to isolate a single panel. Advice: search for the .dc6 names to find the globals that store the panel images. | The function that handles the inventory drawing is located at D2Client.0x3EAC0 ... it also draws the Stash, Cube, Trade Panels corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.AltItem_expanded_item_display_draw_array_ele` — Struct for the expanded on-screen item-display (ALT-highlight) draw array, gptAltDrawArray. Fields: nLeft/nTop/nRight/nBottom rect (+0x00..+0x0C), Unit* pItem (+0x10), wchar_t wszName[128] (+0x14), nRectColor (+0x114), nRectDrawMode (+0x118), nColor (+0x11C). Whist gives a full 1.13c patch table hooking D2Client at 0x58E59/0x59089/0xCDBB9 (max-count patches to 0x7F), and 0x58E83/0x58F07/0x5909D/0x59448/0x58F94/0x58F2 | struct AltItem { int nLeft; int nTop; int nRight; int nBottom; Unit* pItem; wchar_t wszName[128]; int nRectColor; int nRectDrawMode; int nColor; }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Expanded_item_display_drop_filter_patch_offs` — 1.13c D2Client offsets for the on-floor item-name display (the labels drawn over the play area). 0x58E59/0x59089/0xCDBB9 are the max-count caps (patched to 0x7F). The 0x58Exx/0x59xxx set repoint reads at the rect draw into gptAltDrawArray fields. Drop-filter hook site at 0x5907E (call) / 0x5907F (stub). | {D2DLL_D2CLIENT, 0x58E59, ... Max Count Patch ... {D2DLL_D2CLIENT, 0x58E83, ... Expanded Data Array corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDRAW_GetComponents` — Necrolis-labeled function returning the component/draw-priority bytes for a unit's COF given rotation+frame. Indexes into the COF trailing arrays: nPos = ((nRotation*nFrames + nFrame + sizeof(D2COFLayerInfoStrc)) * nLayers) + nFrames, then adds sizeof(D2COFStrc). Part of the character/unit sprite render path. | Function: UNITDRAW_GetComponents Address: D2Client.0x6FB5EFC0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Unit_palette_getter_assigner_incl_Rand_Palet` — D2Client.0x6FAAE3C0 gets a unit's palette including Rand Palettes. D2Client.0x6FB28410 is the palette assigner; the three funcs below it compute the secondary index (source is a packet). Necrolis points a second user (Ogodei) to focus mainly on D2Client.0x6FB27A1F. Render/palette-shift relevant. | have a look at D2Client.0x6FAAE3C0 for what gets a units palette (including Rand Palettes). Look at D2Client.0x6FB28410 for the assigner corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.D2COFStrc_D2COFLayerInfoStrc_animation_COF_l` — COF animation file layout. D2COFLayerInfoStrc sizeof 0x9: nComponent(+0), bShadow(+1), bSelectable(+2), bOverrideTrans(+3), nTransLevel(+4), hitclass szHitClass[4]/dwHitClass(+5). D2COFStrc sizeof 0x1C: nLayers(+00), nFrames(+01), nDirections(+02), nVersion(+03=0x14), dwUnk(+04), then a union of Yoffset/Xoffset box OR nXpos/nWidth/nYpos/nHeight rect (+08..+14), nAnimRate(+18), nArmType(+19). Trailing: pLayerInfo[nLay | struct D2COFStrc //sizeof 0x1C ... BYTE nAnimRate; //+18 BYTE nArmType; //+19 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Health_orb_hover_hit_box_D2Client` — Bounding-box compares for the health orb hover region: X-left CMP EBX,1E ; X-right CMP EBX,6E ; Y-top LEA ECX,[EAX-4B] ; Y-bottom LEA EDX,[EAX-F]. JL/JG jump to 6FAF8C0E to skip. Screen-UI coordinate math for the bottom-left orb panel. | CMP EBX,1E ; Health Hover X Co-Ordinate Left ... CMP EBX,6E ; Health Hover X Co-Ordinate Right corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Mana_orb_hover_hit_box_D2Client` — Mana orb hover bounding box: MOV ECX,[6FB740DC] base; X-left LEA EDX,[ECX-6F]; X-right ADD ECX,-1F; Y-top LEA ECX,[EAX-4B]; Y-bottom ADD EAX,-0F; branches to 6FAF8CD9. 6FB740DC is a global used as the mana orb X anchor. | MOV ECX,DWORD PTR DS:[6FB740DC] ... LEA EDX,DWORD PTR DS:[ECX-6F] ; Mana Hover X Co-Ordinate Left corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Frame_timing_globals_D2Client_render_loop` — D2 does not hardcode client frame time: 6FB75460 holds ms-per-frame; gdwSkipFramesPerSecond compared to 0x16 (=22? note says D2 uses 25fps) at 6FAAA167; gdwFrameRenderSkip read at 6FAAA2C4. Directly governs the client render/skip cadence. | 6FB75460 contains the numbe of ms per frame ... CMP DWORD PTR DS:[],16 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Damage_display_stat_pushes_D2Client` — Damage-display code in D2Client pushes (0,2) as stat args at 6FB0CF20 (left-skill damage display?), 6FB0D688 (right-skill damage display?), and 6FB0EE91. D2Game damage-calc stat table at 6FCBE420. Client-side damage panel rendering needs editing alongside D2Game. | 6FB0CF20 . 6A 00 PUSH 0 ... Damage display for left skill? corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.Dialog_vtable_deposit_stash_dialogs` — Dialog function-pointer table used by deposit dialogs: pfCreate+00, pfDraw+04, pfMouseScroll+08, pfClickDown+0C, pfClickUp+10, pfMouseMove+14, pfNumpadMove+18, pfKeyScrollUp+1C, pfKeyScrollDown+20, pfKeyScrollRight+24, pfKeyScrollLeft+28. Panel dialog draw+input dispatch. | tDialogDraw pfDraw; // +04 ... pfKeyScrollLeft; // +28 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Level_id_change_setter_unused_now_entering_g` — D2Client.0x6FB00C10 updates the player's level id and sets a global on change (presumably for 'now entering' text), but the globals it sets are unused. | D2Client.0x6FB00C10 updates the level id of the player, and then sets a global when it changes ... the globals it sets are unused corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.GetUnitOutlineWhenHit_D2Client_sub_7C39DD0` — Computes the outline color index when a unit is hit: lazily inits color indices via D2WIN_10034_GetClosestColorIndex (green 0,C0,0 / yellow E0,E0,0 / red E0,0,0 / blue 0,0,C0); for monsters checks MonStatsFlags bit1 (->blue), else colors by life% (<33 red, <66 yellow, else green). Uses ConvertMode #11013, GetAnimOrSeqMode #10369, QuickGetTotalMaxLife #10564, GetUnitStat #10519. | gGreenColorIndex = D2WIN_10034_GetClosestColorIndex(0, 0xC0u, 0); ... nLifePercentage < 33) return gRedColorIndex corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.INVENTORYSCREEN_DrawItem_D2Client_0x6B5A0` — Draws an item into the inventory/character panel: validates UNIT_ITEM, gets client player, GetItemCacheGfxData + D2CMP_InitGfxData, animates via GfxData.nFrame=((GetTickCount()>>7)+item LoSeed)%nMaxFrames, applies DRAWMODE_TRANS50 for ethereal, then D2GFX_DrawImage(&gfx, nPosX, nPosY+cell->nHeight, -1, drawMode, ITEMS_GetItemPalette(NULL,pItem,&nColor,1)). The pOwner=NULL fix lives here. | INVENTORYSCREEN_DrawItem Address: D2Client.0x6B5A0 ... D2GFX_DrawImage(&GfxData, nPosX, (nPosY + pCell->nHeight), -1, nDrawMode, ITEMS_GetItemPalette( corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2CheckUnitSelectable_D2Client_6FB5F7F0` — __fastcall(D2UnitStrc* pUnit, D2COFStrc* pCOF) BOOL — determines whether a unit/COF is selectable (mouse-over hit eligibility). | D2Client.6FB5F7F0 BOOL __fastcall D2CheckUnitSelectable(D2UnitStrc* pUnit, D2COFStrc* pCOF) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.gdwClientGameFrame_D2DrawUI` — D2Client global holding current client frame counter (drives animrate advancement, runs at 25fps and slows with the client); D2DrawUI is the UI render routine. Necrolis advises hooking the UI drawing to draw text then draw UI, rather than spawning threads. Core render-loop anchors. | 6FBA7768; gdwClientGameFrame 6FB21B70; D2DrawUI corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_INVENTORY_DrawItemBackground` — Draws the colored cell background behind an inventory item, given the grid-info struct, X/Y, item width/height in cells, and a color index. Side-panel (inventory) rendering primitive. | void __fastcall UI_INVENTORY_DrawItemBackground(D2InventoryGridInfoStrc* pInventoryGridInfo, int nX, int nY, BYTE nItemWidth, BYTE nItemHeight, DWORD corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_INVENTORY_LoadItemBackgroundColors` — Loads the inventory item-background colors in advance (paired with UI_INVENTORY_DrawItemBackground). | void __fastcall UI_INVENTORY_LoadItemBackgroundColors() corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.sub_6FAE0DC0_DrawInventoryItemSockets` — Draws socket overlays in the inventory grid. Signature sub_6FAE0DC0(UnitAny* ptItem, InventoryGrid* pGrid, int nLeft, int nBottom). Uses a socket-position lookup array (first 2 dwords = where socket should be drawn, then 10 zero dwords per entry). 1.13c client base is 0x6FAA0000. | void __fastcall sub_6FAE0DC0(UnitAny *ptItem, InventoryGrid *pGrid, int nLeft, int nBottom) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDRAW_CheckSelection` — Checks whether a unit is the current selection target during unit drawing; called from the unit-draw routine at 0x6FB5A720. Part of the play-area unit render path. | BOOL __fastcall UNITDRAW_CheckSelection(D2UnitStrc* pUnit) // D2Client.0x6FB286B0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2CLIENT_GetItemGfxData` — Fills a D2GfxDataStrc for an item given cell file, direction, frame, component and mode. Item graphic-data resolver used in inventory/ground rendering. | BOOL __fastcall D2CLIENT_GetItemGfxData_6FB5EC80(D2GfxDataStrc* pGfxData, D2UnitStrc* pItem, D2CellFileStrc* pCellFile, int nDirection, int nFrame, in corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.D2FloorTileParamsStrc` — The parameter struct passed to the D2Client floor/tile-drawing routine. Directly names the render-pipeline fields we care about: nScreenPanels at +0x20 (which side panels are open), pView (D2ViewStrc*) at +0x24, bDrawEdgeTiles at +0x28, tile bounds nXMin/nXMax/nYMin/nYMax at +0x2C..+0x38, and the crucial nViewXOffset (+0x3C) and nViewYOffset (+0x40, typo'd nnViewYOffset). This is the screen-shift/dead-zone control bl | struct D2FloorTileParamsStrc //sizeof 0x44 - D2Client.0x6FB9A990 { ... int nScreenPanels; //+20 D2ViewStrc* pView; //+24 ... int nViewXOffset; //+3C corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.tile_drawing_setup_fn_D2Client_6FAA3330` — Whist identifies D2Client 6FAA3330 (1.10f) as where the tile-draw params struct is set up; Lectem notes one member is ptRect at +0x24 (matches pView slot in the 1.13c struct). Confirms the floor-render param block is built here before the d2gfx tile ordinals are called. | only def I have is the struct that gets passed to the tile drawing d2gfx ordinals and it's really incomplete corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.D2GameViewStrc` — The per-view render struct that controls the play-area viewport: dwFlags, ViewRadius (RECT), CheckRadius (RECT), nOffsetX/nOffsetY screen offsets, three Gouraud lighting tables, and embedded render data. This is the struct passed to every tile-draw call as pView. | struct D2GameViewStrc { int dwFlags; RECT ViewRadius; RECT CheckRadius; int nOffsetX; int nOffsetY; ... corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.rain_storm_wind_factor_float` — Float global at 6FBA763C is the storm wind factor (governs rain slant); part of the weather/particle system tied to the ground-tile draw params. | its the storm wind factor IIRC corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.floor_tile_draw_prologue_6FAA2E95` — Disassembly inside the ground-tile drawing routine (reads [EBX+24], adds 0x14 = the -20/0xFFFFFFEC pView sentinel check). Part of MULTIRES_DrawGroundTiles at 6FAA2E60/2E70. | 6FAA2E95 |. 8B43 24 MOV EAX,DWORD PTR DS:[EBX+24] corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.RainDrawAndPlaySounds_thunder_bool` — 6FAA8240 is RainDrawAndPlaySounds; 6FBA76AC is a boolean that triggers the thunder1 sound. Weather effects driven off the tile-draw frame counters. | 6FAA8240 is actually RainDrawAndPlaySounds corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Waypoint_panel_draw_WindowStartX_NegWindowSt` — Waypoint-panel drawing loop: WaypointTable at D2CLIENT 0xFCD8C, WaypointCoordTable at 0xDE6D8. Every cell/text is drawn at (*D2CLIENT_WindowStartX + coordTable->nX, coordTable->nY - *D2CLIENT_NegWindowStartY) via D2GFX_DrawCellContext / D2WIN_DrawText. Shows how side-panel UI is offset by the WindowStartX / NegWindowStartY globals -- directly relevant to panel-vs-playarea coordinate origin. | D2GFX_DrawCellContext(&pData, *D2CLIENT_WindowStartX + ptCoordTable->nX, ptCoordTable->nY - *D2CLIENT_NegWindowStartY, ...) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.MULTIRES_DrawRoofTiles` — D2Client.0x6FAA3B90 -- draws roof tile layer for the play area, takes D2ViewStrc* pView. | //D2Client.0x6FAA3B90 void __fastcall MULTIRES_DrawRoofTiles(D2ViewStrc* pView) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.MULTIRES_DrawUpperWallTiles` — D2Client.0x6FAA63E0 -- draws upper wall tiles; takes a D2DrawSlotStrc, a bOnlyShadows flag, and pView. | void __fastcall MULTIRES_DrawUpperWallTiles(D2DrawSlotStrc* pDrawSlot, BOOL bOnlyShadows, D2ViewStrc* pView) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.MULTIRES_DrawLowerWallTiles` — D2Client.0x6FAA6670 -- draws lower wall tiles; takes pView. | void __fastcall MULTIRES_DrawLowerWallTiles(D2ViewStrc* pView) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.MULTIRES_DrawGroundTiles` — D2Client.0x6FAA2E60 & D2Client.0x6FAA3330 -- draws the ground tile layers; takes pView. This is the core floor/ground draw pair that Lectem's dFloor snippets belong to. | //D2Client.0x6FAA2E60 & D2Client.0x6FAA3330 void __fastcall MULTIRES_DrawGroundTiles(D2ViewStrc* pView) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.D2DrawSlotStrc` — sizeof 0x24 render batching struct: nStart(+08), nEnd(+0C), then five linked lists of D2UnitDrawStrc -- pShadows(+10), pUpperWallTiles(+14), pUnits(+18), pRoofTiles(+1C), pLowerWallTiles(+20). This is the per-slot draw list that orders shadows/walls/units/roofs in the play area. | struct D2DrawSlotStrc { ... D2UnitDrawStrc* pShadows; //+10 ... pLowerWallTiles; //+20 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.eD2TileDataFlags_draw_control_flags` — Tile dwFlags bit meanings: 0x4000 DRAW_GROUND (layers 1-4), 0x8000 DRAW_FLOOR (trans tiles above ground e.g. fog), 0x10000 DRAW_ROOF, 0x20000 DRAWN, 0x40000 REVEALED, 0x8 HIDDEN, 0x400 FADED. DRAWCTRL mask = ROOF|FLOOR|GROUND; remaining layers are shadow. Governs which layer a tile draws in. | TILEFLAG_DRAW_GROUND = 0x00004000, //these are tile layers 1,2,3,4 ... TILEFLAG_DRAW_ROOF = 0x00010000 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.MULTIRES_SetResolution` — D2Client.0x6FAA23B0. Sets gdwScreenWidth/Height (640x480 or 800x600), sets gpExpansionUI, then gdwViewWidth = gdwScreenWidth and gdwViewHeight = gdwScreenHeight - kMultiResolutionUIHeightAdjustment. Calls MULTIRES_ResolutionChanged, MULTIRES_UpdateView(*gdwScreenPanels), D2ResizeAutomap, D2ResizeDialogs. THE function that derives the play-area view size from screen size minus UI height -- core to panel/viewport layou | *gdwViewWidth = *gdwScreenWidth; *gdwViewHeight = *gdwScreenHeight - kMultiResolutionUIHeightAdjustment; ... MULTIRES_UpdateView(*gdwScreenPanels); corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.D2CLIENT_ScreenXShift` — D2CLIENT INT global at 0x11C418 (1.13c) named ScreenXShift -- the horizontal screen/camera shift value. Directly the 'screen SHIFT' global for the play-area viewport. | D2VAR(D2CLIENT, ScreenXShift, INT, 0x11C418)//1.13c corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.ParticleData` — Weather/particle entry: dataArrayVersion, nPosX, nPosY, dwSomeThresholdForSin (destroys particle randomly by Y position), nSizeIdx, a boolean, colorIdx, alpha. align(4). | struct ParticleData { ... DWORD dwSomeThresholdForSin; // Used to destroy the particle randomly based on the Y position ... corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.RainParticleData` — Splash/bubble particle: dataArrayVersion, nXpos, nYpos, nWhichRain, nCurFrame, rainGfxIdx. Used for rain ripples/bubbles on water tiles. | struct RainParticleData { ... DWORD nWhichRain; DWORD nCurFrame; DWORD rainGfxIdx; }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.D2AutoMapCacheStrc_D2AutoMapCellStrc` — Automap cache linked-list node: array of 512 automap cells plus a pPrev link. The automap-overlay data structure sitting over the play area. | struct D2AutoMapCacheStrc //sizeof 0x2804 { D2AutoMapCellStrc pCells[512]; //+0000 D2AutoMapCacheStrc* pPrev; //+2800 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.gpAutoMapCache_gnAutoMapCaches` — Global pointer to the automap cache and the automap-cache count; writes at D2Client 6FACBAC6/6FACBA76 (MOV [6FBB1998],ESI / MOV [6FBB199C],ECX). | MOV DWORD PTR DS:[6FBB1998],ESI ; gpAutoMapCache / MOV DWORD PTR DS:[6FBB199C],ECX ; gnAutoMapCaches corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Charm_background_jump_targets_blue_red` — Firehawk's fixed 1.13c targets for the charm-cell background colour hack: 6FB45803 colours the inventory cell blue (reqs met), 6FB457BF colours it red (reqs not met). | D2Client.dll+0x95803 is the jump to color them blue / D2Client.dll+0x957BF is the jump to color them red corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.CLIENT_RunGame_client_app_mode_state_machine` — Full client entry point: loads LNG, UI_Init, tables, then runs the app-mode function table (nMode loop) until APPMODE_CONTINUE. APPMODE_RENDER=6 is one of the client app modes. gszInterfaces[] maps D2Client/D2Server/D2Multi/D2Launch/D2EClient. | appmode_t* pfModes = VDEF(appmode_t*,D2CLIENT,0x44EC); ... *VDEF(DWORD*,D2CLIENT,0xD44D8) = nMode; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2gfx_DrawRectangle_centered_box_call` — Draws a rectangle at (ScreenWidth/2 - 0x7F, ScreenHeight-0x1B) sized region -- another centered-on-play-area placement using ScreenWidth/2. | sub eax, 7Fh ... call D2gfx_DrawRectangle corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_DrawHealthGlobe_UI_DrawManaGlobe_UI_DrawE` — Bottom-panel HUD draw functions (health globe, mana globe, exp bar). | //D2Client.0x6DDE0 - UI_DrawHealthGlobe corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_DrawActiveSkillIcon_left_right_skill_pane` — Draws the active skill icons in the bottom control panel. Left uses nX=0x75; right uses ScreenWidth + 0xFFFFFF5B (i.e. ScreenWidth-0xA5). bLeftSide flag distinguishes the two panels. | push 75h ; nX ... call UI_DrawActiveSkillIcon corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_DrawSkillIcons_UI_DrawSkillSelect` — Skill-select menu draw. Right column at nX=0x50; left column at ScreenWidth + 0xFFFFFF80 (ScreenWidth-0x80). Input coords for the buttons live at 0x6F710; shifting the listed X offsets moves the popup menu too. | That's in UI_DrawSkillSelect, which is D2Client.0xA77D0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.GAMEUI_DrawInterface` — whist: THE entry point for in-game UI drawing -- 'the best place to start looking for ui drawing code is always there'. Takes a GameView*. Top of the panel/HUD draw tree; prime hook target for panel/play-area work. | the best place to start looking for ui drawing code is always there ... GAMEUI_DrawInterface corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Mini_Panel_handlers_draw` — The mini-panel (the small button strip that opens inventory/character/skill panels). Input handler table + the drawing routine at 0x64990. | The drawing code is at D2Client.0x64990 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.UI_background_color_handler` — Szumigajowy: this address 'handle background color' -- candidate for the panel/dead-zone background fill. | D2Client.0x6FAE1710 handle background color corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_DrawSkillSelect_skill_button_input_coords` — Where the majority of input coordinates for the skill-button inputs live; shifting the X offsets also moves the skill-select popup that appears. | D2Client.0x6F710 is where the majority of the input coordinates for the skill button inputs are on 1.13d corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2PreLoadImages` — Client image preload routine at 0x5E370 + ClientOffset. | D2PreLoadImages = (0x5E370 + ClientOffset); // D2Client.6FAFE370 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.ALT_item_popup_draw_routines` — Ground-item name popups (shown while ALT held) are driven from d2client.6FB21B70; the actual popup draw is at 0x6FB09F60. | How the hell game does draw item popups when pressing alt? ... 0x6FB09F60 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDRAW_DrawUnit` — CORE RENDER: __fastcall(pUnit,dwColorTint,nXpos,nYpos,bFade,bDrawOverlays) - draws a unit's sprite at screen X/Y with tint/fade/overlays. This is the per-unit draw entry in the play-area render path. | Function: UNITDRAW_DrawUnit Address: D2Client.0x6FB5A720 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDRAW_CacheGFXData` — RENDER: __forceinline __fastcall(pGFXData,pUnit,pCellFile,nDirection,nFrame,fFlags,dwColorTint,pIndex) - caches the cell/DCC gfx data for a unit before drawing; called from UNITDRAW_DrawUnit. | Function: UNITDRAW_CacheGFXData Address: D2Client.0x6FB5EC80 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.HUD_close_menu_click_coordinate_1_10` — 1.10 address near 6FB0124D controls X,Y of the click on the HUD rectangle; modifying byte 75 to 0FF with a JMP changes menu-close coords. Render/HUD-layout relevant. | for 1.10 it is near to 6FB0124D because modifying those values slightly modifies me ... to make the HUD more elegant corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_GetState_D2CLIENT_gnUiToggle` — UI panel open/close state reader: BOOL __fastcall UI_GetState(int nUiIndex) at D2Client.0x6FB23230 (1.10f); reads global array D2CLIENT_gnUiToggle[nUiIndex]; UITOGGLE_ESCMENU=9, bounded by NUM_UI. Core of knowing which side panels are open. | Address: D2Client.0x6FB23230 ... return D2CLIENT_gnUiToggle[nUiIndex]; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.D2Client_0x521C0_string_desc_builder` — D2Client.0x521C0 builds the item/stat string descriptions; entry point to find the desc-generation function. | D2Client.0x521C0 builds the string descs, you can find the func from there corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2GetItemName_D2Client_6FAD7210` — D2Client.6FAD7210 = D2GetItemName(pItem, wszBuffer, nBufferSize); called from 5 sites (6FADBBD6/6FADC43D/6FADC9B5/6FADD488/6FADD90D) — the item-description name builders, hookable to inject extra tooltip lines. | CALL D2Client.6FAD7210 ; D2GetItemName(D2UnitStrc* pItem, wchar_t* wszBuffer, size_t nBufferSize) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2CLIENT_ItemLevel_tooltip_hook_offsets` — Item-Level-on-tooltip hook: patch site file offset 0x438A1 (1.9-1.13 variants 3D47C/438A1/ADD0A/789DA/AE0AA/941C0); 1.13 live 0x6FB341C0-... ; Set-item variant patch at 0x427BE. Uses D2CLIENT_SetColourPopUp (D2SetColorPopup, D2Client 85A60 in 1.11). | {D2DLL_D2CLIENT, 0x438A1, PATCH_CALL, FALSE, 1}, //6FAE38A1 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.SPEEDBOOK_DrawMenu` — Skill speed-book (radial skill selector) draw routine: void __fastcall SPEEDBOOK_DrawMenu(int nStart, BOOL bLeft, BOOL bDesc) at D2Client.0x6FB19720. On-screen HUD panel render. | Function: SPEEDBOOK_DrawMenu Address: D2Client.0x6FB19720 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.SPEEDBOOK_ClickDown` — Speed-book click handler: void __stdcall SPEEDBOOK_ClickDown(D2WinMsgStrc* pMsg) at D2Client.0x6FB19BB0. | Function: SPEEDBOOK_ClickDown Address: D2Client.0x6FB19BB0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.SPEEDBOOK_DrawIconEx` — Speed-book skill-icon draw: void __fastcall SPEEDBOOK_DrawIconEx(D2CellFileStrc* pCells, D2SkillStrc* pSkill, SkillsTXT* pRecord, DWORD dwGUID, int nValue, int nColor, int nXpos, int nYpos, BOOL bLeft) at D2Client.0x6FB18AC0. Takes explicit X/Y screen coords. | Function: SPEEDBOOK_DrawIconEx Address: D2Client.0x6FB18AC0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.SKILLTREE_DrawTabIcons` — Skill-tree panel tab-icon draw: void __fastcall SKILLTREE_DrawTabIcons(D2UnitStrc* pPlayer, int nTab, int nClass) at D2Client.0x6FB163B0 and 0x6FB16A50 (two entry points). Left-side skill panel render. | Function: SKILLTREE_DrawTabIcons Address: D2Client.0x6FB163B0 & D2Client.0x6FB16A50 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.SKILLTREE_ClickDown_SKILLTREE_ClickUp` — Skill-tree panel mouse handlers: SKILLTREE_ClickDown at D2Client.0x6FB17A80 and SKILLTREE_ClickUp at D2Client.0x6FB18100, both __stdcall(D2WinMsgStrc*). | Function: SKILLTREE_ClickDown Address: D2Client.0x6FB17A80 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.GetUnitX_GetUnitY_subtile_position` — D2Client exports giving a unit's subtile X/Y position; also discussion of obtaining the 'world view subtile' (where the character stands / camera anchor). Relevant to screen-to-world mapping and viewport offset. | the ones I can get with 1.13c d2client.dll +0x1630 and +0x1660 ... how do I get the world view subtile? Is that where my character stands? corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.gold_drop_dialog_UI_functions_1_13c` — whist's UI notes: input handlers (14) at D2Client.0xDE858; draw the gold drop dialog at D2Client.0xA3840; open-dialog function D2Client.0xC30E0; in-memory dialog struct D2Client.0xFAD60. Panel drawing/UI | draw the gold drop dialog: D2Client.0xA3840 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.D2Client_viewport_camera_globals_1_13c_relat` — Deleted User's D2Client global-offset table: resolution X/Y and the WINDOW START X / negative window-start Y offsets = the play-area origin / screen-shift/camera-offset globals. Highly relevant to viewport positioning beside panels. | ptNegWindowStartY = (DWORD*)(offset_D2Client + 0x3A285C); ptWindowStartX = (DWORD*)(offset_D2Client + 0x3A2858); corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2SetScrollEndFunc_sets_two_scroll_end_callb` — void __fastcall(func1,func2) that initializes two function-pointer globals. Kingpin named it D2SetScrollEndFunc. Named for scroll-end handling (viewport scroll callbacks). | static D2Client6FB1CBD0 D2SetScrollEndFunc = (D2Client6FB1CBD0)(0x7CBD0 + ClientOffset); // D2Client.6FB1CBD0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Skill_icon_drawing_function` — Function responsible for drawing the skill icons in 1.10 | In 1.10 have a look at D2Client.0x6FB18AC0, its responsible for drawing the icons. corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.CDraw_Game_UI` — Core game-UI draw function; hook target for drawing UI (calls #10072). Called from within larger UI routine; also builds a 0x13C stack frame at 6FB21B70 | //D2Client.6FB20A30 D2FUNC(D2CLIENT, CDraw_Game_UI, void, __fastcall, (DWORD Arg01), 0x80A30) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.GUI_stat_skill_button_draw_calls` — Inside the GUI draw routine: CALL 6FAEF360 draws stat-points buttons, CALL 6FAEF730 draws skill-points buttons (at 6FB22030/6FB22044) | CALL d2client.6FAEF360 ; GUI Buttons -> stat points ... CALL d2client.6FAEF730 ; GUI Buttons -> skillpts corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.ITEMS_InventoryDraw` — Necrolis' reversed inventory item-draw function (draws an item cel inside the inventory/side panel, with overlay support). Shows the full draw path: UNITDRAW_CacheGFXData -> D2LoadCelFile -> GFXUTIL_CellWithinScreen -> D2DrawImage -> UNITDRAW_DrawOverlays. Directly the side-panel render pipeline. | Function: ITEMS_InventoryDraw Address: D2Client.0x6FB5A320 ... BOOL __fastcall ITEMS_InventoryDraw(D2UnitStrc* pItem, int nXpos, int nYpos) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.gScreenWidth_gScreenHeight_globals` — kain_abel_666 has labelled the client screen-dimension globals: gScreenWidth @ 6FB740EC and gScreenHeight @ 6FB740F0. Relevant to viewport/play-area sizing. | i have the screen width and height labeled as D2Client.gScreenWidth @ 6FB740EC and .gScreenHeight @ 6FB740F0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.GetMouseYCoordinate` — D2Client.6FB57BD0 reads global at 6FBC1AE8; Harvest identifies it as GetMouseYCoordinate (RVA 0xB7BD0, __stdcall, no args). Screen-space coordinate accessor. | D2FUNC(D2CLIENT, GetMouseYCoordinate, DWORD, __stdcall, (), 0xB7BD0) //D2Client6FB57BD0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Set_item_color_inventory_vs_ground` — oli: changing set-item color in 1.13c. Ground color patch at 6FB6FF1D works; the inventory (side-panel) copy is a separate address 6FB4408F (changed 02000000 -> 08000000). Concrete evidence that inventory-panel drawing uses a distinct code path from world/ground drawing. | this is the adress for inv 6FB4408F changed 02000000 to 08000000 ... 6FB6FF1D is for the ground and it works corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Item_Click` — Client-side handler run when clicking an item; takes inventory pointer, mouse X/Y and inventory page. Relevant to side-panel/inventory hit-testing. | //D2Client.0x475C0 D2FUNC(D2CLIENT, Item_Click, int32_t, __fastcall, (D2UnitStrc* pPlayer, D2InventoryStrc* pInventory, int pMouse_X, int pMouse_Y, ui corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.gpCurrentMouseOverItem` — Global holding the item the cursor is hovering (inventory/ground); compared at D2Client 6FAE10A1 as CMP ESI,[gpCurrentMouseOverItem] (ptr at 0x1732BC4 in the BlackSSE build). Directly tied to inventory/panel item interaction. | 6FAE10A1 |> 3B35 C42B1703 |CMP ESI,DWORD PTR DS:[BlackSSE.gpCurrentMouseOverItem corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.main_UI_loop_ALT_item_box_draw` — whist: 0xC39E0 is the main ui loop where all panels are rendered; it calls 0x58FB0 which draws the boxes shown when ALT is pressed to show ground items; hookable via d2template | at D2Client.0xC39E0 is the main ui loop where all the different panels are rendered ... a call to D2Client.0x58FB0, which is the function that handles corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.INTERFACE_CORE_InputAndOutputMsgWindow` — Master click/message dispatcher for all menu panels; found by locating the 4 adjacent Storm#414-417 Input* calls, the function just before them handles all menu clicks; called ~40 times, one callback per panel type; callbacks share globals with the drawing function | the function immediately before is the one that controls all the Clicks of the Menus ... called about 40 times ... one for each type of Panel corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.D2ClientFnMsg_D2WinMSGS_CLT` — Per-panel message table entry: param, key, __stdcall callback taking D2WinMsgStrc* | struct D2ClientFnMsg { uint32_t dwPar; DWORD dwKey; D2WinMSGS_CLT pCallback; }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.UIPanelDrawYOffset_UIPanelDrawXOffset` — Client panel draw offset globals used to compute inventory/stash/cube item positions; X offset scales with resolution width (simple), Y offset value (e.g. 196) source is unclear and larger than expected — used for resolution-height support of an item mover | D2VAR(D2CLIENT, UIPanelDrawYOffset, signed int, 0x124958) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.palshift_Translvl_column_patch` — eezstreet: first two palshifts are ignored; changing 0x6FB27A23 to 'mov edi,0' shifts back the Translvl column so must add 2. Item draw uses the color index (one of 21) as offset into the transform/invtransform shift | Even if I change 0x6FB27A23 to be mov edi, 0 it will just shift back the Translvl column corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.ITEMDESC_DrawGroundMultiHover` — Draws multi-item ground hover boxes; second half is the relevant part; uses FONTS_GetTextDimensions then nWidth += 8 for box sizing | Function: ITEMDESC_DrawGroundMultiHover Address: D2Client.0x6FB09F60 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.D2Client_top_level_UI_control_panel_exp_bar_` — Single top-level handler that draws the entire bottom control panel and the exp bar. NOPing its no-arg calls reveals what each sub-call draws. | that's the function I mentioned ... D2Client.0xC39E0 (top level handler necrolis mentioned) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.ControlPanel_OrbTextDisplay` — Renders the orb text (life/mana orb display) on the control panel. | Function: ControlPanel::OrbTextDisplay Address: D2Client.0x6FAFF0B0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Inventory_charm_req_code_region` — whist's old notes point here for higher-level inventory/charm-requirement handling. | old notes pointing to D2Client.0x95AB8 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Item_charm_cell_background_color_draw_block` — Pushes RGB values and calls draw (0000D390) to set inventory item cell background colors: red-requirement bg, hover bg, default blue bg. Bytes stored at 6FBBB330-334. | PUSH 80 -> red color (red requirement background) ... default blue background corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UNITDESC_DrawInformation` — void __fastcall UNITDESC_DrawInformation(D2UnitStrc* pUnit) at D2Client.0x6FB20A30 (1.10). Draws the unit info/nameplate; contains a large per-unit-type switch that looks up the display name. Render-relevant (unit nameplate drawing). | Function: UNITDESC_DrawInformation Address: D2Client.0x6FB20A30 ... large switch inside it for each unit type corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Frameless_loop_sleep_flag` — Boolean set TRUE in most cases (solo) that governs the game's sleep; main loop otherwise spins with no vsync, which is why removing the frame limit is easy | it does have a sleep but D2Client.0x6FB747C0 is set to TRUE in most cases corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Hot_boot_profiling_function` — Function so hot that profiling it makes the game slow to boot (likely main-loop / per-frame) | why profiling 0x6FAA9AF0 made the game so slow to boot corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Stamina_bar_draw_color_palette` — Function responsible for drawing the stamina bar; 0x7BEFBA is an array of palette indexes picking bar color by remaining stamina | 0x4975B0. that is the func responsible for drawing the stamina bar ... 0x7BEFBA is an array of pallette indexes corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.XP_bar_draw` — Function that draws the experience bar | try looking at 0x498EA0 for xp bar corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.D2EnvironmentCycleStrc_day_night_tint_tables` — Environment lighting cycle tables: {angle, phase, R,G,B, 0} keyframes driving the screen ambient color tint per act | const D2EnvironmentCycleStrc gNormalEnvironmentCycle[6] = { { 320, 3, 125, 144, 243, 0 }, ... corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.INTERFACE_FillRender` — The callback that fills the Units renderer structure to later draw; invoked from D2Common.#10049 DUNGEON_CallRoomCallback. Central to the play-area unit render pipeline. Signature: BOOL __stdcall INTERFACE_FillRender(D2RoomStrc* pRoom, D2GameViewRenderDataCallBackStrc* pArgs). Hazel hit a crash here throwing many-CtC items into a crowd. | This is a callback from D2Common.#10049 (DUNGEON_CallRoomCallback). It is the function that is responsible for filling the Units renderer structure to corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Panel_always_display_patch_button_visibility` — NOP-ing CMP DWORD PTR DS:[6FBBA6FC],EBX / JE at 6FB22062-6FB22068 makes a UI panel always displayed, but its buttons stop working when 'closed'. 6FBBA6FC is the panel-open global flag. Relevant to forcing side panels on. | nooping the following will make it always displayed, but the buttons won't work if u "close" the panel. 6FB22062 |. 391D FCA6BB6F CMP DWORD PTR DS:[6F corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Panel_button_click_handler` — 6FB00CD0 is the handler for when you click that (panel) button; NOP everything up to the retn 4 to disable the click action. | 6FB00CD0 i think is the handler for when u click that button. if u basically noop everything up to the retn 4 it'll disable what happens when u click corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Waypoint_panel_draw` — Function that draws the waypoint panel in 1.13c. Active tab is a D2Client global variable (no server round-trip to switch tabs). Good entry point for locating panel-tab state globals. | the function that draws the waypoint panel (D2Client.0xAAEC0 on 1.13c) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.WaypointClientData` — Client-local simplified struct used to draw the waypoints panel: +00 int levelId, +04 byte activated. Client keeps its own copy separate from server PlayerData waypointData. | the client has it's own local copy, and also uses a separate simplified struct to draw the waypoints panel // D2Client.0xFCD8C struct WaypointClientDa corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.CDraw_Game_UI_INTERFACE_DrawUnitSelectedName` — Draws the selected-unit name over the play area. Two names for same addr: D2FUNC CDraw_Game_UI (0x80A30) and Araksson's INTERFACE_DrawUnitSelectedName (void __fastcall(D2UnitStrc*)). Silvermane long wanted to rewrite this hover-name function. | Function: INTERFACE_DrawUnitSelectedName Adress: D2Client.0x80A30 void __fastcall INTERFACE_DrawUnitSelectedName(D2UnitStrc* pUnit) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.UI_DrawGroundHoverItem` — Draws the ground item hover name in the play area: void __fastcall UI_DrawGroundHoverItem(D2UnitStrc* pItem, wchar_t* pBuffer, int nSize). | //D2Client.6FB20740 void __fastcall UI_DrawGroundHoverItem(D2UnitStrc* pItem, wchar_t* pBuffer, int nSize) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Ground_unit_name_lookup_funcs` — D2Client.0x6FADD360 handles item names only; D2Client.0x6FB297F0 works for everything but players and vistiles (items always return base name). Used to resolve hovered-unit display names. | D2Client.0x6FADD360 is purely for item names, D2Client.0x6FB297F0 works for everything but players and vistiles corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.CharScreenStringTable_gpCharScreenStringTabl` — Char/stat-screen string layout table array at D2Client.6FB78010. Struct: int nXstart, int nY, int nXend, short nString, int nArg. Governs stat-panel text positioning. | //D2Client.6FB78010 CharScreenStringTable gpCharScreenStringTable[] ... struct CharScreenStringTable { int nXstart; int nY; int nXend; short nString; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.Stat_screen_click_handlers` — INTERFACE_StatScreenClickDown (D2Client.0x31290) and INTERFACE_StatScreenClickUp (0x31470), both void __stdcall(D2WinMsgStrc*). Handle input on the character/stat side panel. | Function: INTERFACE_StatScreenClickDown Adress: D2Client.0x31290 ... INTERFACE_StatScreenClickUp Adress: D2Client.0x31470 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.Mana_Health_orb_DC6_size` — MOV EBX,50 instructions that set the DC6 sprite draw size for the mana and health globes; patch target to enlarge the orb bubbles. | 6FAFFB8B BB 50000000 MOV EBX,50 ; Mana DC6 size ... 6FAFF9F9 ... ; Health DC6 size corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.light_radius_clamp_sites` — Four+ CMP/MOV sites that clamp the light radius (CMP EBP,imm / MOV EBP,imm). Patching the immediate (e.g. to 0x1F) forces a large on-screen lit radius regardless of torch/quest state. Verified working in olly. | 6FAA4293 83FD 1F CMP EBP,1F ... 6FAA4298 BD 1F000000 MOV EBP,1F ... This works perfectly corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.INTERFACE_InventoryLButtonDown_UP_1_13c` — 1.13c inventory-panel left-button handlers, __stdcall taking D2WinMsgStrc*. (1.10 equivalents: down 0x46E60, up 0x48140, InteractClickItem 0x475C0.) | D2FUNC(D2CLIENT, INTERFACE_InventoryLButtonDown, ... 0x98DC0) ... INTERFACE_InventoryLButtonUP ... 0x9C630) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.gbCursorItem_SelectedInvItem` — Global UnitAny* for the item currently held on the cursor / selected in inventory (1.13c). | VARPTR(D2CLIENT, SelectedInvItem, UnitAny*, 0x11BC38) // Updated 1.13c corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.hovered_item_GUID_accessor` — Used to read the hovered inventory item's GUID (only works for inventory items; user seeking equivalent for chest/stash object units). | I use d2client 0x1158F4 to get the hovered item's guid, but that is for inventory items corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.ShiftClickStats_character_panel` — Hook site for the character-screen shift-click stat allocation; patched to cap shift-click stat input at 5. | //Shift clicking only imputs 5 { D2DLL_D2CLIENT, 0x315D3, (DWORD)PATCH_JMP, FALSE, 0x01 } corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.belt_itemtype_row_check` — Belt UI row count: CALL D2Common.#10751/#11088 get item type, CMP EAX,13 (row 19 belt), JNZ hides extra rows | CMP EAX,13 ; Check for row 19 (belt itemtype) ... no additional rows is show corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.item_durability_desc_ethereal_check` — Nop JMP at 6FAE2F0E; calls to D2Client.0x6FAE4060 set up the durability desc line | look for the calls to D2Client.0x6FAE4060 which sets up the durability desc line corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.NewAutomapCell_automap_cell_draw` — NewAutomapCell returns pCell; +0x04 nCellNo determines the graphic drawn in the automap cell (asks re lookup table) | pCell(returned by NewAutomapCell)->nCellNo(offset +0x04) is what determines what is drawn in the cell corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.AUTOMAP_DrawCells` — Automap cell drawing routine | See AUTOMAP_DrawCells 1.10f:0x6FACE230 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.weather_storm_phase_setter_rain` — Sets phase of a storm: phase 0 no rain, 1 wind-up, 2 rain, 3 wind-down; contains act5 level-id switch feeding snow/weather globals | D2Client.0x6FAA6D40 ... sets the phase of a storm (with phase 2 being the rain phase corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.NPC_tab_index_top_3_bottom_1` — Controls NPC/store tab selection; topmost tab 3, bottom 1 | It's in D2Client.0xE24F8. top most tab is 3. bottom most is 1 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.VendorTab_array` — Vendor/store tab struct {int x@0;int y@4;short stringId@8;BOOL isSelected@0xA;BOOL isActive@0xE}; array based at D2Client.0xE1178 | struct VendorTab { int x; int y; short stringId; BOOL isSelected; BOOL isActive; } corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Client` `corpus.stash_close_button_Y_pos` — Patch at D2Client+0x9963C adjusts stash-panel close button Y position | {D2DLL_D2CLIENT, 0x9963C, {0xEB}, 0x01}, // Stash close button Y pos corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.spirit_vine_golem_hp_bar_draw` — Draws HP bar for spirits, vines and golems (shared); monID check inside failed for user | its offset is 6FADB5F0. But it draws hp bar for spirits, vines and golems corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.experience_bar_hover_WIN_text_draw` — Exp-bar popup init: hit-test uses gnScreenWidth/2-0x92..-0x17 and *D2CLIENT_ScreenHeight-0x2b..-0x22, calls WIN_HoverText; waypoint text via WIN_DrawNormalText(text,x,y,color,transTbl) at 0x6fb2610a | Init here D2Client.0x6faffd58 ... gnScreenWidth / 2 + -0x92 <= nMouseXPos ... WIN_DrawNormalText corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client` `struct.StatMonitor` — { wchar_t wszDesc[2][30]; DWORD dwStatNo; DWORD dwTimer; DWORD dwColor; BOOL fEnable; } (asks if same in 1.11b/1.13c) | struct StatMonitor { wchar_t wszDesc[2][30]; DWORD dwStatNo; DWORD dwTimer; DWORD dwColor; BOOL fEnable; } corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Client` `corpus.unit_palette_assignment_monsters` — Unit palette assignment: 0x6FB27A23 begins it, 0x6FB5BFC0 writes palette index into unit draw data; bosses/champions roll random PL2 shift from unit GUID (source of random color shifts) | D2Client.6FB27A23 is the start of the unit palette assignment (D2Client.0x6FB5BFC0 sets the palette index in the units draw data) for monsters corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Client (1.14 merged Game.exe) (1 symbols) - **function** `D2Client (1.14 merged Game.exe)` `corpus.D2isLODGame_D2GetDifficultyLevel_D2GetMouseX` — 1.14 (post-DLL-merge) addresses; note subtract 0x400000 to get RVA. Mouse X/Y getters useful for cursor-vs-viewport mapping. | D2GetMouseX ... -> 0x468730 ... D2GetMouseY ... -> 0x468740 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Client (uses D2Common exports) (1 symbols) - **function** `D2Client (uses D2Common exports)` `ext.D2Client (uses D2Common exports).UNITS_GetDrawingPosition_non_perspective_branc` — Non-perspective (isometric) screen position of a unit. Two-line math: X offset minus (camera-X minus secondary-X), Y offset minus camera-Y. This is the core render-position formula for the FOCUS constants. | src: pPos->nX = D2COMMON_GetUnitOffsetX(pUnit) - (*D2CLIENT_6FBC9960 - *D2CLIENT_6FBCC418); pPos->nY = D2COMMON_GetUnitOffsetY(pUnit) - *D2CLIENT_6FBC995C; ### D2Client (uses D2Gfx + D2Common exports) (1 symbols) - **function** `D2Client (uses D2Gfx + D2Common exports)` `ext.D2Client (uses D2Gfx + D2Common exports).UNITS_GetDrawingPosition_perspective_branch` — Perspective-mode screen position. Reads unit world position (not offset), guards with D2GFX_CheckPerspectivePosition, transforms via D2GFX_AdjustPerspectivePosition, then applies +/-256 X shift based on UI panel state. | src: int nX = D2COMMON_GetUnitPositionX(pUnit); int nY = D2COMMON_GetUnitPositionY(pUnit); int nAdjustX = 0; int nAdjustY = 0; if (!D2GFX_CheckPerspectivePosition(nX, nY)) return FALSE; ### D2Client / D2Common (1 symbols) - **function** `D2Client / D2Common` `corpus.D2AddRoomToDrawList_D2IterateRooms` — The room draw-list build step: D2Client passes its D2AddRoomToDrawList callback to D2IterateRooms (D2Common #10049) to walk visible rooms and enqueue them for drawing. Core of the world/floor render pipeline. | PUSH ... CALL ; JMP to D2Common.#10049 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Client / D2Common / D2gfx (1 symbols) - **function** `D2Client / D2Common / D2gfx` `corpus.UI_INVENTORY_socket_item_draw_internals` — Decompiled socket-drawing loop: indexes gSocketPositions by [height + 4*(width + 2*nbSockets) - 13], computes cell-centered X/Y from nGridBoxWidth/Height, sets up a D2GfxData with pCelFile = GEMSOCKET cellfile global (0x6FBB5E34) and calls D2GFX ordinal #10072 DrawImage, then draws each socketed item via DrawInventoryItems at 0x6FB5A320. Panel item/socket render pipeline. | pData.pCelFile = (CelFile *)gpCellfile_GEMSOCKET_6FBB5E34; D2GFX_10072_DrawImage(&pData, v10 - 1, v12, -1, 0, 0); corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Client.dll (12 symbols) - **global** `D2Client.dll+0x11B9A0` `ext.D2Client.ScreenShiftX` — Horizontal screen shift (viewport pan offset). Named exactly in FOCUS. | src: D2Client.dll ScreenShiftX Offset 0x11B9A0 - **global** `D2Client.dll+0x11B9A4` `ext.D2Client.ScreenShiftY` — Vertical screen shift (viewport pan offset). Named exactly in FOCUS. | src: D2Client.dll ScreenShiftY Offset 0x11B9A4 - **global** `D2Client.dll+0xDBC4C` `ext.D2Client.GeneralDisplayHeight` — General display height. Named exactly in FOCUS. | src: D2Client.dll GeneralDisplayHeight Offset 0xDBC4C - **global** `D2Client.dll+0xFADA8` `ext.D2Client.IsAutomapOpen` — Automap open flag. | src: D2Client.dll IsAutomapOpen Offset 0xFADA8 - **global** `D2Client.dll+0xFADA4` `ext.D2Client.IsGameMenuOpen` — Game menu open flag. | src: D2Client.dll IsGameMenuOpen Offset 0xFADA4 - **global** `D2Client.dll+0xFAE04` `ext.D2Client.IsHelpScreenOpen` — Help screen open flag. | src: D2Client.dll IsHelpScreenOpen Offset 0xFAE04 - **global** `D2Client.dll+0x11B99C` `ext.D2Client.InventoryArrangeMode` — Inventory arrange mode (UI panel layout state). | src: D2Client.dll InventoryArrangeMode Offset 0x11B99C - **global** `D2Client.dll+0x11C30C` `ext.D2Client.IsNewSkillButtonPressed` — New-skill button pressed flag (UI element state). | src: D2Client.dll IsNewSkillButtonPressed Offset 0x11C30C - **global** `D2Client.dll+0x11C308` `ext.D2Client.IsNewStatsButtonPressed` — New-stats button pressed flag (UI element state). | src: D2Client.dll IsNewStatsButtonPressed Offset 0x11C308 - **global** `D2Client.dll+0x11B828` `ext.D2Client.IngameMousePositionX` — In-game mouse cursor X (screen coordinate). | src: D2Client.dll IngameMousePositionX Offset 0x11B828 - **global** `D2Client.dll+0x11B824` `ext.D2Client.IngameMousePositionY` — In-game mouse cursor Y (screen coordinate). | src: D2Client.dll IngameMousePositionY Offset 0x11B824 - **function** `D2Client.dll+0xBC4E0` `ext.D2Client.DrawCenteredUnicodeText` — Draws centered unicode text on screen. | src: D2Client.dll DrawCenteredUnicodeText Offset 0xBC4E0 ### D2Client/D2Common (2 symbols) - **struct** `D2Client/D2Common` `struct.InventoryGrid` — Layout of the inventory-panel grid array (Inventory+0x14 -> pInvInfo points to array of these). Fields: pFirstItem +0x00, pLastItem +0x04, nWidth +0x08, nHeight +0x09, ppItems (grid cell pointer array) +0x0C. Grid index enum: BODYLOC=0, BELT=1, INVENTORY=2. | struct InventoryGrid { Unit* pFirstItem; //0x00 ... BYTE nWidth; //0x08 BYTE nHeight; //0x09 ... Unit** ppItems; //0x0C }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Client/D2Common` `struct.D2RoomCullArgsStrc` — Args struct for room culling during render: +00 D2ViewStrc* pView, +04 RECT pCullingRect, +14 DRLGRoom* pClientRoom, +18 BOOL bTilesOnly. Directly about the viewport/culling rectangle that decides what tiles/rooms get drawn -- relevant to play-area vs panel clipping. | struct D2RoomCullArgsStrc { D2ViewStrc* pView; //+00 RECT pCullingRect; //+04 DRLGRoom* pClientRoom; //+14 BOOL bTilesOnly; //+18 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Client/D2Gfx (1 symbols) - **function** `D2Client/D2Gfx` `corpus.D2Gfx_DrawLine_D2Gfx_DrawRectangle` — Line/rect draw helpers positioned via ScreenWidth/2 - 0x90 (144) etc. DrawRectangle callable at D2Client.0xD0BA on 1.13c; D2gfx_DrawRectangle used with ScreenWidth/ScreenHeight to place a centered rect. | call D2Gfx_DrawLine -> D2Gfx_DrawRectangle corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Client/D2gfx (1 symbols) - **function** `D2Client/D2gfx` `corpus.Composite_draw_loop_GetItemGfxData_DrawImage` — Layer draw loop over COF layers: for each selectable component, memset gfx, D2CLIENT_GetItemGfxData_6FB5EC80 fills a CelContext (dir, frame, component), then D2GFX_10076_DrawImageFast(&gfx, pXAdjust, pYAdjust, palette). Iterates layerIdx < cofFile->header.layers. Core unit/item composite rendering. | D2CLIENT_GetItemGfxData_6FB5EC80(...) ... D2GFX_10076_DrawImageFast(&tmpGfxInfo, pXAdjust, pYAdjust, nPaletteIndex[0]); corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Common (46 symbols) - **function** `D2Common` `ext.D2Common.D2COMMON_GetUnitOffsetX_D2COMMON_GetUnitOffset` — Supply the isometric screen-offset X/Y of a unit; consumed by the NON-perspective branch of both functions. | src: D2COMMON_GetUnitOffsetX(pUnit) ... D2COMMON_GetUnitOffsetY(pUnit) - **function** `D2Common` `ext.D2Common.D2COMMON_GetUnitPositionX_D2COMMON_GetUnitPosi` — Supply the world-space X/Y of a unit; consumed by the PERSPECTIVE branch (fed into D2GFX_AdjustPerspectivePosition). | src: int nX = D2COMMON_GetUnitPositionX(pUnit); int nY = D2COMMON_GetUnitPositionY(pUnit); - **function** `D2Common` `corpus.COLLISION_CheckMask` — Reads the per-subtile collision mask for a room coord: indexes pRoom->pCollisionGrid->pCollisionMask via ((y - subtilesTop)*subtilesWidth - subtilesLeft + x) & fMask. Reveals D2RoomCollisionStrc layout (pCollisionMask, pCoords with dwSubtilesTop/Left/Width). Governs floor/tile walkability that render+pathing share. | WORD __fastcall COLLISION_CheckMask(DRLGRoom* pRoom, int nXpos, int nYpos, WORD fMask) ... pMask[(((nYpos - pCoords->dwSubtilesTop) * pCoords->dwSubti corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.COLLIDE_tile_collision_flags` — Full per-subtile collision bitmask enum. Notably COLLIDE_BLOCK_PLAYER 0x0001 = 'black space' in Arcane Sanctuary / cliff walls (the void/dead-zone tiles); COLLIDE_ALTERNATE_FLOOR 0x0010 (some floors set it); COLLIDE_BLANK 0x0020 returned for invalid subtile; COLLIDE_WALL 0x0004. Directly relevant to floor/void rendering behavior. | COLLIDE_BLOCK_PLAYER = 0x0001, // 'black space' in arcane sanctuary, cliff walls etc ... COLLIDE_ALTERNATE_FLOOR = 0x0010 ... COLLIDE_BLANK = 0x0020, corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Common` `corpus.Charm_inventory_grid_function_1_13c` — Function responsible for the charm inventory in 1.13c. Thaison supplies a CharmZone hook checking item grid position: pInventory at [EAX+0x2C], leftBorder/rightBorder at [+0x0C], top/bottomBorder at [+0x10] (top>=4, bottom<=8) to define the charm grid zone. Panel/inventory-grid render logic. | D2Common.dll - 0x28846 ... CMP DWORD PTR DS:[EAX+0x0C], 0 //leftBorder ... CMP DWORD PTR DS:[EAX+0x10], 4 //topBorder corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.PATH_NormalizeCoords` — Normalizes an X/Y coord pair (in place). __forceinline void __fastcall PATH_NormalizeCoords(int* pXpos, int* pYpos). Coordinate transform used in pathing/positioning. | Function: PATH_NormalizeCoords Address: D2Common.#10111 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.ITEMS_GetCompData` — Builds the composite comp/color arrays (nComp[16] +088, nColor[16] +098). eD2CompositeItem enum: HEAD0,TORSO1,LEGS2,RARM3,LARM4,RHAND5,LHAND6,SHIELD7,SPECIAL1-8=8..15,COUNT16,INVALID0xFF | Function: ITEMS_GetCompData Address: D2Common.#10298 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.TileGetRarity_export` — D2Common export at 6FC711D6 named TileGetRarity (tile rarity accessor). | 6FC711D6 .text Export TileGetRarity corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.TileProjectSerialize_export` — D2Common export at 6FC71668 named TileProjectSerialize (tile projection serialization). | 6FC71668 .text Export TileProjectSerialize corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.Cell_UnitInfo_draw_flags` — Item cell draw flags: CELLFLAG_UNIQUE=0x01, CELLFLAG_OVERLAY=0x02, CELLFLAG_CLIENT=0x08, CELLFLAG_SET=0x10. Drive item graphic/overlay drawing. | CELLFLAG_UNIQUE = 0x01, CELLFLAG_OVERLAY = 0x02, CELLFLAG_CLIENT = 0x08, CELLFLAG_SET = 0x10 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.AnimDataRecord` — AnimData.d2 record: char szAnimDataName[8]@0x00, DWORD dwFrames@0x08, DWORD dwAnimSpeed@0x0C. Frame count + speed for animations. | char szAnimDataName[8]; //0x00 DWORD dwFrames; //0x08 DWORD dwAnimSpeed; //0x0C corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.ITEMS_GetItemPalette_D2Common_10893` — Returns the color/transform palette for drawing an item: __stdcall(pOwner, pItem, pColor, nTransType). Key gotcha: passing the player as pOwner tints inventory items by the player's palette; must pass NULL as pOwner so inventory items draw with correct colors. | BYTE* __stdcall ITEMS_GetItemPalette(Unit* pOwner, Unit* pItem, BYTE* pColor, int nTransType) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.DrlgAct_0x18_DrlgTileData` — 1.13c: pDrlgAct + 0x18 holds a DrlgTileData structure (level tile data). | DrlgTileData DrlgTileData; //0x18 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2MonSeqTxt` — Monster sequence record: WORD wSequence@0x00, BYTE nMode@0x02, nFrame@0x03, nDir@0x04, nEvent@0x05. | WORD wSequence; //0x00 BYTE nMode; //0x02 BYTE nFrame; //0x03 BYTE nDir; //0x04 BYTE nEvent; //0x05 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.PATH_ScreenToWorld` — Screen->world isometric coordinate transform (D2Common ordinal #10108). nX*=2; outX=(nX+nY)>>5; outY=(nX-nY)>>5. Core camera/iso projection math used when converting cursor/screen to tile world coords. | PATH_ScreenToWorld ... nXpos *= 2; *pXpos = (nXpos + nYpos) >> 5; *pYpos = (nXpos - nYpos) >> 5; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.PATH_WorldToScreen` — World->screen isometric transform (D2Common #10111): outX=(nX-nY)<<4; outY=(nX+nY)<<3. The inverse projection that places world tiles onto the play-area viewport. | PATH_WorldToScreen ... *pXpos = (nXpos - nYpos) << 4; *pYpos = (nXpos + nYpos) << 3; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.PATH_FlattenCoords` — D2Common #10112 coordinate flatten: outX=(nX-nY)>>1; outY=(nX+nY)>>2. Another iso coordinate transform in the path/render coordinate family. | PATH_FlattenCoords ... *pXpos = (nXpos - nYpos) >> 1; *pYpos = (nXpos + nYpos) >> 2; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.DUNGEON_TILES_ExpandCoords_DUNGEON_ExpandCoo` — Tile coordinate expansion helpers: DUNGEON_TILES_ExpandCoords = D2Common #10110, DUNGEON_ExpandCoords = D2Common #10113. Part of the dungeon-tile coordinate transform set. | DUNGEON_TILES_ExpandCoords ... Address: D2Common.#10110 ... DUNGEON_ExpandCoords ... Address: D2Common.#10113 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.CLIENT_LoadDataTables` — Client-side data-table loader; note bPrecacheTileLibraries param governs tile-library precache (floor/tile relevance). | CLIENT_LoadDataTables(D2PoolManagerStrc* pMemPool, BOOL bPrecacheTileLibraries, BOOL bCreateLvlSubList) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.Act_RoomCoords` — Act: pEnviroment+0x04, nTownLevel+0x08, dwMapSeed+0x0C, pRoom1+0x10, TileData+0x18, ActMisc*+0x48, pMemoryPool+0x5C. RoomCoords (Room1+0x4C, 0x20 bytes): dwXStart/YStart/XSize/YSize, wTileX+0x10, wTileY+0x14, dwTilesWidth+0x18, dwTilesHeight+0x1C -- tile origin/extent of a room. | struct RoomCoords // sizeof(0x20) 32 bytes of Room1+4C corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.ActEnvironment` — Day/night lighting env: nTimeIndex, nDaytime, nIntensity+0x0C, nRed/nGreen/nBlue +0x18-0x1A, fCos/fLast/fSin +0x1C-0x24, dwTimeRate+0x28, bEclipse+0x30. Drives the outdoor floor/ambient light color. | struct ActEnvironment // sizeof(0x38) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.TileData` — Tile placement record: nWidth/nXposEx+00, nHeight/nYposEx+04, nXpos+08, nYpos+0C, fFlags+14, D2TileLibraryEntryStrc* pTile+18, nRed/nGreen/nBlue/nIntensity +0x28-0x2B. Per-tile screen position + tint for floor rendering. | struct TileData //sizeof 0x30 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2TileLibraryEntryStrc` — Tile library entry; DT1 filename pointer at offset 0x58 (88 dwords in). | char* szDT1FileName; //0x058 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.DRLG_grid_cell_lookup_sub_6FD75CA0_DRLGGrid_` — Cell lookup: pCellPos[nCellPosId + pCellFlags[nCellFlagId]]. DRLGGrid = {int* pCellPos, int* pCellFlags, nWidth(nCells), nHeight, nullOnInit}; Lectem's D2DrlgCoordIndexStrc variant has pIndexList checked via bitmasks. drlg tile flags at D2DrlgTileDataStrc+0x14. | return pDrlgGrid->pCellPos[nCellPosId + pDrlgGrid->pCellFlags[nCellFlagId]]; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.itemData_invPage_INVPAGE_INVENTORY_CUBE_STAS` — Item location is read from itemData->invPage compared against INVPAGE_INVENTORY / INVPAGE_CUBE / INVPAGE_STASH to distinguish inventory vs cube vs stash. Panel-context test. | return itemData->invPage == INVPAGE_STASH; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.DrlgLevel_LevelPreset_offset` — DrlgLevel+0x1CC (1.13c) / +0x21C (1.10) points to the LevelPreset structure {dwDef, dwFiles, dwPreset, pNext*}. Level/DRLG generation data. | [1.13c] DrlgLevel + 0x1CC [1.10] DrlgLevel + 0x21C ... struct LevelPreset corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2AutoMapRecordStrc_gpMapGfxCells` — Automap cell-range record {int nFirst;+00, int nLast;+04} indexed per level-type; gpMapGfxCells[MAX_LEVELTYPES] gives first/last automap gfx cell for each of the 45 level types (full name table listed). Automap/render data | struct D2AutoMapRecordStrc { int nFirst; //+00 int nLast; //+04 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Common` `corpus.automap_gfx_cell_mapname_table_relocation_of` — D2Common code sites patched to repoint automap cell lookup to a custom gpMapGfxCells array: CommonOffset +0x22C7F/0x22D46 (nFirst), +0x22C4E/0x22C9F/0x22D4D (nLast); +0x22AF5/0x22B32 for map-name strings. Original formula (ID*4)+(GameOffset+0xFF018), asm 6FC6F5E6 | 6FC6F5E6 (ID * 4) + (GameOffset + 0xFF018) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.Get_the_Node_Page_item_container_page` — Returns which container/page an item is in: 00 Inventory, 01 BodyLoc, 02 Belt, 03 Ground, 04 Cursor. Core mapping for inventory/equip side-panel item location. | Get the Node Page Offset: 00052100 (6FD92100) ... 00: Inventory 01: BodyLoc 02: Belt 03: Ground 04: Cursor corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.DRLGLevelStruct_Level` — Full DRLG Level struct layout: dwDrlgType@0x00 (1 maze,2 preset,3 wilderness), pRoom2First@0x10, union maze/preset/wilderness@0x14, dwPosX@0x1C, dwPosY@0x20, dwSizeX@0x24, dwSizeY@0x28, pNextLevel@0x1AC, pMisc@0x1B4, dwLevelType@0x1C0, hSeed@0x1C4, pLevelPresets@0x1CC, dwLevelNo@0x1D0, RoomCenterX/WarpX[9]@0x1E0, RoomCenterY/WarpY[9]@0x204, dwRoomEntries@0x228. Crash in D2Common #10322 DRLGInitLevel tied to sizeX/siz | DWORD dwDrlgType; //0x00 1 - maze, 2 - preset, 3 - wilderness ... DWORD dwSizeX; //0x24 DWORD dwSizeY; //0x28 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2AutoMapRecordStrc_gpfMapNamesSize` — Automap record {int nFirst;+00, int nLast;+04}; MAX_LEVELTYPES 255; level-type name table gpfMapNamesSize[] (0=None..35 '5 Lava', 36+ Dark Alliance/dummy). Patch points to repoint gpMapGfxCells and name table at D2Common +0x22C7F/22D46 (nFirst), +0x22C4E/22C9F/22D4D (nLast), +0x22AF5/22B32 (names) | struct D2AutoMapRecordStrc { int nFirst; //+00 int nLast; //+04 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2RoomExFlags_enum` — RoomEx flag bits incl HAS_WARP_0..7 (0x10-0x800), SUBSHRINE_ROW1-4, HAS_WAYPOINT 0x10000, AUTOMAP_REVEAL 0x40000, NO_LOS_DRAW 0x80000 (line-of-sight draw suppression), HAS_ROOM 0x100000, HASPORTAL 0x400000, POPULATION_ZERO 0x800000 (towns/nospawn), TILELIB_LOADED 0x1000000, PRESET_UNITS_ADDED/SPAWNED, ANIMATED_FLOOR 0x8000000 (river of flame, hell act5) | ROOMEXFLAG_NO_LOS_DRAW = 0x00080000, ... ROOMEXFLAG_ANIMATED_FLOOR = 0x08000000, //animated floors (river of flame, hell act5) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.UNITS_GetInventoryRecordId` — Gets the inventory record id for a unit/inv page (Inventory.txt). Used for inventory/panel geometry. | //D2Common.0x6FDBEBE0 (#10409) int __stdcall UNITS_GetInventoryRecordId(D2UnitStrc* pUnit, int nInvPage, BOOL bLoD) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.STATES_GetGfxStateFlags_STATES_GetStatFlags` — Two state-flag accessors returning DWORD* into a unit's state bitfield: #10492 GetGfxStateFlags @0x6FDB4760, #10494 GetStatFlags @0x6FDB48F0. GfxStateFlags governs which overlay/graphic states are active on a unit. | //D2Common.0x6FDB4760 (#10492) DWORD* __stdcall STATES_GetGfxStateFlags(D2UnitStrc* pUnit) ... //D2Common.0x6FDB48F0 (#10494) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2StaticPathStrc` — Static path struct (used by items/objects on ground): pRoom@0x00, nTargetX@0x04, nTargetY@0x08, nXPos@0x0C, nYPos@0x10, unk[2]@0x14, nDirection(BYTE)@0x1C. (Canight variant puts dwHeight@0x50.) Ground-position data feeding unit placement. | struct D2StaticPathStrc { D2RoomStrc* pRoom; //0x00 int nTargetX; //0x04 ... int nXPos; //0x0C int nYPos; //0x10 ... BYTE nDirection; //0x1C }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.DRLGROOM_AllocRoomNears_ReAllocRoomNears_roo` — Builds each room's ppRoomsNear list by scanning level rooms within tile distance <10 in X and Y (nTileXPos/YPos/Width/Height), sorts by position; D2Moo raises near cap 6->10 (ppNearRooms[1024]) | Function: DRLGROOM_AllocRoomNears Adress: D2Common.0x6FD77BB0 ... Improvement 6 --> 10 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2AutoMapRecordStrc_gpMapGfxCells_gpfMapName` — Automap graphics cell first/last per level type; level-type name table (0=None..44) drives automap tile ranges; two dword patches point the engine at an enlarged name table | D2AutoMapRecordStrc gpMapGfxCells[MAX_LEVELTYPES]; ... struct { int nFirst; int nLast; } corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.RoomTile_flag_setter_tile_dwFlags_bit_8` — Sets bit 8 of pTileData->dwFlags from the sign bit (0x80000000) of nTileFlags; floor/room tile flag handling. | 0x6FD88AC0 ... if (nTileFlags & 0x80000000) pTileData->dwFlags |= 8 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2DrlgStrc_active_room_offsets` — 1.13c D2DrlgStrc fields: +0x098 (=1.10 +0x3E0, DWORD added-active-rooms counter, never decremented), +0x45C (=1.10 +0x3DC, BYTE counter, can overflow), +0x008 (=1.10 +0x3DD, set to 5 or 7 per +0x24 dwFlags, can be decremented). Client-side active-room processing; see D2Common.#10715 (1.13c) / #10003 (1.10). | offsets for these fields in 1.13c are D2DrlgStrc +0x098 +0x45C +0x008 ... 0x3E0 is the amount of added active rooms corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.DUNGEON_StreamRoomAtCoords` — D2RoomStrc* __stdcall DUNGEON_StreamRoomAtCoords(D2DrlgActStrc* pAct, int nX, int nY) — resolves the room at world coords | D2RoomStrc* __stdcall DUNGEON_StreamRoomAtCoords(D2DrlgActStrc* pAct, int nX, int nY) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.DRLG_AllocLevel_D2DrlgLevelStrc_fields` — Allocates a DRLG level from mempool; sets pDrlg, nLevelId, nLevelType, nDrlgType from DATATBLS_GetLevelDefRecord | D2DrlgLevelStrc* __fastcall DRLG_AllocLevel(D2DrlgStrc* pDrlg, int nLevelId) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.D2RoomStrc` — Full room struct layout -- the world floor/tile geometry a floor renderer walks. nSubtileX 0x00, nSubtileY 0x04, nSubtileWidth 0x08, nSubtileHeight 0x0C, nTileXPos 0x10, nTileYPos 0x14, nTileWidth 0x18, nTileHeight 0x1C; pRoomTiles 0x20; ppRoomList 0x24; nNumRooms 0x28; pUnitFirst 0x2C; pUnitUpdate 0x30; pCollisionGrid 0x34; pRoomEx 0x38; pSeed 0x3C; dwFlags 0x48; ppClients 0x4C; nTileCount 0x58; pAct 0x78; pRoomNext | int nSubtileX;//0x00 ... int nTileXPos;//0x10 ... D2RoomCollisionGridStrc* pCollisionGrid;//0x34 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Common` `struct.item_bounds_check_inventory_grid_pos` — Disassembly of D2Common.0x6F68D570 charm-requirement/inventory-slot bounds check. pItem is [ESP+4]; pItem->pDynamicPath/pStaticPath union at +0x2C; grid posX at pPath+0x0C; posY at pPath+0x10. This is the inventory PANEL grid coordinate model used by CharmZone. | 6F68D57B . 8B40 2C MOV EAX,DWORD PTR DS:[EAX+2C] ; pitem->pPath ... CMP DWORD PTR DS:[EAX+0C],5 ; pPath->posX corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.DUNGEON_GetLevelIdFromRoom` — BYTE __stdcall taking D2RoomStrc*; returns the level id for a room. Pair with UNITS_GetRoom to get the level a unit is standing in. | //D2Common.0x6FD8C7A0 (#10057) BYTE __stdcall DUNGEON_GetLevelIdFromRoom(D2RoomStrc* pRoom) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.UNITS_GetRoom` — D2RoomStrc* __stdcall taking D2UnitStrc*; returns the room a unit occupies. | //D2Common.0x6FDBE270 (#10342) D2RoomStrc* __stdcall UNITS_GetRoom(D2UnitStrc* pUnit) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Common` `corpus.D2Common_CharmRequirements_10840_rewrite` — CharmZone technique: rewrite of D2Common.#10840 requirement check restricting charms to an inventory sub-rectangle. Returns TRUE only if item is a charm, unit-type UNIT_ITEM, node page NODEPAGE_STORAGE, and pStaticPath->nYPos >= 6 and reqs met. Hooked from both D2Common (0x5FE70) and D2Client (0x4118B). | { D2DLL_D2COMMON, 0x5FE70, (DWORD)PATCH_JMP, FALSE, 0x01 }, { D2DLL_D2COMMON, 0x5FE71, (DWORD)D2Common_CharmRequirements, TRUE, 0x00 }, { D2DLL_D2CLIE corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Common (DRLG) (1 symbols) - **struct** `D2Common (DRLG)` `struct.DLRGTileGrid` — Per-room tile container holding wall/floor/shadow tile data; sizeof 0x2C. Fields: pMapLinks+00, pAnimTiles+04, nWalls+08, nFloors+0C, nShadows+10, pTiles+14. SwineFlu asks whether it can be surgically removed and rebuilt from the room seed without client/server desync. Core of floor/tile rendering data. | struct DLRGTileGrid //sizeof 0x2C { DRLGTileLink* pMapLinks; //+00 ... int nFloors; //+0C int nShadows; //+10 DRLGRoomTiles pTiles; //+14 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Common / D2Game (1 symbols) - **global** `D2Common / D2Game` `corpus.gpMapGfxCells_map_name_table_patches` — Patch sites that repoint D2Common map graphics cell lookups (nFirst/nLast) and map-name-size table into a custom gpMapGfxCells / gpfMapNamesSize array. Involves floor/level tile graphics cell indexing: base formula (ID*4)+(GameOffset+0xFF018), original ref 6FC6F5E6. | (DWORD)&gpMapGfxCells[0].nFirst; // 6FC6F5E6 (ID * 4) + (GameOffset + 0xFF018) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Common/tooling (1 symbols) - **struct** `D2Common/tooling` `struct.D2TileStrc` — Full tile struct layout for floor/tile rendering: +00 nDirection, +04 nRoofHeight, +06 fMaterial, +08 nHeight, +0C nWidth, +10 nHeightToBottom, +14 nType, +18 nStyle, +1C nSequence, +20 nRarity/nFrame union, +24 dw24To8BitFilterMask, +28 fTileFlags[26] (first byte index, 25 subtile flags), +42 wCacheIndex, +44 dwUnk44, +48 dwBlockOffset, +4C nBlockSize, +50 nBlocks, +54 pBlock, +58 pLibraryName (runtime), +5C pLRUCac | struct D2TileStrc //sizeof 0x60 { int32_t nDirection; //+00 ... int32_t nHeightToBottom; //+10 ... D2TileBlockStrc* pBlock; //+54 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2DDraw (1 symbols) - **global** `D2DDraw` `corpus.D2DDraw_screen_clear_blit_REP_STOS` — The screen-clear routine at D2DDraw ~6F8C637A: loads front-buffer ptr from 6F8CF8A8, height/pitch from 6F8D01D8, width factor from 6F8D01C8; when a flag (ESI) is set it does ADD ECX,-2F (the 47px bottom-panel adjust) before IMUL. Zeroes the buffer with REP STOS at 6F8C639B. On failure pushes 'Blit (screen clear) failed!' string at 6F8CCE64. This is the exact HOM/dead-zone clear path. | 6F8C638C 83C1 D1 ADD ECX,-2F ... 6F8C639B F3:AB REP STOS DWORD PTR ES:[EDI] ... Blit (screen clear) failed! corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2DDraw.dll (6 symbols) - **global** `D2DDraw.dll+0x101D4` `ext.D2DDraw.D2DDraw_DisplayWidth` — DirectDraw backend display width. | src: D2DDraw.dll DisplayWidth Offset 0x101D4 - **global** `D2DDraw.dll+0x101CC` `ext.D2DDraw.D2DDraw_DisplayHeight` — DirectDraw backend display height. | src: D2DDraw.dll DisplayHeight Offset 0x101CC - **global** `D2DDraw.dll+0x101C8` `ext.D2DDraw.D2DDraw_BitBlockWidth` — DirectDraw bit-block (blit) width. | src: D2DDraw.dll BitBlockWidth Offset 0x101C8 - **global** `D2DDraw.dll+0x101D8` `ext.D2DDraw.D2DDraw_BitBlockHeight` — DirectDraw bit-block (blit) height. | src: D2DDraw.dll BitBlockHeight Offset 0x101D8 - **global** `D2DDraw.dll+0x101E8` `ext.D2DDraw.D2DDraw_CelDisplayLeft` — DirectDraw CEL display left bound. | src: D2DDraw.dll CelDisplayLeft Offset 0x101E8 - **global** `D2DDraw.dll+0x101EC` `ext.D2DDraw.D2DDraw_CelDisplayRight` — DirectDraw CEL display right bound. | src: D2DDraw.dll CelDisplayRight Offset 0x101EC ### D2Direct3D.dll (2 symbols) - **global** `D2Direct3D.dll+0x1AB44` `ext.D2Direct3D.D2Direct3D_DisplayWidth` — Direct3D backend display width. | src: D2Direct3D.dll DisplayWidth Offset 0x1AB44 - **global** `D2Direct3D.dll+0x1AFD4` `ext.D2Direct3D.D2Direct3D_DisplayHeight` — Direct3D backend display height. | src: D2Direct3D.dll DisplayHeight Offset 0x1AFD4 ### D2GDI.dll (4 symbols) - **global** `D2GDI.dll+0xCA80` `ext.D2GDI.D2GDI_BitBlockWidth` — GDI backend bit-block (blit) width. | src: D2GDI.dll BitBlockWidth Offset 0xCA80 - **global** `D2GDI.dll+0xCA90` `ext.D2GDI.D2GDI_BitBlockHeight` — GDI backend bit-block (blit) height. | src: D2GDI.dll BitBlockHeight Offset 0xCA90 - **global** `D2GDI.dll+0xCA98` `ext.D2GDI.D2GDI_CelDisplayLeft` — GDI CEL display left bound. | src: D2GDI.dll CelDisplayLeft Offset 0xCA98 - **global** `D2GDI.dll+0xCA9C` `ext.D2GDI.D2GDI_CelDisplayRight` — GDI CEL display right bound. | src: D2GDI.dll CelDisplayRight Offset 0xCA9C ### D2GFX (13 symbols) - **function** `D2GFX` `ext.D2GFX.D2GFX_DrawLine` — Draws a line between two screen points with color. | src: FUNCPTR(D2GFX, DrawLine, void __stdcall, (int X1, int Y1, int X2, int Y2, DWORD dwColor, DWORD dwUnk), -10010) - **function** `D2GFX` `ext.D2GFX.D2GFX_DrawRectangle` — Fills/draws a screen rectangle with color and transparency mode. | src: FUNCPTR(D2GFX, DrawRectangle, void __stdcall, (int X1, int Y1, int X2, int Y2, DWORD dwColor, DWORD dwTrans), -10014) - **function** `D2GFX` `ext.D2GFX.D2GFX_DrawAutomapCell2` — Renders a single automap cell/tile at screen xpos/ypos with two brightness values and a color table. Core automap-draw primitive. | src: FUNCPTR(D2GFX, DrawAutomapCell2, void __stdcall, (CellContext* context, DWORD xpos, DWORD ypos, DWORD bright2, DWORD bright, BYTE *coltab), -10041) - **function** `D2GFX` `ext.D2GFX.D2GFX_GetScreenSize` — Returns encoded screen resolution (packed WxH). Render viewport sizing. | src: FUNCPTR(D2GFX, GetScreenSize, DWORD __stdcall, (void), -10031) - **function** `D2GFX` `ext.D2GFX.D2GFX_GetHwnd` — Returns the game render window HWND. | src: FUNCPTR(D2GFX, GetHwnd, HWND __stdcall, (void), -10048) - **global** `D2GFX+0x11258` `ext.D2GFX.VideoMode_2` — Current video/render mode (WORD) — DDraw/D3D/Glide selector. | src: VARPTR(D2GFX, VideoMode, WORD, 0x11258, 0x14A38) - **function** `D2GFX` `ext.D2GFX.DrawLine` — Draws a line. Args (int X1,int Y1,int X2,int Y2,DWORD dwColor,DWORD dwUnk), __stdcall. Dual = export ordinals. | src: FUNCPTR(D2GFX, DrawLine, void __stdcall, (int X1, int Y1, int X2, int Y2, DWORD dwColor, DWORD dwUnk), -10010, -10013) - **function** `D2GFX` `ext.D2GFX.DrawRectangle_2` — Draws a filled/trans rectangle. Args (int X1,int Y1,int X2,int Y2,DWORD dwColor,DWORD dwTrans), __stdcall. | src: FUNCPTR(D2GFX, DrawRectangle, void __stdcall, (int X1, int Y1, int X2, int Y2, DWORD dwColor, DWORD dwTrans), -10014, -10028) - **function** `D2GFX` `ext.D2GFX.DrawAutomapCell` — Core automap-cell blitter. ASMPTR. Args (CellContext *context,DWORD xpos,DWORD ypos,RECT *cliprect,DWORD bright), __stdcall. | src: ASMPTR(D2GFX, DrawAutomapCell, void __stdcall, (CellContext *context, DWORD xpos, DWORD ypos, RECT *cliprect, DWORD bright), -10079, -10060) - **function** `D2GFX` `ext.D2GFX.DrawAutomapCell2` — Second automap-cell draw variant. Args (CellContext* context,DWORD xpos,DWORD ypos,DWORD bright2,DWORD bright,BYTE *coltab), __stdcall. | src: FUNCPTR(D2GFX, DrawAutomapCell2, void __stdcall, (CellContext* context, DWORD xpos, DWORD ypos, DWORD bright2, DWORD bright, BYTE *coltab), -10041, -10042) - **function** `D2GFX` `ext.D2GFX.DrawCellContextEx` — Draws a cell context (sprite) with transparency/color. Args (CellContext *context,int Xpos,int Ypos,int dwl,int nTransLvl,BYTE Color), __stdcall. | src: FUNCPTR(D2GFX, DrawCellContextEx, void __stdcall, (CellContext *context, int Xpos, int Ypos, int dwl, int nTransLvl, BYTE Color), -10019, -10067) - **function** `D2GFX` `ext.D2GFX.GetScreenSize` — Returns the current screen size. __stdcall (void). Render/viewport query. | src: FUNCPTR(D2GFX, GetScreenSize, DWORD __stdcall, (void), -10031, -10012) - **global** `D2GFX` `corpus.prevent_minimize_stay_running_unfocused` — NOP these (11 bytes at each D2GFX site, 24 bytes at D2Win site) to stop the game minimizing on focus loss and keep it running while unfocused. | //D2GFX.6FA74932/6FA74D11 prevent minimizing ... //D2Win.6F8ADC9E Stays running while unfocused corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2GFX.dll (6 symbols) - **global** `D2GFX.dll+0x11260` `ext.D2GFX.ResolutionMode` — Render resolution mode selector. Comment gives enum values. | src: D2GFX.dll ResolutionMode Offset 0x11260 0 = 640x480, 1 = Main Menu, 2 = 800x600, 3 = 1344x700 - **global** `D2GFX.dll+0x11258` `ext.D2GFX.VideoMode` — Active video/render backend selector. Comment gives enum values. | src: D2GFX.dll VideoMode Offset 0x11258 1 = GDI, 2 = Software, 3 = DirectDraw, 4 = Glide, 5 = OpenGL, 6 = Direct3D, 7 = Rave - **global** `D2GFX.dll+0x1125C` `ext.D2GFX.IsWindowedMode` — Windowed vs fullscreen flag. | src: D2GFX.dll IsWindowedMode Offset 0x1125C - **global** `D2GFX.dll+0x11264` `ext.D2GFX.WindowHandle` — HWND of the render window. | src: D2GFX.dll WindowHandle Offset 0x11264 gtGfxSystem.hWnd - **function** `D2GFX.dll` `ext.D2GFX.DrawCelContext` — Draws a CEL (sprite) via graphics context — core sprite/cell blit entry point. | src: D2GFX.dll DrawCelContext Ordinal 10041 - **function** `D2GFX.dll` `ext.D2GFX.DrawRectangle` — Draws a rectangle primitive. Named in FOCUS. | src: D2GFX.dll DrawRectangle Ordinal 10014 ### D2Game (5 symbols) - **function** `D2Game` `corpus.D2ReInitRoom` — void __fastcall(D2GameStrc* pGame, DRLGRoom* pRoom) - re-initialises a room | D2FUNC(D2GAME,0x85A0,void,__fastcall,D2ReInitRoom,(D2GameStrc* pGame, DRLGRoom* pRoom)); corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Game` `corpus.inventory_input_handler` — Inventory action handler sig (Game* pGame, Inventory* pInv, int nInput, int* a5, int* a6); a5/a6 last two args undetermined. | (Game* pGame, Inventory* pInv, int nInput, int* a5, int* a6) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Game` `corpus.PLAYERMODE_Change` — Necrolis: void __fastcall PLAYERMODE_Change(pGame,pPlayer,pSkill,BYTE nMode,int nUnitType,DWORD dwGUID,BOOL bAllowReEnter) at D2Game.0x6FC81A00 (RVA 0x51A00). Changes a unit's animation/mode. | Function: PLAYERMODE_Change Address: D2Game.0x6FC81A00 ... void __fastcall PLAYERMODE_Change(D2GameStrc* pGame, D2UnitStrc* pPlayer, D2SkillStrc* pSki corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Game` `struct.D2VendorChainStrc` — Vendor gamble/inventory chain, size 0xC: +0x00 dwPlayerGUID, +0x04 bGamble (whether sold item appears in vendor inventory), +0x08 pPrev. | struct D2VendorChainStrc //sizeof 0xC { DWORD dwPlayerGUID; BOOL bGamble; D2VendorChainStrc* pPrev; } corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Game` `corpus.D2GAME_GetRoom_6FC52070_DUNGEON_IsRoomInTown` — D2ActiveRoomStrc* getter at a target X/Y within a unit's room; DUNGEON_IsRoomInTown tests town membership. | D2ActiveRoomStrc* pRoom = D2GAME_GetRoom_6FC52070(UNITS_GetRoom(pUnit), nTargetX, nTargetY); corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Gdi/Game (1 symbols) - **function** `D2Gdi/Game` `corpus.D2FillArea_underlying_renderer_call` — D2FillArea passes 6 params; in 1.13d the CALL EAX lands in D2Gdi 0x6f877370 (takes all 6), in 1.14d lands at 0x006c8a60 windowed renderer (uses only params 3-6). Area-fill (solid rect) render path. | in 13d the CALL goes to this address 0x6f877370 in D2Gdi.dll that function takes all 6 paramters from D2FillArea corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Gfx (4 symbols) - **function** `D2Gfx` `ext.D2Gfx.D2GFX_CheckPerspective` — Boolean gate selecting perspective vs isometric branch. Returns nonzero when D3D perspective (depth distortion) mode is active. No numeric address given in the thread. | src: if (!D2GFX_CheckPerspective()) - **function** `D2Gfx` `ext.D2Gfx.D2GFX_CheckPerspectivePosition` — Validates a world (nX,nY) coordinate for perspective transform; failure aborts (return FALSE / return). Called before D2GFX_AdjustPerspectivePosition. | src: if (!D2GFX_CheckPerspectivePosition(nX, nY)) return FALSE; - **function** `D2Gfx` `ext.D2Gfx.D2GFX_AdjustPerspectivePosition` — The perspective-adjust entry point. Signature as used: (int nX, int nY, int 0, int* pAdjustX, int* pAdjustY) — takes world X/Y and a literal 0 third arg, writes screen-adjusted X/Y through the two out-pointers. Output then gets the UISTATE +/-256 X correction. | src: D2GFX_AdjustPerspectivePosition(nX, nY, 0, &nAdjustX, &nAdjustY); - **function** `D2Gfx` `corpus.D2DrawImageToWindow_D2Gfx_10072` — D2Gfx image-blit-to-window function (ordinal 10072) at GfxOffset+0x42A0 | D2DrawImageToWindow = (GfxOffset + 0x42A0); // D2Gfx.10072 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Glide (13 symbols) - **function** `0x6F8561D0 D2Glide+0x61D0` `D2Glide.DeadTwin` — DECOY -- DO NOT HOOK. Uncalled twin of 0x6290 (0 E8/dword refs); identical width math; register-arg convention. Dead - **function** `0x6F856290 D2Glide+0x6290` `D2Glide.DeadSoftwareDrawer` — DECOY -- DO NOT HOOK. Software/LFB drawer reached only via the non-taken branch of worker 0x7080; PROVEN dead (v1.9.0 hook never fired). Computes DisplayWidth-DisplayWidth/4=960 but never executes -- the 960 was mis-inferred off this dead code - **function** `0x6F856840 D2Glide+0x6840` `D2Glide.LiveDrawer` — THE live cel->vertex drawer (confirmed fires: [GLIDECULL] edx=ScreenOpenMode). Per-subtile cull vs [esp+0x30]left,[esp+0x38]right narrowed by panel-mode (mode1 right=DispW/2=640 @0x6889, mode2 left=DispW/2-0x20=608 @0x689E). v1.9.1 NOP'd both -> NO visual change => this cull is world-space, NOT the screen clip. Submits via grDrawVertexArrayContiguous - **hook** `0x6F856889 D2Glide+0x6889` `hook.PanelWorldGlideCull1` — NOP the mode-1 right-narrow store in the live drawer. v1.9.1: applied cleanly, NO visual change -> the cull isn't the screen clip. FAILED attempt expect `89 4C 24 38` patch `90 90 90 90` - **hook** `0x6F85689E D2Glide+0x689E` `hook.PanelWorldGlideCull2` — NOP the mode-2 left-narrow store. Same v1.9.1 FAILED result expect `89 5C 24 30` patch `90 90 90 90` - **function** `0x6F857080 D2Glide+0x7080` `D2Glide.Worker` — gates on [0x6F865A8C]: null -> LIVE drawer 0x6840; non-null -> DEAD 0x6290 - **function** `0x6F8572F0 D2Glide+0x72F0` `D2Glide.LiveCelHandler` — pfTileDrawLit (dispatch struct +0x9c); the live cel handler D2Client #10001 reaches; calls worker 0x7080 - **function** `0x6F8592D0 D2Glide+0x92D0 ord 10000` `D2Glide.DriverInit_10000` — the ONLY export: mov eax,0x6F8649F0; ret -- hands D2gfx the driver dispatch struct expect `B8 F0 49 86 6F C3` - **global** `0x6F8611D0 D2Glide+0x111D0` `D2Glide.grDrawVertexArrayContiguous_IAT` — D2Glide IAT slot for glide3x!grDrawVertexArrayContiguous@16 (into D2DX). 0x6F856032 = jmp [this]. THE D2Glide->D2DX crossing; hook target for the pipeline-trace probe - **global** `0x6F8649F0 D2Glide+0x149F0` `D2Glide.DriverDispatchStruct` — the driver vtable D2gfx installs at [0x6FA91268]; +0x9c=0x6F8572F0 (cel handler) - **global** `0x6F865A68 D2Glide+0x15A68` `D2Glide.DisplayWidth` — driver DisplayWidth; SGD2FreeRes rewrites 800->1280 at RVA 0xDCF1 at runtime; the -/4 math survives -> 960 - **global** `0x6F865A8C D2Glide+0x15A8C` `D2Glide.LiveDrawerGate` — [this]+4 / [this] gate the worker 0x7080: both-nonzero -> DEAD software drawer 0x6290; else -> LIVE drawer 0x6840 - **global** `0x6F865B04 D2Glide+0x15B04` `D2Glide.DisplayHeight` — driver DisplayHeight; stock 600, SGD2->720 ### D2WIN (17 symbols) - **function** `D2WIN+0x18750` `ext.D2WIN.D2WIN_DrawSprites` — Draws sprite batch (fastcall). Under 'D2Win Functions' header. | src: FUNCPTR(D2WIN, DrawSprites, void __fastcall, (void), 0x18750) - **function** `D2WIN+0x17EB0` `ext.D2WIN.D2WIN_TakeScreenshot` — Captures the current frame to a screenshot. | src: FUNCPTR(D2WIN, TakeScreenshot, void __fastcall, (), 0x17EB0) - **function** `D2WIN` `ext.D2WIN.D2WIN_DrawText` — Draws a wide-char string at screen xPos/yPos with color. Primary on-screen text primitive. | src: FUNCPTR(D2WIN, DrawText, void __fastcall, (wchar_t *wStr, int xPos, int yPos, DWORD dwColor, DWORD dwUnk), -10150) - **function** `D2WIN` `ext.D2WIN.D2WIN_GetTextSize` — Measures pixel width (and font file no) of a string for layout/centering. | src: FUNCPTR(D2WIN, GetTextSize, DWORD __fastcall, (wchar_t *wStr, DWORD* dwWidth, DWORD* dwFileNo), -10177) - **function** `D2WIN` `ext.D2WIN.D2WIN_SetTextSize` — Selects the active font/text size for subsequent DrawText calls. | src: FUNCPTR(D2WIN, SetTextSize, DWORD __fastcall, (DWORD dwSize), -10184) - **function** `D2WIN` `ext.D2WIN.D2WIN_SplitText` — Word-wraps text to a pixel width, returning line array + count (text-layout helper). | src: FUNCPTR(D2WIN, SplitText, SplitText* __fastcall, (wchar_t* text, int* lineCount, int width), -10059) - **function** `D2WIN` `ext.D2WIN.D2WIN_FreeSplitText` — Frees a SplitText layout result. | src: FUNCPTR(D2WIN, FreeSplitText, void __fastcall, (SplitText* text), -10008) - **function** `D2WIN` `ext.D2WIN.D2WIN_SetControlText` — Sets the text of a UI Control (edit box/button). UI, borderline render. | src: FUNCPTR(D2WIN, SetControlText, void* __fastcall, (Control* box, wchar_t* txt), -10042) - **global** `D2WIN+0x214A0` `ext.D2WIN.D2WIN_FirstControl` — Head of the UI Control linked list. UI, borderline render. | src: VARPTR(D2WIN, FirstControl, Control*, 0x214A0) - **global** `D2WIN+0x214B0` `ext.D2WIN.D2WIN_FocusedControl` — Currently focused UI Control. | src: VARPTR(D2WIN, FocusedControl, Control*, 0x214B0) - **function** `D2WIN+0x18750` `ext.D2WIN.DrawSprites` — Batched sprite draw. __fastcall (void). NOTE: dual values are DIRECT OFFSETS (positive hex), not ordinals — key rebasing distinction. | src: FUNCPTR(D2WIN, DrawSprites, void __fastcall, (void), 0x18750, 0xEAA0) - **function** `D2WIN` `ext.D2WIN.DrawCellFile` — Draws a CellFile sprite. Args (CellFile * pCellFile,int xPos,int yPos,int div,int trans,int Color), __fastcall. | src: FUNCPTR(D2WIN, DrawCellFile, void __fastcall, (CellFile * pCellFile, int xPos, int yPos, int div, int trans, int Color), -10138, -10172) - **function** `D2WIN` `ext.D2WIN.DrawText` — Draws a unicode text string. Args (const wchar_t *wStr,int xPos,int yPos,DWORD dwColor,DWORD dwUnk), __fastcall. | src: FUNCPTR(D2WIN, DrawText, void __fastcall, (const wchar_t *wStr, int xPos, int yPos, DWORD dwColor, DWORD dwUnk), -10150, -10076) - **function** `D2WIN+0x12940` `ext.D2WIN.DrawTextBuffer` — Text-buffer draw routine. ASMPTR with DIRECT OFFSETS (positive hex), not ordinals. | src: ASMPTR(D2WIN, DrawTextBuffer, 0x12940, 0x134D0) - **function** `D2WIN` `ext.D2WIN.GetTextSize` — Measures text width/height. Args (wchar_t *wStr,DWORD* dwWidth,DWORD* dwFileNo), __fastcall. | src: FUNCPTR(D2WIN, GetTextSize, DWORD __fastcall, (wchar_t *wStr, DWORD* dwWidth, DWORD* dwFileNo), -10177, -10179) - **function** `D2WIN` `ext.D2WIN.SetTextSize` — Sets the active font/text size. Args (DWORD dwSize), __fastcall. | src: FUNCPTR(D2WIN, SetTextSize, DWORD __fastcall, (DWORD dwSize), -10184, -10047) - **function** `D2WIN` `ext.D2WIN.GetTextWidthFileNo` — Text width + file-number query. Same ordinals as GetTextSize (-10177/-10179) — aliased export. Args (wchar_t *wStr,DWORD* dwWidth,DWORD* dwFileNo), __fastcall. | src: FUNCPTR(D2WIN, GetTextWidthFileNo, DWORD __fastcall, (wchar_t *wStr, DWORD* dwWidth, DWORD* dwFileNo), -10177, -10179) ### D2Win (10 symbols) - **function** `D2Win` `corpus.DrawFramedTextExt` — Draws framed text: (wchar_t* text, int nX, int nY, int nTextColor, int nAlign, int nRectColor). UI text-render primitive. | D2FUNC(D2WIN, DrawFramedTextExt, void, __fastcall, (wchar_t *wszText, int nX, int nY, int nTextColor, int nAlign, int nRectColor), 0xACD0) //D2Win.#10 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **struct** `D2Win` `struct.Control_D2Win_UI_control` — D2Win UI control struct: dwType +0x00, pCellFile +0x04, dwDisabled +0x08, pos X/Y +0x0C/0x10, size X/Y +0x14/0x18, Initialize fn +0x1C, CallFunc (button) +0x28, CallBackFunc +0x34, pNext +0x3C, dwMaxLength/ButtonFlag +0x44, pFirstText/dwScrollEntries +0x48, pLastText/dwScrollPosition +0x4C, pSelectedText +0x50, wText[0x1A] +0x5C, pChildControl +0x90 | struct Control { DWORD dwType; //0x00 ... Control* pChildControl; //0x90 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Win` `corpus.Menu_char_draw_loop_start` — Start of the character drawing loop in menus | look at D2Win.0x6F8AD818 for the start of the char drawin loop in menus corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Win` `corpus.D2SetFont_D2Win_10127` — D2Win ordinal 10127 = D2SetFont, at WinOffset+0x09FF0 (6F8A9FF0). Text-render setup. | D2SetFont = (D2Win10127)(WinOffset + 0x09FF0); // D2Win.6F8A9FF0 #10127 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Win` `corpus.D2WIN_ResizeWindow_10176` — D2WIN.0x17B60 (ordinal #10176), __stdcall(nResolutionMode). Passes the resolution mode down to D2GFX #10025 to resize the window rect. | D2FUNC(D2WIN, ResizeWindow, void, __stdcall, (int nResolutionMode), 0x17B60);//D2WIN.#10176 - Pass resolution mode to D2GFX.#10025 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Win` `corpus.PAL_Update_PAL_SetUpdate` — D2Win #10179 = PAL_Update() (applies palette); D2Win #10176 = PAL_SetUpdate(BOOL) sets the palette globals to activate/deactivate the update. (Note ordinal #10176 also cited for ResizeWindow -- version ambiguity to watch.) | //D2Win.#10176 void __fastcall PAL_SetUpdate(BOOL bActive) ... activate or deactivate D2Win.#10179 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Win` `corpus.D2Win_D2DrawText_10117` — void __fastcall(wchar_t* pString,int nXpos,int nYpos,int nColour,int nTransTbl). Main HUD unicode draw call. | D2FUNC(D2WIN, D2DrawText, void, __fastcall, (wchar_t* pString, int nXpos, int nYpos, int nColour, int nTransTbl), 0xAD40) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Win` `corpus.FONTS_DrawHoverText_D2Win` — Hover/tooltip text draw. Clamps X/Y against screen size via D2GFX_GetScreenSize(&nGameWidth,&nGameHeight); positions box so it stays on screen (nXpos > nGameWidth-nWidth -> nGameWidth-nWidth-5). Draws background via D2GFX_DrawSolidRectEx. Shows how the game reads viewport dimensions for UI placement -- the nGameWidth/nGameHeight globals bound the drawable area. | D2GFX_GetScreenSize(&nGameWidth, &nGameHeight); ... if ((uint32_t)nXpos > nGameWidth - nWidth) nXpos = nGameWidth - nWidth - 5; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2Win` `corpus.MENUS_DrawRegisteredControls_menu_input_hand` — OOG (out-of-game) menu draw/input entry points (1.13c): MENUS_DrawRegisteredControls (D2Win.#10024), INPUT_HandleMenuMouseInput (0x180F0), INPUT_HandleMenuKeyDownInput (0x182A0), INPUT_HandleMenuKeyCharInput (0x18370). | Function: MENUS_DrawRegisteredControls Address: D2Win.#10024 ... INPUT_HandleMenuMouseInput D2Win.0x180F0 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2Win` `corpus.text_draw_mode_color_site` — PUSH 4 that supplies the draw-mode/color index for first-line text; color index 9 = white, another index = gold. | 6F8EFA43 6A 04 PUSH 4 ; draw mode first line texts 9 is for white which number is for gold corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Win / D2Client (1 symbols) - **function** `D2Win / D2Client` `corpus.PAL_LoadActData_per_frame_palette_render_ord` — Act palette load: D2Client sets act palette via D2Win.#10177 PAL_LoadActData(nActPalette) which calls D2Win.0x6F8AE5E0 with pl2/dat pair; to swap palettes load tile palette each frame, then reload units palette AFTER all tiles rendered (optional 3rd call for UI) | load the tile palette each frame ... make a second call ... just after all tiles have been rendered to load the units palette (and UI corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Win / D2gfx (1 symbols) - **function** `D2Win / D2gfx` `corpus.D2DrawText_D2Win_10117_item_gfx_GFX_10073` — kain_abel_666 repositioning D2BuffIcons: text via D2Win.#10117 D2DrawText(pString,nX,nY,nColour,nTransTbl); icon images via GFX.#10073. Asks what -1 as the transtbl arg means. UI-panel overlay positioning. | D2DrawText(wchar_t* pString, int nXpos, int nYpos, int nColour, int nTransTbl); ... the only called to GFX.#10073 for the images corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Win.dll (10 symbols) - **function** `D2Win.dll` `ext.D2Win.DrawUnicodeText` — Draws unicode text. | src: D2Win.dll DrawUnicodeText Ordinal 10150 - **function** `D2Win.dll` `ext.D2Win.GetPopupUnicodeTextWidthAndHeight` — Measures popup text width/height (layout). | src: D2Win.dll GetPopupUnicodeTextWidthAndHeight Ordinal 10177 - **function** `D2Win.dll` `ext.D2Win.GetUnicodeTextDrawWidth` — Measures drawn text width (layout). | src: D2Win.dll GetUnicodeTextDrawWidth Ordinal 10028 - **function** `D2Win.dll` `ext.D2Win.GetUnicodeTextNDrawWidth` — Measures drawn text width for N chars (layout). | src: D2Win.dll GetUnicodeTextNDrawWidth Ordinal 10055 - **function** `D2Win.dll` `ext.D2Win.SetPopUpUnicodeText` — Sets popup text content. | src: D2Win.dll SetPopUpUnicodeText Ordinal 10085 - **function** `D2Win.dll` `ext.D2Win.SetUnicodeTextFont` — Selects active text font for drawing. | src: D2Win.dll SetUnicodeTextFont Ordinal 10184 - **function** `D2Win.dll` `ext.D2Win.LoadCelFile` — Loads a CEL sprite file. | src: D2Win.dll LoadCelFile Ordinal 10111 - **function** `D2Win.dll` `ext.D2Win.UnloadCelFile` — Unloads a CEL sprite file. | src: D2Win.dll UnloadCelFile Ordinal 10126 - **global** `D2Win.dll+0x21488` `ext.D2Win.MainMenuMousePositionX` — Main-menu mouse cursor X (screen coordinate). | src: D2Win.dll MainMenuMousePositionX Offset 0x21488 - **global** `D2Win.dll+0x2148C` `ext.D2Win.MainMenuMousePositionY` — Main-menu mouse cursor Y (screen coordinate). | src: D2Win.dll MainMenuMousePositionY Offset 0x2148C ### D2Win/D2Client (1 symbols) - **function** `D2Win/D2Client` `corpus.Display_Text_D2Win_10171` — Draws a Unicode string: ECX=ptr to unicode string, EDX=x, Arg1=y, Arg2=colour, Arg3=always 0. Called from D2Client to print stats on character screen. | Function: Display Text Address: 6F8AAD40 in v1.10 ECX - pointer to Unicode String EDX - x coord Arg1 - y coord Arg2 - colour corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2Win/D2GFX (1 symbols) - **function** `D2Win/D2GFX` `corpus.Text_background_width_height_D2GFX_DrawRecta` — D2Win ordinal 10110 draws a text popup background; helpers 6f8f1fb0 (text->background width, param in EAX) and 6f8f1e80 (text->background height, param in ESI); width/height feed D2GFX_DrawRectangle | 6f8f1fb0, takes text as param1 ... returns background width ... passed into the D2GFX_DrawRectangle function corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2client.dll (7 symbols) - **function** `D2client.dll+0x10E29` `ext.D2client.D2Client_dll_Part_1_FOV_width_height_logic` — THE 0x10E29 BLOCK. Sets D2 field-of-vision, character/map positioning, and UI element positions. mov eax,0x280 then stores eax to [D2client+0xDBC48] (width logic), then mov [D2client+0xDBC4C],0x1E0 (height logic). Unmodified: renders as 640x480 and places game at upper-left of window. The width store (A3 48BCB86F) is left UNMODIFIED; only the eax immediate (640->1066) and the height DWORD (480->600) change. | src: ORIG: B8 80020000 mov eax,00000280 (640); A3 48BCB86F mov [D2client.dll+0xDBC48],eax (width logic); C7 05 4CBCB86F E0010000 mov [D2client.dll+0xDBC4C],000001E0 (480 height logic). - **global** `D2client.dll+0xDBC4C` `ext.D2client.D2client_dll_0xDBC4C_game_height_logic` — Memory location storing the game's logic height. Written directly as DWORD constant in the 0x10E29 block. | src: C7 05 4CBCB86F 58020000 mov [D2client.dll+0xDBC4C], 00000258 ; 600 height logic - **function** `D2client.dll+0xC3A11` `ext.D2client.D2Client_dll_Part_2_UI_panel_offset_table_orig` — THE 0xC3A11 UI-PANEL OFFSET SITE. Original 640x480-mode panel placement: writes ebx (=0) into panel x-offset [+0x11B9A0] and y-offset [+0x11B9A4]. Governs alignment of quest buttons, waypoint buttons, mercenary stats. reg->mem is fewer bytes than DWORD-const->mem, so 8 extra bytes needed to inject HD constants. | src: 89 1D A0B9BC6F mov [D2client.dll+0x11B9A0],ebx (x-offset of panels, ebx=0); 89 1D A4B9BC6F mov [D2client.dll+0x11B9A4],ebx (y-offset of panels, ebx=0) - **global** `D2client.dll+0x11B9A0` `ext.D2client.D2client_dll_0x11B9A0_panel_X_offset` — Memory storing X-offset positioning of UI panels. 640x480 -> 0; 800x600 -> 0x50 (80); 1066x600 -> 0xD5 (213). Written via 89 1D A0B9BC6F (reg) or C7 05 A0B9BC6F (const). | src: stores the x-offset positioning of panels; 1066x600 value 000000D5 (213) - **global** `D2client.dll+0x11B9A4` `ext.D2client.D2client_dll_0x11B9A4_panel_Y_offset` — Memory storing Y-offset positioning of UI panels. 640x480 -> 0; 800x600 AND 1066x600 -> 0xFFFFFFC4 (-60). Because 1066x600 shares 800x600's height (600), the y-offset code for 800x600 is reused. | src: stores the y-offset positioning of panels; value FFFFFFC4 (-60) - **function** `D2client.dll+0xC39F9` `ext.D2client.D2Client_dll_Part_2_wider_view_w_75_16_jne_ORI` — THE 75 16 PER-RESOLUTION BRANCH. Original: jne to 0xC3A11 (640x480 code) if not 800x600 mode. 800x600 block sets x-offset=0x50(80) and y-offset=0xFFFFFFC4(-60), then EB 0C jmp to 0xC3A1D past the 640x480 code. 640x480 block (at 0xC3A11) writes ebx(0) to both offsets. jne displacement = 0x16. | src: 75 16 jne D2client.dll+0xC3A11 (jump to 640x480 code if not 800x600); C7 05 A0B9BC6F 50000000 mov [+0x11B9A0],00000050 (80 x-offset); C7 05 A4B9BC6F C4FFFFFF mov [+0x11B9A4],FFFFF - **function** `D2client.dll+0xC39F9` `ext.D2client.D2Client_dll_Part_2_SOLUTION_reordered_75_0C_j` — PATCHED per-resolution branch: author reorders so the shared y-offset store (-60) runs FIRST for both 800x600 and 1066x600 (both height 600), freeing 6 bytes and cutting needed bytes 8->4. jne displacement CHANGED from 0x16 to 0x0C. Now: write y-offset -60; jne 0xC3A11 (=1066x600 code) if not 800x600; 800x600 sets x=0x50(80); jmp past; the 0xC3A11 '1066x600 code' sets x-offset=0xD5(213); two trailing 90 NOPs (leftover bytes). CONCLUSION ON PANEL SHIFT: panel x-offset 213 for | src: C7 05 A4B9BC6F C4FFFFFF mov [+0x11B9A4],FFFFFFC4 (-60 y-offset); 75 0C jne D2client.dll+0xC3A11 (jump to 1066x600 code if not 800x600); C7 05 A0B9BC6F 50000000 mov [+0x11B9A0],0000 ### D2gdi.dll (2 symbols) - **function** `D2gdi.dll+0x6D55` `ext.D2gdi.D2gdi_dll_Part_1_game_width_height_RENDER` — Sets values determining how D2 renders the game (width render into esi, height render into edx). Unaltered: stretches 640x480 to fill window then crashes. WIDTH RENDER USES 1064 (0x428) NOT 1066 — author: non-multiple-of-4 width overflows onto LEFT side of screen. Height render 600. | src: ORIG: BE 80020000 mov esi,00000280 (640 game's width render); BE E0010000 mov edx,000001E0 (480 height render). PATCH: BE 28040000 mov esi,00000428 (1064 width render); BE 58020000 - **function** `D2gdi.dll+0x706B` `ext.D2gdi.D2gdi_dll_Part_2_x_position_render_cutoff` — Sets x-position where D2 should STOP rendering entities (walls, pillars). Unmodified: anything past x=640 not rendered (or incorrectly); no crash. Write target [D2gdi.dll+0xCA9C]. RENDER BOUNDARY. | src: ORIG: C7 05 9CCA876F 80020000 mov [D2gdi.dll+0xCA9C],00000280 (640, x-pos stop rendering). PATCH: C7 05 9CCA876F 2A040000 mov [D2gdi.dll+0xCA9C],0000042A (1066) ### D2gfx (6 symbols) - **function** `0x6FA8B080 D2gfx+0xB080 ord 10041` `D2gfx.Fill_10041` — solid fill; forwarder to driver vtable +0x84; 295 D2Client callers, none targets [960,1280] (overdraw ruled out) - **function** `0x6FA8BB50 D2gfx+0xBB50 ord 10001` `D2gfx.CelDrawOpaque_10001` — mov eax,[0x6FA91268]; call [eax+0x9c]; ret 0x14 -- thin forwarder into the driver cel handler, no clip - **global** `0x6FA91268 D2gfx+0x11268` `D2gfx.DriverInterfacePtr` — *[this] = the active video-driver dispatch struct (= D2Glide's 0x6F8649F0); slot +0x9c = the cel handler D2Client #10001 invokes - **struct** `D2gfx` `struct.TileGfxData` — Whist's (incomplete) definition of the struct passed to the d2gfx tile-drawing ordinals; only szDT1FileName mapped at +0x058 (the DT1 tile file name), first 0x58 bytes unknown. | struct TileGfxData { DWORD __000[88]; //0x000 char* szDT1FileName; //0x058 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `D2gfx` `corpus.D2GFX_SelectResolutionMode_10025` — D2GFX.0x7FD0 (ordinal #10025). Maps resolution mode -> out RECT right/bottom: 0=640x480, 1/2=800x600, 3=1344x700, default=0x0. Selects resolution mode and resizes the window rect. | D2FUNC(D2GFX,SelectResolutionMode,void,__stdcall,(int nResolutionMode,int *Out_RECT_right,int *Out_RECT_Bottom),0x7FD0) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `D2gfx` `corpus.D2gfx_flush_window_function_18F46_flag` — D2gfx 6FA748C0 calls #10004 then 6FA7BCEE; 6FA7BCEE reads [esp+8], on TEST EAX zeroes ecx, writes result to [eax+0x18F46] (a D2gfx window/state flag). Kieran patch subject, Necrolis gave clean naked stub | 6FA748C5 E8 24740000 CALL D2gfx.6FA7BCEE corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2gfx.dll (1 symbols) - **function** `D2gfx.dll+0x7FE8` `ext.D2gfx.D2gfx_dll_window_resize_width_height` — Resizes the game window on resolution change. Sets screen width [eax] then screen height [ecx]. Left unmodified: game crunches elements, mouse cannot move past 640th x / 480th y, black lines. RENDER/window sizing. | src: ORIG: C7 00 80020000 mov [eax],00000280 (640 screen width); C7 01 E0010000 mov [ecx],000001E0 (480 screen height). PATCH: C7 00 2A040000 mov [eax],0000042A (1066); C7 01 5802000 ### D2gfx/cel (1 symbols) - **struct** `D2gfx/cel` `struct.D2GFXCelStrc` — Cel graphic header: +00 bFlip, +04 nWidth, +08 nHeight, +0C nXOffset, +10 nYOffset, +14 dwNodeLength, +18 pCelNode (also offset to next cel in file), +1C dwLength, +20 pPixels[]. Core sprite/floor-tile blit struct. | struct D2GFXCelStrc //sizeof 0x20 + x { BOOL bFlip; //+00 int nWidth; //+04 ... uint8_t pPixels[]; //+20 }; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2gfx/renderer (1 symbols) - **struct** `D2gfx/renderer` `struct.D2RendererFunctionsStrc` — Full vtable of the swappable renderer backend (GDI/DirectDraw/Glide/D3D). D2 swaps this table on init depending on selected renderer. Includes fpBeginScene, fpEndScene, fpFlipBufferProbably, fpGetBackBuffer, fpGetScreenSize, fpUpdateScaleFactor, fpSetPerspectiveScale, fpAdjustPerspectivePosition, fpPerspectiveScalePosition, fpSetDefaultPerspectiveFactor, fpSetPalette, fpSetGlobalLight, fpDrawRect/RectEx, fpDrawSolidR | I am trying to figure out what are those undefined functions for each of renderer modes ... D2 swap table upon initialization depending on selected re corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### D2glide.dll (1 symbols) - **function** `D2glide.dll+0xDCD5` `ext.D2glide.D2glide_dll_render_width_height` — Glide mode entity render dimensions. Writes rendering width [D2glide.dll+0x15A68] and height [D2glide.dll+0x15B04]. Unchanged: entities rendered at incorrect location. RENDER. | src: ORIG: C7 05 685A866F 80020000 mov [D2glide.dll+0x15A68],00000280 (width 640); C7 05 045B866F E0010000 mov [D2glide.dll+0x15B04],000001E0 (height 480). PATCH: ...2A040000 (1066); .. ### Game (1.14) (3 symbols) - **function** `Game (1.14)` `corpus.D2GFX_DrawSolidRectEx` — void(nXStart,nYStart,nXEnd,nYEnd,dwColor,nDrawMode) - wrapper to every renderer. Guards x<=xEnd && y<=yEnd then calls [DAT_007c8cc0+0xB8](xEnd,yEnd,color,mode). This is the solid-rectangle fill primitive (relevant to dead-zone/panel-area fills). | void D2GFX_DrawSolidRectEx(int nXStart,int nYStart,int nXEnd,int nYEnd,DWORD dwColor,int nDrawMode) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **function** `Game (1.14)` `corpus.RENDERER_WINDOWED_DrawSolidRectEx` — Windowed-mode renderer solid-rect fill; __fastcall(x1,y1,x2,y2,byte color,int mode). The concrete windowed backend behind D2GFX_DrawSolidRectEx wrapper (0x4f6300). | //0x6c8a60 void __fastcall RENDERER_WINDOWED_DrawSolidRectEx (uint param_1,uint param_2,...) corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use - **global** `Game (1.14)` `corpus.resolution_mode_global_1_14d` — Resolution mode stored at 0x7c8cb8; the renderer object base DAT_007c8cc0 has draw funcs at +0xB8. Directly governs play-area sizing / resolution. | resolution mode in 1.14d is stored here 0x7c8cb8 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### OpenD2 (1 symbols) - **struct** `OpenD2` `struct.D2Renderer_function_table_OpenD2_reimpl` — Reverse-engineer's abstracted renderer interface mirroring D2's draw model: RF_Init/Shutdown/Present, RF_RegisterTexture, RF_SetTexturePixels(tex,pixels,nPalette), RF_DrawTexture(tex,x,y,w,h,u,v). Notes a TODO for palshifted-cel drawing. Useful mental model of the D2 blit pipeline. | struct D2Renderer{ void (*RF_Present)(); ... void (*RF_DrawTexture)(tex_handle texture, DWORD x, DWORD y, DWORD w, DWORD h, DWORD u, DWORD v); // TODO corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### d2client draw_screen_background (2 symbols) - **function** `d2client draw_screen_background` `ext.d2client draw_screen_background.DrawLeftScreenBackground` — Fills the LEFT half of the screen. First draws a solid black rectangle over the left half to block transparent-DC6 cheats, then tiles the config screen-background DC6 across it. This is the perimeter/letterbox fill on the left of the 640-wide play area. | src: ::d2::d2gfx::DrawRectangle(0, 0, resolution.width / 2, resolution.height, 0, ::d2::DrawEffect::kNone); - **function** `d2client draw_screen_background` `ext.d2client draw_screen_background.DrawRightScreenBackground` — Fills the RIGHT half of the screen. Draws a black rectangle from x=width/2 to x=width over full height, then tiles the background DC6 rightward. Left+Right together produce the full perimeter fill around the centered 640x480 UI region. | src: ::d2::d2gfx::DrawRectangle(resolution.width / 2, 0, resolution.width, resolution.height, 0, ::d2::DrawEffect::kNone); ### d2dx (28 symbols) - **function** `d2dx` `ext.d2dx.RenderContext_UpdateViewport` — Sets viewport + scissor from a Rect, then updates the Constants CB. Verbatim: CD3D11_VIEWPORT viewport{ (float)rect.offset.x, (float)rect.offset.y, (float)rect.size.width, (float)rect.size.height }; _deviceContext->RSSetViewports(1,&viewport); CD3D11_RECT scissorRect{ rect.offset.x, rect.offset.y, rect.size.width, rect.size.height }; _deviceContext->RSSetScissorRects(1,&scissorRect); then writes _constants.screenSize[0/1]=size.width/height, invScreenSize=1/size, flags[0]=NoAn | src: CD3D11_VIEWPORT viewport{ (float)rect.offset.x, (float)rect.offset.y, (float)rect.size.width, (float)rect.size.height }; - **function** `d2dx` `ext.d2dx.UpdateViewport_call_sites` — Called 3 places. SetSizes: UpdateViewport({0,0,_gameSize.width,_gameSize.height}) (offscreen game-size target, offset 0). Present: called with game-size dims AND with _renderRect (the centered/scaled final blit to screen). WriteToScreen: UpdateViewport({0,0,_gameSize.width,_gameSize.height}). Only the Present-with-_renderRect path carries a non-zero centering offset. - **function** `d2dx` `ext.d2dx.RenderContext_SetSizes` — Stores _gameSize/_windowSize. displaySize = (FullscreenDefault ? _desktopSize : _windowSize). _renderRect = Metrics::GetRenderRect(_gameSize, displaySize, !NoWide flag). Computes desktopCenterX=_desktopSize.width/2, desktopCenterY=(FullscreenDefault?_desktopSize.height/2:_desktopClientMaxHeight/2), reads GetWindowPosition() preferred position (used iff x>=0 && y>=0). Branches Windowed vs FullscreenDefault for placement. Then ClipCursor(); and if(_resources){ ResizeBackbuffer( | src: _renderRect = Metrics::GetRenderRect(_gameSize, displaySize, !_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoWide)); - **function** `d2dx` `ext.d2dx.RenderContext_Draw` — SetBlendState(batch.GetAlphaBlend()); picks atlas via GetTextureCache(batch); SetShaderState with Game vertex+pixel shaders, atlas SRV (batch.GetTextureAtlas()) and Palette Texture1D SRV; then _deviceContext->Draw(batch.GetVertexCount(), startVertexLocation + batch.GetStartVertex()). No positional transform here — geometry comes straight from the vertex buffer; the only offset is the vertex START index. | src: _deviceContext->Draw(batch.GetVertexCount(), startVertexLocation + batch.GetStartVertex()); - **struct** `d2dx` `struct.Constants_shader_constant_buffer` — Full layout (32 bytes, static_assert): float screenSize[2]={0,0}; float invScreenSize[2]={0,0}; uint32_t flags[4]={0,0,0,0};. NO translate/offset/textureOffset field exists. Therefore the GPU-side constants apply NO per-batch or per-geometry positional translate; any floor-vs-wall positional difference must originate in the CPU vertex data, not the CB. | src: float screenSize[2]; float invScreenSize[2]; uint32_t flags[4]; // static_assert size==32 - **struct** `d2dx` `struct.MetricsTableEntry_baked_gameSize_table` — struct with 3 Size fields: desktopSize, gameSize, gameSizeWide. 26 rows (desktop -> game / gameWide): 1280x720->960x720/1280x720; 1360x768->1024x768/1360x768; 1366x768->1024x768/1366x768; 1280x800->1066x800/1280x800; 1536x864->1152x864/1536x864; 1440x900->1200x900/1440x900; 1600x900->1200x900/1600x900; 1280x1024->640x480/640x512; 1400x1050->700x525/700x525; 1680x1050->700x525/840x525; 1920x1080->720x540/960x540; 2560x1080->720x540/960x540; 2048x1152->768x576/1024x576; 1600x12 - **function** `d2dx` `ext.d2dx.Metrics_GetSuggestedGameSize` — First tries MetricsTable exact-match on desktop size (narrow vs wide variant). Fallback: loop scaleFactor=1..8 { size.width=desktop.width/scaleFactor; size.height=desktop.height/scaleFactor; if (desktop.height/(scaleFactor+1) <= 600) break; }. Height cap: if size.height>720 { aspect=desktop.width/desktop.height; size.width=(int32_t)(aspect*720); size.height=720; }. Constants: 600 (break threshold), 720 (max game height), scale range 1..8. - **function** `d2dx` `ext.d2dx.Metrics_GetRenderRect` — Integer scale: scaleFactor=1; while(gameSize.width*(scaleFactor+1)<=desktop.width && gameSize.height*(scaleFactor+1)<=desktop.height) ++scaleFactor;. Centering (integer): rect.size={gameSize.width*scaleFactor, gameSize.height*scaleFactor}; rect.offset.x=(desktop.width-gameSize.width*scaleFactor)/2; rect.offset.y=(desktop.height-gameSize.height*scaleFactor)/2. Non-integer trigger: if(rect.offset.x<0 || rect.offset.y<0 || (rect.offset.x>=16 && rect.offset.y>=16)) apply fraction - **function** `d2dx` `ext.d2dx.SurfaceIdTracker_UpdateBatchSurfaceId_retag_ru` — Assigns/reuses a surfaceId per batch (tag stored on vertices via SetSurfaceId; used by AA/edge pixel shader, NOT a positional transform). Order: (1) if majorGameState!=InGame -> D2DX_SURFACE_ID_USER_INTERFACE. (2) if chroma-key disabled (text bg) -> UI. (3) gameplay-with-chroma UI reclassifications: draw-line single-pixel '(maxy - miny) == 1' -> UI; bottom band 'miny >= (gameSize.height - 48)' -> UI (48px); panels: '(_gameHelper->ScreenOpenMode() & 1)' left-panel open && 'min - **function** `d2dx` `ext.d2dx.OnBufferSwap_UnitMotionPrediction_per_vertex_t` — KEY per-batch positional translate. Gated by IsFeatureEnabled(Feature::UnitMotionPrediction) && _majorGameState==MajorGameState::InGame. offset = _unitMotionPredictor.GetOffset(_gameHelper->GetPlayerUnit()). Applied to EVERY batch EXCEPT surfaceId==D2DX_SURFACE_ID_USER_INTERFACE and TextureCategory::Player, via _vertices.items[vertexIndex++].AddOffset(-offset.x, -offset.y) (NEGATED). Floor AND wall batches are non-UI/non-player, so BOTH receive -offset; player+UI stay put. No | src: _vertices.items[vertexIndex++].AddOffset(-offset.x, -offset.y); - **function** `d2dx` `ext.d2dx.OnDrawLine_weather_particle_motion_offset` — Weather/rain particle motion prediction: computes a directional offset and applies startPos += offset; endPos += offset; to line endpoints before vertex creation (~640-644). Separate from the world-geometry offset; affects only line primitives (weather). - **function** `d2dx` `ext.d2dx.Mouse_position_scale_center_mapping` — pos.x=(int32_t)(pos.x*scale+mouseOffsetX); pos.y=(int32_t)(pos.y*scale); maps OS cursor into scaled/centered render area (mouseOffsetX centers horizontally in fullscreen). Confirms centering offset is horizontal-only for cursor; not applied to world geometry. - **function** `d2dx` `ext.d2dx.D2DXContext_PrepareBatchForSubmit` — Where a Batch's category is finalized. gameAddress = IdentifyGameAddress(returnAddress); category = RefineTextureCategoryFromGameAddress(existing, gameAddress). Sets StartVertex=_vertexCount, VertexCount. NO position offset applied for any category. | src: batch.SetTextureCategory(_gameHelper->RefineTextureCategoryFromGameAddress(batch.GetTextureCategory(), gameAddress)); - **function** `d2dx` `ext.d2dx.D2DXContext_OnBufferSwap_unit_MoP_offset_loop` — THE ONLY post-capture positional shift of world geometry. If UnitMotionPrediction enabled and InGame: offset=_unitMotionPredictor.GetOffset(playerUnit); then for every batch with surfaceId != D2DX_SURFACE_ID_USER_INTERFACE AND category != Player, each vertex gets AddOffset(-offset.x,-offset.y). Floor AND wall both qualify -> identical uniform shift. | src: if (surfaceId != D2DX_SURFACE_ID_USER_INTERFACE && batch.GetTextureCategory() != TextureCategory::Player){ ... _vertices.items[vertexIndex++].AddOffset(-offset.x, -offset.y); } - **function** `d2dx` `ext.d2dx.D2DXContext_DrawBatches_merge` — After BulkWriteVertices, merges consecutive batches when same texture-cache/atlas/alphaBlend and combined vertexCount<=65535, else flushes via _renderContext->Draw(mergedBatch,startVertexLocation). Category is NOT part of the merge key and NOT used to offset. | src: _renderContext->GetTextureCache(batch) != _renderContext->GetTextureCache(mergedBatch) || batch.GetTextureAtlas() != mergedBatch.GetTextureAtlas() || batch.GetAlphaBlend() != merge - **function** `d2dx` `ext.d2dx.RenderContext_UpdateViewport_single_uniform_vi` — Sets ONE viewport and ONE scissor rect, both == the passed Rect (offset+size). Also writes Constants.screenSize/invScreenSize used by the vertex shader to map int16 screen coords to NDC. No per-batch or per-category viewport/scissor; set per render-pass only. | src: CD3D11_VIEWPORT viewport{ (float)rect.offset.x, (float)rect.offset.y, (float)rect.size.width, (float)rect.size.height }; ... CD3D11_RECT scissorRect{ rect.offset.x, rect.offset.y, - **function** `d2dx` `ext.d2dx.RenderContext_Present_viewport_sequence` — Game batches render into the Game framebuffer under UpdateViewport({0,0,_gameSize.width,_gameSize.height}) (set at end of prior Present, line 491). Gamma/AA passes also use {0,0,gameSize}. Final display blit to backbuffer uses UpdateViewport(_renderRect) (centered, letterboxed dest). gameSize=internal render res; renderRect=on-screen placement. | src: UpdateViewport({ 0,0,_gameSize.width, _gameSize.height }); ... UpdateViewport(_renderRect); - **function** `d2dx` `ext.d2dx.RenderContext_Draw_per_batch_no_transform` — Per merged batch: SetBlendState, pick atlas SRV, SetShaderState(Game VS/PS), then Draw(vertexCount, startVertexLocation + batch.GetStartVertex()). No matrix/offset/scissor per batch or category. | src: _deviceContext->Draw(batch.GetVertexCount(), startVertexLocation + batch.GetStartVertex()); - **function** `d2dx` `ext.d2dx.UnitMotionPredictor_UnitMotion_GetOffset_iso_p` — Converts predicted vs last world pos (16.16 fixed) into a SCREEN-space isometric pixel offset. scaleFactors={32/sqrt2,16/sqrt2}; screenOffset=scaleFactors*{dx-dy,dx+dy}+0.5. Magnitude of the uniform world shift; a few px, never hundreds. | src: const OffsetF scaleFactors{ 32.0f / sqrtf(2.0f), 16.0f / sqrtf(2.0f) }; const OffsetF screenOffset = scaleFactors * OffsetF{ offset.x - offset.y, offset.x + offset.y } + 0.5f; - **function** `d2dx` `ext.d2dx.GameHelper_RefineTextureCategoryFromGameAddres` — Maps captured return address's GameAddress to a category ONLY when previous category is Unknown: DrawFloor->Floor, DrawWall1/DrawWall2->Wall, else unchanged. Category never triggers coordinate change. | src: case GameAddress::DrawFloor: return TextureCategory::Floor; case GameAddress::DrawWall1: case GameAddress::DrawWall2: return TextureCategory::Wall; - **global** `d2dx` `ext.d2dx.gameAddresses_tables_DrawFloor_DrawWall_return` — Per-version glide3x.dll return-address constants identifying floor/wall draws. Index order: [1]=DrawWall1,[2]=DrawWall2,[3]=DrawFloor. | src: 113c: 0x6f8567ab DrawWall1, 0x6f8567b9 DrawWall2, 0x6f85befc DrawFloor | 114d: 0x50d39f DrawWall1, 0x50d3ae DrawWall2, 0x50db03 DrawFloor | 109d: 0x6f818468/0x6f818476/0x6f813e2f - **struct** `d2dx` `struct.Batch_16_bytes` — Packed 16-byte batch key. uint32 _textureHash @0; uint16 _startVertexLow @4; uint16 _vertexCount @6; uint16 _textureStartAddress @8 (byte addr/256 stored as (addr>>8)+1); uint16 _startVertexHigh_textureIndex @10 (VVVV + 12-bit texIndex); uint8 _textureHeight_textureWidth_alphaBlend @12 (HHHWWWBB); uint8 _isChromaKeyEnabled_gameAddress_paletteIndex @13 (CGGGPPPP); uint8 _textureCategory_primitiveType_combiners @14 (category in bits 5-7); uint8 _textureAtlas @15 (bits 0-2). No | src: uint8_t _textureCategory_primitiveType_combiners; // TTT.PPCC (GetTextureCategory = value>>5) - **struct** `d2dx` `struct.Vertex_16_bytes_int16_screen_coords` — int16 _x,_y,_s,_t; uint32 _color; uint16 _paletteIndex_atlasIndex (palette<<12 | atlas&4095); uint16 _isChromaKeyEnabled_surfaceId (bit14 chroma | surfaceId&16383). Position is int16 screen-space; ctor asserts x,y in [INT16_MIN,INT16_MAX]. AddOffset(x,y) does _x+=x,_y+=y — mechanism the MoP loop uses. | src: int16_t _x; int16_t _y; int16_t _s; int16_t _t; ... inline void AddOffset(int32_t x,int32_t y){ _x += x; _y += y; } - **struct** `d2dx` `struct.TextureCategory_enum` — Unknown=0, MousePointer=1, Player=2, LoadingScreen=3, Floor=4, TitleScreen=5, Wall=6, UserInterface=7, Count=8. Floor and Wall distinct values but geometry-identical. | src: Floor = 4, TitleScreen = 5, Wall = 6, UserInterface = 7 - **struct** `d2dx` `struct.GameAddress_enum` — Unknown=0, DrawWall1=1, DrawWall2=2, DrawFloor=3, DrawShadow=4, DrawDynamic=5, DrawSomething1=6, DrawLine=7, Count=8. Index into gameAddresses_* tables. | src: DrawWall1 = 1, DrawWall2 = 2, DrawFloor = 3, DrawShadow = 4, DrawDynamic = 5 - **global** `d2dx` `ext.d2dx.D2DX_SURFACE_ID_USER_INTERFACE` — =16383. UI surfaceId; MoP loop skips batches whose start vertex has this surfaceId (UI never shifts, world geometry incl. floor/wall does). - **function** `d2dx` `ext.d2dx.OnSstWinOpen_gameSize_windowSize_logic` — Computes gameSize from width/height (or _customGameSize override); if NoResMod off, windowSize forced 800x600; if gameSize != 640x480 then windowSize=gameSize. Creates/resizes RenderContext with (gameSize, windowSize*windowScale). Establishes internal render res (gameSize) that becomes the {0,0,gameSize} viewport. | src: Size gameSize{ width, height }; if (_customGameSize.width > 0){ gameSize = _customGameSize; } if (gameSize.width != 640 || gameSize.height != 480){ windowSize = gameSize; } - **function** `d2dx` `ext.d2dx.Metrics_GetRenderRect_letterbox_placement` — Computes on-screen _renderRect: integer-scales gameSize into desktop/window, centers it (offset=(dst-game*scale)/2); if margins exceed 16px falls back to non-integer scale filling width or height. This centered offset shifts only the final display blit, NOT world batches. | src: Rect rect{ (desktopSize.width - gameSize.width * scaleFactor) / 2, (desktopSize.height - gameSize.height * scaleFactor) / 2, gameSize.width * scaleFactor, gameSize.height * scaleFa ### d2gdi set_cel_display_left_and_right/patch_1_13c.cpp (1 symbols) - **function** `d2gdi set_cel_display_left_and_right/patch_1_13c.cpp+0x7049` `ext.d2gdi set_cel_display_left_and_right/patch_1_13ccpp.SetCelDisplayLeftAndRight_JMP_hook_1_13c` — Jump-branch patch in D2GDI (not D2Client) for the cel display left/right. 1.11 0x6529, 1.11B 0x7239, 1.12A 0x6599, 1.13ABeta 0x65F9, 1.13C 0x7049, 1.13D 0x7E99 (size 0x2E each). Library = kD2GDI. ### d2interface (6 symbols) - **struct** `d2interface` `struct.DyPos` — Dynamic (moving) entity position. #[repr(C)]. Fields in order: linear_pos: LinearM2d; iso_pos: IsoP2d; target_pos: [LinearM2d; 3]; room: Option>; _padding1: [u32;4]; entity: NonNull. This is the struct apply_pos_change mutates (ptr in ESI). | src: #[repr(C)] pub struct DyPos { pub linear_pos: LinearM2d, pub iso_pos: IsoP2d, pub target_pos: [LinearM2d; 3], pub room: Option>, pub _padding1: [u32; - **struct** `d2interface` `struct.StaticPos` — Static (non-moving) entity position. #[repr(C)]. Fields: room: Option>; iso_pos: IsoP2d; linear_pos: LinearM2d; _padding1: [u32;3]. | src: #[repr(C)] pub struct StaticPos { pub room: Option>, pub iso_pos: IsoP2d, pub linear_pos: LinearM2d, pub _padding1: [u32; 3], } - **struct** `d2interface` `struct.EntityPos` — #[repr(C)] union: s: Option> | d: Option>. Which arm is valid depends on Entity.kind. | src: #[repr(C)] pub union EntityPos { pub s: Option>, pub d: Option>, } - **struct** `d2interface` `struct.Entity` — #[repr(C)]. Render-relevant fields: kind: EntityKind; class_id:u32; mem_pool; id:u32; state:u32; data; act_id:u32; dungeon; rng:Rng; seed:u32; pos: EntityPos; anim_frame_def; seq_frame_count:FU8; seq_frame:FU8; seq_anim_rate:FU8; anim_state:u32; frame:FU8; frame_count:FU8; anim_rate:u16; frame_event:u8; anim_data; gfx_info; _padding0:[u32;1]; stats; inv; light; light_width:u32; _padding1:[u32;30]; next_entity: Option>; next_in_room: Option>; _p | src: pub kind: EntityKind, pub class_id: u32, ... pub pos: EntityPos, pub anim_frame_def: *mut (), pub seq_frame_count: FU8, pub seq_frame: FU8, pub seq_anim_rate: FU8, pub anim_state: - **struct** `d2interface` `struct.Room` — #[repr(C)]. connected: Option>>; _padding1:[u32;3]; data:u32; _padding2:[u32;4]; connected_count:u32; _padding3:[u32;9]; pos: LinearM2d; size: M2d; _padding4:[u32;9]. pos/size are the room world placement used to resolve iso/linear coords. | src: #[repr(C)] pub struct Room { pub connected: Option>>, pub _padding1: [u32; 3], pub data: u32, pub _padding2: [u32; 4], pub connected_count: u32, pub _padding3 - **struct** `d2interface` `struct.BaseAddresses` — pub struct BaseAddresses { pub client: usize, pub common: usize, pub fog: usize, pub game: usize, pub gfx: usize, pub win: usize } | src: pub struct BaseAddresses { pub client: usize, pub common: usize, pub fog: usize, pub game: usize, pub gfx: usize, pub win: usize, } ### ddraw (1 symbols) - **global** `ddraw` `corpus.DDRAW_surface_dims_800x600_0x320_0x258` — The buffer descriptor built in ddraw (505BEA43+): stores 0x320 (=800) and 0x258 (=600) as surface width/height, sourced from the DDSURFACEDESC at [ESI+1C]/[ESI+1E]. Confirms the front-buffer geometry the 47px clear operates on. | MOV DWORD PTR DS:[EDX+C],EAX ; 0x320 ... MOV DWORD PTR DS:[EDX+8],EAX ; 0x258 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### draw_screen_background (1 symbols) - **global** `draw_screen_background` `ext.draw_screen_background.kOriginalScreenBorderFrameImagePath` — Original 800x600 border-frame DC6 path used when IsUseOriginalScreenBorderFrame() is true. | src: constexpr std::string_view kOriginalScreenBorderFrameImagePath = "data\\global\\UI\\Panel\\800BorderFrame"; ### draw_screen_background entry (1 symbols) - **function** `draw_screen_background entry` `ext.draw_screen_background entry.Sgd2fr_D2Client_DrawScreenBackground` — Public interception entry (extern "C" __cdecl). Reads d2client::GetScreenOpenMode() and dispatches: kRight -> DrawRight{Background,BorderFrame,BackgroundRibbon}; kLeft -> DrawLeft*; kBoth -> both; kNone -> nothing. Then calls original d2::d2client::DrawScreens(). Declared in replacement.hpp. | src: extern "C" void __cdecl Sgd2fr_D2Client_DrawScreenBackground(); ### file/COF (1 symbols) - **struct** `file/COF` `struct.COF_Header_layout` — COF file header: layers,frames,directions,version(=20),unknown1(bitfield e.g. loopAnim),bbox xMin/xMax/yMin/yMax(int32),animRate(int16,256),zeros(int16). Governs animation component layers & bounding box. | uint8_t layers; ... int32_t xMin; int32_t xMax; int32_t yMin; int32_t yMax; int16_t animRate; corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### file/DCC (1 symbols) - **struct** `file/DCC` `struct.DCC_Header_layout` — DCC file header: signature(0x74),version(6),directions(max32),framesPerDir,padding0[3],tag(=1),finalDc6Size. Sprite-encoding header. | uint8_t signature; ///< Magic number for DCC files, must be 0x74 corpus-sourced; addresses may be 1.10f-era -- rebase to 1.13c + verify before use ### glide3x (4 symbols) - **function** `0x10007850 glide3x+0x7850 ord 1` `glide3x.D2DXGetConfigurator` — exported _D2DXGetConfigurator@0 (NOT @4 -- the header D2DXConfigurator.h:98 string is a bug; use @0 or ordinal 1). Returns ID2DXConfigurator*; vtable [0]QI [1]AddRef [2]Release [3]SetCustomResolution(w,h) __stdcall. SetCustomResolution sets _customGameSize (a flag consumed at the next grSstWinOpen). - **function** `0x1000D970 glide3x+0xD970 ord 66` `glide3x.grSstWinOpen` — D2DX grSstWinOpen (export _grSstWinOpen@28). OnSstWinOpen consumes _customGameSize as gameSize (D2DXContext.cpp:176-180) then creates/sizes the RenderContext. THE fix hook site: detour entry, call SetCustomResolution(1280,720) before delegating so the world target opens WIDE. Prologue 55 8B EC 6A FF (push ebp;mov ebp,esp;push -1). - **hook** `0x1000D970 glide3x+0xD970` `hook.D2DXWiden` — v1.11.0 THE FIX: detour grSstWinOpen; before each open call ID2DXConfigurator::SetCustomResolution(1280,720) so D2DX opens its WIDE game target (1280 vs the narrow 960 that clipped the world). Installed by a thread that waits for glide3x (late video driver). glide3x relocates -> resolve by export/ordinal. Keeps the native left-shift (it's in the submitted vertices). Awaiting Doug's test. expect `55 8B EC 6A FF` - **function** `glide3x` `glide3x.grDrawVertexArrayContiguous` — D2DX Glide-vertex submit = the D2Glide->D2DX crossing. PROBE v1.10.0 (filtered to the tile drawer by return-addr): floor geometry crosses at max screenX 1296, ~138 verts/frame in dead-zone [960,1280). The world geometry REACHES D2DX past 960 yet renders BLACK -> the panel-world clip is IN D2DX (m2 was right). ### glide3x.cpp (5 symbols) - **function** `glide3x.cpp` `ext.glide3xcpp.grDrawVertexArray_capture_seam` — Primary draw capture. Grabs _ReturnAddress() and forwards to the context factory singleton. This return address is later used to classify the draw as floor/wall/etc. | src: const auto returnAddress = (uintptr_t)_ReturnAddress(); D2DXContextFactory::GetInstance()->OnDrawVertexArray(mode, Count, (uint8_t**)pointers, returnAddress); - **function** `glide3x.cpp` `ext.glide3xcpp.grDrawVertexArrayContiguous_capture_seam` — Second capture path (used for 4-vertex TRIANGLE_FAN quads). Same _ReturnAddress() -> OnDrawVertexArrayContiguous(mode,Count,vertex,stride,returnAddress). | src: D2DXContextFactory::GetInstance()->OnDrawVertexArrayContiguous(mode, Count, (uint8_t*)vertex, stride, returnAddress); - **function** `glide3x.cpp` `ext.glide3xcpp.grViewport_is_a_no_op_asserts_origin_0_0` — Glide grViewport does nothing except assert x==0 && y==0. The game never sets a shifted Glide viewport; all viewport control is D2DX-internal. | src: grViewport(FxI32 x, FxI32 y, FxI32 width, FxI32 height){ assert(x == 0 && y == 0); } - **function** `glide3x.cpp` `ext.glide3xcpp.grClipWindow_unsupported_grCoordinateSpace_emp` — grClipWindow => assert(false, Unsupported). grCoordinateSpace is an empty stub. No Glide-level scissor/clip or coordinate-space transform enters the pipeline. | src: grClipWindow(...){ assert(false && "grClipWindow: Unsupported"); } grCoordinateSpace(GrCoordinateSpaceMode_t mode){ } - **function** `glide3x.cpp` `ext.glide3xcpp.grSstWinOpen_OnSstWinOpen` — Resolves 640x480/800x600/1024x768 then calls OnSstWinOpen(hWnd,width,height). Asserts color_format==GR_COLORFORMAT_RGBA. | src: D2DXContextFactory::GetInstance()->OnSstWinOpen(hWnd, width, height); return 1; ### glide3x.dll (1 symbols) - **function** `glide3x.dll+0xC0D5` `ext.glide3x.glide3x_dll_window_width_height` — Sets D2 (Glide) window size. Loads ptr [glide3x.dll+0x1C9A0] (width addr) into edx then writes 640; loads ptr [glide3x.dll+0x1C82C] (height addr) into ecx then writes 480. NOTE: Cheat-Engine relative addresses differ from hex-editor addresses (offset by 0xA00). In the printed PATCH the loader/immediate opcodes C7 02 80020000 / C7 01 E0010000 are shown unchanged but the target values in comments become 1066/600 — the immediates to actually write are 0000042A and 00000258. | src: 8B 15 A0C94100 mov edx,[glide3x.dll+0x1C9A0] (Glide window width addr); C7 02 80020000 mov [edx],00000280 (640->1066 0000042A); 8B 0D 2CC84100 mov ecx,[glide3x.dll+0x1C82C] (height ### patch_1_09d.cpp GetPatchAddressAndSize01 (1 symbols) - **function** `patch_1_09d.cpp GetPatchAddressAndSize01+0xC3AA6` `ext.patch_1_09dcpp GetPatchAddressAndSize01.DrawScreenBackground_CALL_hook_1_13c` — 1.13c call-branch patch that redirects into Sgd2fr_D2Client_DrawScreenBackground via the asm shim InterceptionFunc01. Found by tracing the 'ACT%d' string usage to the function return. Other versions: 1.09D 0x8696B, 1.10 0x81F08, 1.12A 0x90216, 1.13D 0x1D4A6, LoD1.14C 0x5264D, LoD1.14D 0x56FAF (all size 5). ### patch_1_09d.cpp GetPatchAddressAndSize02 (1 symbols) - **function** `patch_1_09d.cpp GetPatchAddressAndSize02+0x271ED` `ext.patch_1_09dcpp GetPatchAddressAndSize02.Disable_stock_LEFT_screen_bg_1_13c` — NOP patch removing the game's original left-screen 800BorderFrame draw. Located via the 'Panel\800BorderFrame' string (two usages; this is the left one). 1.09D 0x58F1B(sz0x96), 1.10 0x5F4EB(0x9B), 1.12A 0x8218D(0x9A), 1.13D 0x6D2DD(0x9A), LoD1.14C 0x94B0C(0xAA), LoD1.14D 0x9865E(0x9E). ### patch_1_09d.cpp GetPatchAddressAndSize03 (1 symbols) - **function** `patch_1_09d.cpp GetPatchAddressAndSize03+0x270F1` `ext.patch_1_09dcpp GetPatchAddressAndSize03.Disable_stock_RIGHT_screen_bg_1_13c` — NOP patch removing the game's original right-screen 800BorderFrame draw. 1.09D 0x58FF0(0xAC), 1.10 0x5F5C0(0xAC), 1.12A 0x82094(0xB9), 1.13D 0x6D1E1(0xBD), LoD1.14C 0x94BF1(0xB3), LoD1.14D 0x98733(0xA7). ### position_realignment (2 symbols) - **function** `position_realignment` `ext.position_realignment.RealignPositionFromCenter` — Re-anchors a PositionalRectangle from the source (inventory-arrange) resolution center to the current resolution center, preserving width/height. dist = out.Left - source.width/2; out.Left = current.width/2 + dist; out.Right = out.Left + rectangle_width. Same for top/bottom about height/2. Uses GetIngameResolutionFromId(GetResolutionMode()) and GetSourceInventoryArrangeResolution(). | src: out_position_wrapper.SetLeft((current_resolution.width / 2) + dist_from_rect_left_to_display_center); - **function** `position_realignment` `ext.position_realignment.RealignPositionFromBottomCenter` — Like RealignPositionFromCenter for X, but anchors Y to the display BOTTOM: dist_from_rect_top_to_display_bottom = source.height - out.Top; out.Top = current.height - dist; out.Bottom = out.Top + rectangle_height. Used for bottom-docked UI (belt/skill bar). ### set_cel_display_left_and_right (1 symbols) - **function** `set_cel_display_left_and_right` `ext.set_cel_display_left_and_right.Sgd2fr_D2GDI_SetCelDisplayLeftAndRight` — Sets the D2GDI cel horizontal draw window to the full width: SetCelDisplayLeft(0), SetCelDisplayRight(resolution.width). Resolution from GetIngameResolutionFromId(resolution_mode). This is the render-window widening that removes the stock 640-wide clip so the perimeter can be drawn. | src: d2::d2gdi::SetCelDisplayLeft(0); d2::d2gdi::SetCelDisplayRight(resolution.width); ### set_general_display_width_and_height (1 symbols) - **function** `set_general_display_width_and_height` `ext.set_general_display_width_and_height.Sgd2fr_D2Client_SetGeneralDisplayWidthAndHeigh` — Feeds the resolution into the GeneralDisplayWidth/Height globals that everything above reads: SetGeneralDisplayWidth(resolution.width), SetGeneralDisplayHeight(resolution.height). Also cycles SetInventoryArrangeMode(inventory_arrange_mode++) as a workaround to stop inventory arrangement from transferring (overflow intentional). | src: d2::d2client::SetGeneralDisplayWidth(resolution.width); d2::d2client::SetGeneralDisplayHeight(resolution.height); ### set_screen_shift (1 symbols) - **function** `set_screen_shift` `ext.set_screen_shift.Sgd2fr_D2Client_SetScreenShift` — THE origin of the -320 render shift. Recomputes D2's screen-shift so the 640x480 play area is centered. screen_shift_x = (general_display_width/2) - (640/2); screen_shift_y = -((general_display_height/2) - (480/2)). Note the 640/2 = 320 term and the NEGATION on the Y term. For a 1280x720 display: shift_x = 640-320 = 320, shift_y = -(360-240) = -120. The '-320'-family value is the (640/2) offset that recenters the viewport origin. Uses d2client::GetGeneralDisplayWidth/Height a | src: int screen_shift_x = (general_display_width / 2) - (640 / 2); int screen_shift_y = -((general_display_height / 2) - (480 / 2)); ::d2::d2client::SetScreenShiftX(screen_shift_x); ### set_screen_shift/patch_1_09d.cpp (1 symbols) - **function** `set_screen_shift/patch_1_09d.cpp+0xC39F6` `ext.set_screen_shift/patch_1_09dcpp.SetScreenShift_CALL_hook_1_13c` — Call-branch patch feeding Sgd2fr_D2Client_SetScreenShift. Found via write-breakpoint on ScreenShiftX/Y variable. 1.09D 0x865BF, 1.10 0x81B8F, 1.12A 0x90166, 1.13D 0x1D3F6, LoD1.14C 0x5259B, LoD1.14D 0x56EFD (all size 0x27 = 0x...1D-0x...F6). ### set_tile_culling_bound (2 symbols) - **struct** `set_tile_culling_bound` `struct.CullingSpec` — Struct the game passes to the tile-culling function. Layout verified by static_assert: flags at 0x0 (uint32_t), draw_window_rect (RECT) at 0x4, tile_culling_window (RECT) at 0x14 (20). RECT is 16 bytes (left/top/right/bottom int32). | src: struct CullingSpec { uint32_t flags; RECT draw_window_rect; RECT tile_culling_window; }; static_assert(offsetof(CullingSpec, flags)==0); static_assert(offsetof(CullingSpec, draw_wi - **function** `set_tile_culling_bound` `ext.set_tile_culling_bound.Sgd2fr_D2Client_SetTileCullingBound` — Sets the map/tile draw and culling windows. draw_window_rect = SetRect(left,top,right,bottom) from args. tile_culling_window base = SetRect(-80, -80, width+80, height+40) using GetGeneralDisplayWidth/Height. If perspective mode enabled, expands top/left/right/bottom by ratios (866px up = -204.375 unit space; ratio = 1732/(1732-height/2) - 1, capped at 1.0; bottom uses ratio = 1 - 1500/(1500+height/2)). Finally sets culling_spec->flags |= 0x1. The -80/-80/+80/+40 margins are t | src: SetRect(tile_culling_rect, -80, -80, width + 80, height + 40); ... culling_spec->flags |= 0x1; ### set_tile_culling_bound/patch_1_11.cpp (1 symbols) - **function** `set_tile_culling_bound/patch_1_11.cpp+0x8A4D0` `ext.set_tile_culling_bound/patch_1_11cpp.SetTileCullingBound_JMP_hook_1_13c` — Jump-branch patch to the tile-culling replacement (via patch_1_11_shim). Found via 'Perspective' Video-Options string -> first D2Client-space call. 1.11 0x5B560, 1.11B 0x927A0, 1.12A 0xBA990, 1.13ABeta 0x3F490, 1.13D 0xB5330, LoD1.14A 0xB5330 (all size 5). Separate patch_1_07.cpp covers 1.07-1.10 & 1.14A-D with different offsets (e.g. 1.09D 0x16000, LoD1.14C 0x71D30, LoD1.14D 0x76000).