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.