Skip to main content

ltk_overlay/
lib.rs

1//! WAD overlay builder for League of Legends mods.
2//!
3//! This crate builds WAD overlay directories from a set of enabled mods. The overlay
4//! contains patched copies of game WAD files with mod content applied on top, which a
5//! patcher DLL can redirect the game to load instead of the originals.
6//!
7//! # How It Works
8//!
9//! The overlay build process has four stages:
10//!
11//! 1. **Indexing** — Scan the game's `DATA/FINAL` directory and mount every
12//!    `.wad.client` file. Build two indexes:
13//!    - *Filename index*: WAD filename (case-insensitive) -> filesystem paths
14//!    - *Hash index*: chunk path hash (`u64`) -> list of WAD files containing it
15//!
16//! 2. **Collecting overrides** — For each enabled mod (in order), read its layer
17//!    structure and WAD override files through the [`ModContentProvider`] trait.
18//!    Each override file is resolved to a `u64` path hash (either parsed from a hex
19//!    filename or computed from the normalized path). All overrides are collected
20//!    into a single `HashMap<u64, Vec<u8>>`. When multiple mods override the same
21//!    hash, the first mod in the list (highest priority) wins.
22//!
23//! 3. **Distributing to WADs** — Using the hash index, each override is distributed
24//!    to *every* game WAD that contains that path hash ("cross-WAD matching"). This
25//!    means a single skin texture override will automatically be applied to both
26//!    the champion WAD and any map WAD that shares the same asset.
27//!
28//! 4. **Patching WADs** — For each affected game WAD, a patched copy is built in the
29//!    overlay directory. The patched WAD contains all original chunks plus the
30//!    overrides, with optimizations for audio files (kept uncompressed) and chunk
31//!    deduplication.
32//!
33//! # Content Provider Abstraction
34//!
35//! Mod content is accessed through the [`ModContentProvider`] trait, which decouples
36//! the builder from any particular storage format. Implementations can read from:
37//!
38//! - Filesystem directories ([`FsModContent`])
39//! - `.modpkg` archives ([`ModpkgContent`])
40//! - `.fantome` ZIP archives ([`FantomeContent`])
41//!
42//! # Incremental Rebuild
43//!
44//! After a successful build, an `overlay.json` state file is persisted (in the
45//! *state directory*) containing the list of enabled mod IDs, a game directory
46//! fingerprint, and per-WAD override fingerprints. On the next build:
47//!
48//! - **Exact match**: mod list, game fingerprint, and all per-WAD fingerprints match,
49//!   and every overlay WAD exists on disk — the build is skipped entirely.
50//! - **Incremental**: game fingerprint matches but the mod list changed. Per-WAD
51//!   override fingerprints are compared and only WADs whose inputs changed are
52//!   re-patched. Stale WADs (no longer needed) are removed.
53//! - **Full rebuild**: game fingerprint or state version changed — all overlay WADs
54//!   are wiped and rebuilt from scratch.
55//!
56//! The game index (`GameIndex`) is also cached to disk to avoid re-mounting every
57//! WAD file on subsequent builds when the game hasn't been patched.
58//!
59//! # Example
60//!
61//! ```no_run
62//! use ltk_overlay::{OverlayBuilder, EnabledMod, FsModContent};
63//! use camino::Utf8PathBuf;
64//!
65//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
66//! let game_dir = Utf8PathBuf::from("C:/Riot Games/League of Legends/Game");
67//! let profile_dir = Utf8PathBuf::from("C:/Users/.../profiles/default");
68//! let overlay_root = profile_dir.join("overlay");
69//!
70//! let mut builder = OverlayBuilder::new(game_dir, overlay_root, profile_dir)
71//!     .with_progress(|progress| {
72//!         println!("Stage: {:?}, Progress: {}/{}",
73//!             progress.stage, progress.current, progress.total);
74//!     });
75//!
76//! builder.set_enabled_mods(vec![
77//!     EnabledMod {
78//!         id: "my-mod".to_string(),
79//!         content: Box::new(FsModContent::new(Utf8PathBuf::from("/path/to/mod"))),
80//!         enabled_layers: None,
81//!     },
82//! ]);
83//!
84//! let result = builder.build()?;
85//! println!("Built {} WADs in {:?}", result.wads_built.len(), result.build_time);
86//! # Ok(())
87//! # }
88
89pub mod builder;
90pub mod content;
91pub mod error;
92pub mod fantome_content;
93pub mod game_index;
94pub mod meta_cache;
95pub mod modpkg_content;
96pub mod state;
97pub mod utils;
98pub mod wad_builder;
99
100// Re-export main public API.
101pub use builder::{
102    EnabledMod, ModWadReport, OverlayBuildResult, OverlayBuilder, OverlayProgress, OverlayStage,
103    BASE_LAYER_NAME,
104};
105pub use content::{FsModContent, ModContentProvider};
106pub use error::{Error, Result};
107pub use fantome_content::FantomeContent;
108pub use game_index::GameIndex;
109pub use modpkg_content::ModpkgContent;
110pub use state::OverlayState;