ltk_anim 0.3.8

Animation formats support for League Toolkit
Documentation

League Toolkit

CI Crates.io Docs License

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 crates.io - Umbrella crate; feature-gated re-exports of the crates below
ltk_wad crates.io .wad.client Archive reading, extraction and building; name resolution; signatures
ltk_meta crates.io .bin (PROP, PTCH) Property bins: the object tree, streaming views, property paths, override bins
ltk_texture crates.io .tex, .dds Decoding for every shipped format, encoding for BC1/BC3/BC7 and raw
ltk_mesh crates.io .skn, .scb, .sco Skinned and static meshes, with typed vertex-buffer accessors
ltk_anim crates.io .skl, .anm Skeletons, joints and animations
ltk_mapgeo crates.io .mapgeo Map environment geometry
ltk_rst crates.io .stringtable Riot String Tables: localized strings keyed by hash
ltk_ritobin crates.io ritobin text The human-readable bin dialect: parse, print, round-trip
ltk_shader crates.io - Shader table of contents, defines and loading
ltk_file crates.io - File kind detection from magic bytes (LeagueFileKind)
ltk_hash crates.io - The hashes the formats key on: xxh64 for WAD paths, FNV-1a for bin names, ELF
ltk_primitives crates.io - Geometry shared by the mesh and map crates: AABB, Sphere, Color
ltk_io_ext crates.io - 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:

[dependencies]
league-toolkit = { version = "0.2", features = ["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:

[dependencies]
ltk_wad = "0.5"
ltk_meta = "0.8"
ltk_texture = "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:

[dependencies]
league-toolkit = { version = "0.2", default-features = false, features = ["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 std::fs::File;
use ltk_hash::Hash as _;
use ltk_wad::{Wad, WadHash};

let mut wad = Wad::mount(File::open("Aatrox.wad.client")?)?;
println!("{} chunks", wad.chunks().len());

let hash = WadHash::hash_str("data/characters/aatrox/aatrox.bin");
if let Some(chunk) = wad.chunks().get(hash).copied() {
    let bytes = wad.load_chunk_decompressed(&chunk)?;
    println!("{} bytes of bin", bytes.len());
}

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 ltk_texture::Tex;
use std::fs::File;

let tex = Tex::from_reader(&mut File::open("texture.tex")?)?;
println!("{}x{} {:?}, {} mips", tex.width, tex.height, tex.format, tex.mip_count);

let surface = tex.decode_mipmap(0)?;
surface.into_rgba_image()?.save("output.png")?;

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 ltk_mesh::SkinnedMesh;
use std::fs::File;

let mesh = SkinnedMesh::from_reader(&mut File::open("champion.skn")?)?;
println!("{} vertices in {} submeshes", mesh.vertex_buffer().count(), mesh.ranges().len());

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 std::fs::File;
use ltk_meta::concrete::{values, Bin, BinObject};

// Read
let bin = Bin::from_reader(&mut File::open("data.bin")?)?;
for (path_hash, object) in &bin.objects {
    println!("{path_hash:08x} ({:08x}): {} properties", object.class_hash, object.properties.len());
}

// Build
let bin = Bin::builder()
    .dependency("shared/data.bin")
    .object(
        BinObject::builder(0x12345678u32, 0xABCDEF00u32)
            .property(0x1111u32, values::I32::new(42))
            .build(),
    )
    .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 examples
  • docs/design/ - what a feature's surface and wire format are
  • docs/adr/ - one record per architectural decision, with the options it beat
  • docs/prd/ - why a feature exists, and its numbered requirements

Development

The toolchain is pinned to stable in rust-toolchain.toml, with rustfmt and clippy.

cargo build              # whole workspace
cargo test               # whole workspace
cargo test -p ltk_meta   # one crate
cargo clippy --all-targets
cargo fmt
cargo doc --open

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 .modpkg mod 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 .wad archives
  • Mimir - hash-to-path tables as compact memory-mapped .hashdb files
  • lol-meta-wiki - documentation and a JSON API for .bin meta 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.