Sound Archives (.snd)
Sound archives store DOOM: Eternal and DOOM: The Dark Ages audio files (samples).
File Format
// Eternal version of sndEntry
struct sndEntry_Eternal
{
uint64_t farmhash; // Farmhash64 of the entry's DECODED data
uint32_t sampleID; // ID used to to reference this sample in the sound banks
uint32_t encodedSize; // Size of the entry's data as it's stored in the archive.
uint64_t offset; // Location of entry's data in the file. Relative to beginning of file
uint32_t decodedSize; // Size of the entry's data after being decoded
uint16_t encoding; // Either 2 or 3
uint16_t metaSize; // Size of this entry's meta section inside the meta blob
uint32_t metaOffset; // Location of this entry's meta section. Relative to global offset 0xC
}
// Dark Ages version of sndEntry
struct sndEntry_DarkAges
{
uint64_t farmhash; // Farmhash64 of the entry's data
uint32_t sampleID; // ID used to to reference this sample in the sound banks
uint32_t encodedSize; // Size of the entry's data as it's stored in the archive.
uint64_t offset; // Location of entry's data in the file. Relative to beginning of file
uint32_t decodedSize; // Always equal to encodedSize
uint32_t metaSize; // Size of this entry's meta section inside the meta blob
uint32_t metaOffset; // Location of this entry's meta section. Relative to global offset 0xC
}
struct sndHeaderChunk
{
uint32_t metaSize; // Size of the header chunk's meta blob + 4
char* metaBlob; // Header chunk's meta blob. Length == metaSize - 4
uint32_t numEntries; // Number of entries
sndEntry* entries; // Array of entry information. Length == numEntries. Exact format depends on the game
}
struct snd
{
uint32_t version; // Always 6
uint32_t headerSize; // Size of the header chunk
sndHeaderChunk; // Header chunk. Exact size given by headerSize
char* dataChunk; // Data chunk. Stores the complete audio samples. Length == remainder of file
}
About: Meta Blob
Audio samples in both games use RIFFwems as their basicaudio file format. A sample's "meta" section consists of the start of the file, up to and including the length field of it's data chunk.
Code that demonstrates how to calculate the length of a sample's meta chunk can be found here
Notes: Encoding Setting
This section concerns the encoding property of Eternal's .snd entries. As mentioned in the definition, this value can either be 2 or 3.
When the value is 3, this means the sample's data is stored as a .wem. All DOOM: The Dark Ages audio also behaves this way.
When the value is 2, this means the sample's data is compressed and stored as an OPUS file. For these entries, the encodedSize is the size of the OPUS file. The decodedSize will match the size of the original WEM. Additionally, the sample's meta chunk, normally unused, gets read and parsed by the game. Both the decodedSize and meta chunk matter. If their data is incorrect, the sample may fail to play, get cut off early, or even crash the game when played.
Effectively, an encoding value of 2 implies an archive-level compression of the audio data. It would be extremely difficult to create sound mods that utilize this setting without calling external audio conversion tools while loading mods, or without having access to both the OPUS and original WEM file. Thankfully, we can simply flip the encoding value from 2 to 3 and store the modded sample as a WEM without consequence. This is an overwhelmingly easier option, with the only "tradeoff" being slightly more storage space required for WEM data.