All integers are little-endian.
1. File header
| offset | type | value |
| 0x00 | char[8] | "TEXTURES" |
| 0x08 | u32 | version — 0x00001200 |
| 0x0C | u32 | texture count — 26805 |
| 0x10 | u32 | 0 |
| 0x14 | u32 | 0 |
| 0x18 | u32 | name-table size in bytes — 546198 |
2. Name table (at 0x1C)
count consecutive Pascal-style strings:
u8 len ; includes the trailing NUL
u8 bytes[len] ; CP1251 text, last byte is 0x00
Names are Russian (CP1251), e.g. Городишко_Мерия_1_0. The table ends exactly at 0x855B2, where the record table begins.
3. Record table (0x855B2, count × 136 bytes)
A record is a u32 storage type followed by a verbatim MicrosoftDDSURFACEDESC2 / DDS_HEADER (dwSize = 124), then the block location.
Because the DDS header starts at +0x04, the usual DDS offsets are shifted by four:
| offset | field |
| 0x00 | u32 type — 0 = raw, 2 = compressed |
| 0x04 | dwSize = 124 |
| 0x08 | dwFlags = 0 |
| 0x0C | dwHeight |
| 0x10 | dwWidth |
| 0x14 | dwPitchOrLinearSize — unreliable, ignore |
| 0x18 | dwDepth = 0 |
| 0x1C | dwMipMapCount = 0 |
| 0x20 | dwReserved1[11] — all zero |
| 0x4C | DDPIXELFORMAT: dwSize = 32, dwFlags = 0x41 (32bpp) or 0x40 (24bpp), dwFourCC = 0, dwRGBBitCount = 32 or 24, masks R=0x00FF0000 G=0x0000FF00 B=0x000000FF A=0xFF000000 |
| 0x6C | dwCapsCaps2Caps3Caps4dwReserved2 — all zero |
| 0x80 | u32 dataSize |
| 0x84 | u32 dataOffset — absolute file offset |
The channel masks describe a 32-bit ARGB DWORD, i.e. B, G, R, A byte order in memory.
592 records store pitch = width × 3 even though the pixel format is 32bpp - a stale field left over from a 24-bit build.
Derive the stride from width instead. The 12 genuinely 24bpp records are all type 0 and are tiny UI fills.
Data blocks are perfectly contiguous; the last one ends exactly at EOF.
4. Payload — type 0 (18 records, raw)
width × height × bpp bytes, row-major, no padding: BGRA for 32bpp, BGRfor 24bpp. No colour transform.
5. Payload — type 2 (26,787 records, compressed)
u16 width
u16 height
u8 bytesPerPixel ; always 4
4 ×
{
u24 compressedSize ; little-endian
u8 compressedData[compressedSize]
}
Each of the four chunks is one independently compressed 8-bit plane that decompresses to exactly width × height bytes.
5.1 Plane codec — NRV2B (UCL)
The planes use NRV2B, the LZ77 variant from Markus Oberhumer's [UCL] (https://www.oberhumer.com/opensource/ucl/) library (the algorithm UPX uses), in its 32-bit-bit-buffer form:
a 32-bit bit buffer is refilled with a little-endian u32 taken from the stream whenever it runs dry, and its bits are consumed LSB-first;
literal bytes and match-offset bytes are read as raw bytes from the same stream, interleaved with those refills - so the byte layout of a plane is
word · literals · word · … · 0xFF;
M2_MAX_OFFSET is 0xd00 (3328): a match longer than that gets one extra byte;
the stream ends with the standard 0xFFFFFFFF offset marker, which is why every plane's final byte is 0xFF.
last = 1;
for (;;) {
while (getbit()) emit(nextbyte()); /* literal run */
m_off = 1;
do { m_off = m_off*2 + getbit(); } while (!getbit());
if (m_off == 2) { /* reuse previous offset */
m_off = last;
m_len = getbit();
} else {
v = (m_off - 3)*256 + nextbyte();
if (v == 0xffffffff) break; /* end of stream */
m_len = getbit();
m_off = v + 1;
last = m_off;
}
if (m_len) m_len = 3 + getbit(); /* 3..4 */
else if (getbit()) m_len = 2; /* 2 */
else { /* >=5 */
m_len = 1;
do { m_len = m_len*2 + getbit(); } while (!getbit());
m_len += 3;
}
if (m_off > 0xd00) m_len++;
copy m_len bytes from distance m_off;
}
5.2 Colour transform
The four planes are not B, G, R, A directly.
Green is hoisted to plane 0 and the other two colour channels are stored as differences against it, biased
by 128 - a reversible decorrelation that makes the planes far more compressible (planes 1 and 2 of a typical texture sit tightly around 128):
G = p0
B = p0 + p1 - 128
R = p0 + p2 - 128
A = p3
6. Tools in this zip.
| file | what it is |
| extract_tex.py | full extractor → PNG (uses nrv2b.dll, falls back to a pure-python decoder) |
| nrv2b.c | the NRV2B decompressor in C, plus a helper that decodes all four planes of a block |
| nrv2b.dll | prebuilt x64 build of the above (MSVC) |
malgrimia-tex-tools.zip
By
Hazza12555 ·