League Toolkit
Rust libraries for reading, editing and writing League of Legends file formats - WAD archives, property bins, textures, meshes, animations, map geometry and string tables.
Documentation - Guide - Changelog
What is here
One crate per format family, each usable on its own, plus league-toolkit: an umbrella crate that
re-exports them behind feature flags. Nothing in the workspace knows about mods, managers or
installers - these crates read and write what the game ships. Building mods on top of them is
league-mod's job.
| Crate | Version | Formats | What it is |
|---|---|---|---|
league-toolkit |
- | Umbrella crate; feature-gated re-exports of the crates below | |
ltk_wad |
.wad.client |
Archive reading, extraction and building; name resolution; signatures | |
ltk_meta |
.bin (PROP, PTCH) |
Property bins: the object tree, streaming views, property paths, override bins | |
ltk_texture |
.tex, .dds |
Decoding for every shipped format, encoding for BC1/BC3/BC7 and raw | |
ltk_mesh |
.skn, .scb, .sco |
Skinned and static meshes, with typed vertex-buffer accessors | |
ltk_anim |
.skl, .anm |
Skeletons, joints and animations | |
ltk_mapgeo |
.mapgeo |
Map environment geometry | |
ltk_rst |
.stringtable |
Riot String Tables: localized strings keyed by hash | |
ltk_ritobin |
ritobin text | The human-readable bin dialect: parse, print, round-trip | |
ltk_shader |
- | Shader table of contents, defines and loading | |
ltk_file |
- | File kind detection from magic bytes (LeagueFileKind) |
|
ltk_hash |
- | The hashes the formats key on: xxh64 for WAD paths, FNV-1a for bin names, ELF | |
ltk_primitives |
- | Geometry shared by the mesh and map crates: AABB, Sphere, Color |
|
ltk_io_ext |
- | Reader and writer extensions the format crates share |
ltk_wad, ltk_meta, ltk_texture and ltk_ritobin carry their own READMEs, which are the
reference for those surfaces. The rest document themselves on docs.rs.
The ritobin language itself is specified in
ritobin-lang, the home for its syntax and the
standards the ecosystem around it follows. ltk_ritobin is an implementation of that
specification.
Installation
The umbrella crate, with the subsystems you want:
[]
= { = "0.2", = ["wad", "meta", "texture"] }
Each subsystem is then a module: league_toolkit::wad, league_toolkit::meta, and so on.
Depending on a crate directly is equivalent, and keeps the dependency graph smaller:
[]
= "0.5"
= "0.8"
= "0.6"
ltk_mapgeo, ltk_ritobin, ltk_shader and ltk_io_ext are reachable only as direct
dependencies; the umbrella crate does not re-export them.
Feature flags
| Feature | Enables | Default |
|---|---|---|
anim |
ltk_anim |
yes |
file |
ltk_file |
yes |
hash |
ltk_hash |
yes |
mesh |
ltk_mesh |
yes |
meta |
ltk_meta |
yes |
primitives |
ltk_primitives |
yes |
texture |
ltk_texture |
yes |
wad |
ltk_wad |
yes |
rst |
ltk_rst |
no |
serde |
Serialize and Deserialize on ltk_wad, ltk_file, ltk_meta and ltk_rst types |
no |
For a minimal build, turn the defaults off and opt in:
[]
= { = "0.2", = false, = ["wad"] }
Individual crates carry flags of their own. ltk_wad picks its zstd and deflate backends
(zstd, ruzstd, rust_backend) - rust_backend compiles no C at all. ltk_texture gates BC7
encoding behind intel-tex; decoding never needs a feature.
Quick start
Read a WAD archive
A WAD keeps no file names. A chunk is keyed by the xxh64 of its lowercased path, so a lookup hashes the path, and a listing shows hashes until a hash table names them.
use File;
use Hash as _;
use ;
let mut wad = mount?;
println!;
let hash = hash_str;
if let Some = wad.chunks.get.copied
Extraction to disk, name recovery out of the bins, archive building and signature checking are all
in the ltk_wad README.
Decode a texture
use Tex;
use File;
let tex = from_reader?;
println!;
let surface = tex.decode_mipmap?;
surface.into_rgba_image?.save?;
Texture::from_reader covers the case where a file may be either .tex or .dds. Signed and
float formats keep their real values through as_pixels; into_rgba_image is a presentation
conversion, and it clamps. See the ltk_texture README.
Read a skinned mesh
use SkinnedMesh;
use File;
let mesh = from_reader?;
println!;
Vertex data comes out through typed accessors -
vertex_buffer().accessor::<Vec3>(ElementName::Position) - rather than one fixed vertex struct, so
a layout the format allows but a struct does not is still readable.
Read and build a property bin
use File;
use ;
// Read
let bin = from_reader?;
for in &bin.objects
// Build
let bin = builder
.dependency
.object
.build;
Object and property names are FNV-1a hashes of the lowercased string. Bin::from_reader parses
the whole file; BinStream mounts one and reads only the objects asked for, which is what makes
sweeping thousands of bins affordable. Property paths, override bins (PTCH) and the streaming
views are in the ltk_meta README.
Documentation
- docs.rs/league-toolkit - rustdoc for every crate
docs/LTK_GUIDE.md- a walkthrough with worked examplesdocs/design/- what a feature's surface and wire format aredocs/adr/- one record per architectural decision, with the options it beatdocs/prd/- why a feature exists, and its numbered requirements
Development
The toolchain is pinned to stable in rust-toolchain.toml, with rustfmt and clippy.
Workspace lints deny clippy::correctness and clippy::suspicious, and warn on perf, style
and complexity. CI runs cargo fmt --check, clippy, the build and the tests on every push and
pull request.
AI-assisted development
AI agents produce large, hard-to-review changesets. This repository answers that with a document trail rather than a tool pipeline: work is specified, decided and sliced in the repo before it is written, and each artifact is reviewable on its own.
| Document | Holds | Where |
|---|---|---|
| PRD | Why a feature exists, who asks for it, numbered requirements (FR-N) |
docs/prd/NNN-slug.md |
| ADR | One architectural decision: what forced it, what it beat, what it costs | docs/adr/NNNN-slug.md |
| Design doc | The API surface and the wire format | docs/design/<feature>.md |
| Ticket | One slice of implementable work, rendered to a GitHub issue | .scratch/<project>/issues/*.md |
The rule that keeps them readable: each cites the others rather than restating them. A design doc
cites requirements as FR-N and decisions as ADR-NNNN. Two copies of one argument drift.
GitHub issues are rendered from the ticket files. The repo is the source of truth, and an issue that disagrees with its ticket is fixed by re-rendering it, not by editing it on GitHub.
Claude Code users get five skills in .claude/skills/ that write and maintain all of this:
write-prd, write-adr, write-spec, write-ticket and sync-issues. Worked example: PRD-001
with ADR-0001 to ADR-0006 and docs/design/ptch-property-patches.md.
Contributors using AI agents SHOULD follow this workflow. A PR that arrives with no written
reasoning behind it may need extra review cycles. Day-to-day rules for agents are in
CLAUDE.md.
Project structure
league-toolkit
|-- crates
| |-- league-toolkit # umbrella crate
| |-- ltk_wad # WAD archives
| |-- ltk_meta # property bins
| |-- ltk_ritobin # ritobin text format
| |-- ltk_texture # textures
| |-- ltk_mesh # meshes
| |-- ltk_anim # skeletons and animations
| |-- ltk_mapgeo # map geometry
| |-- ltk_rst # string tables
| |-- ltk_shader # shaders
| |-- ltk_file # file kind detection
| |-- ltk_hash # hashes
| |-- ltk_primitives # geometric primitives
| |-- ltk_io_ext # I/O extensions
|-- docs
| |-- adr # architectural decision records
| |-- design # surface and format specs
| |-- prd # requirements
| |-- LTK_GUIDE.md # usage guide
|-- .scratch # ticket files, one directory per project
Releasing
release-plz handles versioning and publishing. Only feat, fix
and perf commits trigger a release: pushing one to main opens a draft release PR carrying the
version bumps and changelogs, and merging it publishes the affected crates to crates.io.
Related
- league-mod - the
.modpkgmod format, project packing, and the WAD overlay builder, all built on these crates - ltk-manager - the desktop mod manager
- ritobin-lang - the ritobin language specification, and the standards for the ecosystem around it
- wadtools - CLI for extracting, listing and
comparing
.wadarchives - Mimir - hash-to-path tables as compact memory-mapped
.hashdbfiles - lol-meta-wiki - documentation and a JSON API
for
.binmeta classes and properties - awesome-league - a curated list of tools, libraries and resources for League of Legends files, assets and mods
License
Licensed under the Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0).
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.