Half-Life 1 Demofile Specifications -------------------------------------------------------------------------------- The Half-Life Demofile format has it's roots in the Quake 1 Demo format and is basically just an extension to it. Thus it contains only basic descriptors which rapidly points into raw recorded network data. It requires a lot of game engine related logic to be used for parsing such a file in a non-game client application. This explains the lack of any documentation or examples in the SDK as it would require to heavily open up game engine sources. General Fileformat Data Hiarchy: DemoFile { DemoHeader DemoSegment { DemoMacroBlock { DemoMacroHeader DemoMacroData { } } ... } ... DemoDirectoryEntry ... } Demofile Header -------------------------------------------------------------------------------- The header starting at the beginning of the file contains the magic and further global game related information. DemoHeader->directory_offset points to a table of DemoDirectoryEntry structs prefixed by a count and suffixed by a file offset to the end of table. The location of the entries is usually somewhere at the end of the demo file, which is often a cause for "corrupt" demofiles if the data inbetween got altered/wrongly written by the game itself (raw network stream insanity or crash) thus resulting in a wrong offset. This can be fixed by parsing the end of the file for directory entries and fixing the proper offset value which allows playback in the game again. Known network_version -> Half-Life version mappings: 40 = '1.0.1.6' 42 = '1.1.0.1' 43 = '1.1.0.4/6' 45 = '1.1.0.8' 46 = '1.1.1.x' 47 = '1.1.1.x' struct DemoHeader { char[8] magic; uint32 demo_version; uint32 network_version; char[260] map_name; char[260] game_dll; uint32 map_crc; uint32 directory_offset { uint32 dir_count; // 1 to 1024 DemoDirectoryEntry* entries; uint32 dir_end; } } Demofile Directory Entries -------------------------------------------------------------------------------- A directory entry is used to define a series of data segments of the recording which contain the game data. Usually this is used to split data of the "Loading..." part and the actual game recording part. DemoDirectoryEntry->offset points to the start of a data segment within the demofile. struct DemoDirectoryEntry { uint32 number; char[64] title; uint32 flags; int32 play; float32 time; uint32 frames; uint32 offset; // points to a DemoSegment uint32 length; // length of the DemoSegment at offset } Demofile Data Segments -------------------------------------------------------------------------------- Each segment is of variable size and contains a sequence of numbered macro blocks. We use it here purely as a virtual container. struct DemoSegment { DemoMacro* macros; } Demofile Macro Blocks -------------------------------------------------------------------------------- Each macro block is "stamped" with time and frame information from the recording and is followed by the individual macro type data which has to be parsed individually. This relates to the game engine's status information at a specific time. The game uses 10 macro types: 0 = 'Game Data' 1 = 'Game Data' 2 = 'N/A' // unused, skip 3 = 'Client Command' // string 4 = 'N/A' // string 5 = 'Last in segment' 6 = 'N/A' 7 = 'N/A'-------------------------------------------------------------------------------- 8 = 'Play Sound' 9 = 'N/A' // delta data struct DemoMacroHeader { uint32 file_offset; uint8 type; float32 time; uiint32 frame; ... } Demofile Macro Types -------------------------------------------------------------------------------- Each macro type has it's own structure closely related to the game engine and it's parsing differs among the demo's network version. # 0 and 1 / Game Data: struct DemoMacro_GameData { if(network_version = 42) { uint8[560] extinfo; } else if(network_version >= 45) { uint8[220] extinfo; uint16 recorded_resolution_width; uint16 recorded_resolution_height; uint16 recorded_resolution_depth; uint8[238] extinfo; } int32 chunklength; // size of following DemoServerMessage ... } A server message follows the game data information and is parsed dynamically. struct DemoServerMessage { uint8 id; ... } List of Server Message IDs: {0} 'bad', {1} 'nop', {2} 'disconnect', {3} 'event', {4} 'version', {5} 'setview', {6} 'sound', {7} 'time', {8} 'print', {9} 'stufftext', {10} 'setangle', {11} 'serverinfo', {12} 'lightstyle', {13} 'updateuserinfo', {14} 'deltadescription', {15} '', {16} 'stopsound', {17} '', {18} '', {19} '', {20} '', {21} '', {22} '', {23} 'temp_entity', {24} 'setpause', {25} '', {26} '', {27} '', {28} '', {29} 'spawnstaticsound', {30} 'intermission', {31} '', {32} 'cdtrack', {33} '', {34} '', {35} 'weaponanim', {36} '', {37} 'roomtype', {38} 'addangle', {39} 'newusermsg', {40} '', {41} '', {42} '', {43} '', {44} '', {45} 'classinfo', {46} '', {47} '', {48} '', {49} '', {50} 'hltv', {51} 'director', {52} '', {53} '', {54} 'sendextrainfo', {55} '', {56} '', {57} '', {58} '', {59} '', {60} '', {61} '', {62} '', {63} '', {64} '', {65} '', {66} '', {67} '', {68} '', {69} '', {70} '', {71} '', {72} '', {73} '', {74} '', {75} '', {76} '', {77} '', {78} '', {79} '', {80} '', {81} '', {82} '', {83} '', {84} '', {85} '', {86} '', {87} '', {88} '', {89} '', {90} '', {91} '', {92} '', {93} '', {94} '', {95} '', {96} '', {97} '', {98} '', {99} '', {100} '' # 2 / Unknown This is an unused macro type in Half-Life and should never be encountered. # 3 / Client Command A null-terminated string corresponding to a client command such as "+weapon". # 4 / Unknown A null-terminated string. # 5 / Last in segment This macro type marks the end of this segment for verification and switch to the next one. # 6 / Unknown