Graphics (DirectDraw)
Purpose
DirectDraw 3 initialization, cooperative level, palette handling, and display
surface setup for the 640×480 (0x280×0x1e0) game framebuffer. The dirty
rectangle and present path is now mapped from the fixed-size map/UI region
markers through the DirectDraw primary/back-buffer blit.
Key entry points
| Symbol |
Address |
Role |
lotr2_gfx_init_directdraw |
0x436c80 |
DirectDrawCreate, set cooperative level |
lotr2_gfx_init_complete |
0x4ccf34 |
Full init chain after DirectDrawCreate |
lotr2_gfx_init_display |
0x4cd008 |
Post-window display setup; loads base01.256 |
lotr2_gfx_release_ddraw |
0x4ccfb3 |
Release DirectDraw interface |
lotr2_gfx_lpdd |
0x507a6c |
IDirectDraw* global |
lotr2_gfx_create_surface |
0x37799 |
Create primary/offscreen surfaces via IDirectDraw::CreateSurface |
lotr2_gfx_mark_dirty_rect |
0x45cd26 |
Union a dirty rectangle into global bounds and arm present |
lotr2_gfx_mark_fullscreen_dirty |
0x45ce2a |
Mark the full 640×480 framebuffer dirty |
lotr2_gfx_present_frame |
0x45cb80 |
Throttled DirectDraw present from back buffer to primary |
lotr2_gfx_surfaces_restorable |
0x4cce16 |
Check primary/back surface IsLost state before idle tick |
lotr2_gfx_on_surface_lost |
0x4ccd9e |
Surface-lost UI recovery path; invalidates main window |
lotr2_gfx_load_pl8_screen |
0x40f895 |
Load full-screen PL8 pixels into the software framebuffer |
lotr2_gfx_load_pl8_rect |
0x4116dc |
Read one PL8 frame header and payload into a caller buffer |
lotr2_gfx_load_pl8_and_palette |
0x40f8f5 |
Load a PL8 screen plus companion .256 palette |
lotr2_gfx_apply_palette_rgb |
0x4cb1f9 |
Convert 0x300-byte RGB palette data and refresh DirectDraw |
Palette Helpers
| Symbol |
Address |
Role |
lotr2_gfx_create_dd_palette |
0x437830 |
Create and attach the DirectDraw palette |
lotr2_gfx_update_dd_palette |
0x437bdd |
Update active palette entries through SetEntries |
lotr2_gfx_build_hicolor_palette_entries |
0x437957 |
Build high-color palette entry data |
lotr2_gfx_build_8bit_palette_entries |
0x437a9a |
Build 8-bit palette entry data |
lotr2_gfx_copy_palette_rgb |
0x4cb2cd |
Copy 256 RGB triples between palette buffers |
lotr2_gfx_palette_scale_down |
0x4cb348 |
Convert 8-bit RGB components to 6-bit VGA scale |
lotr2_gfx_palette_scale_up |
0x4cb3a0 |
Convert 6-bit RGB components to 8-bit scale |
PL8 Blit Helpers
| Symbol |
Address |
Role |
lotr2_gfx_set_blit_mode |
0x40f870 |
Set the current blit/transparency mode |
lotr2_gfx_blit_clip_x |
0x409073 |
Compute horizontal clipping and effective width |
lotr2_gfx_blit_clip_y |
0x4091a0 |
Compute vertical clipping and source row offset |
lotr2_gfx_blit_sprite_frame |
0x410ae4 |
Draw a PL8 frame by parsing its 16-byte header |
lotr2_gfx_blit_sprite_frame_alt |
0x410c9a |
Alternate frame draw path using RLE row variants |
lotr2_gfx_blit_sprite_frame_noclip |
0x410e31 |
Frame draw variant with reduced clip adjustment |
lotr2_gfx_blit_opaque_row |
0x4cfc65 |
Copy non-zero sprite pixels into the framebuffer |
lotr2_gfx_blit_tile16_strip |
0x4d02ab |
Copy a 16x16 tile strip to the framebuffer |
lotr2_gfx_blit_tile16_block |
0x4d056c |
Copy a full 16x16 tile block |
lotr2_gfx_text_advance_packed_ptr |
0x4d020e |
Advance through packed eng-string glyph data |
lotr2_gfx_blit_glyph_pair_2x16 |
0x4d026b |
Blit two 16px-tall glyph rows (UI text) |
lotr2_gfx_blit_tile24_block |
0x4d0be7 |
Copy a 24-row UI tile block into the backbuf |
lotr2_gfx_glyph_src_base |
0x5fcf9c |
Base offset for glyph/tile source buffers |
lotr2_gfx_blit_origin_x |
0x5bc6a4 |
Current backbuf blit X |
lotr2_gfx_blit_origin_y |
0x5bc6a8 |
Current backbuf blit Y |
lotr2_gfx_blit_sprite_scaled |
0x4108e7 |
Draw a scaled rectangular sprite region |
Isometric map backbuffer stamp/fill (pass 41, 0x460000–0x46ffff)
Shared helpers that write into lotr2_gfx_backbuf_pixels using staggered
isometric offsets. Map draw, combat message panel, and UI paint all call into this
cluster.
| Symbol |
Address |
Role |
lotr2_gfx_fill_iso_cell_rot0_edge0 |
0x46f5a6 |
Fill one map cell diamond; view rot 0, no edge clip |
lotr2_gfx_fill_iso_cell_rot0_edge2 |
0x46f789 |
Fill; view rot 0, edge clip mode 2 |
lotr2_gfx_fill_iso_cell_rot0_edge1 |
0x46f889 |
Fill; view rot 0, edge clip mode 1 |
lotr2_gfx_fill_iso_cell_rot1_edge0 |
0x46f989 |
Fill; view rot 1, edge clip 0 |
lotr2_gfx_fill_iso_cell_rot1_edge2 |
0x46fa8c |
Fill; view rot 1, edge clip 2 |
lotr2_gfx_fill_iso_cell_rot1_edge1 |
0x46fb1c |
Fill; view rot 1, edge clip 1 |
lotr2_gfx_fill_iso_cell_rot2_edge0 |
0x46fbac |
Fill; view rot 2, edge clip 0 |
lotr2_gfx_fill_iso_cell_rot2_edge2 |
0x46fc3f |
Fill; view rot 2, edge clip 2 |
lotr2_gfx_fill_iso_cell_rot2_edge1 |
0x46fc97 |
Fill; view rot 2, edge clip 1 |
lotr2_gfx_blit_map_sprite_scaled |
0x46b141 |
Copy scaled map sprite rows into backbuf |
lotr2_gfx_blit_map_sprite_unscaled |
0x46b3dd |
Copy unscaled map sprite rows |
lotr2_gfx_blit_map_sprite_scaled_alt |
0x46b51e |
Alternate scaled map sprite layout |
lotr2_gfx_stamp_pl8_diag_stride282 |
0x460b44 |
PL8 diagonal stamp loop (stride 0x282) |
Full pass-41 table: symbol-map.md Pass 41.
Panel / Tile Grid Helpers
| Symbol |
Address |
Role |
lotr2_gfx_blit_tile16_grid_bordered |
0x40fa15 |
Draw a 16x16 bordered tile grid |
lotr2_gfx_blit_tile16_grid_flat |
0x40fb44 |
Draw a flat repeated 16x16 tile grid |
lotr2_gfx_draw_panel_bordered |
0x40fc10 |
Draw a bordered panel and fill its interior |
lotr2_gfx_draw_panel_style0 |
0x40fc61 |
Panel wrapper for style 0 tiles |
lotr2_gfx_draw_panel_style1 |
0x40fcaa |
Panel wrapper for style 1 tiles |
lotr2_gfx_draw_panel_style2 |
0x40fcf3 |
Panel wrapper for style 2 tiles |
lotr2_gfx_blit_panel_border_tiles |
0x40fd37 |
Emit panel border tile spans |
lotr2_gfx_blit_panel_inner_fill |
0x41008f |
Fill panel interior tiles |
lotr2_gfx_blit_panel_corner_set |
0x4101fe |
Select style-specific panel corner tiles |
lotr2_gfx_blit_panel_edge_fill |
0x41055d |
Fill panel edge spans |
lotr2_gfx_blit_panel_edge_fill_alt |
0x4106d3 |
Alternate panel edge fill helper |
Dirty Region Helpers
| Symbol |
Address |
Role |
lotr2_gfx_mark_dirty_tile32 |
0x45ce56 |
Mark a 32×32 tile-aligned dirty region |
lotr2_gfx_mark_dirty_tile48 |
0x45cea9 |
Mark a 48×48 tile-aligned dirty region |
lotr2_gfx_mark_dirty_tile64 |
0x45cefc |
Mark a 64×64 tile-aligned dirty region |
lotr2_gfx_mark_dirty_tile80x48 |
0x45d048 |
Mark an 80×48 tile-aligned dirty region |
lotr2_gfx_mark_dirty_tile80 |
0x45d080 |
Mark an 80×80 tile-aligned dirty region |
lotr2_gfx_mark_dirty_region512x160 |
0x45d0d3 |
Mark a 512×160 tile-aligned dirty region |
lotr2_gfx_mark_dirty_tiles |
0x45d1c6 |
Mark (x,y,w,h) where width/height are 16-pixel tile counts |
lotr2_gfx_mark_dirty_tiles_now |
0x45d195 |
Force-present variant of lotr2_gfx_mark_dirty_tiles |
lotr2_gfx_mark_dirty_status_bar |
0x45d12a |
Mark lower status screen region dirty |
lotr2_gfx_mark_dirty_status_bar_urgent |
0x45d155 |
Status-region marker that forces an extra present pass |
lotr2_gfx_refresh_fullscreen |
0x45c990 |
Mark full screen dirty and force one present pass |
lotr2_gfx_refresh_and_arm_ui |
0x45c9aa |
Mark full screen dirty and arm UI/game redraw flags |
Present Pipeline
flowchart LR
drawCode[Map and UI draw code] --> markDirty[lotr2_gfx_mark_dirty_rect]
tileHelpers[Tile dirty helpers] --> markDirty
fullscreen[lotr2_gfx_mark_fullscreen_dirty] --> markDirty
markDirty --> pending[lotr2_gfx_dirty_pending]
frameLoop[Frame loop] --> delta[lotr2_gfx_frame_delta_ms]
delta --> present[lotr2_gfx_present_frame]
pending --> present
videoGate[lotr2_video_is_playing] --> present
backbuf[lotr2_gfx_surf_backbuf] --> present
present --> primary[lotr2_gfx_surf_primary]
lotr2_gfx_mark_dirty_rect records a dirty rectangle and raises
lotr2_gfx_dirty_pending. lotr2_gfx_present_frame throttles present work,
then copies from lotr2_gfx_surf_backbuf to lotr2_gfx_surf_primary using
DirectDraw surface vtable calls. lotr2_gfx_use_hicolor selects the 8-bit
Blt path or high-color coordinate-based path.
PL8/256 Pipeline
flowchart LR
pl8File[PL8 file] --> loadScreen[lotr2_gfx_load_pl8_screen]
pl8File --> loadRect[lotr2_gfx_load_pl8_rect]
paletteFile[256 palette] --> paletteRgb[lotr2_gfx_palette_rgb]
paletteRgb --> applyPalette[lotr2_gfx_apply_palette_rgb]
applyPalette --> ddPalette[DirectDraw palette]
loadScreen --> framebuffer[lotr2_gfx_backbuf_pixels]
loadRect --> spriteBuffer[PL8 sprite buffer]
spriteBuffer --> frameBlit[lotr2_gfx_blit_sprite_frame]
frameBlit --> framebuffer
panelTiles[Panel tile helpers] --> framebuffer
PL8 frame draw paths use a compact 16-byte frame header: width, height,
payload offset, and draw flags are copied into global blit state before
clipping and row-copy dispatch. Palette index 0 is treated as transparent in
the primary sprite row copy path.
Recovered globals
| Symbol |
Address |
Role |
lotr2_gfx_surf_primary |
0x505360 |
Primary DirectDraw surface |
lotr2_gfx_surf_backbuf |
0x504530 |
Offscreen back buffer surface |
lotr2_gfx_dirty_pending |
0x52931c |
Pending present counter |
lotr2_gfx_dirty_left |
0x526748 |
Dirty rectangle left bound |
lotr2_gfx_dirty_top |
0x526744 |
Dirty rectangle top bound |
lotr2_gfx_dirty_right |
0x529320 |
Dirty rectangle right bound |
lotr2_gfx_dirty_bottom |
0x529318 |
Dirty rectangle bottom bound |
lotr2_gfx_use_hicolor |
0x59b848 |
8-bit vs high-color display path gate |
lotr2_gfx_backbuf_pixels |
0x504268 |
640x480 software framebuffer pointer |
lotr2_gfx_backbuf_stride |
0x505380 |
Framebuffer row stride, normally 0x280 |
lotr2_gfx_palette_rgb |
0x507760 |
0x300-byte RGB palette buffer from .256 files |
Dependencies
ddraw.dll — DirectDrawCreate, IDirectDraw::CreateSurface,
IDirectDrawSurface::Blt, IDirectDrawSurface::IsLost
gdi32.dll — GetDeviceCaps, palette APIs
ui/MODULE.md — requires lotr2_ui_hwnd_main
asset/MODULE.md — .256 / .pl8 palette and sprite loads
Data files
base01.256 — default 256-color palette (loaded during display init)
.256 files such as gateway.256, armoury.256, and merchant.256
provide 256 RGB triples consumed by lotr2_gfx_apply_palette_rgb.
.pl8 files such as gateway.pl8, armoury.pl8, merchant.pl8, and
caspics.pl8 provide screen images, frame tables, and sprite payloads.
Open questions
- Confirm the exact high-color format selected when
lotr2_gfx_use_hicolor is
non-zero.
- Dirty bounds are written by marker helpers, but no decompiled read has been
found yet; confirm whether they are consumed by an un-recovered path or are
historical state left from a partial-present implementation.
- Confirm the exact meaning of each byte in the PL8 16-byte frame header.