Skip to content

Extractor Documentation

The Khada Extractor is a C# .NET application that converts League of Legends WAD (WAD Archive) files into GLTF format for 3D model viewing. It can be used standalone or integrated into the Khada processing pipeline.

Overview

The extractor performs the following tasks:

  1. WAD Resolution: Finds or downloads WAD files from Riot's CDN
  2. Hash Table Management: Loads hash tables to resolve hashed file paths
  3. WAD Parsing: Extracts skin data from WAD archives
  4. Skin Conversion: Converts binary skin data to GLTF format with materials, meshes, skeletons, and animations

Installation

Prerequisites

  • .NET SDK (8.0 or later)
  • Python 3.10+ (for WAD downloading via CDTB)
  • CDTB Python package: pip install cdtb

Building from Source

bash
cd packages/extractor  # or khada-extractor repository
dotnet build

Pre-built Binaries

Pre-built binaries are available in GitHub Releases for:

  • Windows (x64)
  • macOS (x64, ARM64)
  • Linux (x64, ARM64, musl)

Usage

Basic Commands

Convert a Single WAD

bash
# Using champion name (auto-downloads if not found locally)
dotnet run -- convert-wad Zoe -o output/

# Using file path
dotnet run -- convert-wad "/path/to/Zoe.wad.client" -o output/

# Extract specific skins only
dotnet run -- convert-wad Zoe -o output/ --skins 0 43 99

# Download from live servers instead of PBE
dotnet run -- convert-wad Zoe -o output/ --live

# Don't download, only use local files
dotnet run -- convert-wad Zoe -o output/ --no-download

Convert All WADs in a Directory

bash
dotnet run -- convert-all /path/to/wads/ -o output/ -r

Command Options

convert-wad Command

  • Positional Arguments:

    • wads: One or more WAD names (e.g., "Zoe", "TFTSet16") or file paths
  • Options:

    • -o, --output <path>: Output directory (default: output)
    • -s, --skeletons: Include skeletons (default: true)
    • -a, --animations: Include animations (default: true)
    • --skins <ids...>: Filter by skin IDs (e.g., --skins 0 43 99)
    • -l, --league-path <path>: Path to League installation (auto-detected if not provided)
    • --pbe: Download from PBE (default)
    • --live: Download from live servers
    • --no-download: Don't download WADs, only use local files
    • -g, --game-hash <path>: Path to hashes.game.txt
    • -b, --bin-hashes <path>: Path to hashes.binhashes.txt
    • --force-scale: Flip X-axis for correct orientation (default: true)
    • -k, --keep-hidden: Keep hidden sub meshes (default: true)

convert-all Command

  • Positional Arguments:

    • path: Directory containing WAD files
  • Options:

    • -o, --output <path>: Output directory (default: output)
    • -s, --skeletons: Include skeletons (default: false)
    • -a, --animations: Include animations (default: false)
    • -r, --recurse: Search recursively
    • --skins <ids...>: Filter by skin IDs
    • Other options same as convert-wad

Architecture

WAD File Format

League of Legends uses WAD (Riot archive) files to store game assets. Key characteristics:

  • Magic: RW (ASCII, 2 bytes) followed by version_major / version_minor bytes.
  • Versions: 1 (no signature), 2 (83-byte ECDSA + data checksum), 3 (256-byte ECDSA). v3.4+ packs compression_type / subchunk_count / subchunk_start into a single u32 in the TOC entry and removes the duplicate-chunk flag.
  • Path hashing: Chunk file paths are hashed with xxHash64 (seed 0) over the lowercased, forward-slash-normalized path. Only hashes are stored in the WAD — resolving back to readable paths requires a dictionary (hashes.game.txt).
  • Compression types (TOC compression_type byte):
    ValueNameNotes
    0NoneStored as-is (default for .bnk / .wpk)
    1GZipDeflate
    2SatelliteData lives in a separate satellite file
    3ZstdSingle-frame Zstandard
    4ZstdChunkedMultiple independently-decompressable Zstandard sub-chunks
  • See the LeagueToolkit WAD reference for full byte-level structure.

Hash Tables

The extractor requires two hash table files:

  1. hashes.game.txt: Maps XXHash64 hashes to file paths

    • Format: {hash} {path}
    • Example: a1b2c3d4e5f6 data/characters/zoe/skins/skin0/skin.bin
    • Used to resolve WAD entry paths
  2. hashes.binhashes.txt: Maps FNV-1a 32-bit hashes (of lowercased property/class names) to names

    • Format: {hash} {property_name} (hex or decimal uint)
    • Example: 12345678 Tint_Color
    • Used to resolve property names inside .bin files (property-bin format PROP/PTCH)
    • Algorithm: Fnv1a.HashLower(name) (see packages/extractor/OfficialLeagueToolkit/Hashing/Fnv1a.cs)
    • Do not confuse with SDBM — SDBM exists in the codebase but is only used for a few legacy auxiliary hashes, not for binhashes

Loading Priority:

  1. User-provided files (-g and -b options)
  2. Latest from CommunityDragon (auto-downloaded)
  3. Cached versions in temp directory

WAD Downloading

The extractor uses CDTB (CommunityDragon Toolbox) to download WAD files from Riot's CDN.

Process:

  1. Checks cache: ~/.khada-extractor/cache/Game/
  2. For PBE: Always re-downloads (clears cache first)
  3. For live: Uses cache if available
  4. Downloads via Python script that uses CDTB
  5. Caches downloaded WADs for future use

Cache Location:

  • macOS/Linux: ~/.khada-extractor/cache/Game/DATA/FINAL/Champions/{Champion}.wad.client
  • Windows: %USERPROFILE%\.khada-extractor\cache\Game\...

League Installation Detection

The extractor auto-detects League installations in common locations:

macOS:

  • /Applications/League of Legends.app
  • /Applications/League of Legends (PBE).app
  • ~/Applications/League of Legends.app

Windows:

  • C:\Riot Games\League of Legends
  • C:\Riot Games\League of Legends (PBE)
  • C:\Program Files\Riot Games\League of Legends
  • C:\Program Files (x86)\Riot Games\League of Legends

Linux:

  • ~/.local/share/lutris/runners/wine/lol
  • /opt/League of Legends

Skin Extraction Process

  1. WAD Loading: Opens WAD file and resolves hashed paths using hash tables
  2. Skin Discovery: Finds entries matching data/characters/{champion}/skins/{skinName}/
  3. Dependency Resolution: Recursively loads all dependencies (shared meshes, textures, etc.)
  4. Skin Parsing: Parses binary skin files using OfficialLeagueToolkit
  5. Material Resolution: Resolves shader parameters using shader profiles
  6. GLTF Generation: Converts to GLTF with:
    • Meshes (with UVs, normals, tangents)
    • Materials (with textures, shader parameters in extras)
    • Skeletons (if -s flag used)
    • Animations (if -a flag used)

Key Components

StringWad.cs

Wraps the low-level WAD file parser and provides string-based path resolution.

Key Methods:

  • GetSkins(): Enumerates all skins in the WAD
  • GetEntryByName(): Gets a WAD entry by path name
  • EntryExists(): Checks if a path exists in the WAD

WadDownloader.cs

Handles downloading WAD files from Riot's CDN using CDTB.

Key Methods:

  • DownloadWadAsync(): Downloads a WAD for a given champion/patchline
  • IsCached(): Checks if a WAD is already cached
  • GetCachedWadPath(): Gets the cache path for a WAD

Python Integration:

  • Generates a Python script that uses CDTB
  • Executes script and captures output
  • Handles cache clearing for PBE

LeaguePathResolver.cs

Resolves champion names to WAD file paths.

Key Methods:

  • ResolveInputAsync(): Resolves input (name or path) to full WAD path
  • FindDataFinalFolder(): Auto-detects League installation
  • IsFilePath(): Checks if input is a file path vs. champion name

HashTables.cs

Manages hash table loading and lookup.

Key Methods:

  • TryLoadLatest(): Downloads and loads latest hash tables
  • TryLoadFile(): Loads hash tables from file paths
  • Game: Dictionary mapping XXHash64 → file path
  • BinHashes: Dictionary mapping SDBM hash → property name

Integration with Khada Pipeline

The extractor is used by the @khada/processor package in the following ways:

  1. Binary Execution: Processor calls the extractor binary directly
  2. Batch Processing: Processes multiple champions/skins
  3. Output: GLTF files are then optimized by the processor
  4. Settings: Uses model-fixes.yml to apply manual fixes post-extraction

Flipbook Animation Systems

The extractor supports three types of flipbook animations:

1. Standard Flipbooks (Animation-Synced)

Used by champions like Milio. The flipbook frame is controlled by animation clips via ClipAccessoryToRead.

Data Structure:

dynamicMaterial.parameters[].driver.ClipAccessoryToRead = {accessory_hash}

Output:

json
{
  "flipbookSize": [4, 2],
  "flipbookFrameIndex": 0,
  "flipbookAccessoryHash": 1466359605
}

Frontend: Looks up flipbookAccessoryHash in animation keyframe data exported in skeleton userData.

2. Keyframe Flipbooks (TFT Companions)

Used by TFT companions like PetChibiLeeSin. The flipbook frame is controlled by direct animation name → frame mappings.

Data Structure:

dynamicMaterial.parameters[].driver = {
  mDrivers: [
    { animHash: 3603393739, frameValue: 2 },
    { animHash: 1055876912, frameValue: 7 },
    ...
  ],
  mDefaultValue: { value: 7 }
}

Output:

json
{
  "flipbookSize": [4, 4],
  "flipbookFrameIndex": 7,
  "isKeyframeFlipbook": true,
  "flipbookKeyframes": {
    "Cast_Cycle": 2,
    "Dance_In": 7,
    "Idle": 7
  }
}

Frontend: Direct lookup by animation name - no hashing required. Falls back to flipbookFrameIndex if animation not found.

3. Auto-Animated Flipbooks (Time-Based)

Used by champions like Annie Skin31. The flipbook cycles through frames automatically based on elapsed time.

Data Structure:

staticMaterial.FlipbookSpeed = 6.0 // frames per second

Output:

json
{
  "flipbookSize": [4, 4],
  "flipbookFrameIndex": 0,
  "flipbookSpeed": 6
}

Frontend: Cycles frames automatically at the specified FPS, pausing when animation is paused.

Implementation Notes

  1. Texture Cloning: Always clone flipbook textures before modifying repeat/offset to prevent cascading changes across materials sharing the same texture instance.

  2. Hash to Name Conversion: Keyframe flipbooks convert animation hashes to names in the backend using _clipHashToAnimationName mapping - frontend never does hashing.

  3. Default Frame: For keyframe flipbooks, prefer FlipbookDefaultFrame (from mDefaultValue) over the static shader parameter, as it represents the actual idle/default frame.

Troubleshooting

"CDTB not installed" Error

Solution: Install CDTB:

bash
pip install cdtb

"No hash tables were loaded" Error

Solution:

  • Ensure internet connection for auto-download
  • Or provide hash files manually: -g hashes.game.txt -b hashes.binhashes.txt

"WAD not found" Error

Possible Causes:

  1. Champion name misspelled
  2. WAD not available for specified patchline
  3. League installation not found and download failed

Solutions:

  • Check champion name spelling
  • Try --live if using --pbe (or vice versa)
  • Provide --league-path manually
  • Check CDTB installation and internet connection

Python Path Issues

Solution: Set KHADA_PYTHON_PATH environment variable:

bash
export KHADA_PYTHON_PATH=/usr/bin/python3.10

Cache Issues (Stale PBE Data)

Solution: The extractor automatically clears PBE cache, but you can manually clear:

bash
rm -rf ~/.khada-extractor/cache/Game/channels
rm -rf ~/.khada-extractor/cache/Game/cdtb

Development

Project Structure

packages/extractor/
├── LeagueConvert.CommandLine/    # CLI application
├── LeagueConvert/                # Core conversion logic
├── SimpleGltf/                   # GLTF format implementation
├── OfficialLeagueToolkit/        # WAD file parsing
├── LeagueToolkit/                # Additional utilities
└── Octokit.Extensions/           # GitHub API for update checks

Building for Release

bash
# Windows
dotnet publish LeagueConvert.CommandLine/LeagueConvert.CommandLine.csproj \
  -r win-x64 -c Release --self-contained true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -o ./publish/win-x64-single

# macOS ARM64
dotnet publish LeagueConvert.CommandLine/LeagueConvert.CommandLine.csproj \
  -r osx-arm64 -c Release --self-contained true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -o ./publish/osx-arm64-single

Testing

Test with known problematic skins:

  • Soraka Skin16: Previously had incorrect red tint (generic "Color" param)
  • Renata Skin0: Has procedural glass shaders
  • Annie Skin31: Has time-based auto-animated flipbooks
  • Milio Skin0: Has animation-synced flipbooks (standard flipbookAccessoryHash)
  • PetChibiLeeSin Skin1: Has keyframe flipbooks (TFT companion, isKeyframeFlipbook: true)
  • Aurora Skin11: "Pattern" material uses UseTextureAsAlpha (Gold scrolling pattern)
  • Jhin Skin55 (202055): Scrolling01/02 uses AlphaIntensity from TintColor[3] = 0.2 (20% opacity)
  • Riven Skin55 (92055): Cape smoke uses disableUvScroll manual fix + alpha blend

Built for engineers and AI assistants working on Khada.