Find textures inside any binary file and extract them.
Many games keep standard DDS and KTX2 textures, headers and all, inside their own archive formats. texscan finds them by their headers, works out each texture's exact size from its dimensions, mip levels and pixel format, and writes them out as ordinary .dds and .ktx2 files. No engine-specific tool needed.
It's a sibling of zscan, which does the same for compressed streams, and works the same way: a scan writes a JSON manifest, and later steps work from it.
- Scan a file for DDS and KTX2 textures, with their exact sizes, dimensions, mips and pixel formats.
- Extract them as
.ddsfiles, or export them as PNG. - Pack edited textures back into a copy of the file, like packzip: edit the PNG (texscan re-encodes it and rebuilds the mips) or drop in a new
.ddsor.ktx2. - Browse them in a desktop app: thumbnails, a sortable table, and a preview with mip, face, slice and channel controls.
Status: early. More formats (KTX1, PNG and others) are next.
AssetStudio is the usual tool for hunting textures, but it's a Unity asset browser: it reads Unity's own files and exports what it finds. texscan works at a lower level and goes both ways:
- Any engine, any container. texscan doesn't need to understand the archive. It finds DDS and KTX2 textures by their headers in whatever file they're stored in: custom pack formats, extracted blobs, memory dumps, other engines' asset files.
- Put textures back. AssetStudio exports only. texscan packs edited textures into a copy of the file: edit the PNG and it's re-encoded in the texture's own format (BC1–BC7 included) with its mips rebuilt, or drop in a replacement
.dds/.ktx2. The texture keeps its exact size, so nothing else in the file moves. - Checked, and never destructive. Every texture's size is worked out from its header and checked against the file. Pack writes a new file, reads it back and verifies every changed texture. The input is never modified.
- Scriptable. A command line with
--jsonoutput and a manifest, so a scan, extract and pack can run in a batch script or a build. AssetStudio is mainly a GUI. - Modern formats. KTX2 with Zstandard supercompression, ETC2/EAC, ASTC (including HDR) and Basis Universal (ETC1S, UASTC), as well as every DDS layout: legacy and DX10 headers, cube maps, volumes, arrays.
- Fast on big files. It scans by memory-mapping the file and searching for headers, so a 1 GiB file takes under a second.
- Light. One native program, no .NET runtime, nothing to install.
When AssetStudio is the better choice: for Unity games it understands the asset files themselves, so it finds textures stored without a DDS or KTX2 header (Unity's Texture2D keeps raw pixel data), knows their names, and handles meshes, audio and other asset types. texscan can't find headerless textures yet. The two work well together: AssetStudio to explore a Unity game, texscan for everything else and for putting edits back.
Windows x64 builds are on the Releases page. The zip holds texscan.exe (command line) and texscan-gui.exe (desktop app). Nothing to install: the C runtime is built in.
Rust 1.95 or later (1.89 for the command line alone):
cargo build --releasetexscan scan game.pak -o manifest.json # list textures, write a manifest
texscan extract game.pak -m manifest.json -d textures/
texscan extract game.pak -m manifest.json -d textures/ --png # also as PNG
# ...edit textures/*.png, or replace a .dds...
texscan pack game.pak -m manifest.json -d textures/ --dry-run # what would change
texscan pack game.pak -m manifest.json -d textures/ # writes game.packed.pak OFFSET SIZE FORMAT DIMENSIONS MIPS PIXEL FORMAT
0x72 21992 dds 256x128 9 DXT1
0x567f 4224 dds 64x64 1 DXT5
0x66ff 2816 dds 32x16 3 A8R8G8B8
0x7204 22020 dds 128x128 8 BC7_UNORM_SRGB
...
Every command takes --json. The input is never modified. extract refuses a file that no longer matches the manifest (--force overrides).
--png also saves each texture's full-size image as PNG: one file per array element, cube face (_px, _nx, _py, _ny, _pz, _nz) and volume slice. Colours are exported as stored: sRGB stays sRGB, HDR values are clipped to 0–1, and single-channel formats come out grey.
pack looks through the extract folder for textures you changed:
- An edited PNG (same size as the texture). texscan encodes it in the texture's pixel format, BC1–BC7 included, and rebuilds its mip levels from it. PNGs you didn't touch are recognised and left alone, so you can export everything and edit one.
- A replacement
.ddsor.ktx2with the same dimensions, mip count, faces, array size and pixel format. Either container can replace either (a DDS for a KTX2 texture, say), and a DX10 or legacy DDS header both work. Its pixel data is used as is.
Textures keep their exact size and their original header, so nothing else in the file moves and no offsets need fixing. The input is never modified: pack writes a new file (game.packed.pak by default), reads it back and checks every changed texture before putting it in place.
Some things can't be written back yet: signed BC4/BC5/BC6H, 4:2:2 video formats, R11G11B10 and R9G9B9E5. A palettized texture can only take colours already in its palette. PNGs are 8-bit, so an HDR texture edited as PNG loses values above 1; replace its .dds instead. BC1 is written opaque.
--show-rejected lists headers that look like textures but can't be used, and why (for example an unknown pixel format, or a texture cut off by the end of the file).
texscan-gui [FILE]Open a file (or drop one on the window) and it's scanned straight away. Every texture shows as a thumbnail, or as a row in a sortable table; filter by pixel format, size or offset. Click one to preview it:
- pick the mip level, cube face, array element or volume slice;
- turn the R, G, B and A channels on and off (one channel on its own shows as grey);
- fit to the pane, or zoom with the slider or Ctrl + mouse wheel; hover to read a pixel's value;
- save it as
.dds, or the image shown as PNG.
To edit, use Replace with PNG… (or drop a PNG on the window) for the image shown, or Replace with DDS/KTX2… for the whole texture. The preview's Edited view shows the texture exactly as it will be written, encoded in its own pixel format; Original shows what's in the file. Textures > Import edits from folder… picks up everything you changed in a folder written by Extract. Then Pack…: a dry run lists what will change, and Write packed file… writes a new file and checks it.
The strip along the top shows where each texture sits in the file (blue: block-compressed, green: uncompressed, orange: high precision), with headers that couldn't be used marked in red. Textures > Extract all saves every texture at once.
The screenshots use made-up textures from docs/make_demo.py.
- Legacy headers: DXT1–5, ATI1/ATI2, BC4/BC5, RXGB, RGB, luminance, alpha, bump-map, palettized (P4/P8) and D3D9 float formats.
- DX10 headers: every DXGI format except planar video formats (NV12 and similar).
- Mipmaps, cube maps, volume textures and texture arrays.
Checked against 123 real DDS files covering most legacy formats: each one's size is worked out exactly, and the PNGs match an independent decoder (Pillow) to within rounding wherever it supports the format.
Every format above can be exported to PNG except CxV8U8, planar video formats, depth-stencil formats and most typeless formats.
Textures stored without a header can't be found this way. That includes Unity's Texture2D and Unreal's .uasset/.ubulk, which keep raw pixel data. A later version will let you describe them by hand.
- Every KTX2 layout: mipmaps, cube maps, arrays and volume textures, with the level index read as written (usually smallest mip first).
- Vulkan formats map to the same decoders and encoders as DDS: BC1–BC7 and the uncompressed formats preview, export and pack.
- ETC2, EAC and ASTC textures preview and export as PNG (ASTC HDR clipped to 0–1). They can't be written from a PNG yet (there's no encoder), but can be replaced with a
.ktx2of the same format. - Zstandard- and zlib-supercompressed textures preview and export as PNG, but can't be packed yet: a re-compressed level would change size.
- Basis Universal textures (ETC1S/BasisLZ, UASTC, UASTC HDR, XUASTC) preview and export as PNG, transcoded by the pure-Rust
basisucrate. They can't be written back. KTXorientationis honoured: a texture stored bottom-up is shown and exported the right way up, and turned back when packed.
Checked against the 50 KTX2 test files in Khronos's KTX-Software repository (BC, ETC2, ASTC including HDR, UASTC, Basis ETC1S, Zstandard, 3D, arrays, cube maps): every one is sized exactly. The Zstandard test texture decodes to exactly the pixels of its uncompressed twin; the ETC2 and ASTC versions of the same image come within 2 levels of the uncompressed one (ASTC 4x4 exactly). Basis Universal decodes accurately too (ETC1S within 2 levels of the raw original, UASTC mean error 0.3 on a colour grid), and the three HDR encodings of the same scene agree with each other. 49 of the 50 files export; the one that doesn't (desk.ktx2, from Basis Universal's own web demo) isn't accepted by basisu. The plain ones pack back.
Texture names in the list are Vulkan's without the VK_FORMAT_ prefix (BC7_SRGB_BLOCK), with the matching DXGI format alongside.
texscan is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version (GPL-2.0-or-later). See LICENSE.
Basis Universal decoding uses the basisu crate, which is Apache-2.0 only. Apache-2.0 code can be combined with GPL version 3 but not with version 2 alone, so texscan binaries (which include it) can be distributed under GPL-3.0. The source stays GPL-2.0-or-later.



