btx: textures
- Source
docs/formats/btx.md- SHA-256
0abe1b08…66fb779f
Where they are · Header · Name and padding · Pixel data · Pixel formats · Names · How the game reads it · Replacing a texture · Rebuilding · Leads
A texture: a 0x34-byte header, the texture's name, then its pixel data, ready to hand to Direct3D 11. Every texture in the game is one: the gallery and ending pictures, backgrounds, map names, Warren Report portraits, menu and font textures, and the textures of models and event sprites. A btx is a whole file's payload, a pakd entry, or sits inside another container (ARC\0, BMAR, the background archives, the fonts); it has no wrapping of its own. The engine's one texture loader reads it (How the game reads it). It's named after its magic, btx\0; Gibbed's TextureFile reads the same layout.
| Based on | Every btx in the game's 10,016 .dat files: 28,518 in 8,890 files (Where they are); Gibbed's TextureFile.cs, TextureFormat.cs and TextureFormatHelpers.cs (reference_tools/Gibbed.LetUsClingTogether/projects/Gibbed.Reborn.FileFormats/) and Gibbed.Reborn.ExportTexture/Program.cs, checked 2026-10-11; the unpacked executable, SHA-256 7b0230a11498f40396c9048345e7cfe0ef442cb7ca8faffc6f197920d2669599; Microsoft's DXGI_FORMAT list for the format numbers, checked 2026-10-11. |
| Checked | 2026-10-11 |
| Method | Single-file C# programs over the decrypted files (with the toolchain's Containers library): find btx\0 at every 4-byte boundary of every payload, read every header field, work out each texture's data size the way the game does and compare it with the space the texture has. Static analysis with Ghidra 12.1.4 (the decompiled code in local/exe/decomp/) of the loader, and of the executable's format tables read by address. Not yet decoded or rebuilt by the toolchain; no in-game test. |
Where they are
confirmed (C# scan, all 10,016 .dat files): 28,518 btx, every one with a whole header as laid out below (version 0x103, name_offset 0x34, a known format). The pakd entry paths are as in containers.md.
| Where | How it's stored | Files | btx |
|---|---|---|---|
Gallery/, ending/, bg/back/, ScreenShot/ |
The whole payload; ZIP member <name>.btx |
13, 33, 99, 1 | 146 |
WarrenReport/common/, WarrenReport/<lang>/ |
The whole payload; member <name>.img |
446 + 8 × 481 | 4,294 |
menu/mapname/<lang>/ |
The whole payload; member <name>.img |
8 × 345 | 2,760 |
bg/*.dat |
pakd entries #1 on |
393 | 2,110 |
bg/*.dat |
Inside entry #0, an untagged resource archive |
394 | 3,827 |
event/scene/ |
Nested pakd entries (#n/0/1, #n/0/2) |
364 | 6,344 |
object/ |
Nested pakd entries |
746 | 1,492 |
worldmap/ |
pakd entries; and inside Worldmap.dat's BMAR |
46; 1 | 1,796; 18 |
menu/texture_x4/, menu/menu_layout.dat, menu/menu_parts.dat |
Inside ARC\0 archives |
75; 2 | 4,679; 634 |
menu/sequence/ |
pakd entries |
10 | 14 |
Font/ |
Inside the payload | 8 | 138 |
SaveThumb/ |
pakd entries |
9 | 91 |
WarrenReport/WarrenReport.dat; 4 pakd files in each WarrenReport/<lang>/ |
pakd entries |
1; 32 | 54; 64 |
battle/battle_data_release.dat |
Nested pakd entries |
1 | 35 |
title/title.dat |
Nested pakd entries |
1 | 22 |
- confirmed: a whole-payload
btxends exactly where its pixel data ends (all 7,200), and apakdentry holding one is its data end plus 1–16 zero bytes, the usualpakdpadding (all 12,022). Source: C# scan. - confirmed: the name in a whole-payload
btxis the file's name without its extension, ignoring case (all 7,200). Source: C# scan. - likely: more textures are stored compressed inside
ashgsprites inevent/,object/andbattle/: the scan finds 393 brokenbtx\0headers there, mixed with what look like compression flag bytes. They aren't counted above. Source: C# scan; hex inspection ofevent/scene/C1_ST_010.datentry#19/1.
Header
0x34 bytes. Offsets are from the start of the btx (its magic), and every _offset field is an offset from there. "Not read" means the loader (0x140079db0, 0x140079de0) never reads it.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 4 | raw[4] | magic |
btx\0. The loader checks it. |
likely | 0x140079db0 (compares with 0x140bb8298); C# scan, all 28,518. |
| 0x04 | 2 | u16 | version |
0x103 in every file. The loader accepts 0x103 or lower. |
likely | 0x140079db0; C# scan. Gibbed reads 0x04–0x07 as one u32 0x103. |
| 0x06 | 1 | u8 | linear |
0 in all but 2 files, which hold 1 (test_bg_alphamask in bg/bg_common.dat, an alpha mask). 0 lets the texture be created in an _SRGB format, so the GPU reads its colours as sRGB; 1 keeps it linear (_UNORM). |
likely | 0x140079de0 (sets texture flag 8 when it's 0), 0x1400a5d10, 0x1400a6050; C# scan. |
| 0x07 | 1 | u8 | unk_0x07 |
0 in every file. Not read. | likely | C# scan. |
| 0x08 | 2 | u16 | width |
In pixels: 4 to 2,500 in the files. | likely | Gibbed Width; 0x140079de0; C# scan. |
| 0x0A | 2 | u16 | height |
In pixels: 4 to 3,528 in the files. | likely | Gibbed Height; 0x140079de0; C# scan. |
| 0x0C | 4 | u32 | row_pitch |
Bytes per row of level 0: the row pitch the game works out. Not read: the game computes it. | confirmed | Equals the computed pitch in all 28,518 (C# scan); not read by 0x140079de0. Gibbed Stride. |
| 0x10 | 1 | enum8 | format |
The pixel format. See Pixel formats. The files use 13 (BC7, 21,983), 0 (R8G8B8A8, 5,298) and 7 (BC1, 1,237). | likely | Gibbed TextureFormat; the format table at 0x1406b0670; C# scan. |
| 0x11 | 1 | u8 | tiling |
0 in every file. When 2, the game pads every level to whole 32 × 32 pixel tiles when it sizes it; Gibbed reads 3 as Nintendo Switch swizzled data. | likely | 0x1401035e0; Gibbed Unknown11 and ExportTexture; C# scan. |
| 0x12 | 1 | u8 | mip_count_minus_1 |
Number of mip levels, less 1: 0 in 28,498 files, 1 in 4, 2 in 16. | likely | Gibbed MipCount; 0x140079de0; C# scan (each texture's size matches its space with this many levels). |
| 0x13 | 1 | u8 | unk_0x13 |
1 in every file. Copied into the texture's sampler settings, twice; probably the address mode for U and V (guess). | — | 0x140079de0; C# scan. |
| 0x14 | 1 | u8 | unk_0x14 |
1 in every file. Copied into the sampler settings; probably a filter (guess). | — | 0x140079de0; the engine's default sampler holds the same 1, 1 and 5 (0x1400a5de0). |
| 0x15 | 1 | u8 | unk_0x15 |
5 in every file. Copied into the sampler settings; probably a filter (guess). | — | As unk_0x14. |
| 0x16 | 1 | u8 | unk_0x16 |
0 in every file. Stored in the texture at +0x118, with a "changed" bit when it differs. |
likely | 0x140079de0, 0x14007a0a0; C# scan. |
| 0x17 | 1 | u8 | flags |
0 in every file. Bit 0: cube map (6 faces). Bit 2: volume texture of depth slices. Bit 3: never sRGB. |
likely | 0x140079de0, 0x1400a6050, 0x140103720; C# scan. |
| 0x18 | 4 | u32 | data_offset |
Start of the pixel data: a multiple of 16 in every file, 0x40 to 0x70. |
likely | Gibbed DataOffset; 0x140079de0; C# scan. |
| 0x1C | 1 | u8 | unk_0x1C |
0xFF in every file, read as −1; probably a clut's format, none (guess). |
— | 0x140079de0; C# scan. Gibbed requires 255 (PaletteFormat). |
| 0x1D | 1 | u8 | unk_0x1D |
0 in every file. Not read. | likely | C# scan. |
| 0x1E | 2 | u16 | unk_0x1E |
0 in every file; probably a clut count (guess). | — | 0x140079de0; C# scan. Gibbed requires 0 (PaletteCount). |
| 0x20 | 4 | u32 | unk_0x20_offset |
The end of the pixel data, rounded up to 64, in every file. The loader turns it into a pointer beside unk_0x1C and unk_0x1E, but doesn't pass any of the three to the texture. |
— | 0x140079de0, 0x14007a0a0; C# scan (all 28,518). Gibbed PaletteOffset. |
| 0x24 | 4 | u32 | name_offset |
The name: 0x34, right after the header, in every file. |
likely | Gibbed NameOffset; 0x140079de0, 0x140168680; C# scan. |
| 0x28 | 2 | u16 | depth |
1 in every file: the number of slices of a volume texture. | likely | Gibbed Depth; 0x140079de0, 0x1400a6050; C# scan. |
| 0x2A | 2 | u16 | unk_0x2A |
0 in every file. Not read. | likely | C# scan. |
| 0x2C | 4 | u32 | unk_0x2C_offset |
A u32 0 between the name and the pixel data, always 4 or 12 bytes before data_offset. Not read. |
likely | Gibbed Unknown2COffset; C# scan. |
| 0x30 | 4 | u32 | unk_0x30 |
0 in every file. Not read. | likely | C# scan. |
- confirmed: every
btxpasses Gibbed's header checks except its mip count: Gibbed accepts 1 or 3 levels, and the 4 two-level textures inworldmap/Worldmap.dathave 2. Source: C# scan.
Name and padding
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
name_offset (0x34) |
name + 1 | raw[n] | name |
The texture's name, NUL-terminated ASCII: the file's name for whole-payload textures, else names like bg015_00_c0_t00-85abd907d1487483 or bg015_00_c0_t00_m*.tga. The game uses it to share textures (Names). |
likely | 0x140168180, 0x140063760; C# scan (all ASCII). |
after name |
varies | raw[n] | padding |
Zeros to unk_0x2C_offset, then the u32 0 it points at, then zeros to data_offset. |
confirmed | C# scan: every byte from the name's end to data_offset is 0, in all 28,518. |
- confirmed:
data_offsetisunk_0x2C_offset + 4rounded up to 16 in every file, andunk_0x2C_offsetis 0 to 21 bytes past the name's NUL, a multiple of 8 plus 4. The rule that places it isn't known; a rebuild keeps it. Source: C# scan.
Pixel data
The levels follow data_offset back to back, largest first, with no padding between them. Level m is width >> m by height >> m pixels.
- likely: a level of an uncompressed format is its row pitch × its height, where the row pitch is
width × bits per pixel / 8rounded up to 16 bytes. Source:0x1401035e0(row alignment from the format table at0x1406b0670). - likely: a level of a block format (BC1–BC7) is
ceil(width / 4) × ceil(height / 4)blocks of 8 bytes (BC1, BC4) or 16 (the rest), stored row by row. Source:0x1401035e0. - confirmed: with these sizes and
mip_count_minus_1 + 1levels, the data fits each texture's space exactly as Where they are says, androw_pitchis level 0's pitch, in all 28,518. Source: C# scan. - confirmed: every BC texture's width and height are multiples of 4 (23,220), and every R8G8B8A8 row is already a multiple of 16 bytes (5,298), so no file has row or block padding. 21,783 textures have a power-of-two height and 24,129 a power-of-two width; the rest don't. Source: C# scan.
unk_0x20_offset points past the data, at the next 64-byte boundary; in a whole-payload btx that is past the end of the payload, since the payload ends with the data.
Pixel formats
The format codes are indexes into the engine's table of 41 formats at 0x1406b0670 (0x18 bytes each: a name pointer, bits per pixel, row alignment or block width, block height, alpha bits, a clut flag, a block-compression flag). The names below are the table's own. A format is created on the GPU as the DXGI format from the table at 0x140b9a2f0, or, when the texture is sRGB, from the one at 0x140b9a3a0 (How the game reads it). All likely (static analysis), except the file counts, confirmed by the C# scan.
| Code | Name | Bits per pixel | DXGI format (sRGB) | What the game does with it | btx |
|---|---|---|---|---|---|
| 0 | R8G8B8A8 |
32 | 28 R8G8B8A8_UNORM (29 _SRGB) |
Uploads it as is. | 5,298 |
| 1–4 | R8G8B8, R5G6B5, R5G5B5A1, R4G4B4A4 |
24, 16, 16, 16 | — | Converts it to R8G8B8A8 on the CPU first. | 0 |
| 5, 6 | P8, P4 |
8, 4 | — | Converts it to R8G8B8A8 through a clut, which the btx loader doesn't supply. |
0 |
| 7 | BC1 |
4 | 71 BC1_UNORM (72 _SRGB) |
Uploads it, with the GPU size rounded up to multiples of 4. | 1,237 |
| 8 | BC2 |
8 | 74 BC2_UNORM (75) |
As BC1. | 0 |
| 9 | BC3 |
8 | 77 BC3_UNORM (78) |
As BC1. | 0 |
| 10, 11 | BC4, BC5 |
4, 8 | 80 BC4_UNORM, 83 BC5_UNORM |
The table gives them block size 0, so the game can't size them: unusable. | 0 |
| 12 | BC6H |
8 | 95 BC6H_UF16 |
Uploads it as is. | 0 |
| 13 | BC7 |
8 | 98 BC7_UNORM (99 _SRGB) |
As BC1. | 21,983 |
| 14 | D24S8 |
32 | 45 D24_UNORM_S8_UINT |
A depth format. | 0 |
| 15 | D15S1 |
16 | 0 (none) | Fails: no DXGI format. | 0 |
| 16, 17 | L8, A8 |
8 | 61 R8_UNORM, 65 A8_UNORM |
Uploads it as is. | 0 |
| 18–22 | L4A4, L8A8, YUV, PVRTII_4, PVRTII_2 |
8, 16, 32, 4, 2 | 0 (none) | Fails: no DXGI format. | 0 |
| 23–25 | U16U16, F16F16, F32 |
32 | 36 R16G16_UINT, 34 R16G16_FLOAT, 41 R32_FLOAT |
Uploads it as is. | 0 |
| 26–28 | RGBA_U32U32, R16G16B16A16F, R32G32B32A32F |
64, 64, 128 | 17 R32G32_UINT, 10 R16G16B16A16_FLOAT, 2 R32G32B32A32_FLOAT |
Uploads it as is. | 0 |
| 29–33 | D32F, R10G10B10A2, R11G11B10, R16_UNorm, R16F |
32, 32, 32, 16, 16 | 40 D32_FLOAT, 24 R10G10B10A2_UNORM, 26 R11G11B10_FLOAT, 56 R16_UNORM, 54 R16_FLOAT |
Uploads it as is. | 0 |
| 34 | B8G8R8A8 |
32 | 87 B8G8R8A8_UNORM (91 _SRGB) |
Uploads it as is. | 0 |
| 35–40 | A8R8G8B8, A8B8G8R8, B8G8R8, B5G6R5, B5G5R5A1, B4G4G4A4 |
32, 32, 24, 16, 16, 16 | — | Converts it to R8G8B8A8 on the CPU first. | 0 |
Source: the format table (0x1406b0670) and the two DXGI tables (0x140b9a2f0, 0x140b9a3a0) read from the executable by address; the conversion list and the rounding in 0x1400a6050; the converter 0x140104dc0 ("pixelconv"). Gibbed's TextureFormat has the same 41 names in the same order. Codes 41 and up are past the end of the table; the game doesn't check for them.
Names
- likely: the menu image path caches textures by a key made from the name and level 0's byte size: the FNV-1a 64 hash of the name followed by the size in decimal. A second
btxwith the same name and size gets the first one's texture. Source:0x140168180(BladeGraphicsManager::vfunction5),0x140168680(the key). - likely: the texture database looks the name up before it creates a texture, and returns the loaded texture of that name when it's shared or in one of the requested groups. Source:
0x140063760; it files each new texture under its name with any extension cut off (0x140063cb0). - confirmed: the files hold 11,265 distinct names for 28,518 textures: most repeats are copies of the same texture in several files, and 34 names have more than one size, used by different languages. Source: C# scan.
How the game reads it
Every fact here is static analysis of the unpacked executable (Ghidra 12.1.4, the decompiled code in local/exe/decomp/), so at most likely; the functions are in the names list.
- likely: every texture goes through the texture database's create (
0x140063760). It walks a list of registered texture loaders (head at0x140fd4ca0), calls each one's check and, on the first that accepts, its create. Only one loader is registered, thebtxone (the record at0x140caa758: create0x140079de0, check0x140079db0; registered at startup in0x140051b40), sobtxis the only texture format the game reads. Source: the list is used only by0x140063760and0x140051b40. - likely: the check (
0x140079db0, not in Ghidra's function list; read from its bytes) passes when the data starts withbtx\0and the u16 at0x04is at most0x103. Otherwise no loader takes it and the request returns texture −1: the image doesn't appear. - likely: the create (
0x140079de0) readswidth,height,format,tiling,mip_count_minus_1(capped at the caller's maximum, 255 on the menu path), the sampler bytes0x13–0x16,flags,data_offset,unk_0x1C,unk_0x1E,unk_0x20_offset,name_offset,depthandlinear. It checks none of them, and never compares the data's size with the buffer's: a short file is read past its end. It doesn't readrow_pitch,unk_0x07,unk_0x1D,unk_0x2A,unk_0x2C_offsetorunk_0x30. - likely: the texture is then made on the GPU (
TextureCore::vfunction12,0x1400a6050) with D3D11'sCreateTexture2D(CreateTexture3Dfor a volume), one subresource per level, each level's pitch worked out as in Pixel data. It's created sRGB when the device has sRGB on (bit 2 at+0x18of the render device) and the texture's flag 8 is set:linear0,flagsbit 3 clear, and the caller not passing0x40000(0x1400a5d10). - likely: before any of this, a buffer starting
cmp\0would be unpacked by the registeredzliborlzmadecompressor (0x14007a0a0,bl::archive::CompressibleData). No game file uses it: the scan finds nocmp\0. Source:0x14007a0a0; C# scan. - likely: game screens load a texture file through one routine,
0x14026f4b0: given abtxit creates the texture through0x140168180, given apakdit reads it as a sprite pack (0x1402702a0), anything else as a sprite. About 40 functions call it, among them the gallery (0x140513e60, which opens group0x2f8) and functions next to the openers ofbg/back/(0x1402ddda0), the Warren Report (0x140489eb0) andSaveThumb/(0x14041e4e0). Source:exe.calls. - likely: the gallery scales its picture to the screen from the texture's own size (fills 1920 × 1080,
0x140513e60), so a gallery picture can be any size. - likely: textures inside
ARC\0archives are found by entry typetexand the hash of a file name (0x1400640b0), and handed to the same create. The archive formats are not part of this doc.
Replacing a texture
What the code above says a replacement btx has to be (all likely: static analysis, no in-game test yet):
- Valid, not identical. Nothing in a
btxis checked but the magic and the version, so a replacement has to be a correctbtx, not a byte-identical one. Its header has to describe its data exactly, because the game trusts it: a wrongdata_offset, size or format reads the wrong bytes or past the end. - Any size. Width and height can be anything up to D3D11's limit of 16,384 (resource limits, checked 2026-10-11); powers of two aren't needed, and over 6,700 of the shipped textures aren't. For BC formats keep both a multiple of 4, as every shipped one is. Data elsewhere that points into a texture by pixel position (sprite sheets, layouts, models) may expect the old size.
- A working format. BC7 (13), BC1 (7) or R8G8B8A8 (0), the three the game ships, are safe. BC2, BC3, BC6H, B8G8R8A8 and the other formats with a DXGI format load too; BC4, BC5 and the ones without a DXGI format don't.
- Mips are optional. One level is the norm (28,498 of 28,518). With more, keep each level at least 1 pixel on both sides.
- Keep the name when replacing a texture, and give a new texture a name no other texture uses (Names).
- Keep
linear0 for colour images, so they keep the original's sRGB handling. - Mind the container. A whole-payload
btxchanges the file's size, soFileTable.binhas to match (filetable.md); apakdentry is padded as in containers.md.
Rebuilding
To rebuild a btx byte for byte from its decoded form: write the header fields as read, the name, zeros to data_offset (the u32 at unk_0x2C_offset is 0), then the pixel data. row_pitch and unk_0x20_offset follow from the other fields (Pixel data), but are kept as read. For a new texture, place unk_0x2C_offset at data_offset − 4, as about half the files do. likely: no toolchain round trip yet.
Leads
- Decode and round-trip every
btxin the toolchain (R30), and test a replaced texture in the game (R7): a new size, a new format, and whether the sRGB handling is on. - What sampler values
0x13–0x15select, from the code that builds D3D11 samplers from a texture's settings (the 16-byte block at TextureBtx+0x58intoCreateSamplerState); a trace would give them names. - The textures compressed inside
ashgsprites, and the0x8003-version headers among them, belong to theashgformat. - The containers that hold
btx: entry#0of eachbg/*.datand entry#n/0/0of the event and object packs (an untagged resource archive starting00000000, then its size),ARC\0,BMARand theFont/payloads. - The sprite-pack reader
0x1402702a0reads the u32 after apakd'spack_sizeand expects0x1FF10000or0x1FF10001there; with0x1FF10001it loads entries#1and#2as the sprite's_auraand_masktextures. Which files have it. - Where
unk_0x1C,unk_0x1Eandunk_0x20_offsetwould go: nothing reads them after the create, and no file has a clut. - "Gfx" prefix:
Gfx_BtxCacheKey(0x140168680) andGfx_CreateBtxTexture(0x14016d960) areMx::Graphicscode, while "Gfx" elsewhere meansbl::gfx; aBladeGraphics_prefix, orTextureBlade_for0x14016d960(it builds aMx::Graphics::blGraphics::TextureBlade), would keep one term per concept.