.tga File Extension (BIMAGE)
The ".tga" files used in Doom Eternal are not targa images. They are actually "BIM" (or .bimage) files.
These files are stored in two parts. The header is embedded in Doom Eternal's .resources files. The body of the file is embedded in Doom Eternal's .streamdb files.
File Signature
Doom Eternal ".tga" files can be identified by the first 3 bytes of the file header, which is always 0x42 0x49 0x4D or "BIM", short for "bimage" or "binary image".
Header Structure
The .tga header is extracted from Doom Eternal's .resources files. It consists of a 63-byteHEADER struct, followed by a variable number of 36-byte MIPMAP structs. Everything is little-endian.
The technical details of .resources file extraction are beyond the scope of this article. You can extract the .tga headers yourself using the EternalResourceExtractor tool.
struct ETERNAL_TGA_HEADER
{
HEADER header;
MIPMAP mipmap[]; // Flexible array member, length varies with header.MipCount;
};
The HEADER struct is a 63-byte sequence:
struct HEADER
{
char Signature[3]; // "BIM"
byte Version; // 0x15
int TextureType; // enum textureType_t
int TextureMaterialKind; // enum textureMaterialKind_t
int PixelWidth; // image width in pixels
int PixelHeight; // image height in pixels
int Depth;
int MipCount; // determines # of MIPMAP structs to follow
__int64 MipLevel;
float unkFloat1; // usually 1.0, purpose unknown
byte boolIsEnvironmentMap; // 1 if image is an environment map
int TextureFormat; // enum textureFormat_t
int Always7; // literally always 7, purpose unknown
int nullPadding;
short AtlasPadding;
byte boolIsStreamed; // 1 if file is located in streamDB
byte unkBool;
byte boolNoMips; // 1 if image has no mips
byte boolFFTBloom; // 1 if using FFT Bloom
int StreamDBMipCount; // usually same number of mips stored in streamdb
};
TextureTypeis a member of the enumtextureType_t- it marks the image as 2-dimensional, 3-dimensional, or cubic.TextureMaterialKindis a member of the enumtextureMaterialKind_t- it tells the engine what type of material this texture is (albedo, normal, specular, etc).TextureFormatis a member of the enumtextureFormat_t- it tells the engine how the image is encoded (image format, block compression type, etc).
Mipmap Struct
The MIPMAP struct is a 36-byte sequence that comes immediately after the HEADER. This struct will be repeated n times, where n is equal to HEADER.MipCount above.
struct MIPMAP
{
__int64 MipLevel; // Starts at 0, increment by 1 each time it repeats
int MipPixelWidth; // Original PixelWidth reduced by 50% for each MipLevel
int MipPixelHeight; // Original PixelHeight reduced by 50% for each MipLevel
int UnknownFlagA;
int DecompressedSize; // Decompressed size in bytes
int FlagIsCompressed; // 1 if the texture is compressed
int CompressedSize; // Compressed size in bytes
int CumulativeSizeStreamDB;
};
CumulativeSizeStreamDBis always zero for the first mipmap. For additional mipmaps, it is the sum of previous mipmaps compressed sizes.
The total size (in bytes) of the TGA_FILE_HEADER can be calculated as: 63 + (36 * HEADER.MipCount).
Non-Streaming Images
In most cases, the full-size versions of these ".tga" images are stored in .streamdb files, where they are accessed by the game engine as needed. However, some images are "non-streaming," which means they aren't present in the .streamdb files at all. Non-streaming images will always have a HEADER.boolIsStreamed of 0 (false).
In that case, the body of the image is stored in the .resources file, and begins immediately after the TGA_FILE_HEADER structure described above. The offset (in bytes) at which the TGA_FILE_HEADER ends and the non-streamed image begins can be calculated as 63 + (36 * HEADER.MipCount).
Often, there will be several images packed together here (starting with the largest "mip"). The size of each of each image (in bytes) is given by MIPMAP.DecompressedSize.
010 Editor Template
A 010 Editor template for use with Doom Eternal's TGA file headers can be found here: https://github.com/brongo/eternal-010-templates/blob/main/templates/DoomEternalTGA.bt