Skip to content

Game logic#

Purpose#

Core game simulation, configuration, and main-loop driving. Occupies the bulk of the executable (~0x0040xxxx–0x0045xxxx). The lotr2_winmain call tree is named from startup through idle-tick dispatch and the first sim_tick callees.

Key entry points#

Symbol Retail addr GoG addr Role
lotr2_winmain 0x41680b 0x40e9ab Application entry; owns Win32 message loop
lotr2_asset_read_file 0x4ca01c 0x4af8d9 Read l2_eng string table at startup
lotr2_game_load_lords2_inf 0x4c8559 0x4ae1b8 Read lords2.inf install config
lotr2_game_load_config 0x416aea 0x40ecc4 Read l2.ini / _sierra.ini; set display/audio globals
lotr2_game_check_easter_egg 0x4c979f 0x4aef7e Pumpkin WAV easter egg; sets lotr2_net_join_flags
lotr2_game_play_intro_or_enable_tick 0x4cec91 0x4b3571 Play intro Smacker or enable tick gate
lotr2_game_init_phase1 0x4adbc9 0x4975d3 Pre-game init; loads base .256 assets
lotr2_game_init_phase2 0x4adb00 0x497500 New-game setup; loads PL8 assets
lotr2_game_reset_ui_slots 0x4890df 0x476a5d Reset UI slot table before main loop
lotr2_game_idle_tick 0x41671b 0x40e8bb Idle handler when queue empty; DD flip + game tick
lotr2_game_frame_advance 0x4166f0 0x40e890 Advance frame counter; set tick-pending flag
lotr2_game_tick_sp 0x4addaa 0x4978ad Single-player tick prep
lotr2_game_tick_mp 0x4ade2b 0x49792e Multiplayer tick prep
lotr2_game_sim_tick 0x4adf31 0x497a34 Per-frame simulation (SP/MP via param)
lotr2_game_ui_input_dispatch 0x489129 Route UI actions from slot flags
lotr2_game_shutdown 0x4adc73 0x49767d Winmain exit cleanup

Screen state machine (pass 21–24)#

See ../../docs/scenes/README.md for the full interactive screen catalog grouped by scene type.

Symbol Address Role
lotr2_game_screen_mode 0x507ab8 Current screen/mode id (char); central switch key
lotr2_game_screen_phase 0x59347c Screen phase (1 init / 2 active) paired with mode transitions
lotr2_game_wizard_step 0x593494 Sub-step within mode 0x1f (save/load, MP lobby wizard)
lotr2_game_turn_phase 0x5d9c88 Turn-phase index (1–4) set by action handlers
lotr2_game_screen_mode_tick 0x438b90 Main per-frame screen handler; branches on lotr2_game_screen_mode
lotr2_game_pick_scroll_dir 0x43a5af Compute 8-way scroll direction; call lotr2_game_screen_tick
lotr2_game_ui_scroll_bar_hit 0x43a9ce Scroll-bar hit zone from mouse position
lotr2_game_validate_scroll 0x4d65fc Debounce scroll apply via timeGetTime
lotr2_game_screen_tick 0x43a2e7 Apply map/panel scroll delta after direction pick
lotr2_game_dispatch_click 0x43a78c Route click to map or panel handler by lotr2_game_active_view
lotr2_game_handle_map_click 0x43a8c8 Hit-test map grid; call lotr2_game_select_province
lotr2_game_handle_panel_click 0x43a7d1 Panel viewport click; battle prep or map blit
lotr2_game_select_province 0x43ab19 Select province from map click; update scroll
lotr2_game_ui_hit_test_campaign_toolbar 0x43acbb Campaign toolbar button hit test
lotr2_game_ui_hit_test_status_buttons 0x43acf5 Status bar button hit test
lotr2_game_ui_hit_test_battle_toolbar 0x43ad32 Battle screen toolbar hit test
lotr2_game_on_mp_lobby_start 0x43ad7a MP lobby complete → start game or send net msg
lotr2_game_return_to_campaign_map 0x43cbd2 Cancel overlay; restore campaign map view
lotr2_game_economy_tick_player 0x456516 Per-player economy tick at end of turn
lotr2_game_diplomacy_tick_player 0x451511 Per-player diplomacy tick at end of turn
lotr2_game_reset_lobby_flags 0x43c007 Clear lobby globals before MP setup
lotr2_game_action_enter_options_dialog 0x43c023 Options dialog (mode '9'); MP client defers via msg 0xe3
lotr2_game_action_turn_phase1_toggle 0x43c087 Turn phase 1; toggles lotr2_game_turn_phase1_ack; MP opcode 0x32
lotr2_game_action_end_turn 0x43c101 Turn phase 2; toggles lotr2_game_turn_phase2_ack; economy/diplomacy loop
lotr2_game_action_turn_phase3_toggle 0x43c1c4 Turn phase 3; toggles lotr2_game_turn_phase3_ack
lotr2_game_action_turn_phase4_toggle 0x43c23e Turn phase 4; toggles lotr2_game_turn_phase4_ack

Hub leaf callees (pass 25)#

Symbol Address Role
lotr2_game_ui_hit_test_dialog_ok 0x415da6 Dialog OK button hit test (0x18×0x18); hub callee in modes 2, 0xf
lotr2_game_clear_bottom_panel_rows 0x41c2f0 Clear bottom panel screen rows 0x1da–0x1df on dialog dismiss
lotr2_game_mark_status_bar_dirty 0x444488 Mark status bar dirty region after scroll/exit
lotr2_game_wizard_on_mp_host 0x43ae19 MP host wizard step; calls lotr2_net_start_session
lotr2_game_wizard_on_save_step 0x43af56 Save/load wizard turn-phase dispatch
lotr2_game_ui_draw_modal_backdrop 0x43bf96 Draw 0xa0×0xa0 modal backdrop tile

Turn-action table (pass 26)#

Symbol Address Role
lotr2_game_action_toggle_ui_slots 0x43c2b8 Toggle lotr2_game_ui_action_slots_active; reset UI slots
lotr2_game_action_toggle_map_scroll_lock 0x43c2f8 Toggle lotr2_game_map_scroll_lock; clears scroll state
lotr2_game_mp_send_toolbar_context_panel_0_4 0x43c33d–0x43c435 MP client toolbar context panels (msg ids 0x1230x127, style 0x13)
lotr2_game_action_show_help 0x43c473 Open l2help.hlp via WinHelpA
lotr2_game_action_enter_game_options_dialog 0x43c49d Enter game-options dialog (mode 'B')
lotr2_game_action_enter_display_options_dialog 0x43c4b9 Enter display-options dialog (mode 'C')
lotr2_game_action_enter_diplomacy_faction_dialog 0x43db3d Enter diplomacy faction dialog (mode 0x0b); turn phase 5
lotr2_game_action_toggle_music 0x43c4d5 Toggle campaign/battle music stream
lotr2_game_action_toggle_sfx 0x43c55a Toggle sound-effects flag
lotr2_game_action_toggle_flag_b82c 0x43c5cb Toggle DAT_0059b82c
lotr2_game_action_toggle_flag_b868 0x43c606 Toggle DAT_0059b868
lotr2_game_action_toggle_hicolor 0x43c641 Toggle hi-color mode; re-init DirectDraw
lotr2_game_action_show_quit_confirm 0x43c891 Enter mode 0x25; draw quit-confirm dialog (pass 49)
lotr2_game_action_show_scroll_options 0x43c864 Enter mode 0x31; draw scroll-speed options (pass 49)
lotr2_game_mp_send_menu_selection_inc 0x441bf3 MP build-menu increment; SP local or net opcode 0x1f
lotr2_game_mp_send_menu_selection_dec 0x441cb7 MP build-menu decrement; SP local or net opcode 0x20

Campaign UI cluster (pass 27)#

Symbol Address Role
lotr2_game_ui_on_army_panel_recruit 0x423f7 Army panel lower-right → mode 0x19
lotr2_game_ui_on_army_panel_trade 0x424a3 Army panel upper-left → mode 0x14
lotr2_game_ui_on_army_panel_disband 0x424d3 Army panel upper-right → mode 0x16
lotr2_game_ui_on_army_panel_move 0x424ea Army panel lower-left → mode 0x15
lotr2_game_ui_detect_map_drag 0x40df7 Detect map drag gesture (9 px threshold)
lotr2_game_ui_on_mode_5_panel_click 0x40f4d Mode 5 overlay panel click handler
lotr2_game_ui_hit_test_minimap_province 0x417fa Minimap province hit test; MP sends opcode 0x26
lotr2_game_ui_apply_minimap_action 0x418e5 Apply minimap province action (SP)
lotr2_game_ui_hit_test_build_action 0x41b2d Build-menu toolbar hit test; sets build action
lotr2_game_ui_on_end_turn_click 0x42501 End-turn button; MP sends opcode 0x21

Campaign UI / sync cluster (pass 35)#

Full rename sweep for 0x0043e0000x0044ffff (146 symbols). Apply: tools/ghidra/game_renames_pass35.py.

Turn finalize / end-turn chain

Symbol Address Role
lotr2_game_finalize_army_turn 0x4400a0 Apply army 0x1a4 record to player; economy/diplomacy ticks
lotr2_game_ui_apply_player_turn 0x42548 Write turn phase; prosperity + diplomacy for active player
lotr2_game_ui_commit_map_cell_action 0x440521 Commit map cell action; MP opcode 0x22
lotr2_game_apply_map_cell_action_sp 0x440626 SP map cell: diplomacy bucket + economy ticks
lotr2_game_mp_send_upkeep_increase 0x4425e2 MP opcode 0x23 upkeep +1
lotr2_game_mp_send_upkeep_decrease 0x44267f MP opcode 0x24 upkeep −1
lotr2_game_ui_exit_build_submode 0x442c37 Exit build sub-screen; restore map or mode 0x18

Battle exit / siege

Symbol Address Role
lotr2_game_ui_on_post_battle_return 0x43f517 Post-battle screen restore; siege assault or MP 0x2d
lotr2_game_start_siege_assault_battle 0x43f9f7 Start siege assault from army pair
lotr2_game_ui_on_siege_battle_end 0x43f0cf Siege end → campaign; mode 0x11
lotr2_game_move_army_to_map_cell 0x43ef48 SP army move to map cell; may trigger battle

Panel hit-tests (extends pass 27)

Symbol Address Role
lotr2_game_ui_hit_test_turn_toolbar 0x4403af Turn-phase toolbar widget hit
lotr2_game_ui_hit_test_mode5_buildings 0x440f96 Mode-5 building grid hit test
lotr2_game_ui_minimap_tile_at_point 0x441300 Minimap province index from coords
lotr2_game_ui_on_minimap_province_click 0x4413ba Minimap click; MP opcode 0x25
lotr2_game_ui_hit_test_army_order_bar 0x43ea1f Army order bar when army selected

Battle UI (in-battle scroll / figure pick)

Symbol Address Role
lotr2_game_ui_on_battle_scroll_drag 0x443859 Battle map scroll drag; MP opcode 0x41
lotr2_game_ui_on_battle_figure_click 0x443b7c Battle figure group pick; MP 0x42/0x44
lotr2_combat_apply_scroll_reveal_orders 0x443b26 Scroll reveal + optional group orders

Post-siege outcome + battle HUD (pass 45)

Symbol Address Role
lotr2_game_ui_draw_battle_player_compare_rows 0x427fea Dual-player resource compare (7 types; layout modes 0–2)
lotr2_game_ui_draw_siege_field_battle_outcome_panel 0x428715 Screen mode 0x12; post field-battle siege assault outcome
lotr2_game_ui_draw_siege_defender_routed_panel 0x4289ec Screen mode 0x13; defender routed; calls lotr2_game_battle_on_defender_routed
lotr2_game_ui_draw_battle_narrative_panel 0x428c2d Screen mode 0x30; battle event narrative text
lotr2_combat_init_battle_hud_frame 0x428de3 Screen modes 0x28/0x2c; status bar + viewport + palette setup
lotr2_combat_redraw_hud_resource_counters 0x428f1c Food/gold counters on battle HUD strip
lotr2_combat_tick_hud_army_roster 0x429113 Refresh roster icons when army headcount changes
lotr2_combat_blit_hud_roster_icons 0x429292 Blit per-type figure icons on HUD roster
lotr2_combat_draw_hud_roster_headcounts 0x4293af Head-count labels beside roster icons
lotr2_combat_draw_paused_overlay 0x429527 Paused-game overlay during battle
lotr2_game_ui_draw_intro_chapter_dialog 0x42ace1 Screen mode 0x44; intro Smacker chapter picker

Net remote-apply (0x44f000)

Thin unpack dispatch targets named lotr2_net_apply_remote_* — mirror the MP send wrappers above (upkeep, turn phase, battle moves, diplomacy). See symbol-map Pass 35d.

l2_eng text draw (pass 29a)#

Symbol Address Role
lotr2_game_ui_draw_eng_string 0x40780e Draw l2_eng string by sheet index + string index
lotr2_game_ui_draw_number_field 0x407a3b Format number with prefix char and draw
lotr2_game_ui_draw_eng_wrapped 0x407cb4 Word-wrap l2_eng text into rows
lotr2_game_ui_draw_text_run 0x40710e Walk byte string; expand #; blit glyphs
lotr2_game_ui_blit_font_glyph 0x4074eb Single glyph lookup + clip + blit
lotr2_game_ui_draw_line 0x4084b4 Bresenham line draw
lotr2_game_ui_draw_panel_frame_style0 0x408812 4-edge panel frame (style 0)

UI widget toolkit (pass 29b)#

Descriptor tables live in .rdata (e.g. DAT_004f8ee0); each entry is 0x18 bytes. Type byte at offset 0xf: 0x02 toggle, 0x04/0x05 action (with press timer at +0xd).

Screen dispatch

Symbol Address Role
lotr2_game_ui_draw_screen_widgets 0x4d51a6 Frame render: draw widget tables for current lotr2_game_screen_mode
lotr2_game_ui_handle_screen_widget_input 0x4d57b4 lotr2_game_screen_mode_tick: per-mode widget click/hit-test before toolbar tests

Core draw / blit

Symbol Address Role
lotr2_game_ui_draw_widget_table 0x414611 Walk descriptor table; blit tiles; mark dirty
lotr2_game_ui_blit_tile_at 0x4147c9 Blit single tile from UI tile sheet at coords
lotr2_game_ui_store_widget_positions 0x41459b Copy widget origins into blit globals
lotr2_game_ui_draw_sliders 0x41487e Draw slider track, thumb, value line
lotr2_game_ui_redraw_scroll_list 0x413d65 Redraw bottom-panel scroll list rows

Hit-test / input

Symbol Address Role
lotr2_game_ui_handle_widget_click 0x415021 AABB hit-test, press timers, callback dispatch
lotr2_game_ui_hit_test_widget_table 0x4159ce Return 1-based widget index under cursor
lotr2_game_ui_hit_test_rect_table 0x415ba9 Generic rect-table hit test
lotr2_game_ui_clear_widget_pressed 0x414faf Clear press-state on widget entries
lotr2_game_ui_hit_test_slider 0x414a02 Slider decrement/track/increment zones
lotr2_game_ui_slider_set_from_mouse 0x414bad Map mouse X to slider value
lotr2_game_ui_hit_test_dialog_ok 0x415da6 Modal OK button (0x18×0x18) hit test

Modals, menus, popups

Symbol Address Role
lotr2_game_ui_enter_modal_mode 0x415cb4 Save screen mode; enter overlay mode 0x1e
lotr2_game_ui_enter_parameter_dialog 0x415d23 Save screen mode; enter parameter mode 0x21
lotr2_game_ui_enter_popup_menu 0x4154af Enter popup submenu mode 0x32
lotr2_game_ui_handle_popup_menu 0x415377 Popup menu input loop
lotr2_game_ui_run_menu_dialog 0x41570e Blocking menu dialog (measure/draw/hit-test)
lotr2_game_ui_run_wait_dialog 0x415c42 Blocking wait dialog with message pump
lotr2_game_ui_draw_modal_frame_small 0x414339 Small modal frame + title
lotr2_game_ui_draw_modal_frame_large 0x414397 Large parameter dialog frame

Screen-mode dialogs (pass 31d)#

Full-screen and panel overlays keyed by lotr2_game_screen_mode. Teardown when lotr2_game_screen_phase clears runs through lotr2_game_ui_on_screen_phase_exit.

Symbol Address Role
lotr2_game_ui_on_screen_phase_exit 0x417150 Per-mode exit/teardown dispatcher
lotr2_game_ui_enter_campaign_map_mode 0x41755f Campaign map draw/init (mode 0)
lotr2_game_ui_redraw_province_panel 0x417735 Province summary strip refresh
lotr2_game_ui_draw_trade_dialog 0x418ffe Trade (mode 0x14)
lotr2_game_ui_draw_move_army_dialog 0x41947c Move army (mode 0x15)
lotr2_game_ui_draw_disband_dialog 0x419648 Disband (mode 0x16)
lotr2_game_ui_draw_recruit_dialog 0x419abf Recruit (mode 0x19)
lotr2_game_ui_draw_save_wizard_chrome 0x41c77d Save/load wizard chrome (modes '5'/'6')
lotr2_game_ui_draw_save_slot_list 0x41c950 Save slot list inside wizard
lotr2_game_ui_draw_options_dialog 0x41cd6c Options (mode '9')
lotr2_game_ui_enter_merchant_screen 0x41ddbd Merchant overlay (mode 0x08)
lotr2_game_ui_enter_armoury_screen 0x41fcc1 Armoury overlay (mode 0x0a)

Castle interior / gateway UI (pass 37)#

Armoury refresh, province overview, event popups, and gateway screen callees in VA 0x41f0000x425fff. See symbol-map.md Pass 37.

Symbol Address Role
lotr2_game_ui_refresh_armoury_screen 0x41ff10 Reload armoury assets after dialog dismiss (mode 0x0d0x0a)
lotr2_game_ui_draw_armoury_stat_rows 0x420005 Draw 8 armoury build-slot stat rows
lotr2_game_ui_blit_armoury_build_slots 0x420243 Blit active build-slot sprites
lotr2_game_ui_draw_armoury_weapon_panel 0x4202e3 Load/draw selected weapon type PL8 panel
lotr2_game_ui_draw_province_overview_panel 0x420470 Province gold/pop summary (mode 0x17)
lotr2_game_ui_enter_post_siege_screen 0x4210d0 Post-siege battle result screen (mode 0x11)
lotr2_game_ui_enter_siege_battle_prep_screen 0x4215a8 Siege battle prep backdrop (mode 0x1b)
lotr2_game_ui_draw_diplomacy_minimap_panel 0x422acd Diplomacy minimap overlay (mode 0x18)
lotr2_game_ui_present_province_event_popup 0x422da2 Present province event modal; owned vs generic
lotr2_game_ui_setup_province_event_chrome 0x423c6e Event popup panel layout from lotr2_game_province_event_flags
lotr2_game_ui_draw_province_event_body 0x423f78 Event popup body text and icons
lotr2_game_ui_enter_gateway_screen 0x425f59 Gateway/new-game screen (gateway.256)
lotr2_game_ui_draw_number_with_table_caption 0x4228d7 Shared number + table-8 caption helper
lotr2_game_ui_draw_status_bar_overlay 0x421a97 Bottom status strip and debug mem overlay

Castle overview rows (lotr2_game_ui_build_overview_row_list, lotr2_game_ui_draw_overview_*_row) and mode-5 panel setup are pass 33 — see symbol-map.md Pass 32d.

Wizard step renderers (pass 39)#

New-game / save-load wizard draw chain for screen mode 0x1f, continuing from pass 37 gateway entry. Sub-steps dispatched via lotr2_game_wizard_step (1–8). See symbol-map.md Pass 39.

Symbol Address Role
lotr2_game_wizard_load_step_assets 0x42634c Load gateway/custom/panels2 assets for current wizard step
lotr2_game_wizard_redraw_current_step 0x426498 Redraw dispatcher; branches on lotr2_game_wizard_step
lotr2_game_wizard_draw_loading_message 0x426596 Loading overlay + present frame
lotr2_game_wizard_draw_step1_panel 0x426648 Step 1 intro panel
lotr2_game_wizard_draw_step1_option_rows 0x4266d7 Step 1 selectable option rows
lotr2_game_wizard_draw_step2_panel 0x426849 Step 2 panel
lotr2_game_wizard_draw_step2_option_rows 0x4268a5 Step 2 option rows
lotr2_game_wizard_draw_step3_load_panel 0x426a8c Step 3 save/load slot panel
lotr2_game_wizard_draw_step4_name_panel 0x426aa1 Step 4 game name text input
lotr2_game_wizard_draw_step4_mp_options 0x426b7a Step 4 MP host/client option rows
lotr2_game_wizard_blit_player_slot_icons 0x426c9c Player slot icon sprites
lotr2_game_wizard_draw_name_text_input 0x426dd3 Name field + blinking cursor
lotr2_game_wizard_draw_step5_sp_setup 0x426e9b Step 5 SP game setup (map + players)
lotr2_game_wizard_draw_step6_mp_setup 0x426f4e Step 6 MP host setup
lotr2_game_wizard_draw_config_value_rows 0x427073 Config value label rows
lotr2_game_wizard_draw_map_size_selector 0x427191 Map size minimap + selector bar
lotr2_game_wizard_draw_player_slot_rows 0x4273dc Nation/culture player slot rows
lotr2_game_wizard_draw_step7_player_count 0x4275e7 Step 7 player-count panel
lotr2_game_wizard_draw_mp_player_list 0x427747 MP connected player name list
lotr2_game_wizard_draw_step6_name_field 0x427853 MP ruler name input overlay
lotr2_game_wizard_draw_step8_panel 0x427968 Step 8 final confirmation panel
lotr2_game_ui_draw_siege_assault_prep_panel 0x427a17 Siege assault prep (mode 0x1d; sgeplans.pl8)

Wizard + turn actions (pass 29c, pass 38)#

Symbol Address Role
lotr2_game_wizard_on_start_step 0x43b316 Wizard turn-phase dispatch; MP lobby start
lotr2_game_wizard_apply_config_choice 0x43b7f7 Apply wizard config row; MP net msg 9
lotr2_game_wizard_open_options_modal 0x43bb91 Open options modal (lotr2_game_ui_enter_modal_mode)
lotr2_game_wizard_cancel_and_resume 0x43bbb5 Cancel wizard; SP tick or MP net opcode 0x19
lotr2_game_action_enter_diplomacy 0x43ccf5 Enter diplomacy screen mode 0x20
lotr2_game_action_toggle_castle_view 0x43cad2 Toggle castle siege map view
lotr2_game_action_enter_replay_mode 0x43cdb7 Enter replay (mode 8); SP stat apply or MP opcode 0x28
lotr2_game_action_set_replay_speed_campaign 0x43d072 Set campaign replay speed; s068_02_wav
lotr2_game_action_set_replay_speed_battle 0x43d10c Set battle replay speed; s068_01_wav
lotr2_game_action_build_queue_increment_slot 0x43d46b Increment armoury build-queue slot
lotr2_game_action_build_queue_decrement_slot 0x43d4ed Decrement build-queue slot
lotr2_game_action_enter_diplomacy_text_mode_1_6 0x43dbad–0x43dd40 Diplomacy text-input sub-modes (screen 0x1a)
lotr2_game_action_apply_diplomacy_text_submit 0x43de2a Submit diplomacy text; SP queue or MP 0x48/0x49
lotr2_game_ui_hit_test_tax_slider 0x43d820 Tax slider hit-test; calls lotr2_game_apply_tax_rate

Pass 38 renamed 56 symbols in 0x0043b0000x0043dfff (turn-action table tail, lobby flags, replay speed, build-queue ops). Empty table slots use lotr2_game_action_noop_reserved_01_20 (pass 48; was hex-suffix noop_c*) — see symbol-map.md Pass 38/48 and tools/ghidra/game_action_noop_renames_pass48.py.

Text input cursor (pass 33a)#

Symbol Address Role
lotr2_game_ui_text_input_on_return 0x406931 Enter key; sets selection max
lotr2_game_ui_text_input_on_backspace 0x406946 Backspace; cursor left + delete
lotr2_game_ui_text_input_on_delete_key 0x406969 Delete key at cursor
lotr2_game_ui_text_input_toggle_insert_mode 0x406979 Insert/overtype toggle
lotr2_game_ui_text_input_cursor_leftcursor_end 0x406992–0x4069e7 Arrow/home/end cursor movement
lotr2_game_ui_text_input_update_length 0x406c1f Recompute buffer length; sim-tick callee

RNG + distance helpers (pass 33b)#

Symbol Address Role
lotr2_game_ui_advance_frame_counters 0x4092c2 Blink/animation counter tick
lotr2_game_rng_lfsr_shift_wide_pair 0x4093fb 31-bit LFSR (wide pair)
lotr2_game_rng_bounded 0x40954e Bounded random via LFSR
lotr2_game_dist_manhattan 0x409862 Manhattan distance; combat + map
lotr2_game_dist_chebyshev 0x409901 Chebyshev (max axis) distance

Campaign map entity draw (pass 33c)#

Symbol Address Role
lotr2_game_ui_campaign_map_redraw_tick 0x40a9d0 Per-frame campaign map redraw entry
lotr2_game_ui_draw_map_entities_layered 0x40ab7f Full layered entity pass
lotr2_game_ui_draw_map_entities_simple 0x40ad98 Simple entity chain blit
lotr2_game_ui_draw_map_entities_with_badges 0x40af13 Entity + building badge pass

Campaign map entity draw tail (pass 46)#

Completes pass 33c: column row passes, stamp/clip dispatch, stack-icon blit.

Symbol Address Role
lotr2_game_ui_draw_map_column_interior_row 0x40b3f9 Interior column cells in layered draw
lotr2_game_ui_draw_map_column_bridge_row 0x40b53f Selected-column bridge row (pass types 0/3/4)
lotr2_game_ui_prep_map_entity_stamp 0x40bcd3 Entity metadata + stamp dispatch by pass type
lotr2_game_ui_stamp_map_entity_pl8 0x40bf85 PL8 stamp router for map entities
lotr2_game_ui_prep_map_entity_clip_blit 0x40c4cc Clip-blitz path for map entity sprites
lotr2_game_ui_blit_entity_stack_icon 0x40cab2 Stack-count icon sprite (entity flag 0x80)
lotr2_gfx_stamp_map_entity_stride282 0x45d460 Inline stride-0x282 row stamp helper

See symbol-map.md Pass 46 and tools/ghidra/game_renames_pass46.py.

Castle overview panel (pass 33d)#

Symbol Address Role
lotr2_game_ui_build_overview_row_list 0x417e20 Build overview row type list for player
lotr2_game_ui_draw_overview_farm_row 0x41800e Farm/production overview row
lotr2_game_ui_draw_overview_lumber_row 0x418199 Lumber overview row
lotr2_game_ui_setup_mode5_panel 0x41a02d Mode 5 castle panel setup

Campaign UI blit + LZSS (pass 29d)#

Symbol Address Role
lotr2_game_ui_blit_ui_sprite 0x410f4c Primary UI sprite blit from global sheet
lotr2_game_ui_draw_map_entity_chain 0x40dd4a Draw linked map entity icons
lotr2_game_save_compress_buffer 0x4130b8 LZSS compress (save blob helper)
lotr2_game_save_decompress_buffer 0x41342d LZSS decompress

End-turn economy / diplomacy (pass 29e)#

Symbol Address Role
lotr2_game_end_turn_tick 0x4513b0 Season orchestrator (events, tax, morale)
lotr2_game_end_turn_all_players 0x4514b0 Per-player economy + diplomacy tick loop
lotr2_game_apply_season_random_events 0x451785 Random seasonal event dispatch
lotr2_game_apply_tax_income 0x452602 Apply tax income; update map stamps
lotr2_game_diplomacy_plan_slot 0x4578cf Plan diplomacy slot spend/target
lotr2_game_allocate_prosperity 0x457c9f Distribute prosperity across buckets

Province development / allocation sliders (pass 44)#

Symbol Address Role
lotr2_game_resolve_capital_sieges 0x458e96 End-turn province build completion; calls development helpers
lotr2_game_deduct_province_development_stock 0x459285 Deduct faction food/goods during active province build
lotr2_game_update_province_development_targets 0x4593fe Refresh growth targets after build progress check
lotr2_game_calc_province_development_pct 0x45956c Province build completion percent
lotr2_game_recount_faction_developing_castles 0x459619 Count in-progress castles per faction (map cell status 0x03)
lotr2_game_init_new_campaign_players 0x459709 New-game defaults for all player records
lotr2_game_recalc_allocation_sliders 0x459bd7 Recompute tax/allocation slider rows for local player
lotr2_game_refresh_minimap_overlay_colors 0x45a173 Update minimap overlay indices before pixel paint

See symbol-map.md Pass 44 and tools/ghidra/game_renames_pass44.py.

See symbol-map.md Pass 29 and tools/ghidra/game_renames_pass29.py for the full 219-symbol list.

Turn simulation / campaign AI (pass 34)#

Symbol Address Role
lotr2_game_ai_turn_phase_tick 0x4b0980 Sim-tick AI driver; branches on lotr2_game_ai_turn_phase (1–7)
lotr2_game_ai_advance_turn_phase 0x4b30a9 Advance outer phase; reset step counters
lotr2_game_ai_player_step_loop 0x4b0cde Per-player 14-step AI loop (lotr2_game_ai_player_step)
lotr2_game_ai_downgrade_inactive_garrisons 0x4b10c1 First step of lotr2_game_end_turn_tick for AI garrisons
lotr2_game_ai_compute_player_rankings 0x4b113f Sort players by score after end-turn (phase 7)
lotr2_game_ai_turn_state_init 0x4b2bed Initialize AI turn state at new game
lotr2_game_ai_init_new_game_players 0x4b1ff1 Reset player records; assign capitals
lotr2_game_ai_process_diplomacy_queue 0x4b86e5 Drain per-player diplomacy action queue
lotr2_game_ai_build_recruit_pass 0x4b3f8c AI build/recruit pass (step 5)
lotr2_game_ai_army_attack_plan 0x4b5c09 Pick attack target; spawn army (step 9)
lotr2_game_battle_on_army_enter_province 0x4bf708 Army tile entry → battle or conquest
lotr2_game_conquest_capture_province 0x4bfd36 Peaceful province capture

Army movement AI (lotr2_map_ai_*) documented in map/MODULE.md. Full 97-symbol list: tools/ghidra/game_renames_pass34.py.

Castle / build / recruit (0x4c, pass 36)#

Army slot records use stride 0x1a4 (lotr2_game_army_record_table base). Build queue sub-fields at +0x52/+0x54/+0x56 (+6 per slot, 3 slots). Province records remain stride 0x300.

Symbol Address Role
lotr2_game_build_find_active_slot 0x4c0eca Scan build slots from lotr2_game_build_slot_index; advance progress
lotr2_game_build_tick_slot_progress 0x4c0f17 Per-turn build queue labor allocation
lotr2_game_build_place_on_province 0x4c03db Place building/army on province; merge or garrison
lotr2_game_build_start_castle_action 0x4c0842 Enter castle build screen (lotr2_game_screen_mode = 0x1d)
lotr2_game_recruit_spawn_army 0x4c23f1 Recruit unit stack; spawn army; debit gold
lotr2_game_army_merge_into 0x4c2ad7 Merge two army slots (strength + build queue)
lotr2_game_battle_start_siege_assault 0x4c14bb Begin siege assault; modes 0x12/0x13
lotr2_game_battle_resolve_siege_outcome 0x4c3cde Post-siege capture, army release, end-turn
lotr2_game_upkeep_tick_army_slots 0x4c56eb Upkeep starvation per army slot
lotr2_game_sum_faction_army_strength 0x4c5d22 Faction total army strength for UI/budget
lotr2_game_castles_dat_write_player_layout 0x4c8cf3 Write siege castle layout slice to castles.dat
lotr2_gfx_blit_castle_mask_to_backbuf 0x4cfa6b Castle interior view mask blit to backbuf

Full 109-symbol list: tools/ghidra/game_renames_pass36.py.

Symbol Address Role
lotr2_game_ui_apply_player_turn 0x42548 Apply turn phase for one player locally
flowchart TD
  idleTick[lotr2_game_idle_tick]
  uiDispatch[lotr2_game_ui_input_dispatch]
  scrollPick[lotr2_game_pick_scroll_dir]
  scrollApply[lotr2_game_screen_tick]
  clickDispatch[lotr2_game_dispatch_click]
  mapClick[lotr2_game_handle_map_click]
  panelClick[lotr2_game_handle_panel_click]
  selectProv[lotr2_game_select_province]
  screenMode[lotr2_game_screen_mode]
  mainTick[lotr2_game_screen_mode_tick]

  idleTick --> uiDispatch
  uiDispatch --> mainTick
  mainTick --> screenMode
  scrollPick --> scrollApply
  clickDispatch --> mapClick
  clickDispatch --> panelClick
  mapClick --> selectProv

See symbol-map.md passes 25–27 for full rename tables.

Asset loading (init chain)#

Symbol Address Role
lotr2_game_load_base_assets 0x4af92e Load 13× base01.256 palette/tile files
lotr2_game_reset_pl8_ptrs 0x4af1ce Zero PL8 heap pointer globals
lotr2_game_load_pl8_assets 0x4ae81a Load base1a.pl8 / misc_cty.pl8 slots
lotr2_asset_verify_pl8 0x4c9eaa Verify/open PL8 with decompress fallback

Sim tick teardown / map#

See map/MODULE.md for the full map subsystem (pass 14–15).

Symbol Address Role
lotr2_game_free_pl8_assets 0x4af233 Free PL8 slot malloc blocks
lotr2_game_free_ui_assets 0x4af43b Free screen/UI malloc blocks
lotr2_game_free_map_ptrs 0x4af721 Free map-related malloc blocks
lotr2_map_load_new_game 0x477580 New-game map load orchestrator
lotr2_map_load_from_dat 0x477610 Read l2_maps.dat into province grid
lotr2_map_init_province_grid 0x4346a1 Initialize province index grid and facing
lotr2_game_reset_map_state 0x4ae5ae Reset map/view globals at sim-tick start
lotr2_game_clear_map_ptrs 0x4af6ee Zero map malloc pointer globals after free

Sim tick init (game-level)#

Symbol Address Role
lotr2_game_load_system_ui_pl8 0x4afaf1 Load system2.pl8 UI sheet variant
lotr2_game_load_misc_sel_pl8 0x4af109 Load misc_sel.PL8 after asset teardown
lotr2_game_set_difficulty_tier 0x4c896d Set difficulty tier index from scenario param
lotr2_game_sync_ui_scroll_offset 0x43ba95 Recompute UI scroll offset from season counter
lotr2_game_ui_queue_screen_action 0x489419 Queue a UI screen action by slot index
lotr2_game_ui_restore_screen_mode 0x489491 Restore prior screen mode after action overlay

Recovered globals#

Symbol Address Role
lotr2_ui_slot_flags 0x50c3a0 20-byte UI slot availability flags
lotr2_ui_slot_count 0x50c390 Active slot count (init to 0x14)
lotr2_game_tick_pending 0x5efe24 Non-zero when sim tick should run
lotr2_game_screen_mode 0x507ab8 Current screen/mode id
lotr2_game_screen_phase 0x59347c Screen phase (init vs active)
lotr2_game_wizard_step 0x593494 Wizard sub-step for mode 0x1f
lotr2_game_turn_phase 0x5d9c88 Turn-phase index 1–4

lotr2_winmain call tree#

flowchart TD
  winmain[lotr2_winmain @ 0x41680b]
  loadInf[lotr2_game_load_lords2_inf]
  loadEng[lotr2_asset_read_file l2_eng]
  loadCfg[lotr2_game_load_config]
  createWin[lotr2_ui_create_main_window]
  initAudio[lotr2_audio_init_directsound]
  easterEgg[lotr2_game_check_easter_egg]
  intro[lotr2_game_play_intro_or_enable_tick]
  initGame1[lotr2_game_init_phase1]
  loadBase[lotr2_game_load_base_assets]
  initGame2[lotr2_game_init_phase2]
  loadPl8[lotr2_game_load_pl8_assets]
  resetUi[lotr2_game_reset_ui_slots]
  loop[Message loop]
  pollNet[lotr2_net_poll_messages]
  idleTick[lotr2_game_idle_tick]
  tickSP[lotr2_game_tick_sp]
  tickMP[lotr2_game_tick_mp]
  simTick[lotr2_game_sim_tick]
  frameAdv[lotr2_game_frame_advance]

  winmain --> loadInf
  winmain --> loadEng
  winmain --> loadCfg
  winmain --> createWin
  createWin --> initAudio
  winmain --> easterEgg
  winmain --> intro
  winmain --> initGame1
  initGame1 --> loadBase
  initGame1 --> initGame2
  initGame2 --> loadPl8
  initGame2 --> resetUi
  resetUi --> loop
  loop --> pollNet
  loop --> idleTick
  idleTick --> tickSP
  idleTick --> tickMP
  tickSP --> simTick
  tickMP --> simTick
  idleTick --> frameAdv

Startup sequence (ordered)#

  1. lotr2_game_load_lords2_inf — read lords2.inf config
  2. Load English string table: lotr2_asset_read_file("l2_eng", …)
  3. lotr2_game_load_config — parse l2.ini, resolve _sierra.ini path
  4. lotr2_ui_create_main_window — register class, create HWND
  5. lotr2_audio_init_directsound + lotr2_audio_start_stream_from_name — music stream
  6. lotr2_game_check_easter_egg — optional pumpkin WAV check
  7. lotr2_game_play_intro_or_enable_tick — intro Smacker or enable tick
  8. lotr2_game_init_phase1 — load base .256 assets via lotr2_game_load_base_assets
  9. lotr2_game_init_phase2 — load PL8 assets via lotr2_game_load_pl8_assets
  10. lotr2_game_reset_ui_slots — clear UI slot table; lotr2_net_session_active = 0
  11. Enter message loop

Main loop (idle path)#

When PeekMessageA returns false:

  1. lotr2_net_poll_messages — DirectPlay receive pump
  2. If window visible (DAT_005076c0 != 0): lotr2_game_idle_tick
  3. Checks DirectDraw surface state via lotr2_gfx_surfaces_restorable; lotr2_gfx_on_surface_lost invalidates the main window on failure
  4. When lotr2_game_tick_pending: dispatches lotr2_game_tick_sp (SP) or lotr2_game_tick_mp (MP), then lotr2_game_sim_tick
  5. Else: lotr2_game_frame_advance
  6. Else: WaitMessage

Address ranges (pass 29 update)#

Range Subsystem Status
0x004065xx–0x00408exx l2_eng text draw + UI line/panel primitives Mapped (pass 29a)
0x0040dxxx–0x00413xxx Campaign map entity blit + LZSS compress Mapped (pass 29d)
0x00414xxx–0x00416xxx UI widget toolkit (draw, hit-test, modals) Mapped (pass 29b)
0x00417xxx–0x0041fxxx Screen-mode dialog draw / teardown Mapped (pass 31d; overview rows pass 33)
0x00425xxxx Wizard step renderers (gateway wizard draw chain) Mapped (pass 39)
0x0043bxxx–0x0043dxxx Wizard cluster + turn-action table Mapped (pass 29c + pass 38 + pass 51 semantic)
0x004451xx–0x00458exx End-turn economy / diplomacy orchestration Mapped (pass 29e)
0x0043axxx Screen scroll, click dispatch Mapped (pass 21–24)
0x004448xx+ Net handlers Mapped (net/MODULE.md)
0x00456xxx Economy tick player (lotr2_game_economy_tick_player) Mapped (pass 24)
0x0048xxxx–0x004affff Battle, siege Mapped (combat/MODULE.md)
0x004069xx–0x00406cxx Text input cursor/edit helpers Mapped (pass 33a)
0x004092xx–0x004099xx Frame counters, LFSR RNG, distance Mapped (pass 33b)
0x0040a9xx–0x0040b8xx Campaign map entity draw passes Mapped (pass 33c + pass 46)
0x00417exx–0x0041a6xx Castle overview + mode 5 panel Mapped (pass 33d)
0x00406xxx (early) Config/string parsers, CRT helpers Partial
0x0043exxx–0x00444xxx Campaign UI / sync (turn finalize, battle exit, MP wrappers) Mapped (pass 35)
0x004b0000–0x004bffff Turn sim / campaign AI (phase dispatch, 14-step loop) Mapped (pass 34)
0x004d4000–0x004dafff Combat battle viewport / frame glue (shared with combat) Mapped (pass 43; see combat/MODULE.md)

Dependencies#

Data files#

  • lords2.inf — install/save-path config (read at startup via save/MODULE.md)
  • l2_eng — English text table (loaded at startup)
  • l2.ini — paths and display settings
  • _sierra.ini — Sierra install path (resolved from l2.ini)
  • base01.256 … — palette/tile base assets (13 files)
  • base1a.pl8, misc_cty.pl8 — PL8 sprite sheets
  • startXXX.pl8 — start screen (verified in sim tick)
  • map01.pl8 — map tile sheets
  • l2_maps.dat — province map data

Skirmish mode (GoG only)#

Single-battle flow without campaign map — documented in ../../skirmish/README.md. Retail LORDS2.EXE lacks this mode.

Symbol GoG addr Role
FUN_004b7a9a 0x004b7a9a SIGS init; GameType=SkirmishDAT_0057cb38
FUN_00433155 0x00433155 Route MP to wizard 0xb vs campaign step 8
FUN_0042b7f7 0x0042b7f7 Set DAT_0057a0f0 skirmish-active, battle view
FUN_004329ec 0x004329ec Start skirmish battle (SP/MP)
lotr2_game_ui_on_battle_end_return 0x0043be65 Skirmish → score screen 0x2e vs campaign 0x13
lotr2_game_sim_tick 0x00497a34 Skip map load when DAT_0057a0f0==1

Wizard steps 0xb (MP) / 0xc (SP); screen modes 0x1f setup, .// scores, 0x1c gateway return.

Open questions#

  • Remaining lotr2_game_ui_draw_* cluster (~200 symbols) — pass 51c batch 1
  • lotr2_game_wizard_draw_* step naming — pass 51c batch 2
  • Full 8-byte lotr2_map_province_cell field layout (+0..+7)
  • Confirm where lotr2_gfx_dirty_left / right/top/bottom bounds are consumed
  • Recover remaining game-state globals in 0x0055xxxx (see lotr2_game_state.md)
  • Recover player-record struct at army stride 0x1a4 (lotr2_game_army_record_table base)
  • Split early 0x4060 config/string parsers into dedicated submodule
  • Defer DAT_005076c0 / DAT_00505424 visibility globals until wndproc cluster
  • Add provenance for multi-line Ghidra signatures (split void __cdecl / name lines)