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 dependant 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 Hierarchy: DemoFile { DemoHeader DemoSegment { DemoMacroBlock { DemoMacroHeader DemoMacroData { } } ... } ... DemoDirectoryEntry ... } The biggest part of the demo file is made up by the macro for game data. 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 directly after the gamedata and is parsed dynamically. struct DemoServerMessage { uint8 id; ... } List of Server Messages and their structure: {0} 'bad', // Should never be encountered, otherwise stream is corrupt. {1} 'nop', {2} 'disconnect', {3} 'event', uint8 num_events; // ... excessive bit parsing {4} 'version', uint32 version; {5} 'setview', {6} 'sound', {7} 'time', float32 time; {8} 'print', char* text; {9} 'stufftext', char* text; {10} 'setangle', char[5] uk_b; {11} 'serverinfo', uint32 serverversion; uint32 servercount; uint32 server_crc; char[16] client_dll_crc; uint8 maxclients; uint8 playernum; uint8 uk_b5; char* gamedir; if(serverversion >= 45) { char* remotehost; } char* map1; char* map2; uint8 extraflag; if(extraflag > 0) { uint8 extralength; char* extradata; char[16] lastdata; } {12} 'lightstyle', uint8 style_index; // 0 - 63 char* lightinfo; {13} 'updateuserinfo', uint8 slot; uint32 userid; char* userinfo; char[16] uk_data; {14} 'deltadescription', char* text; uint16 uk_i1; // if text = 'entity_t' then // skip constant data chunks due to no delta compression // and read until "00 27" is found as MSGID 39, reseek and continue reading blocks {15} 'clientdata', uint8 slot; uint32 userid; char* userinfo; {16} 'stopsound', uint16 uk_s1; {17} 'createstringtables', {18} 'updatestringtable', {19} 'entitymessage', {20} 'spawnstatic', {21} 'spawnbinary', {22} 'spawnbaseline', {23} 'temp_entity', uint8 entity_event_type; // Rest is the actual event, see common.h in HLSDK {24} 'setpause', uint8 state; {25} '', {26} '', {27} '', {28} '', {29} 'spawnstaticsound', uint16[3] org; uint16 sound_num; uint8 vol; uint8 atten; uint16 uk_s1; uint8 uk_b2; uint8 uk_b3; {30} 'intermission', {31} '', {32} 'cdtrack', uint32 fromtrack; uint32 totrack; {33} '', {34} '', {35} 'weaponanim', uint8 sequence_number; uint8 weaponmodel_bodygroup; {36} '', {37} 'roomtype', sint16 room_type; {38} 'addangle', float32[4] angles; {39} 'newusermsg', uint8 usermsg_id; uint8 default_msg_length; char* usermsg_description; uint8 uk_b2; {40} '', {41} '', {42} '', {43} '', {44} '', {45} 'classinfo', uint8 subpacket; {46} '', {47} '', {48} '', {49} '', {50} 'hltv', uint8 stateflag; // HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands // HLTV_STATUS 1 // send status infos about proxy // HLTV_LISTEN 2 // tell client to listen to a multicast stream {51} 'director', uint8 command_length; uint8 command_event; if(command_event) { "command_event": // DRC_CMD_NONE 0 // NULL director command // DRC_CMD_START 1 // start director mode // DRC_CMD_EVENT 2 // informs about director command // DRC_CMD_MODE 3 // switches camera modes // DRC_CMD_CAMERA 4 // sets camera registers // DRC_CMD_TIMESCALE 5 // sets time scale // DRC_CMD_MESSAGE 6 // send HUD centerprint uint8 effect; uint8[4] col1_rgba; float32 x; float32 y; // ushort 8.8 fadein time float32 fadein_time; // ushort 8.8 fadeout time float32 fadeout_time; // ushort 8.8 hold time float32 hold_time; // optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) float32 fx_time; // string text message (128 chars max sz string) *char text; // DRC_CMD_SOUND 7 // plays a particular sound // DRC_CMD_STATUS 8 // status info about broadcast uint32 hltv_slots; uint32 hltv_spectators; uint16 hltv_proxies; // DRC_CMD_BANNER 9 // banner file name for HLTV gui // DRC_CMD_FADE 10 // send screen fade command // DRC_CMD_SHAKE 11 // send screen shake command // DRC_CMD_STUFFTEXT 12 // like the normal svc_stufftext but as director command // DRC_CMD_LAST 12 } else { uint16 entity_index_prim; // index number of primary entity uint16 entity_index_sec; // index number of secondary entity uint32 event_flag; // eventflags (priority and flags) "event_flag": // DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) // DRC_FLAG_SIDE (1<<4) // // DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene // DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo // DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc) // DRC_FLAG_INTRO (1<<8) // is a introduction scene // DRC_FLAG_FINAL (1<<9) // is a final scene // DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data uint8[command_length-8] uk_data; } {52} '', {53} '', {54} 'sendextrainfo', char* text; uint8 uk_b; {55} '', {56} '', {57} '', {58} '', {59} '', {60} '', {61} '', {62} '', {63} '', -------------------------------------------------------------------------------- {64-256} These server messages are dynamically indexed using message {39}'s usermsg_id See hl1-demofile-spec-cstrike.txt -------------------------------------------------------------------------------- # 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 uint32 uk_i1; uint32 uk_i2; float32 uk_f; char[72] data; # 7 / Unknown uint32 uk_i1; uint32 uk_i2; # 8 / Play Sound uint32 uk_i1; uint32 sound_name_length; char* sound_name; float32 uk_f1; float32 uk_f2; uint32 uk_i2; uint32 uk_i3; # 9 / Unknown sint32 chunklength; char* data;