Skip to content

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.dllDirectDrawCreate, IDirectDraw::CreateSurface, IDirectDrawSurface::Blt, IDirectDrawSurface::IsLost
  • gdi32.dllGetDeviceCaps, 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.