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;