Эта статья документации для всего, что использует платформу Source. Нажмите для получения дополнительной информации.

Ru/DEM (file format): Difference between revisions

From Valve Developer Community
< Ru
Jump to navigation Jump to search
m (otherlang2'd)
m (Multipage removal)
 
(19 intermediate revisions by 10 users not shown)
Line 1: Line 1:
{{otherlang2
{{LanguageBar}}
|en=DEM_Format
{{src topicon}}
}}
'''DEM''' (сокращение от ДЕМонстрации) - это формат исходного файла DEMO. Используется совместно с Source рекордером для записи демонстраций, которые содержат записанные события, которые можно редактировать и воспроизводить в игре. Демо может быть отредактирован для изменения углов камеры, ускорения, замедления, воспроизведения музыки и других функций.
Note that it is not possible to edit what happens in a Source demo, it basically stores the packets right off the wire, and we don't know the packet format.


For the purpose of this, I will be referring to each collection of events as a frame.  This may or may not add up to the number of frames given by the header.
{{Note|В настоящее время нет инструментов для редактирования демонстраций после их записи.}}


For the purpose of this we will be referring to each collection of events as a frame. This may or may not add up to the number of frames given by the header.


===Заголовок Демо===
===Заголовок Демо===
Line 11: Line 11:
! Тип || Поле || Значение
! Тип || Поле || Значение
|-
|-
| [[String]] || Заголовок || 7+NULL characters, should be "HL2DEMO"
| {{L|String}} || Заголовок || 8 символов, должно быть "HL2DEMO"+NULL
|-
|-
| [[Int]] || Демо Протокол || Версия протокола демо, например '3'
| {{L|Int}} || Демо Протокол || Версия демо протокола
|-
|-
| [[Int]] || Протокол Сети || Версия Сетевого протокола  
| {{L|Int}} || Протокол Сети || Номер версии сетевого протокола
|-
|-
| [[String]] || Имя Сервера || Длиной в 260 символов  
| {{L|String}} || Имя сервера || Длинной в 260 символов
|-  
|-  
| [[String]] || Имя клиента || Длиной в 260 символов  
| {{L|String}} || Имя клиента || Длинной в 260 символов
|-
|-
| [[String]] || Имя Карты || Длиной в 260 символов  
| {{L|String}} || Имя карты || Длинной в 260 символов
|-
|-
| [[String]] || Директория Игры  || Длиной в 260 символов  
| {{L|String}} || Директория игры || Длинной в 260 символов
|-
|-
| [[Float]] || Время игры || Продолжительность записи Демо, в секундах
| {{L|Float}} || Время воспроизведения || Продолжительность демонстрации в секундах
|-
|-
| [[Int]] || Ticks || The number of ticks in the demo
| {{L|Int}} || Тиков || Количество тиков в демонстрации
|-  
|-  
| [[Int]] || Кадры || Количество кадров в Демо
| {{L|Int}} || Кадров || Количество кадров в демо
|-
|-
| [[Int]] || Sign on length || ?
| {{L|Int}} || Длина записи || Длина знаков до первого кадра
|}
|}


===Frame===
===Кадр===
Each frame begins with 0 or more of these commands:
Каждый кадр начинается с 0 или более следующих команд:
These are described in hl2sdk\utils\demoinfo\demoformat.h
Они описаны в hl2sdk\utils\demofile\demoformat.h / hl2sdk-ob\public\demofile\demoformat.h / hl2sdk-l4d\public\demofile\demoformat.h
 
====Сетевые протоколы 7 и 8====
{| class=standard-table
{| class=standard-table
! Тип || Значение
! Тип || Значение
Line 57: Line 59:
|}
|}


Depending on the command that was received, a different action needs to be taken.
====Сетевые протоколы 14 и 15====
{| class=standard-table
! Тип || Значение
|-
| dem_stringtables || 8
|-
| dem_lastcommand || dem_stringtables
|}


====dem_stop, dem_lastcommand====
====Сетевые протоколы 36 и выше====
This is a signal that the demo is over, and there is no more data to be parsed.
{| class=standard-table
! Тип || Значение
|-
| dem_customdata || 8
|-
| dem_stringtables || 9
|-
| dem_lastcommand || dem_stringtables
|}


====dem_consolecmd, dem_datatables, dem_usercmd====
В зависимости от полученной команды необходимо выполнить другое действие.
Read a standard data packet, deal with it as necessary.
 
====dem_stop====
Это сигнал о том, что демо закончилось, и больше данных не нужно разбирать.
 
====dem_consolecmd, dem_datatables, dem_usercmd, dem_stringtables====
Чтение стандартных пакет данных, при необходимости они обработываются.


====dem_synctick, dem_signon, dem_packet====
====dem_synctick, dem_signon, dem_packet====
Ignore.
Игнорирование.
 
===Стандартные данные "Пакета"===
Каждый раз, когда читается более одного байта, используется стандартный формат.
 
Это начинается с целого числа, которое имеет количество байтов в этом пакете.
 
Пример кода для этого:
 
<source lang="cpp">
int ReadData(char **buffer)
{
int len;
fread (&len,sizeof(int),1,fDemo);
if (len > 0)
{
*buffer = new char[len];
fread(*buffer,len,1,fDemo);
}
return len;
}
</source>
 
=== Формат Кадров ===
<source lang="cpp">
Frame {
      int ServerFrame;
      int ClientFrame; // ServerFrame и ClientFrame delta, вероятно, соответствуют отклику клиента.
      int SubPacketSize;
      *buffer = new char[SubPacketSize]; // Сообщение о состоянии обновлений
      Packet pkt = (rest of frame as data exists) // Все демо-команды объединены в этой области, структура ниже...
      JunkData data = (unknown) // Бывший: 0x8f 5a b5 04 94 e6 7c 24 00 00 00 00 00 ... (40 байт 0x00 после 0x24)
                                // Это может быть либо конец кадра, либо начало следующего кадра.
}
</source>
 
<source lang="cpp">
Packet {
      char CmdType;
      int Unknown;
      int TickCount; // Это только периодический появляется.
      int SizeOfPacket;
      *buffer = new char[SizeOfPacket];
}
</source>
 
===Пример использования===
 
Может быть интересно захватить заголовок демонстрационных файлов на случай, если вы запустите веб-сервер, на котором размещены ваши демонстрации / телевизоры.
Вот скрипты, который выполняет эту работу за вас.
 
PHP
 
<source lang="php">
<?
/* Source & OrangeBox demos header reader.
By PoLaRiTy (nocheatz.com)
 
Help from  : https://developer.valvesoftware.com/wiki/DEM_Format#Demo_Header
              http://hg.alliedmods.net/hl2sdks/hl2sdk-css/file/1901d5b74430/public/demofile/demoformat.h
 
Types sizes :
Int : 4 bytes
Float : 4 bytes
String : 260 bytes
*/
 
 
class DemoInfo_s
{
var $dem_prot;      // Demo protocol version
var $net_prot;      // Network protocol versio
var $host_name;      // HOSTNAME in case of TV, and IP:PORT or localhost:PORT in case of RIE (Record In eyes).
var $client_name;    // Client name or TV name.
var $map_name;      // Map name
var $gamedir;        // Root game directory
var $time;          // Playback time (s)
var $ticks;          // Number of ticks
var $frames;        // Number of frames
var $tickrate;      // Tickrate
var $type;          // TV or RIE ? (0 = RIE, 1 = TV)
var $status_present; // true if a status command is available in the demo.
}
 
function ExtOfFile($pathtofile)
{
return end(explode('.',$pathtofile));
}
 
function ReadString($handle, $n = 260)
{
$buffer = "";
for($d = 1; ((($char = fgetc($handle)) !== false) && ($d < $n)); $d++) $buffer = $buffer.$char;
return trim($buffer);
}
 
function ReadInt($handle, $n = 4)
{
$buf = fread($handle, $n);
$number = unpack("i", $buf);   
return $number[1];
}
 
function ReadFloat($handle)
{
$buf = fread($handle, 4);
$number = unpack("f", $buf);   
return $number[1];
}
 
function IsGoodIPPORTFormat($string)
{
if(preg_match('/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\:[0-9]{1,5}/', $string)) return true;
else return false;
}
 
function GetDemoInfo($pathtofile, $fast = false) // BOOL fast : if true, doesn't check the presence of the status
{
$infos = NULL;
if(ExtOfFile($pathtofile) === "dem")
{
$handle = fopen($pathtofile, "r");
if($handle)
{
if(ReadString($handle, 8) === "HL2DEMO")
{
$infos = new DemoInfo_s;
$infos->dem_prot = ReadInt($handle);
$infos->net_prot = ReadInt($handle);
$infos->host_name = ReadString($handle);
$infos->client_name = ReadString($handle);
$infos->map_name = ReadString($handle);
$infos->gamedir = ReadString($handle);
$infos->time = ReadFloat($handle);
$infos->ticks = ReadInt($handle);
$infos->frames = ReadInt($handle);
$infos->tickrate = intval($infos->ticks / $infos->time);
if(IsGoodIPPORTFormat($infos->host_name)) $infos->type = 0; // RIE  TODO : Add localhost:PORT check.
else $infos->type = 1; // TV
$infos->status_present = false;
if(!$fast && !($infos->type == 1)) // No status in TV records.
{
while(!(($l = fgets($handle)) === false))
{
if(stripos($l, "\x00status\x00") !== false)
{
$infos->status_present = true;
break;
}
}
}
}
else
{
echo "Bad file format.";
}
fclose($handle);
}
else
{
echo "File not found or unable to read.";
}
}
else
{
echo "Bad file extension.";
}
return $infos;
}
?>
</source>
 
C++
 
<source lang="cpp">
#include <Windows.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <clocale>
#include <fstream>
using namespace std;
 
class SourceDemo
{
private:
struct DemoFile {
FILE *file;
long size;
std::string data;
} DemoFile;
public:
struct DemoInfo
{
std::string DemoFileStamp;
int DemoProtocol;
int NetworkProtocol;
std::string HostIP;
std::string ClientName;
std::string MapName;
std::string GameDIR;
double PlayBack_Time;
int PlayBack_Ticks;
int PlayBack_Frames;
int SignOnLength;
int TickRate;
int Type;
int Commands;
} DemoInfo;
public:
~SourceDemo(void);
SourceDemo(const char* filepath);
int ReadInt(int n = 4);
float ReadFloat(int n = 4);
std::string ReadString(int n = 260);
void Release(void);
};
 
SourceDemo::SourceDemo(const char* filepath)
{
this->DemoFile.file = fopen(filepath, "rb");
this->DemoFile.size = ftell(this->DemoFile.file);
vector<char> buf;
fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
this->DemoFile.data = string(buf.begin(), buf.end());
//---------------------------------------------------------------//
this->DemoInfo.DemoFileStamp = this->ReadString(8);
if (DemoInfo.DemoFileStamp == "HL2DEMO") {
this->DemoInfo.DemoProtocol = this->ReadInt();
this->DemoInfo.NetworkProtocol = this->ReadInt();
this->DemoInfo.HostIP = this->ReadString();
this->DemoInfo.ClientName = this->ReadString();
this->DemoInfo.MapName = this->ReadString();
this->DemoInfo.GameDIR = this->ReadString();
this->DemoInfo.PlayBack_Time = this->ReadFloat();
this->DemoInfo.PlayBack_Ticks = this->ReadInt();
this->DemoInfo.PlayBack_Frames = this->ReadInt();
this->DemoInfo.SignOnLength = this->ReadInt();
this->DemoInfo.TickRate = (int)(this->DemoInfo.PlayBack_Ticks / this->DemoInfo.PlayBack_Time);
this->DemoInfo.Type = -1;
//this->DemoInfo.Commands = static_cast<int>(this->DemoFile.data.find("\x00status\x00"));
this->DemoInfo.Commands = -1;
}
else {
this->DemoInfo.DemoProtocol = -1;
this->DemoInfo.NetworkProtocol = -1;
this->DemoInfo.HostIP = "NONE";
this->DemoInfo.ClientName = "NONE";
this->DemoInfo.MapName = "NONE";
this->DemoInfo.GameDIR = "NONE";
this->DemoInfo.PlayBack_Time = -1.0;
this->DemoInfo.PlayBack_Ticks = -1;
this->DemoInfo.PlayBack_Frames = -1;
this->DemoInfo.TickRate = -1;
this->DemoInfo.Type = -1;
this->DemoInfo.Commands = -1;
}
};
SourceDemo::~SourceDemo() {};
 
int SourceDemo::ReadInt(int n)
{
std::vector<char> buf(n);
fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
std::string buffer(buf.begin(), buf.end());
buffer.erase(std::remove(buffer.begin(), buffer.end(), '\x00'), buffer.end());
return *((int*)&buffer.c_str()[0]);
}
 
float SourceDemo::ReadFloat(int n)
{
std::vector<char> buf(n);
fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
std::string buffer(buf.begin(), buf.end());
buffer.erase(std::remove(buffer.begin(), buffer.end(), '\x00'), buffer.end());
return *((float*)&buffer.c_str()[0]);
}
 
std::string SourceDemo::ReadString(int n)
{
std::vector<char> buf(n);
fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
std::string buffer(buf.begin(), buf.end());
buffer.erase(std::remove(buffer.begin(), buffer.end(), '\x00'), buffer.end());
return buffer;
}
 
void SourceDemo::Release(void)
{
this->DemoInfo.DemoProtocol = -1;
this->DemoInfo.NetworkProtocol = -1;
this->DemoInfo.HostIP = "NONE";
this->DemoInfo.ClientName = "NONE";
this->DemoInfo.MapName = "NONE";
this->DemoInfo.GameDIR = "NONE";
this->DemoInfo.PlayBack_Time = -1.0;
this->DemoInfo.PlayBack_Ticks = -1;
this->DemoInfo.PlayBack_Frames = -1;
this->DemoInfo.TickRate = -1;
this->DemoInfo.Type = -1;
this->DemoInfo.Commands = -1;
fclose(this->DemoFile.file);
return;
}
 
inline bool FileExist(const std::string& name) {
ifstream f(name.c_str());
return f.good();
}
 
int main(int argc, char *argv[])
{
SetConsoleTitle(TEXT("DemoInfo"));
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
std::setlocale(LC_ALL, "en_US.UTF-8");
std::setlocale(LC_ALL, "Russian");
if (argc < 1) {
char szAppPath[MAX_PATH] = "";
GetModuleFileNameA(0, szAppPath, MAX_PATH);
std::string AppName = szAppPath;
AppName = AppName.substr(AppName.rfind("\\") + 1);
std::cout << "########################################################################################" << std::endl;
std::cout << "## Uses: " << AppName << " <demo_file_path.dem>" << std::endl;
std::cout << "########################################################################################" << std::endl;
Sleep(10000);
return 0;
}
const char* main_executable = argv[0];
const char* input_file = argv[1];
if (FileExist(input_file) != false) {
SourceDemo SD(input_file);
std::cout << "########################################################################################" << std::endl;
std::cout << "## Demo Info - Программа для просмотра информации у демонстрации. (OrangeBox\\Source)" << std::endl;
std::cout << "########################################################################################" << std::endl;
std::cout << "## File: " << input_file << std::endl;
std::cout << "########################################################################################" << std::endl;
std::cout << "## FileStamp\t\t-> " << SD.DemoInfo.DemoFileStamp << std::endl;
std::cout << "## Demo Protocol\t-> " << SD.DemoInfo.DemoProtocol << std::endl;
std::cout << "## Network Protocol\t-> " << SD.DemoInfo.NetworkProtocol << std::endl;
std::cout << "## Host IP\t\t-> " << SD.DemoInfo.HostIP << std::endl;
std::cout << "## Map Name\t\t-> " << SD.DemoInfo.MapName << std::endl;
std::cout << "## GameDIR\t\t-> " << SD.DemoInfo.GameDIR << std::endl;
std::cout << "## Time\t\t\t-> " << SD.DemoInfo.PlayBack_Time << " (Погрешность 1-2%)" << std::endl;
std::cout << "## Ticks\t\t-> " << SD.DemoInfo.PlayBack_Ticks << std::endl;
std::cout << "## Frames\t\t-> " << SD.DemoInfo.PlayBack_Frames << std::endl;
std::cout << "## SignOnLength\t\t-> " << SD.DemoInfo.SignOnLength << std::endl;
std::cout << "## TickRate\t\t-> " << SD.DemoInfo.TickRate << std::endl;
std::cout << "## Type\t\t\t-> " << SD.DemoInfo.Type << std::endl;
std::cout << "## Commands\t\t-> " << SD.DemoInfo.Commands << std::endl;
std::cout << "########################################################################################" << std::endl;
std::cout << "## Сделал Sam!" << std::endl;
std::cout << "## https://steamcommunity.com/id/SamXDR" << std::endl;
std::cout << "## https://steamcommunity.com/profiles/76561198312917682" << std::endl;
std::cout << "########################################################################################" << std::endl;
SD.Release();
Sleep(10000);
return 0;
}
else {
char szAppPath[MAX_PATH] = "";
GetModuleFileNameA(0, szAppPath, MAX_PATH);
std::string AppName = szAppPath;
AppName = AppName.substr(AppName.rfind("\\") + 1);
std::cout << "########################################################################################" << std::endl;
std::cout << "## Uses: " << AppName << " <demo_file_path.dem>" << std::endl;
std::cout << "########################################################################################" << std::endl;
Sleep(10000);
return 0;
}
Sleep(10000);
return 0;
}
</source>
 
Python (3.6.1)
 
<source lang="python">
from struct import unpack
from sys import argv
from os.path import exists
from time import sleep


===Standard Data "Packet"===
class SourceDemo(object):
Any time there is more than one byte to be read in, a standard format is used.
    __file = None
    __IsWorking = False
    DemoFileStamp = None
    DemoProtocol = -1
    NetworkProtocol = -1
    HostIP = None
    ClientName = None
    MapName = None
    GameDIR = None
    PlayBack_Time = -1.0
    PlayBack_Ticks = -1
    PlayBack_Frames = -1
    SignOnLength = -1
    TickRate = -1
    def __init__(self, file_path):
        try:
            self.__file = open(file_path, 'rb')
            self.DemoFileStamp = self.__file.read(8).replace(b'\x00', b'').decode()
            if self.DemoFileStamp == 'HL2DEMO':
                self.DemoProtocol = unpack('i', self.__file.read(4))[0]
                self.NetworkProtocol = unpack('i', self.__file.read(4))[0]
                self.HostIP = self.__file.read(260).replace(b'\x00', b'').decode()
                self.ClientName = self.__file.read(260).replace(b'\x00', b'').decode()
                self.MapName = self.__file.read(260).replace(b'\x00', b'').decode()
                self.GameDIR = self.__file.read(260).replace(b'\x00', b'').decode()
                self.PlayBack_Time = int( unpack('f', self.__file.read(4))[0] * 1000 ) / 1000
                self.PlayBack_Ticks = unpack('i', self.__file.read(4))[0]
                self.PlayBack_Frames = unpack('i', self.__file.read(4))[0]
                self.SignOnLength = unpack('i', self.__file.read(4))[0]
                self.TickRate = int(self.PlayBack_Ticks / self.PlayBack_Time)
                self.__IsWorking = True
                return
        except:
            return
    def __call__(self):
        if self.__IsWorking == True:
            if self.__IsWorking == True:
                r = {
                    'DemoFileStamp': self.DemoFileStamp,
                    'DemoProtocol': self.DemoProtocol,
                    'NetworkProtocol': self.NetworkProtocol,
                    'HostIP': self.HostIP,
                    'ClientName': self.ClientName,
                    'MapName': self.MapName,
                    'GameDIR': self.GameDIR,
                    'PlayBack_Time': self.PlayBack_Time,
                    'PlayBack_Ticks': self.PlayBack_Ticks,
                    'PlayBack_Frames': self.PlayBack_Frames,
                    'SignOnLength': self.SignOnLength,
                    'TickRate': self.TickRate,
                }
                return r
        return None
    def GetDemoFileStamp(self):
        if self.__IsWorking == True:
            return self.DemoFileStamp
        return None
    def GetDemoProtocol(self):
        if self.__IsWorking == True:
            return self.DemoProtocol
        return None
    def GetNetworkProtocol(self):
        if self.__IsWorking == True:
            return self.NetworkProtocol
        return None
    def GetHostIP(self):
        if self.__IsWorking == True:
            return self.HostIP
        return None
    def GetClientName(self):
        if self.__IsWorking == True:
            return self.ClientName
        return None
    def GetMapName(self):
        if self.__IsWorking == True:
            return self.MapName
        return None
    def GetGameDIR(self):
        if self.__IsWorking == True:
            return self.GameDIR
        return None
    def GetTime(self):
        if self.__IsWorking == True:
            return self.PlayBack_Time
        return None
    def GetTicks(self):
        if self.__IsWorking == True:
            return self.PlayBack_Ticks
        return None
    def GetFrames(self):
        if self.__IsWorking == True:
            return self.PlayBack_Frames
        return None
    def GetSignOnLength(self):
        if self.__IsWorking == True:
            return self.SignOnLength
        return None
    def GetTickRate(self):
        if self.__IsWorking == True:
            return self.TickRate
        return None


This begins with an integer that has the number of bytes in this packet.
def main(argc, argv):
    file = argv[0].rsplit('\\')
    file = file[len(file)-1]
    if len(argv) == 2:
        if exists(argv[1]) == True:
            file = argv[1].rsplit('\\')
            file = file[len(file)-1]
            s = SourceDemo(argv[1])
            print('########################################################################################')
            print('## DemoInfo.py - Скрипт для просмотра информации у демонстрации. (OrangeBox\\Source)')
            print('########################################################################################')
            print('## File: {}'.format(file))
            print('########################################################################################')
            print('## FileStamp\t\t-> {}'.format(s.GetDemoFileStamp()))
            print('## Demo Protocol\t-> {}'.format(s.GetDemoProtocol()))
            print('## Network Protocol\t-> {}'.format(s.GetNetworkProtocol()))
            print('## Host IP\t\t-> {}'.format(s.GetHostIP()))
            print('## Map Name\t\t-> {}'.format(s.GetMapName()))
            print('## GameDIR\t\t-> {}'.format(s.GetGameDIR()))
            print('## Time\t\t\t-> {} (Имеется погрешность!)'.format(s.GetTime()))
            print('## Ticks\t\t-> {}'.format(s.GetTicks()))
            print('## Frames\t\t-> {}'.format(s.GetFrames()))
            print('## SignOnLength\t\t-> {}'.format(s.GetSignOnLength()))
            print('## TickRate\t\t-> {}'.format(s.GetTickRate()))
            print('########################################################################################')
            return
    print('########################################################################################')
    print('## Uses: {} <demo_file_path.dem>'.format(file))
    print('########################################################################################')
    return


Example code to handle this:
if __name__ == '__main__':
    argc = len(argv)
    argv = argv
    main(argc, argv)
    sleep(25)
    exit(0)
</source>


int ReadData(char **buffer)
==Смотрите также==
{
*{{L|Demo Recording Tools}}
int len;
*{{L|Demo Video Creation}}
fread (&len,sizeof(int),1,fDemo);
if (len > 0)
{
*buffer = new char[len];
fread(*buffer,len,1,fDemo);
}
return len;
}


[[Category:File formats]]


[[Category:Technical:ru]]
{{ACategory|File formats}}

Latest revision as of 06:06, 12 July 2024

English (en)Русский (ru)Translate (Translate)

DEM (сокращение от ДЕМонстрации) - это формат исходного файла DEMO. Используется совместно с Source рекордером для записи демонстраций, которые содержат записанные события, которые можно редактировать и воспроизводить в игре. Демо может быть отредактирован для изменения углов камеры, ускорения, замедления, воспроизведения музыки и других функций.

Note.pngПримечание:В настоящее время нет инструментов для редактирования демонстраций после их записи.

For the purpose of this we will be referring to each collection of events as a frame. This may or may not add up to the number of frames given by the header.

Заголовок Демо

Тип Поле Значение
String(en) Заголовок 8 символов, должно быть "HL2DEMO"+NULL
Int(en) Демо Протокол Версия демо протокола
Int(en) Протокол Сети Номер версии сетевого протокола
String(en) Имя сервера Длинной в 260 символов
String(en) Имя клиента Длинной в 260 символов
String(en) Имя карты Длинной в 260 символов
String(en) Директория игры Длинной в 260 символов
Float(en) Время воспроизведения Продолжительность демонстрации в секундах
Int(en) Тиков Количество тиков в демонстрации
Int(en) Кадров Количество кадров в демо
Int(en) Длина записи Длина знаков до первого кадра

Кадр

Каждый кадр начинается с 0 или более следующих команд: Они описаны в hl2sdk\utils\demofile\demoformat.h / hl2sdk-ob\public\demofile\demoformat.h / hl2sdk-l4d\public\demofile\demoformat.h

Сетевые протоколы 7 и 8

Тип Значение
dem_signon 1
dem_packet 2
dem_synctick 3
dem_consolecmd 4
dem_usercmd 5
dem_datatables 6
dem_stop 7
dem_lastcommand dem_stop

Сетевые протоколы 14 и 15

Тип Значение
dem_stringtables 8
dem_lastcommand dem_stringtables

Сетевые протоколы 36 и выше

Тип Значение
dem_customdata 8
dem_stringtables 9
dem_lastcommand dem_stringtables

В зависимости от полученной команды необходимо выполнить другое действие.

dem_stop

Это сигнал о том, что демо закончилось, и больше данных не нужно разбирать.

dem_consolecmd, dem_datatables, dem_usercmd, dem_stringtables

Чтение стандартных пакет данных, при необходимости они обработываются.

dem_synctick, dem_signon, dem_packet

Игнорирование.

Стандартные данные "Пакета"

Каждый раз, когда читается более одного байта, используется стандартный формат.

Это начинается с целого числа, которое имеет количество байтов в этом пакете.

Пример кода для этого:

int ReadData(char **buffer)
{
	int len;
	fread (&len,sizeof(int),1,fDemo);
	if (len > 0)
	{
		*buffer = new char[len];
		fread(*buffer,len,1,fDemo);
	}
	return len;
}

Формат Кадров

Frame {
       int ServerFrame;
       int ClientFrame; // ServerFrame и ClientFrame delta, вероятно, соответствуют отклику клиента.
       int SubPacketSize;
       *buffer = new char[SubPacketSize]; // Сообщение о состоянии обновлений
       Packet pkt = (rest of frame as data exists) // Все демо-команды объединены в этой области, структура ниже...
       JunkData data = (unknown) // Бывший: 0x8f 5a b5 04 94 e6 7c 24 00 00 00 00 00 ... (40 байт 0x00 после 0x24)
                                 // Это может быть либо конец кадра, либо начало следующего кадра.
}
Packet {
       char CmdType;
       int Unknown;
       int TickCount; // Это только периодический появляется.
       int SizeOfPacket;
       *buffer = new char[SizeOfPacket];
}

Пример использования

Может быть интересно захватить заголовок демонстрационных файлов на случай, если вы запустите веб-сервер, на котором размещены ваши демонстрации / телевизоры. Вот скрипты, который выполняет эту работу за вас.

PHP

<?
/* Source & OrangeBox demos header reader.
By PoLaRiTy (nocheatz.com)

Help from   : https://developer.valvesoftware.com/wiki/DEM_Format#Demo_Header
              http://hg.alliedmods.net/hl2sdks/hl2sdk-css/file/1901d5b74430/public/demofile/demoformat.h

Types sizes :
Int : 4 bytes
Float : 4 bytes
String : 260 bytes
*/


class DemoInfo_s
{
	var $dem_prot;       // Demo protocol version 
	var $net_prot;       // Network protocol versio
	var $host_name;      // HOSTNAME in case of TV, and IP:PORT or localhost:PORT in case of RIE (Record In eyes).
	var $client_name;    // Client name or TV name.
	var $map_name;       // Map name
	var $gamedir;        // Root game directory
	var $time;           // Playback time (s)
	var $ticks;          // Number of ticks
	var $frames;         // Number of frames
	var $tickrate;       // Tickrate
	var $type;           // TV or RIE ? (0 = RIE, 1 = TV)
	var $status_present; // true if a status command is available in the demo.
}

function ExtOfFile($pathtofile)
{
	return end(explode('.',$pathtofile));
}

function ReadString($handle, $n = 260)
{
	$buffer = "";
	for($d = 1; ((($char = fgetc($handle)) !== false) && ($d < $n)); $d++) $buffer = $buffer.$char;
	return trim($buffer);
}

function ReadInt($handle, $n = 4)
{
	$buf = fread($handle, $n);
	$number = unpack("i", $buf);     
	return $number[1];
}

function ReadFloat($handle)
{
	$buf = fread($handle, 4);
	$number = unpack("f", $buf);     
	return $number[1];
}

function IsGoodIPPORTFormat($string)
{
	if(preg_match('/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\:[0-9]{1,5}/', $string))	return true;
	else return false;
}

function GetDemoInfo($pathtofile, $fast = false) // BOOL fast : if true, doesn't check the presence of the status 
{
	$infos = NULL;
	if(ExtOfFile($pathtofile) === "dem")
	{
		$handle = fopen($pathtofile, "r");
		if($handle)
		{
			if(ReadString($handle, 8) === "HL2DEMO")
			{
				$infos = new DemoInfo_s;
				$infos->dem_prot = ReadInt($handle);
				$infos->net_prot = ReadInt($handle);
				$infos->host_name = ReadString($handle);
				$infos->client_name = ReadString($handle);
				$infos->map_name = ReadString($handle);
				$infos->gamedir = ReadString($handle);
				$infos->time = ReadFloat($handle);
				$infos->ticks = ReadInt($handle);
				$infos->frames = ReadInt($handle);
				$infos->tickrate = intval($infos->ticks / $infos->time);
				if(IsGoodIPPORTFormat($infos->host_name)) $infos->type = 0; // RIE   TODO : Add localhost:PORT check.
				else $infos->type = 1; // TV
				$infos->status_present = false;
				if(!$fast && !($infos->type == 1)) // No status in TV records.
				{
					while(!(($l = fgets($handle)) === false))
					{
						if(stripos($l, "\x00status\x00") !== false)
						{
							$infos->status_present = true;
							break;
						}
					}
				}
			}
			else
			{
				echo "Bad file format.";
			}
			fclose($handle);
		}
		else
		{
			echo "File not found or unable to read.";
		}
	}
	else
	{
		echo "Bad file extension.";
	}
	return $infos;
}
?>

C++

#include <Windows.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <clocale>
#include <fstream>
using namespace std;

class SourceDemo
{
private:
	struct DemoFile {
		FILE *file;
		long size;
		std::string data;
	} DemoFile;
public:
	struct DemoInfo
	{
		std::string DemoFileStamp;
		int DemoProtocol;
		int NetworkProtocol;
		std::string HostIP;
		std::string ClientName;
		std::string MapName;
		std::string GameDIR;
		double PlayBack_Time;
		int PlayBack_Ticks;
		int PlayBack_Frames;
		int SignOnLength;
		int TickRate;
		int Type;
		int Commands;
	} DemoInfo;
public:
	~SourceDemo(void);
	SourceDemo(const char* filepath);
	int ReadInt(int n = 4);
	float ReadFloat(int n = 4);
	std::string ReadString(int n = 260);
	void Release(void);
};

SourceDemo::SourceDemo(const char* filepath)
{
	this->DemoFile.file = fopen(filepath, "rb");
	this->DemoFile.size = ftell(this->DemoFile.file);
	vector<char> buf;
	fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
	this->DemoFile.data = string(buf.begin(), buf.end());
	//---------------------------------------------------------------//
	this->DemoInfo.DemoFileStamp = this->ReadString(8);
	if (DemoInfo.DemoFileStamp == "HL2DEMO") {
		this->DemoInfo.DemoProtocol = this->ReadInt();
		this->DemoInfo.NetworkProtocol = this->ReadInt();
		this->DemoInfo.HostIP = this->ReadString();
		this->DemoInfo.ClientName = this->ReadString();
		this->DemoInfo.MapName = this->ReadString();
		this->DemoInfo.GameDIR = this->ReadString();
		this->DemoInfo.PlayBack_Time = this->ReadFloat();
		this->DemoInfo.PlayBack_Ticks = this->ReadInt();
		this->DemoInfo.PlayBack_Frames = this->ReadInt();
		this->DemoInfo.SignOnLength = this->ReadInt();
		this->DemoInfo.TickRate = (int)(this->DemoInfo.PlayBack_Ticks / this->DemoInfo.PlayBack_Time);
		this->DemoInfo.Type = -1;
		//this->DemoInfo.Commands = static_cast<int>(this->DemoFile.data.find("\x00status\x00"));
		this->DemoInfo.Commands = -1;
	}
	else {
		this->DemoInfo.DemoProtocol = -1;
		this->DemoInfo.NetworkProtocol = -1;
		this->DemoInfo.HostIP = "NONE";
		this->DemoInfo.ClientName = "NONE";
		this->DemoInfo.MapName = "NONE";
		this->DemoInfo.GameDIR = "NONE";
		this->DemoInfo.PlayBack_Time = -1.0;
		this->DemoInfo.PlayBack_Ticks = -1;
		this->DemoInfo.PlayBack_Frames = -1;
		this->DemoInfo.TickRate = -1;
		this->DemoInfo.Type = -1;
		this->DemoInfo.Commands = -1;
	}
};
SourceDemo::~SourceDemo() {};

int SourceDemo::ReadInt(int n)
{
	std::vector<char> buf(n);
	fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
	std::string buffer(buf.begin(), buf.end());
	buffer.erase(std::remove(buffer.begin(), buffer.end(), '\x00'), buffer.end());
	return *((int*)&buffer.c_str()[0]);
}

float SourceDemo::ReadFloat(int n)
{
	std::vector<char> buf(n);
	fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
	std::string buffer(buf.begin(), buf.end());
	buffer.erase(std::remove(buffer.begin(), buffer.end(), '\x00'), buffer.end());
	return *((float*)&buffer.c_str()[0]);
}

std::string SourceDemo::ReadString(int n)
{
	std::vector<char> buf(n);
	fread(&buf[0], sizeof(buf[0]), buf.size(), this->DemoFile.file);
	std::string buffer(buf.begin(), buf.end());
	buffer.erase(std::remove(buffer.begin(), buffer.end(), '\x00'), buffer.end());
	return buffer;
}

void SourceDemo::Release(void)
{
	this->DemoInfo.DemoProtocol = -1;
	this->DemoInfo.NetworkProtocol = -1;
	this->DemoInfo.HostIP = "NONE";
	this->DemoInfo.ClientName = "NONE";
	this->DemoInfo.MapName = "NONE";
	this->DemoInfo.GameDIR = "NONE";
	this->DemoInfo.PlayBack_Time = -1.0;
	this->DemoInfo.PlayBack_Ticks = -1;
	this->DemoInfo.PlayBack_Frames = -1;
	this->DemoInfo.TickRate = -1;
	this->DemoInfo.Type = -1;
	this->DemoInfo.Commands = -1;
	fclose(this->DemoFile.file);
	return;
}

inline bool FileExist(const std::string& name) {
	ifstream f(name.c_str());
	return f.good();
}

int main(int argc, char *argv[])
{
	SetConsoleTitle(TEXT("DemoInfo"));
	SetConsoleCP(CP_UTF8);
	SetConsoleOutputCP(CP_UTF8);
	std::setlocale(LC_ALL, "en_US.UTF-8");
	std::setlocale(LC_ALL, "Russian");
	if (argc < 1) {
		char szAppPath[MAX_PATH] = "";
		GetModuleFileNameA(0, szAppPath, MAX_PATH);
		std::string AppName = szAppPath;
		AppName = AppName.substr(AppName.rfind("\\") + 1);
		std::cout << "########################################################################################" << std::endl;
		std::cout << "## Uses: " << AppName << " <demo_file_path.dem>" << std::endl;
		std::cout << "########################################################################################" << std::endl;
		Sleep(10000);
		return 0;
	}
	const char* main_executable = argv[0];
	const char* input_file = argv[1];
	if (FileExist(input_file) != false) {
		SourceDemo SD(input_file);
		std::cout << "########################################################################################" << std::endl;
		std::cout << "## Demo Info - Программа для просмотра информации у демонстрации. (OrangeBox\\Source)" << std::endl;
		std::cout << "########################################################################################" << std::endl;
		std::cout << "## File: " << input_file << std::endl;
		std::cout << "########################################################################################" << std::endl;
		std::cout << "## FileStamp\t\t-> " << SD.DemoInfo.DemoFileStamp << std::endl;
		std::cout << "## Demo Protocol\t-> " << SD.DemoInfo.DemoProtocol << std::endl;
		std::cout << "## Network Protocol\t-> " << SD.DemoInfo.NetworkProtocol << std::endl;
		std::cout << "## Host IP\t\t-> " << SD.DemoInfo.HostIP << std::endl;
		std::cout << "## Map Name\t\t-> " << SD.DemoInfo.MapName << std::endl;
		std::cout << "## GameDIR\t\t-> " << SD.DemoInfo.GameDIR << std::endl;
		std::cout << "## Time\t\t\t-> " << SD.DemoInfo.PlayBack_Time << " (Погрешность 1-2%)" << std::endl;
		std::cout << "## Ticks\t\t-> " << SD.DemoInfo.PlayBack_Ticks << std::endl;
		std::cout << "## Frames\t\t-> " << SD.DemoInfo.PlayBack_Frames << std::endl;
		std::cout << "## SignOnLength\t\t-> " << SD.DemoInfo.SignOnLength << std::endl;
		std::cout << "## TickRate\t\t-> " << SD.DemoInfo.TickRate << std::endl;
		std::cout << "## Type\t\t\t-> " << SD.DemoInfo.Type << std::endl;
		std::cout << "## Commands\t\t-> " << SD.DemoInfo.Commands << std::endl;
		std::cout << "########################################################################################" << std::endl;
		std::cout << "## Сделал Sam!" << std::endl;
		std::cout << "## https://steamcommunity.com/id/SamXDR" << std::endl;
		std::cout << "## https://steamcommunity.com/profiles/76561198312917682" << std::endl;
		std::cout << "########################################################################################" << std::endl;
		SD.Release();
		Sleep(10000);
		return 0;
	}
	else {
		char szAppPath[MAX_PATH] = "";
		GetModuleFileNameA(0, szAppPath, MAX_PATH);
		std::string AppName = szAppPath;
		AppName = AppName.substr(AppName.rfind("\\") + 1);
		std::cout << "########################################################################################" << std::endl;
		std::cout << "## Uses: " << AppName << " <demo_file_path.dem>" << std::endl;
		std::cout << "########################################################################################" << std::endl;
		Sleep(10000);
		return 0;
	}
	Sleep(10000);
	return 0;
}

Python (3.6.1)

from struct import unpack
from sys import argv
from os.path import exists
from time import sleep

class SourceDemo(object):
    __file = None
    __IsWorking = False
    DemoFileStamp = None
    DemoProtocol = -1
    NetworkProtocol = -1
    HostIP = None
    ClientName = None
    MapName = None
    GameDIR = None
    PlayBack_Time = -1.0
    PlayBack_Ticks = -1
    PlayBack_Frames = -1
    SignOnLength = -1
    TickRate = -1
    def __init__(self, file_path):
        try:
            self.__file = open(file_path, 'rb')
            self.DemoFileStamp = self.__file.read(8).replace(b'\x00', b'').decode()
            if self.DemoFileStamp == 'HL2DEMO':
                self.DemoProtocol = unpack('i', self.__file.read(4))[0]
                self.NetworkProtocol = unpack('i', self.__file.read(4))[0]
                self.HostIP = self.__file.read(260).replace(b'\x00', b'').decode()
                self.ClientName = self.__file.read(260).replace(b'\x00', b'').decode()
                self.MapName = self.__file.read(260).replace(b'\x00', b'').decode()
                self.GameDIR = self.__file.read(260).replace(b'\x00', b'').decode()
                self.PlayBack_Time = int( unpack('f', self.__file.read(4))[0] * 1000 ) / 1000
                self.PlayBack_Ticks = unpack('i', self.__file.read(4))[0]
                self.PlayBack_Frames = unpack('i', self.__file.read(4))[0]
                self.SignOnLength = unpack('i', self.__file.read(4))[0]
                self.TickRate = int(self.PlayBack_Ticks / self.PlayBack_Time)
                self.__IsWorking = True
                return
        except:
            return
    def __call__(self):
        if self.__IsWorking == True:
            if self.__IsWorking == True:
                r = {
                    'DemoFileStamp': self.DemoFileStamp,
                    'DemoProtocol': self.DemoProtocol,
                    'NetworkProtocol': self.NetworkProtocol,
                    'HostIP': self.HostIP,
                    'ClientName': self.ClientName,
                    'MapName': self.MapName,
                    'GameDIR': self.GameDIR,
                    'PlayBack_Time': self.PlayBack_Time,
                    'PlayBack_Ticks': self.PlayBack_Ticks,
                    'PlayBack_Frames': self.PlayBack_Frames,
                    'SignOnLength': self.SignOnLength,
                    'TickRate': self.TickRate,
                }
                return r
        return None
    def GetDemoFileStamp(self):
        if self.__IsWorking == True:
            return self.DemoFileStamp
        return None
    def GetDemoProtocol(self):
        if self.__IsWorking == True:
            return self.DemoProtocol
        return None
    def GetNetworkProtocol(self):
        if self.__IsWorking == True:
            return self.NetworkProtocol
        return None
    def GetHostIP(self):
        if self.__IsWorking == True:
            return self.HostIP
        return None
    def GetClientName(self):
        if self.__IsWorking == True:
            return self.ClientName
        return None
    def GetMapName(self):
        if self.__IsWorking == True:
            return self.MapName
        return None
    def GetGameDIR(self):
        if self.__IsWorking == True:
            return self.GameDIR
        return None
    def GetTime(self):
        if self.__IsWorking == True:
            return self.PlayBack_Time
        return None
    def GetTicks(self):
        if self.__IsWorking == True:
            return self.PlayBack_Ticks
        return None
    def GetFrames(self):
        if self.__IsWorking == True:
            return self.PlayBack_Frames
        return None
    def GetSignOnLength(self):
        if self.__IsWorking == True:
            return self.SignOnLength
        return None
    def GetTickRate(self):
        if self.__IsWorking == True:
            return self.TickRate
        return None

def main(argc, argv):
    file = argv[0].rsplit('\\')
    file = file[len(file)-1]
    if len(argv) == 2:
        if exists(argv[1]) == True:
            file = argv[1].rsplit('\\')
            file = file[len(file)-1]
            s = SourceDemo(argv[1])
            print('########################################################################################')
            print('## DemoInfo.py - Скрипт для просмотра информации у демонстрации. (OrangeBox\\Source)')
            print('########################################################################################')
            print('## File: {}'.format(file))
            print('########################################################################################')
            print('## FileStamp\t\t-> {}'.format(s.GetDemoFileStamp()))
            print('## Demo Protocol\t-> {}'.format(s.GetDemoProtocol()))
            print('## Network Protocol\t-> {}'.format(s.GetNetworkProtocol()))
            print('## Host IP\t\t-> {}'.format(s.GetHostIP()))
            print('## Map Name\t\t-> {}'.format(s.GetMapName()))
            print('## GameDIR\t\t-> {}'.format(s.GetGameDIR()))
            print('## Time\t\t\t-> {} (Имеется погрешность!)'.format(s.GetTime()))
            print('## Ticks\t\t-> {}'.format(s.GetTicks()))
            print('## Frames\t\t-> {}'.format(s.GetFrames()))
            print('## SignOnLength\t\t-> {}'.format(s.GetSignOnLength()))
            print('## TickRate\t\t-> {}'.format(s.GetTickRate()))
            print('########################################################################################')
            return
    print('########################################################################################')
    print('## Uses: {} <demo_file_path.dem>'.format(file))
    print('########################################################################################')
    return

if __name__ == '__main__':
    argc = len(argv)
    argv = argv
    main(argc, argv)
    sleep(25)
    exit(0)

Смотрите также