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

Metrics.cpp GetRenderRect(gameSize, desktopSize, wide)

16px margin threshold; integer-then-fractional centering


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

48px bottom band; 32x32 tile adjacency; ScreenOpenMode &1/&2


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);

D2DXContext.cpp OnBufferSwap ~lines 380-410


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).


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.


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.


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);


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);


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); }


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){ }

glide3x.cpp grClipWindow / grCoordinateSpace


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;


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));


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); }


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


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,


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);


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());


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;


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;


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


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)


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; }


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


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


=16383. UI surfaceId; MoP loop skips batches whose start vertex has this surfaceId (UI never shifts, world geometry incl. floor/wall does).


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; }


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


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.


d2bs community (ds-hwang/noah- patch-113)

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)


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)


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)


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)


Returns encoded screen resolution (packed WxH). Render viewport sizing. | src: FUNCPTR(D2GFX, GetScreenSize, DWORD __stdcall, (void), -10031)


Returns the game render window HWND. | src: FUNCPTR(D2GFX, GetHwnd, HWND __stdcall, (void), -10048)


Draws sprite batch (fastcall). Under 'D2Win Functions' header. | src: FUNCPTR(D2WIN, DrawSprites, void __fastcall, (void), 0x18750)


Captures the current frame to a screenshot. | src: FUNCPTR(D2WIN, TakeScreenshot, void __fastcall, (), 0x17EB0)


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)


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)


Selects the active font/text size for subsequent DrawText calls. | src: FUNCPTR(D2WIN, SetTextSize, DWORD __fastcall, (DWORD dwSize), -10184)


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)


Frees a SplitText layout result. | src: FUNCPTR(D2WIN, FreeSplitText, void __fastcall, (SplitText* text), -10008)


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)


Head of the UI Control linked list. UI, borderline render. | src: VARPTR(D2WIN, FirstControl, Control*, 0x214A0)


Currently focused UI Control. | src: VARPTR(D2WIN, FocusedControl, Control*, 0x214B0)


Returns a unit's world X coordinate. Feeds world->screen projection. | src: FUNCPTR(D2CLIENT, GetUnitX, int __fastcall, (UnitAny* pUnit), 0x1630)


Returns a unit's world Y coordinate. | src: FUNCPTR(D2CLIENT, GetUnitY, int __fastcall, (UnitAny* pUnit), 0x1660)


Clears the screen/framebuffer. | src: FUNCPTR(D2CLIENT, ClearScreen, void __fastcall, (void), 0x48350)


Returns automap size metric used for automap draw scaling. | src: FUNCPTR(D2CLIENT, GetAutomapSize, DWORD __stdcall, (void), 0x5F080)


Allocates a new AutomapCell node. | src: FUNCPTR(D2CLIENT, NewAutomapCell, AutomapCell * __fastcall, (void), 0x5F6B0)


Links an AutomapCell into an automap layer node list. | src: FUNCPTR(D2CLIENT, AddAutomapCell, void __fastcall, (AutomapCell *aCell, AutomapCell **node), 0x61320)


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)


Initializes/returns the AutomapLayer for a given layer number. | src: FUNCPTR(D2CLIENT, InitAutomapLayer_I, AutomapLayer* __fastcall, (DWORD nLayerNo), 0x62710)


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)


Returns the mouse X offset used in screen<->world coordinate math. | src: FUNCPTR(D2CLIENT, GetMouseXOffset, DWORD __fastcall, (void), 0x3F6C0)


Returns the mouse Y offset used in screen<->world coordinate math. | src: FUNCPTR(D2CLIENT, GetMouseYOffset, DWORD __fastcall, (void), 0x3F6D0)


Prints a message to the on-screen game message area. | src: FUNCPTR(D2CLIENT, PrintGameString, void __stdcall, (wchar_t *wMessage, int nColor), 0x7D850)


Prints a party message to screen. | src: FUNCPTR(D2CLIENT, PrintPartyString, void __stdcall, (wchar_t *wMessage, int nColor), 0x7D610)


Draws a rectangular frame on screen. | src: FUNCPTR(D2CLIENT, DrawRectFrame, void __fastcall, (DWORD Rect), 0xBE4C0)


Current viewport height in pixels. | src: VARPTR(D2CLIENT, ScreenSizeY, DWORD, 0xDBC4C)


Screen X of the hovered cell/cursor target. | src: VARPTR(D2CLIENT, CursorHoverX, DWORD, 0xE0EB8)


Screen Y of the hovered cell/cursor target. | src: VARPTR(D2CLIENT, CursorHoverY, DWORD, 0xE0EBC)


Current mouse cursor X in screen pixels. | src: VARPTR(D2CLIENT, MouseX, DWORD, 0x11B828)


Current mouse cursor Y in screen pixels. | src: VARPTR(D2CLIENT, MouseY, DWORD, 0x11B824)


Viewport/gamewindow X extent (int). Used with ViewportY for world->screen shift. | src: VARPTR(D2CLIENT, ViewportX, int, 0x119960)


Viewport/gamewindow Y extent (int). | src: VARPTR(D2CLIENT, ViewportY, int, 0x11995C)


Nonzero when the automap is displayed. | src: VARPTR(D2CLIENT, AutomapOn, DWORD, 0xFADA8)


Automap display mode (full/mini/etc). | src: VARPTR(D2CLIENT, AutomapMode, int, 0xF16B0)


POINT screen offset applied when drawing (render/scroll origin). Coordinate-shift global. | src: VARPTR(D2CLIENT, Offset, POINT, 0x11C1F8)


Pointer to the active AutomapLayer being drawn. | src: VARPTR(D2CLIENT, AutomapLayer, AutomapLayer*, 0x11C1C4)


ASM patch anchor near the cursor/hover click-target table (adjacent to CursorHoverX/Y). | src: ASMPTR(D2CLIENT, BodyClickTable, 0xE0EC4)


ASM patch anchor for click-to-move handling (adjacent to AutomapOn 0xFADA8). | src: ASMPTR(D2CLIENT, ClickMap_I, 0xFADA4)


ASM patch anchor for click-map coordinate handling. | src: ASMPTR(D2CLIENT, ClickMap_II, 0x11C4D8)


ASM patch anchor for click-map (near GetMouseX/YOffset 0x3F6C0/0x3F6D0). | src: ASMPTR(D2CLIENT, ClickMap_III, 0x3F5F0)


ASM patch anchor inside ClickMap (0x1BF20 + 0x79). | src: ASMPTR(D2CLIENT, ClickMap_IV, 0x1BF99)


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)


Screen height global (DWORD). | src: VARPTR(D2CLIENT, ScreenSizeY, DWORD, 0xDBC4C, 0xF7038)


Cursor hover X coordinate (DWORD). | src: VARPTR(D2CLIENT, CursorHoverX, DWORD, 0xE0EB8, 0xEE4AC)


Cursor hover Y coordinate (DWORD). | src: VARPTR(D2CLIENT, CursorHoverY, DWORD, 0xE0EBC, 0xEE4B0)


Mouse X screen position (DWORD). | src: VARPTR(D2CLIENT, MouseX, DWORD, 0x11B828, 0x11C950)


Mouse Y screen position (DWORD). | src: VARPTR(D2CLIENT, MouseY, DWORD, 0x11B824, 0x11C94C)


Mouse offset X (int) — screen-to-world/viewport offset. | src: VARPTR(D2CLIENT, MouseOffsetX, int, 0x119960, 0x106844)


Mouse offset Y (int) — screen-to-world/viewport offset. | src: VARPTR(D2CLIENT, MouseOffsetY, int, 0x11995C, 0x106840)


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)


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)


Screen-shake X offset (int) applied to render position. | src: VARPTR(D2CLIENT, xShake, int, 0x11BF00, 0x11CA6C)


Screen-shake Y offset (int) applied to render position. | src: VARPTR(D2CLIENT, yShake, int, 0x10B9DC, 0xFC3DC)


Automap-visible flag (DWORD). | src: VARPTR(D2CLIENT, AutomapOn, DWORD, 0xFADA8, 0x11C8B8)


Automap display mode (int). | src: VARPTR(D2CLIENT, AutomapMode, int, 0xF16B0, 0xF34F8)


Pointer to current AutomapLayer* — root of the automap cell list that gets drawn. | src: VARPTR(D2CLIENT, AutomapLayer, AutomapLayer*, 0x11C1C4, 0x11CF28)


Current video/render mode (WORD) — DDraw/D3D/Glide selector. | src: VARPTR(D2GFX, VideoMode, WORD, 0x11258, 0x14A38)


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)


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)


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)


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)


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)


Returns the current screen size. __stdcall (void). Render/viewport query. | src: FUNCPTR(D2GFX, GetScreenSize, DWORD __stdcall, (void), -10031, -10012)


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)


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)


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)


Text-buffer draw routine. ASMPTR with DIRECT OFFSETS (positive hex), not ordinals. | src: ASMPTR(D2WIN, DrawTextBuffer, 0x12940, 0x134D0)


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)


Sets the active font/text size. Args (DWORD dwSize), __fastcall. | src: FUNCPTR(D2WIN, SetTextSize, DWORD __fastcall, (DWORD dwSize), -10184, -10047)


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)


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;


Small automap layer variant: _1[2] 0x00; nLayerNo at 0x08. | src: DWORD _1[2]; //0x00 | DWORD nLayerNo; //0x08


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


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


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


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


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


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)

pack(1) wraps BnetData..Room2; second pack(1) wraps NPCMenu,OverheadMsg


ext.D2Client (uses D2Common exports).UNITS_GetDrawingPosition_non_perspective_branc

kidpaddle94 / Necrolis (Phrozen Keep t=62562)

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;


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;

else branch when D2GFX_CheckPerspective() is true


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;

BOOL __fastcall(D2UnitStrc* pUnit, D2CoordStrc* pPos)


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()


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);


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:


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)

6FBC9960 (D2Client, absolute VA, dereferenced as *D2CLIENT_6FBC9960)


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)

6FBCC418 (D2Client, absolute VA, dereferenced as *D2CLIENT_6FBCC418)


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;

6FBC995C (D2Client, absolute VA, dereferenced as *D2CLIENT_6FBC995C)


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())

D2Gfx export (name only; no ordinal/offset given in thread)


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;

D2Gfx export (name only; no ordinal/offset given)


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);


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)

D2Common exports (names only; no ordinals given)


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);


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; }

D2Client global (dereferenced *D2CLIENT_gnGlobalUiState; no address given)


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).


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;


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


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


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);


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);


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);


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();


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();


environment effects global (weather/env array)


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,


perspective-mode check (draw/render path)


summit cloud X position (0 = not present/unused in 1.13c)


viewport height global (immediately after width, +4)


viewport shift global (the render offset knob)


generate one weather particle. Signature: stdcall(&mut Rng). | src: // Signature: stdcall(&mut Rng)
  gen_weather_particle: 0x80700,


ext.BaseAddresses (from v111a, re-exported through v112 into v113c).BASE_ADDRESSES_client


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)


Dynamic (moving) entity position. #[repr(C)]. Fields in order: linear_pos: LinearM2d<FU16>; iso_pos: IsoP2d<i32>; target_pos: [LinearM2d<u16>; 3]; room: Option<NonNull<Room>>; _padding1: [u32;4]; entity: NonNull<Entity>. This is the struct apply_pos_change mutates (ptr in ESI). | src: #[repr(C)] pub struct DyPos { pub linear_pos: LinearM2d<FU16>, pub iso_pos: IsoP2d<i32>, pub target_pos: [LinearM2d<u16>; 3], pub room: Option<NonNull<Room>>, pub _padding1: [u32;


Static (non-moving) entity position. #[repr(C)]. Fields: room: Option<NonNull<Room>>; iso_pos: IsoP2d<i32>; linear_pos: LinearM2d<u32>; _padding1: [u32;3]. | src: #[repr(C)] pub struct StaticPos { pub room: Option<NonNull<Room>>, pub iso_pos: IsoP2d<i32>, pub linear_pos: LinearM2d<u32>, pub _padding1: [u32; 3], }


#[repr(C)] union: s: Option<NonNull<StaticPos>> | d: Option<NonNull<DyPos>>. Which arm is valid depends on Entity.kind. | src: #[repr(C)] pub union EntityPos { pub s: Option<NonNull<StaticPos>>, pub d: Option<NonNull<DyPos>>, }


#[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<NonNull<Entity>>; next_in_room: Option<NonNull<Entity>>; _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:


#[repr(C)]. connected: Option<NonNull<NonNull<Room>>>; _padding1:[u32;3]; data:u32; _padding2:[u32;4]; connected_count:u32; _padding3:[u32;9]; pos: LinearM2d<u32>; size: M2d<u32>; _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<NonNull<NonNull<Room>>>, pub _padding1: [u32; 3], pub data: u32, pub _padding2: [u32; 4], pub connected_count: u32, pub _padding3


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, }


The Esoteric Path (SGD2FreeRes + mir + planqi ingest 2026-07-18)

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.


The Esoteric Path (bolrog + Necrolis/d2dx-annihilus source read 2026-07-18)

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.


The Esoteric Path (SGD2FreeRes source read 2026-07-18)

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.


The Esoteric Path (novelty audit 2026-07-18)

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.


The Esoteric Path (live elevated RPM/WPM session 2026-07-18 on running Game.exe)

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.

live: RPM/WPM on Game.exe PID 17412, D2Client.dll base 0x6FAB0000, mod ON


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


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)


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])


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


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;


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


Draws the (life/mana) orb on the HUD panel. Direct panel/HUD render entry. | D2Client.0x6FAFF9D0 draws the orb


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 <D2Client.D2AddRoomToDrawList> ... CALL <D2Client.D2IterateRooms> ; JMP to D2Common.#10049

D2Client.D2AddRoomToDrawList (pushed at 6FAB827B); D2IterateRooms = JMP to D2Common.#10049


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


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,


eezstreet (cla$$ics) (phrozen_code Discord)

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

n/a (eezstreet OpenD2 reimplementation, not stock)


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


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()

D2Client.6FAB6FF0 D2CreateView; 6FAB6AC0 D2BuildGouraudTables; 6FAB6EB0 D2FreeGouraudTables


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


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


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

UNITDRAW_GetWorldXOffset / UNITDRAW_GetWorldYOffset / UNITDRAW_GetPanelAdjust / D2AdjustPerspectiveCoords / GFXUTIL_CheckPerspective; UNITDRAW_GetGFXPosition(pUnit,GFX_DRAW)


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


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

COLLISION_CheckMask (#10118) + tile flags


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 };


The Esoteric Path (Doug, phrozen_code Discord)

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();


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;


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


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


AlphA - The Real One (phrozen_code Discord)

Access-violation fault address inside D2CMP.dll (cross-referenced to d2mods thread) | Fault address: 6FE21614 01:00010614 D:\Spiele\Diablo II\D2CMP.dll


void __fastcall(D2GameStrc* pGame, DRLGRoom* pRoom) - re-initialises a room | D2FUNC(D2GAME,0x85A0,void,__fastcall,D2ReInitRoom,(D2GameStrc* pGame, DRLGRoom* pRoom));


Function where the trade panel is drawn in 1.13c | function where trade panel are drawn 0x99440 (1.13c)


D2Gfx image-blit-to-window function (ordinal 10072) at GfxOffset+0x42A0 | D2DrawImageToWindow = (GfxOffset + 0x42A0); // D2Gfx.10072


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 };


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


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

jumptable at 6FB5CFC7 / code near 0xACFD7


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);


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 <JMP.&D2Common.#10695> ; get item quality ... MOV EBX,9 ; D2_Yellow_Light (rare)

D2Client near 6FB20772 / calls D2Common.#10695 (get item quality)


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


Start of the character drawing loop in menus | look at D2Win.0x6F8AD818 for the start of the char drawin loop in menus


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


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]; ... } };


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


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


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; };


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


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


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

D2Client.0x6FAAE3C0 (getter), 0x6FB28410 (assigner), 0x6FB27A1F


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


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]; } }


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


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


D2Win ordinal 10127 = D2SetFont, at WinOffset+0x09FF0 (6F8A9FF0). Text-render setup. | D2SetFont = (D2Win10127)(WinOffset + 0x09FF0); // D2Win.6F8A9FF0 #10127

D2Win.6F8A9FF0 / WinOffset+0x09FF0 / #10127


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)


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

6FAF8B37..6FAF8B59; branch target 6FAF8C0E


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

6FAF8C0E..6FAF8C3A; global DS:[6FB740DC]; branch 6FAF8CD9


Firehawk / Kieran (phrozen_code Discord)

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

6FAFF1E6..6FAFF1F0 (epilogue); jmp target 6FAFF1F1


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 <JMP.&D2Win.#10117>


D2Common export at 6FC711D6 named TileGetRarity (tile rarity accessor). | 6FC711D6 .text Export TileGetRarity


D2Common export at 6FC71668 named TileProjectSerialize (tile projection serialization). | 6FC71668 .text Export TileProjectSerialize


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:[<gdwSkipFramesPerSecond>],16

6FB75460 (ms/frame); 6FAAA167 gdwSkipFramesPerSecond; 6FAAA2C4 gdwFrameRenderSkip


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


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(); }


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;


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


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?


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


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


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


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


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]);


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


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


whist / Deleted User (phrozen_code Discord)

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)


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(


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);

UNITDRAW_ProcessUnit 6FB4DB40; sub_7C047F0; sub_7C06AD0


1.13c: pDrlgAct + 0x18 holds a DrlgTileData structure (level tile data). | DrlgTileData DrlgTileData; //0x18


__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)


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


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

6FBA7768 (gdwClientGameFrame), 6FB21B70 (D2DrawUI) [1.10]


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


Loads the inventory item-background colors in advance (paired with UI_INVENTORY_DrawItemBackground). | void __fastcall UI_INVENTORY_LoadItemBackgroundColors()


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

sizeof 0x18; nGridLeft +0x04, nGridRight +0x08, nGridTop +0x0C, nGridBottom +0x10, nGridBoxWidth +0x14, nGridBoxHeight +0x15


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)


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);

gSocketPositions array; D2GFX_10072_DrawImage; gpCellfile_GEMSOCKET_6FBB5E34; D2CLIENT_UI_INVENTORY_DrawInventoryItems_6FB5A320; ordinals D2COMMON_10749_GetItemDimensions, 10816_GetItemSockets, 10277/10304/10305


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

D2Client.0x6FB286B0; caller at D2Client.0x6FB5A720


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


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

fCollisionInfo[25] +0x28, wCacheIndex +0x42 (runtime), dwBlockOffset +0x48; also dwLightDirection, roofHeight, wFlags, dwTotalHeight, dwWidth, dwHeightToBottom, dwType, dwIndex_Style, dwSubIndex_Sequence, transparentColorRGB24


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

sizeof 0x14; nXpos +00, nYpos +02, nGridX +06, nGridY +07, wFormat +08, nLength +0A, dwOffset/pData +10


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

eD2TileFlags (TILELIB_RAW 0x01, HASTILES 0x02, INITED 0x04, FEWTILES 0x08), eD2SubTileFlags (NOWALK 0x01, NOLOSLIGHT 0x02, NOJUMPTELEPORT 0x04, NOPLAYERWALK 0x08, NOLIGHT 0x20), eD2GFXTileMaterials, TILECOMP flags (RLE 0x1000, GAMESQUARE 0x2000)


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))


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; }

version +0 (7 or 4), flags +4, then libraryName[260]/numTiles/firstTile (v7) or numTiles/skipColor[4] (v4)


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


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

nRooms +0x004, nActiveRooms +0x00C; D2COMMON GetActiveRooms ordinal -10662


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

pTxt+0xDC region; dwFrameCnt / nFrameCount[8] near +0xD8/0xD9


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"


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


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 };


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

D2Client.0x6FAA3330 (1.10f); member ptRect +0x24


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;


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;


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;


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


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); }


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 };

Inventory+0x14 -> array; fields +0x00..+0x0C


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


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 <d2client.Ordinal10117>  6FAF8B3D | JMP d2client.6FAF8C40 ... MOV DWORD PTR SS:[ESP+20],0

D2Client.0x6FAF8AE0 start; calls Ordinal10117


Blighthorn / Necrolis (phrozen_code Discord)

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 )

+0xC4 bit 0x20 (draw shadow, client-only)


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


D2GetRoomList = D2Common.10035 @0x4BC10; D2PlaySound = D2Client.6FB55820 @0xB5820. Room-list retrieval feeds the render room iteration. | static D2Common2733 D2GetRoomList = (D2Common2733)(0x4BC10 + CommonOffset); // D2Common.10035


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; ...


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


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]


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


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, ...)


D2Client.0x6FAA3B90 -- draws roof tile layer for the play area, takes D2ViewStrc* pView. | //D2Client.0x6FAA3B90 void __fastcall MULTIRES_DrawRoofTiles(D2ViewStrc* pView)


D2Client.0x6FAA63E0 -- draws upper wall tiles; takes a D2DrawSlotStrc, a bOnlyShadows flag, and pView. | void __fastcall MULTIRES_DrawUpperWallTiles(D2DrawSlotStrc* pDrawSlot, BOOL bOnlyShadows, D2ViewStrc* pView)


D2Client.0x6FAA6670 -- draws lower wall tiles; takes pView. | void __fastcall MULTIRES_DrawLowerWallTiles(D2ViewStrc* pView)


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)


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 };


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


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


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


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!


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


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);


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


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)


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


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


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 ...


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; };


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 };


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

1.10: gpAutoMapCache=6FBB1998, gnAutoMapCaches=6FBB199C (offsets 0x111998/0x11199C); 1.13c: 6FBCC1BC/6FBCC1B8 (0x11C1BC/0x11C1B8)


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

1.13c hook site 6FB45790 (offset 0x95790); free-space patch at 6FB7DE60; JE targets 6FB457BF (red bg) / 6FB45803 (blue bg)


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

D2Client.dll+0x95803 = jump to colour blue; D2Client.dll+0x957BF = jump to colour red (1.13c: 6FB45803 / 6FB457BF)


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


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]


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;

gpfInterfaces table; VDEF(D2CLIENT,0x44EC) appmode fn table; gbQuitGame=D2Client+0xD4538; gdwAppMode=D2Client+0xD44D8; hInstance=+0xFA94C


Server->client packet 0x58 opens a user-interface panel (UnitInteract); nUIType selects which UI/panel. | S>C 0x58 - Opens User Interface on Client - UnitInteract

sizeof 0x07: nHeader,int nUnitGUID+0x01,BYTE nUIType+0x05; conditional BP D2Client 6FAC44A1


Client-side data-table loader; note bPrecacheTileLibraries param governs tile-library precache (floor/tile relevance). | CLIENT_LoadDataTables(D2PoolManagerStrc* pMemPool, BOOL bPrecacheTileLibraries, BOOL bCreateLvlSubList)


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);


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


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

D2Client.0xD0BA (DrawRectangle, 1.13c); draw sites at D2Client 0x6CBBD/0x6CBDF, 0x6DA0A


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

D2Client.0xD0BA (1.13c); call at 0x6DA0A (1.13d)


Bottom-panel HUD draw functions (health globe, mana globe, exp bar). | //D2Client.0x6DDE0 - UI_DrawHealthGlobe


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

D2Client.0x6EDEC (left), 0x6EE4A (right) (1.13d)


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

UI_DrawSkillSelect = D2Client.0xA77D0; skill-button input coords at D2Client.0x6F710 (1.13d)


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


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

gptMiniPanelInputHandlers table D2Client.0xD0864; MINIPANEL_HandleInputLDown 0x64BA0; LUp 0x662E0; drawing code D2Client.0x64990


Szumigajowy: this address 'handle background color' -- candidate for the panel/dead-zone background fill. | D2Client.0x6FAE1710 handle background color


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


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


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)


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


Tile library entry; DT1 filename pointer at offset 0x58 (88 dwords in). | char* szDT1FileName;    //0x058


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]];


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


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


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


Client image preload routine at 0x5E370 + ClientOffset. | D2PreLoadImages = (0x5E370 + ClientOffset); // D2Client.6FAFE370


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


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


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


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)


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


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];


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


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 )


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)


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


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


Speed-book click handler: void __stdcall SPEEDBOOK_ClickDown(D2WinMsgStrc* pMsg) at D2Client.0x6FB19BB0. | Function: SPEEDBOOK_ClickDown
    Address: D2Client.0x6FB19BB0


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


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


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


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;


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


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

vtable offsets 0x0..0xd0 (fpBeginScene=+0x18, fpGetScreenSize=+0x50, fpSetPerspectiveScale=+0x60, fpDrawRect region ~+0xa8)


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)

CommonOffset+0x22C7F/0x22D46 (nFirst), +0x22C4E/0x22C9F/0x22D4D (nLast), +0x22AF5/+0x22B32 (map names); orig 6FC6F5E6; GameOffset+0xFF018