Save / load
Purpose
Game-state persistence: enumerate save files, read/write the main save blob,
sync the castles.dat tile slice, manage lords2.inf install config, and
dispatch save/load from the UI (including multiplayer .sva/.svb variants).
Key entry points
Core file I/O
| Symbol |
Address |
Role |
lotr2_save_load_from_file |
0x004c823e |
Load save: write field-table blocks; read 16×0x3200 from castles.dat |
lotr2_save_write_to_file |
0x004c838c |
Save game: read field-table blocks; write 16×0x3200 to castles.dat |
lotr2_save_enum_files |
0x004c9520 |
FindFirstFileA on *.sav/.sva/.svb; fill filename table |
lotr2_save_sort_file_list |
0x004c965b |
Sort enumerated save filenames |
lotr2_save_read_inf_fields |
0x004c89cd |
Read smaller inf field table (100 entries) from opened file |
lotr2_save_sanitize_filename |
0x004c8a6c |
Pad/truncate save filename to 0x20 chars |
lords2.inf config
| Symbol |
Address |
Role |
lotr2_game_load_lords2_inf |
0x004c8559 |
Startup: enum saves, read lords2.inf, apply defaults |
lotr2_game_write_lords2_inf |
0x004c8500 |
Write 0x41c-byte config block to lords2.inf |
lotr2_save_inf_stub |
0x004c864a |
Empty placeholder in inf load path |
lotr2_save_inf_on_load_success |
0x004c865c |
Post-read: increment load counter or reset defaults |
lotr2_save_inf_init_defaults |
0x004c86af |
Seed default gameplay/audio globals in config block |
lotr2_save_inf_init_audio_tiers |
0x004c88c4 |
Default difficulty/audio tiers; MP vs SP branch |
UI flow
| Symbol |
Address |
Role |
lotr2_save_ui_debounce_dispatch |
0x004c7de0 |
Timer dispatch for save, save-as, load screen modes |
lotr2_save_wait_mp_ready |
0x004c80a1 |
Post-save MP player-ready sync loop |
lotr2_save_restart_after_load |
0x004adc92 |
Rebuild map/PL8/army state after save or load |
lotr2_save_continue_lastturn |
0x004b0c1e |
Continue from lastturn.sav/.sva/.svb |
Field tables and globals
| Symbol |
Address |
Role |
lotr2_save_field_ptrs |
0x004fa280 |
Main save field pointer column (up to 500 entries) |
lotr2_save_field_sizes |
0x004fa284 |
Main save field size column; loop ends on size 0 |
lotr2_save_inf_field_ptrs |
0x004fa230 |
Inf field pointer column (100 entries) |
lotr2_save_inf_field_sizes |
0x004fa234 |
Inf field size column |
lotr2_save_file_names |
0x00504900 |
100 × 0xd-byte enumerated filename table |
lotr2_save_file_count |
0x00504544 |
Count of enumerated save files |
lotr2_save_write_ok |
0x005d9c78 |
Set after successful save write |
lotr2_save_io_failed |
0x005bc724 |
Non-zero when save/load I/O returns error |
Save file layout
Decompiled name swap — the Ghidra export has the save/load function names
reversed. Open-flag evidence (0x8301 = write/create/truncate vs
0x8000 = read-only):
| Ghidra name |
Address |
Actual role |
lotr2_save_load_from_file |
0x004c823e |
Save: opens file write-only; crt_writes field blocks into it |
lotr2_save_write_to_file |
0x004c838c |
Load: opens file read-only; __reads blocks into RAM; calls lotr2_save_restart_after_load |
The behavioral spec uses the correct roles; the symbols retain the Ghidra
names until a rename pass updates src/.
Full on-disk layout is specified in docs/spec/save/save-file-format.md.
flowchart LR
save["lotr2_save_load_from_file\n(save path — name reversed)"]
load["lotr2_save_write_to_file\n(load path — name reversed)"]
fields["lotr2_save_field_ptrs + lotr2_save_field_sizes\n203 blocks, 266 940 bytes total"]
castles["castles.dat slice\n16 × 0x3200 = 204 800 bytes"]
save --> fields
load --> fields
save --> castles
load --> castles
- 203 sequential field blocks from the static
(ptr, size) table at
lotr2_save_field_ptrs (0x004fa280); loop ends on size == 0 (entry 203).
Total: 266,940 bytes (0x412bc).
- 16 × 0x3200 bytes of castle/siege tile data appended, synced with
castles.dat. Grand total: 471,740 bytes (fixed; no header/checksum).
Regenerate the field-block table with:
python3 tools/ghidra/dump_save_field_table.py
File extensions
| Extension |
Role |
.sav |
Single-player saves (*.sav, lastturn.sav) |
.sva |
Multiplayer host saves (*.sva, lastturn.sva, diverged.sva) |
.svb |
Multiplayer client saves (*.svb, lastturn.svb, diverged.svb) |
Dependencies
Data files
*.sav / *.sva / *.svb — full game-state saves
lords2.inf — 0x41c-byte install/config block (not a full save)
castles.dat — 16×0x3200 tile slice appended to each save
lastturn.* — auto-save at end of turn (role-specific extension in MP)
diverged.sva / diverged.svb — MP desync recovery loads
Open questions