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:
- WAD Resolution: Finds or downloads WAD files from Riot's CDN
- Hash Table Management: Loads hash tables to resolve hashed file paths
- WAD Parsing: Extracts skin data from WAD archives
- 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
cd packages/extractor # or khada-extractor repository
dotnet buildPre-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
# 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-downloadConvert All WADs in a Directory
dotnet run -- convert-all /path/to/wads/ -o output/ -rCommand 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 tohashes.game.txt-b, --bin-hashes <path>: Path tohashes.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 byversion_major/version_minorbytes. - Versions: 1 (no signature), 2 (83-byte ECDSA + data checksum), 3 (256-byte ECDSA). v3.4+ packs
compression_type / subchunk_count / subchunk_startinto 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_typebyte):Value Name Notes 0 None Stored as-is (default for .bnk/.wpk)1 GZip Deflate 2 Satellite Data lives in a separate satellite file 3 Zstd Single-frame Zstandard 4 ZstdChunked Multiple independently-decompressable Zstandard sub-chunks - See the LeagueToolkit WAD reference for full byte-level structure.
Hash Tables
The extractor requires two hash table files:
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
- Format:
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
.binfiles (property-bin formatPROP/PTCH) - Algorithm:
Fnv1a.HashLower(name)(seepackages/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
- Format:
Loading Priority:
- User-provided files (
-gand-boptions) - Latest from CommunityDragon (auto-downloaded)
- Cached versions in temp directory
WAD Downloading
The extractor uses CDTB (CommunityDragon Toolbox) to download WAD files from Riot's CDN.
Process:
- Checks cache:
~/.khada-extractor/cache/Game/ - For PBE: Always re-downloads (clears cache first)
- For live: Uses cache if available
- Downloads via Python script that uses CDTB
- 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 LegendsC:\Riot Games\League of Legends (PBE)C:\Program Files\Riot Games\League of LegendsC:\Program Files (x86)\Riot Games\League of Legends
Linux:
~/.local/share/lutris/runners/wine/lol/opt/League of Legends
Skin Extraction Process
- WAD Loading: Opens WAD file and resolves hashed paths using hash tables
- Skin Discovery: Finds entries matching
data/characters/{champion}/skins/{skinName}/ - Dependency Resolution: Recursively loads all dependencies (shared meshes, textures, etc.)
- Skin Parsing: Parses binary skin files using
OfficialLeagueToolkit - Material Resolution: Resolves shader parameters using shader profiles
- GLTF Generation: Converts to GLTF with:
- Meshes (with UVs, normals, tangents)
- Materials (with textures, shader parameters in extras)
- Skeletons (if
-sflag used) - Animations (if
-aflag 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 WADGetEntryByName(): Gets a WAD entry by path nameEntryExists(): 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/patchlineIsCached(): Checks if a WAD is already cachedGetCachedWadPath(): 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 pathFindDataFinalFolder(): Auto-detects League installationIsFilePath(): 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 tablesTryLoadFile(): Loads hash tables from file pathsGame: Dictionary mapping XXHash64 → file pathBinHashes: Dictionary mapping SDBM hash → property name
Integration with Khada Pipeline
The extractor is used by the @khada/processor package in the following ways:
- Binary Execution: Processor calls the extractor binary directly
- Batch Processing: Processes multiple champions/skins
- Output: GLTF files are then optimized by the processor
- Settings: Uses
model-fixes.ymlto 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:
{
"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:
{
"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 secondOutput:
{
"flipbookSize": [4, 4],
"flipbookFrameIndex": 0,
"flipbookSpeed": 6
}Frontend: Cycles frames automatically at the specified FPS, pausing when animation is paused.
Implementation Notes
Texture Cloning: Always clone flipbook textures before modifying
repeat/offsetto prevent cascading changes across materials sharing the same texture instance.Hash to Name Conversion: Keyframe flipbooks convert animation hashes to names in the backend using
_clipHashToAnimationNamemapping - frontend never does hashing.Default Frame: For keyframe flipbooks, prefer
FlipbookDefaultFrame(frommDefaultValue) over the static shader parameter, as it represents the actual idle/default frame.
Troubleshooting
"CDTB not installed" Error
Solution: Install CDTB:
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:
- Champion name misspelled
- WAD not available for specified patchline
- League installation not found and download failed
Solutions:
- Check champion name spelling
- Try
--liveif using--pbe(or vice versa) - Provide
--league-pathmanually - Check CDTB installation and internet connection
Python Path Issues
Solution: Set KHADA_PYTHON_PATH environment variable:
export KHADA_PYTHON_PATH=/usr/bin/python3.10Cache Issues (Stale PBE Data)
Solution: The extractor automatically clears PBE cache, but you can manually clear:
rm -rf ~/.khada-extractor/cache/Game/channels
rm -rf ~/.khada-extractor/cache/Game/cdtbDevelopment
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 checksBuilding for Release
# 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-singleTesting
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
AlphaIntensityfromTintColor[3]= 0.2 (20% opacity) - Riven Skin55 (92055): Cape smoke uses
disableUvScrollmanual fix + alpha blend
Related Documentation
- Architecture - Overall system architecture
- Best Practices - Coding patterns and conventions
- Common Pitfalls - Mistakes to avoid