119 lines
5.9 KiB
Markdown
119 lines
5.9 KiB
Markdown
# Diablo II v1.13c Reverse Engineering Specification: `dLightMap.c` Lighting Pipeline
|
||
|
||
> **Ground Truth Binary**: `samples/d2/D2Client.dll` (v1.13c, PE ImageBase `0x6fab0000`, file timestamp 2010-03-09).
|
||
> **Source File Origin**: `..\Source\D2Client\DRAW\dLightMap.c` (string located at VA `0x6fb84104`).
|
||
> **Target Assembly Range**: `0x6fb58000` – `0x6fb5a000` (dLightMap.c), `0x6faef000` – `0x6faefee0` (light debug & map tile interface), `0x6fb10000` – `0x6fb10500` (floor tile rasterizer).
|
||
|
||
---
|
||
|
||
## 1. Light Grid Architecture & Topography
|
||
|
||
### 1.1 Dimensions & Coordinate Space
|
||
- **Grid Dimensions**: Exactly **48 × 48 cells** (2,304 cells total, hex `0x900`).
|
||
- Column index $x \in [0, 47]$ (`cmp ecx, 0x30`).
|
||
- Row index $y \in [0, 47]$ (`cmp eax, 0x30`).
|
||
- Linear address mapping: `index = 48 * y + x` (`lea eax, [eax + eax*2]; shl eax, 4; add eax, ecx`).
|
||
- **Spatial Resolution & Scale**:
|
||
- The lighting grid steps by **8 sub-tiles** per cell (`sar ecx, 3` / `add esi, 8`).
|
||
- World sub-tile coordinates $(x_{\text{sub}}, y_{\text{sub}})$ are converted to grid coordinates by:
|
||
$$x_{\text{grid}} = (x_{\text{sub}} \gg 3) - x_{\text{origin}}$$
|
||
$$y_{\text{grid}} = (y_{\text{sub}} \gg 3) - y_{\text{origin}}$$
|
||
Where $(x_{\text{origin}}, y_{\text{origin}})$ are the grid origin sub-tile offsets (`ds:0x6fbb4df0`, `ds:0x6fbb4df4`).
|
||
|
||
### 1.2 Cell Memory Layout (8 Bytes per Cell)
|
||
The primary lighting array is located at base address `0x6fbb5208` with an 8-byte stride per cell (`ecx * 8 + 0x6fbb5208`):
|
||
```c
|
||
struct D2LightCellStrc {
|
||
uint8_t nFlags; // +0x00: Obstacle / cell flag
|
||
uint8_t pad[3]; // +0x01..+0x03
|
||
uint8_t nIntensity; // +0x04: Clamped intensity [0..255]
|
||
uint8_t nRed; // +0x05: Red color channel [0..255]
|
||
uint8_t nGreen; // +0x06: Green color channel [0..255]
|
||
uint8_t nBlue; // +0x07: Blue color channel [0..255]
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Mathematical Formulas (Decompiled from Assembly)
|
||
|
||
### 2.1 Fast Euclidean Distance Approximation (Alpha Max Plus Beta Min)
|
||
In `0x6fb597b4` – `0x6fb597d9`, Blizzard avoids floating-point square root `sqrt(dx^2 + dy^2)` using an integer approximation:
|
||
```assembly
|
||
6fb597b4: cmp ecx, eax
|
||
6fb597b6: jl 0x6fb597cb
|
||
6fb597b8: imul ecx, ecx, 0x3d7 ; 983
|
||
6fb597be: imul eax, eax, 0x197 ; 407
|
||
6fb597c4: add ecx, eax
|
||
6fb597c6: shr ecx, 0xa ; >> 10 (1024)
|
||
```
|
||
Mathematical representation:
|
||
$$\text{dist}(dx, dy) = \frac{983 \cdot \max(|dx|, |dy|) + 407 \cdot \min(|dx|, |dy|)}{1024}$$
|
||
Where:
|
||
- $\frac{983}{1024} \approx 0.95996$
|
||
- $\frac{407}{1024} \approx 0.39746$
|
||
- Maximum theoretical error across all angles is $\le 3.96\%$.
|
||
|
||
### 2.2 Light Falloff & Obstacle Attenuation Formula
|
||
In `0x6fb597de` – `0x6fb597fa`:
|
||
```assembly
|
||
6fb597de: mov edx, [esi+0x18] ; Light Radius R
|
||
6fb597e1: sub edx, ecx ; (R - dist)
|
||
6fb597e3: imul edx, [esp+0x3c] ; * intensitySlope (16.16 fixed point)
|
||
6fb597f1: sar edx, 16 ; >> 16
|
||
6fb597e8: sar edi, 1 ; edi = obstacle factor
|
||
6fb597ea: mov eax, 8
|
||
6fb597ef: sub eax, edi ; (8 - obstacle)
|
||
6fb597f4: imul eax, edx ; (8 - obstacle) * attenuated
|
||
6fb597f7: sar eax, 3 ; >> 3 (/ 8)
|
||
```
|
||
Mathematical representation:
|
||
$$\text{IntensitySlope} = \frac{\text{BaseIntensity} \ll 16}{R}$$
|
||
$$\text{Attenuated} = \max\left(0, \frac{(R - \text{dist}) \cdot \text{IntensitySlope}}{65536}\right)$$
|
||
$$\text{FinalCellIntensity} = \frac{(8 - \text{ObstacleFactor}) \cdot \text{Attenuated}}{8}$$
|
||
- When $\text{ObstacleFactor} = 0$: Light transmits at $100\%$ ($\frac{8-0}{8} = 1.0$).
|
||
- When $\text{ObstacleFactor} \ge 16$: Light is completely blocked (`cmp edi, 0x10; jge skip`).
|
||
|
||
### 2.3 Multi-Source Color Blending (Weighted Energy Conservation)
|
||
In `0x6fb58a91` – `0x6fb58b45`:
|
||
When multiple lights illuminate the same grid cell, their colors are combined via intensity-weighted averaging:
|
||
```assembly
|
||
6fb58a91: lea ecx, [esi+edx*1] ; newIntensity = existing + incoming
|
||
6fb58a94: cmp ecx, 0xff
|
||
...
|
||
6fb58abd: mov edi, [ebx*4 + 0x6fbb4e00] ; 65536 / newIntensity table lookup
|
||
6fb58ac4: imul ecx, esi ; existingRed * existingIntensity
|
||
6fb58acd: imul ebp, edx ; incomingRed * incomingIntensity
|
||
6fb58ad0: add ecx, ebp
|
||
6fb58ad2: imul ecx, edi ; sum * (65536 / newIntensity)
|
||
6fb58ad5: shr ecx, 0x10 ; >> 16
|
||
```
|
||
Mathematical representation:
|
||
$$I_{\text{total}} = \min(255, I_{\text{existing}} + I_{\text{incoming}})$$
|
||
$$R_{\text{blended}} = \min\left(255, \frac{R_{\text{existing}} \cdot I_{\text{existing}} + R_{\text{incoming}} \cdot I_{\text{incoming}}}{I_{\text{total}}}\right)$$
|
||
$$G_{\text{blended}} = \min\left(255, \frac{G_{\text{existing}} \cdot I_{\text{existing}} + G_{\text{incoming}} \cdot I_{\text{incoming}}}{I_{\text{total}}}\right)$$
|
||
$$B_{\text{blended}} = \min\left(255, \frac{B_{\text{existing}} \cdot I_{\text{existing}} + B_{\text{incoming}} \cdot I_{\text{incoming}}}{I_{\text{total}}}\right)$$
|
||
|
||
### 2.4 Sub-Tile Bilinear Interpolation (8.8 Fixed Point)
|
||
In `0x6fb58373` – `0x6fb58385`:
|
||
```assembly
|
||
mov ebx, 0x100 ; 256
|
||
sub ebx, eax ; 256 - factor
|
||
imul eax, edx ; factor * L2
|
||
imul ebx, edi ; (256 - factor) * L1
|
||
add ebx, eax
|
||
sar ebx, 0x8 ; >> 8
|
||
```
|
||
Interpolates smoothly between neighboring cells using fixed-point factor $f \in [0, 256]$:
|
||
$$\text{Sample} = \frac{(256 - f) \cdot L_1 + f \cdot L_2}{256}$$
|
||
|
||
---
|
||
|
||
## 3. Light Sources & Defaults
|
||
|
||
- **Player Base Light Radius**: Default is radius 10–13 sub-tiles adjusted by item `STAT_LIGHTRADIUS` (`CUnit.c` / `dLightMap.c`).
|
||
- **Monster Default Radius**: 8 sub-tiles (`mov ebx, 0x8` at `0x6fb59894`).
|
||
- **Missile Lights**: Read directly from `Missiles.txt` `Light`, `Red`, `Green`, `Blue` columns.
|
||
- **Flicker**: Perturbation index updated every logic frame (25Hz) modifying radius by $\pm 1$ sub-tile.
|
||
- **2D Software Mode Fallback**: When `bColoredLighting` is false (`ds:0x6fb8e830 == 0`), $R=G=B=I$ and the intensity directly drives the 32-level PL2 colormap row.
|