.tga File Extension (BIMAGE)
The ".tga" files used in DoomDOOM Eternal are not targa images. They are actually "BIM" (or .bimage) files.
These files are stored in two parts. The headerfirst part consists of the HEADER, MIPMAP, and NON_STREAMED_IMAGE sections. This is embedded in DoomDOOM Eternal's .resources files. The bodysecond part consists of the fileSTREAMED_IMAGE section, which is embedded in Doom Eternal's .streamdb files.
File Signature
DoomDOOM Eternal ".tga" files can be identified by the first 3 bytes of the file header,header stored in a .resources file, which is always 0x42 0x49 0x4D or "BIM", short for "bimage" or "binary image".
HeaderHowever, Structure
these Thefiles .tgaare headerusually isstored extractedcompressed fromvia DoomOodle Eternal'sKraken, .resourceswhich files. may Itobscure consiststhe of afile 63-byteHEADER struct, followed by a variable number of 36-byte MIPMAP structs. Everything is little-endian.signature.
TheTo technicalview detailsthese offiles, .resources file extraction are beyond the scope of this article. Youyou can extract the .tga headers yourselffrom a .resources file using the EternalResourceExtractor tool.
File Structure
A DOOM Eternal ".tga" file consists of 4 parts:
- The
HEADERpart, which contains important details about the image size, format and encoding. - The
MIPMAPsection, which contains metadata for each individual image "mip" - The
NON_STREAMED_IMAGEsection, which contains non-streamed versions of the image. Usually, these are any "mips" that are smaller than about 32x32 pixels. Sometimes, though, it can be a full-sized image. - The
STREAMED_IMAGEsection, which is the portion of the image stored in .streamdb. The largest version of the image is normally stored in the .streamdb, along with several smaller mips.
The HEADER, MIPMAP, and NON_STREAMED_IMAGE sections are stored as a single compressed file, embedded within a DOOM Eternal .resources file. The STREAMED_IMAGE is stored separately in a .streamdb file. Even though they are stored separately, they are considered one "file" because exporting them to a usable format such as .DDS, .PNG, etc, requires that we connect all the disparate pieces.
struct ETERNAL_TGA_HEADERETERNAL_TGA_FILE
{
HEADER header;
MIPMAP mipmap[]; // Flexible array member,Array length varies with header.MipCount;
NON_STREAMED_IMAGE non_streamed_image[]; // Smaller mips of the image.
STREAMED_IMAGE streamed_image[]; // Full-size + larger mips of the image.
};
Header Section
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_t 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
Section
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_t 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.
Non-Streamed Images
The totalNON_STREAMED_IMAGE sizesection (inbegins bytes)immediately ofafter the last . The starting offset of this section can be calculated TGA_FILE_HEADERMIPMAPas:as offset = 63 + (36 * HEADER.MipCount).
Non-Streaming
Usually, Imagesany
Inmips mostsmaller cases,than theabout full-50x50 pixels in size versionswill of these ".tga" images arebe stored in .streamdbthis files,section. where theyThey are accessedpacked together back-to-back, from largest mip to smallest (excluding any mips that are located in the .streamdb). The size in bytes and the pixel dimensions of each mip are given by the gamecorresponding engineMIPMAP asstruct.
struct However,NON_STREAMED_IMAGE
some{
BYTE rawImageData[]; // Image format and encoding given in HEADER
};
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 bodyfull-sized image and all mips will be stored here, and there will not be any STREAMED_IMAGE section.
Streamed Images
In most cases, the full-size versions of these ".tga" images are stored in .streamdb files in a headerless format, where they are accessed by the game engine as needed.
struct STREAMED_IMAGE
{
BYTE rawImageData[]; // Image format & encoding given in HEADER
};
Unlike the NON_STREAMED_IMAGE section, these streamed images are not stored back-to-back in the .streamdb. Each streamed mip of the image ishas storedits own entry in the .resourcesstreamdb 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. index.
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