<div align="center">
# π οΈ League Toolkit
**Rust library for parsing, editing, and writing League of Legends file formats**
[](https://github.com/LeagueToolkit/league-toolkit/actions/workflows/ci.yml)
[](https://crates.io/crates/league-toolkit)
[](https://docs.rs/league-toolkit)
[](https://github.com/LeagueToolkit/league-toolkit/blob/main/LICENSE)
[Documentation](https://docs.rs/league-toolkit) β’ [Crates.io](https://crates.io/crates/league-toolkit) β’ [Changelog](CHANGELOG.md)
</div>
---
## β¨ Features
- π¦ **WAD Archives** β Read and write `.wad.client` asset containers
- π¨ **Textures** β Decode/encode `.tex` and `.dds` formats
- π§ **Meshes** β Parse skinned (`.skn`) and static (`.scb`/`.sco`) meshes
- 𦴠**Animation** β Load skeletons (`.skl`) and animations (`.anm`)
- π **Property Bins** β Read/write `.bin` configuration files
- πΊοΈ **Map Geometry** β Parse `.mapgeo` environment assets
- π§ **Modular** β Use individual crates or the umbrella crate
---
## π¦ Installation
Add the umbrella crate to your project:
```toml
[dependencies]
league-toolkit = { version = "0.2", features = ["wad", "mesh", "texture"] }
```
Or use individual crates for a smaller dependency footprint:
```toml
[dependencies]
ltk_wad = "0.2"
ltk_texture = "0.5"
ltk_mesh = "0.3"
```
---
## π Quick Start
### Reading a WAD Archive
```rust
use std::fs::File;
use ltk_wad::Wad;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = File::open("assets.wad.client")?;
let mut wad = Wad::mount(file)?;
println!("Archive contains {} files", wad.chunks().len());
// Decode a specific chunk
let (mut decoder, chunks) = wad.decode();
for chunk in chunks.values().take(5) {
let data = decoder.load_chunk_decompressed(chunk)?;
println!("Chunk {:016x}: {} bytes", chunk.path_hash(), data.len());
}
Ok(())
}
```
### Decoding a Texture
```rust
use ltk_texture::Tex;
use std::fs::File;
let tex = Tex::from_reader(&mut File::open("texture.tex")?)?;
let surface = tex.decode_mipmap(0)?;
surface.into_rgba_image()?.save("output.png")?;
```
See the [`ltk_texture` README](crates/ltk_texture/README.md) for supported formats, raw pixel data access, and encoding.
### Parsing a Skinned Mesh
```rust
use ltk_mesh::SkinnedMesh;
use std::fs::File;
let mesh = SkinnedMesh::from_reader(&mut File::open("champion.skn")?)?;
println!("Vertices: {}", mesh.vertex_buffer().vertex_count());
println!("Submeshes: {}", mesh.ranges().len());
```
### Working with Property Bins
```rust
use ltk_meta::concrete::{values, Bin, BinObject};
use std::fs::File;
// Read
let bin = Bin::from_reader(&mut File::open("data.bin")?)?;
for (path_hash, object) in &bin.objects {
println!("Object {path_hash:08x}");
}
// Create
let bin = Bin::builder()
.dependency("shared/data.bin")
.object(
BinObject::builder(0x12345678u32, 0xABCDEF00u32)
.property(0x1111, values::I32::new(42))
.build()
)
.build();
```
See the [`ltk_meta` README](crates/ltk_meta/README.md) for the full surface: property paths, override bins (`PTCH`), typed values, and round-trip writing.
---
## π Crates
| [`league-toolkit`](https://crates.io/crates/league-toolkit) | Umbrella crate (feature-gated re-exports) | β |
| [`ltk_wad`](https://crates.io/crates/ltk_wad) | WAD archive reading/writing | `.wad.client` |
| [`ltk_texture`](https://crates.io/crates/ltk_texture) | Texture decoding/encoding | `.tex`, `.dds` |
| [`ltk_mesh`](https://crates.io/crates/ltk_mesh) | Skinned & static mesh parsing | `.skn`, `.scb`, `.sco` |
| [`ltk_anim`](https://crates.io/crates/ltk_anim) | Skeleton & animation formats | `.skl`, `.anm` |
| [`ltk_meta`](https://crates.io/crates/ltk_meta) | Property bin files | `.bin` |
| [`ltk_ritobin`](https://crates.io/crates/ltk_ritobin) | Human-readable bin format | ritobin text |
| [`ltk_mapgeo`](https://crates.io/crates/ltk_mapgeo) | Map environment geometry | `.mapgeo` |
| [`ltk_file`](https://crates.io/crates/ltk_file) | File type detection | β |
| [`ltk_hash`](https://crates.io/crates/ltk_hash) | Hash functions (FNV-1a, ELF) | β |
| [`ltk_shader`](https://crates.io/crates/ltk_shader) | Shader path utilities | β |
| [`ltk_primitives`](https://crates.io/crates/ltk_primitives) | Geometric primitives | β |
| [`ltk_io_ext`](https://crates.io/crates/ltk_io_ext) | I/O extensions (internal) | β |
Each crate lives under `crates/<name>`.
---
## βοΈ Feature Flags
The `league-toolkit` umbrella crate uses feature flags to control which subsystems are included:
| `anim` | `ltk_anim` | β
|
| `file` | `ltk_file` | β
|
| `mesh` | `ltk_mesh` | β
|
| `meta` | `ltk_meta` | β
|
| `primitives` | `ltk_primitives` | β
|
| `texture` | `ltk_texture` | β
|
| `wad` | `ltk_wad` | β
|
| `hash` | `ltk_hash` | β
|
| `serde` | Serde support (where available) | β |
For a minimal build, disable defaults and opt-in selectively:
```toml
[dependencies]
league-toolkit = { version = "0.2", default-features = false, features = ["wad"] }
```
Some crates expose their own feature flags β e.g. texture *encoding* requires `intel-tex` on `ltk_texture` (see the [`ltk_texture` README](crates/ltk_texture/README.md)).
---
## π Documentation
- **[API Documentation](https://docs.rs/league-toolkit)** β Full rustdoc reference
- **[LTK Guide](docs/LTK_GUIDE.md)** β Comprehensive usage guide with examples
---
## π οΈ Development
**Prerequisites:** Rust stable toolchain
```bash
# Build all crates
cargo build
# Run tests
cargo test
# Build documentation
cargo doc --open
```
### AI-Assisted Development
AI agents can 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.
| **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, the options 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, not by editing it on GitHub.
Claude Code users get four skills in `.claude/skills/` that write and maintain all of this:
`write-prd`, `write-adr`, `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 live in
[`CLAUDE.md`](CLAUDE.md).
### Project Structure
```
league-toolkit/
|-- crates/
| |-- ltk_texture/ # Textures
| |-- ltk_mesh/ # Meshes
| |-- ltk_anim/ # Animation
| |-- ltk_meta/ # Property bins
| |-- ltk_ritobin/ # Ritobin text format
| |-- ltk_mapgeo/ # Map geometry
| |-- ltk_file/ # File detection
| |-- ltk_hash/ # Hashing
| |-- ltk_shader/ # Shader utilities
| |-- ltk_primitives/ # Primitives
| |-- ltk_io_ext/ # I/O extensions
|-- docs/
|-- LTK_GUIDE.md # Usage guide
```
---
## π Releasing
This repository uses [Release-plz](https://release-plz.ieni.dev/) for automated versioning and publishing:
1. Pushes to `main` trigger Release-plz to open a release PR
2. Merging the release PR publishes updated crates to crates.io
---
## π License
Licensed under either of:
- **Apache License, Version 2.0** ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- **MIT License** ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.
### 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 dual licensed as above, without any additional terms or conditions.
---
<div align="center">
Made with β€οΈ by the [LeagueToolkit](https://github.com/LeagueToolkit) community
</div>