├── demoparser ├── __init__.py ├── protobufs │ ├── __init__.py │ ├── uifontfile_format.proto │ ├── engine_gcmessages.proto │ ├── LICENSE │ ├── fatdemo.proto │ ├── gcsystemmsgs.proto │ ├── network_connection.proto │ ├── econ_gcmessages.proto │ ├── steamdatagram_messages.proto │ ├── gcsdk_gcmessages.proto │ ├── cstrike15_usermessages.proto │ ├── netmessages.proto │ ├── base_gcmessages.proto │ └── steammessages.proto ├── util.pxd ├── entities │ ├── __init__.py │ ├── game_rules.py │ ├── team.py │ ├── base.py │ ├── entity_list.py │ ├── player.py │ └── weapon.py ├── _setup_hooks.py ├── bitbuffer.pxd ├── consts.py ├── examples │ ├── player_death.py │ └── chat.py ├── props.pxd ├── fields.py ├── util.pyx ├── bytebuffer.py ├── structures.py ├── props.pyx └── bitbuffer.pyx ├── test-requirements.txt ├── requirements.txt ├── .coveragerc ├── doc ├── source │ ├── api │ │ ├── modules.rst │ │ ├── demoparser.entities.rst │ │ └── demoparser.rst │ ├── intro.rst │ ├── index.rst │ ├── user_messages.rst │ ├── demopacket.rst │ ├── events.rst │ ├── usage.rst │ ├── game_events.rst │ └── conf.py └── Makefile ├── MANIFEST.in ├── .gitignore ├── README.rst ├── tests ├── test_bytebuffer.py ├── test_demofile.py └── test_bitbuffer.py ├── tox.ini ├── setup.cfg ├── .travis.yml ├── setup.py └── LICENSE /demoparser/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demoparser/protobufs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | flake8 2 | pytest 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | suitcase 2 | cython 3 | protobuf 4 | sphinx 5 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | plugins = Cython.Coverage 3 | omit = demoparser/protobufs/* 4 | -------------------------------------------------------------------------------- /doc/source/api/modules.rst: -------------------------------------------------------------------------------- 1 | demoparser 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | demoparser 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include tox.ini 3 | recursive-include demoparser *.pyx 4 | recursive-include demoparser *.pxd 5 | prune demoparser/examples 6 | -------------------------------------------------------------------------------- /doc/source/intro.rst: -------------------------------------------------------------------------------- 1 | Demoparser is a library for parsing .DEM files generated by CS:GO. 2 | 3 | As the file is processed events are emitted for which callbacks can 4 | be registered. 5 | -------------------------------------------------------------------------------- /demoparser/util.pxd: -------------------------------------------------------------------------------- 1 | from demoparser.bitbuffer cimport Bitbuffer 2 | 3 | cpdef int read_field_index(Bitbuffer buf, int last_index, bint new_way) 4 | cpdef list parse_entity_update(Bitbuffer buf, object server_class) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.egg-info 3 | __pycache__ 4 | .eggs 5 | .tox 6 | *.rar 7 | *.dem 8 | AUTHORS 9 | ChangeLog 10 | *_pb2.py 11 | *.c 12 | *.h 13 | *.so 14 | doc/_build 15 | .cache 16 | .coverage 17 | build 18 | dist 19 | -------------------------------------------------------------------------------- /demoparser/entities/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from demoparser.entities.base import BaseEntity 3 | from demoparser.entities.game_rules import GameRules 4 | from demoparser.entities.player import Player 5 | from demoparser.entities.team import Team 6 | from demoparser.entities.weapon import Weapon 7 | from demoparser.entities.entity_list import EntityList 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ``csgo-demoparser`` is a library for parsing CS:GO demo files. 2 | 3 | As the file is processed events are emitted for which callbacks can 4 | be registered. 5 | 6 | Quick start 7 | ----------- 8 | 9 | 1. Install:: 10 | 11 | pip install csgo-demoparser 12 | 13 | 2. Parse a demo:: 14 | 15 | >>> from demoparser.demofile import DemoFile 16 | >>> data = open('/path/to/demofile', 'rb').read() 17 | >>> df = DemoFile(data) 18 | >>> df.parse() 19 | -------------------------------------------------------------------------------- /demoparser/protobufs/uifontfile_format.proto: -------------------------------------------------------------------------------- 1 | option optimize_for = SPEED; 2 | option cc_generic_services = false; 3 | 4 | message CUIFontFilePB { 5 | optional string font_file_name = 1; 6 | optional bytes opentype_font_data = 2; 7 | } 8 | 9 | message CUIFontFilePackagePB { 10 | message CUIEncryptedFontFilePB { 11 | optional bytes encrypted_contents = 1; 12 | } 13 | 14 | required uint32 package_version = 1; 15 | repeated .CUIFontFilePackagePB.CUIEncryptedFontFilePB encrypted_font_files = 2; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /demoparser/protobufs/engine_gcmessages.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | option cc_generic_services = false; 4 | 5 | message CEngineGotvSyncPacket { 6 | optional uint64 match_id = 1; 7 | optional uint32 instance_id = 2; 8 | optional uint32 signupfragment = 3; 9 | optional uint32 currentfragment = 4; 10 | optional float tickrate = 5; 11 | optional uint32 tick = 6; 12 | optional float rtdelay = 8; 13 | optional float rcvage = 9; 14 | optional float keyframe_interval = 10; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. demoparser documentation master file, created by 2 | sphinx-quickstart on Thu Oct 5 16:48:03 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to demoparser's documentation! 7 | ====================================== 8 | 9 | .. include:: ../../README.rst 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | usage 17 | events 18 | API Documentation 19 | 20 | -------------------------------------------------------------------------------- /tests/test_bytebuffer.py: -------------------------------------------------------------------------------- 1 | from demoparser.bytebuffer import Bytebuffer 2 | from demoparser import structures 3 | 4 | 5 | def test_read(): 6 | b = Bytebuffer(b'\x01\x02\x03') 7 | assert b.read(3) == b'\x01\x02\x03' 8 | 9 | 10 | def test_read_command_header(): 11 | b = Bytebuffer(b'\x01\x02\x03\x04\x05\x06') 12 | header = b.read_command_header() 13 | 14 | assert type(header) == structures.CommandHeader 15 | assert header.command == 1 16 | assert header.tick == 84148994 17 | assert header.player == 6 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35,py36,lint 3 | 4 | [testenv] 5 | deps= 6 | -rtest-requirements.txt 7 | -rrequirements.txt 8 | commands= 9 | python setup.py build_ext --force --inplace --define CYTHON_TRACE 10 | py.test 11 | 12 | [testenv:lint] 13 | basepython = python3 14 | commands = 15 | flake8 16 | flake8 --filename *.pyx --ignore E999,E225,E226,E201,E202,E227 17 | 18 | [flake8] 19 | ignore = D203 20 | exclude = 21 | .tox, 22 | .git, 23 | __pycache__, 24 | doc/source/conf.py, 25 | old, 26 | build, 27 | dist, 28 | protobufs 29 | .eggs 30 | -------------------------------------------------------------------------------- /tests/test_demofile.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from demoparser.demofile import CommandError 4 | from demoparser.demofile import DemoFile 5 | from demoparser.structures import CommandHeader 6 | 7 | 8 | def test_parse_invalid_command(): 9 | # Demo files have a 1072 byte header that isn't needed here 10 | data = b'HL2DEMO\x00' + bytes([0] * 1064) 11 | header = CommandHeader() 12 | header.player = 1 13 | header.tick = 1 14 | header.command = 99 15 | 16 | df = DemoFile(data + header.pack()) 17 | with pytest.raises(CommandError) as exc: 18 | df.parse() 19 | 20 | assert 'Unrecognized command' in str(exc.value) 21 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = csgo-demoparser 3 | author = Ryan Moe 4 | author-email = ryan.moe@gmail.com 5 | summary = CS:GO demo file parser. 6 | description-file = README.rst 7 | home-page = https://github.com/ibm-dev-incubator/demoparser 8 | license = Apache-2 9 | license_file = LICENSE 10 | classifier = 11 | Development Status :: 4 - Beta 12 | License :: OSI Approved :: Apache Software License 13 | Operating System :: OS Independent 14 | Programming Language :: Python 15 | keywords = 16 | setup 17 | distutils 18 | 19 | [global] 20 | commands = demoparser._setup_hooks.ProtobufBuilder 21 | 22 | [files] 23 | packages = 24 | demoparser 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.5" 5 | - "3.6" 6 | 7 | addons: 8 | apt: 9 | sources: 10 | # travis runs ubuntu 14.04 which ships with protobuf 2.5. 11 | # Python 3 support wasn't included until 2.6 12 | - sourceline: ppa:5-james-t/protobuf-ppa 13 | packages: 14 | - protobuf-compiler 15 | - python-protobuf 16 | - libprotobuf-dev 17 | 18 | install: 19 | - protoc -I . -I /usr/include --python_out . ./demoparser/protobufs/*.proto 20 | - pip install cython tox-travis 21 | 22 | script: tox 23 | 24 | stages: 25 | - lint 26 | - test 27 | 28 | jobs: 29 | include: 30 | - stage: lint 31 | script: tox -e lint 32 | -------------------------------------------------------------------------------- /demoparser/_setup_hooks.py: -------------------------------------------------------------------------------- 1 | from distutils.spawn import find_executable 2 | from setuptools.command.build_py import build_py as _build_py 3 | import glob 4 | import subprocess 5 | 6 | 7 | class ProtobufBuilder(_build_py): 8 | command_name = 'build_py' 9 | 10 | def protoc(self): 11 | proto_files = glob.glob('./demoparser/protobufs/*.proto') 12 | subprocess.run([ 13 | find_executable('protoc'), 14 | '--python_out=.', 15 | '--proto_path=/usr/include/', 16 | '--proto_path=.', 17 | *proto_files 18 | ], check=True) 19 | 20 | def run(self): 21 | self.protoc() 22 | _build_py.run(self) 23 | -------------------------------------------------------------------------------- /doc/source/user_messages.rst: -------------------------------------------------------------------------------- 1 | - VGUIMenu 2 | - Geiger 3 | - Train 4 | - HudText 5 | - SayText 6 | - SayText2 7 | - TextMsg 8 | - HudMsg 9 | - ResetHud 10 | - GameTitle 11 | - Shake 12 | - Fade 13 | - Rumble 14 | - CloseCaption 15 | - CloseCaptionDirect 16 | - SendAudio 17 | - RawAudio 18 | - VoiceMask 19 | - RequestState 20 | - Damage 21 | - RadioText 22 | - HintText 23 | - KeyHintText 24 | - ProcessSpottedEntityUpdate 25 | - ReloadEffect 26 | - AdjustMoney 27 | - UpdateTeamMoney 28 | - StopSpectatorMode 29 | - KillCam 30 | - DesiredTimescale 31 | - CurrentTimescale 32 | - AchievementEvent 33 | - MatchEndConditions 34 | - DisconnectToLobby 35 | - PlayerStatsUpdate 36 | - DisplayInventory 37 | - WarmupHasEnded 38 | - ClientInfo 39 | - XRankGet 40 | - XRankUpd 41 | - CallVoteFailed 42 | - VoteStart 43 | - VotePass 44 | - VoteFailed 45 | -------------------------------------------------------------------------------- /demoparser/bitbuffer.pxd: -------------------------------------------------------------------------------- 1 | cdef class Bitbuffer: 2 | cdef unsigned int num_bytes 3 | cdef unsigned int * data 4 | cdef unsigned int index 5 | cdef unsigned int length 6 | cdef unsigned int bits_avail 7 | cdef unsigned int in_buf_word 8 | cdef bytes orig_data 9 | 10 | cpdef unsigned int next_dword(self) 11 | cpdef unsigned char read_bit(self) 12 | cpdef unsigned int read_uint_bits(self, unsigned int bits) 13 | cpdef int read_sint_bits(self, unsigned int bits) 14 | cpdef unsigned int read_var_int(self) 15 | cpdef str read_string(self, int length=*) 16 | cpdef float read_bit_normal(self) 17 | cpdef float read_bit_coord(self) 18 | cpdef float read_bit_cell_coord(self, unsigned int bits, 19 | unsigned int coord_type) 20 | cpdef bytes read_user_data(self, unsigned int bits) 21 | -------------------------------------------------------------------------------- /doc/source/demopacket.rst: -------------------------------------------------------------------------------- 1 | - net_NOP 2 | - net_Disconnect 3 | - net_File 4 | - net_SplitScreenUser 5 | - net_Tick 6 | - net_StringCmd 7 | - net_SetConVar 8 | - net_SignonState 9 | - net_PlayerAvatarData 10 | - svc_ServerInfo 11 | - svc_SendTable 12 | - svc_ClassInfo 13 | - svc_SetPause 14 | - svc_CreateStringTable 15 | - svc_UpdateStringTable 16 | - svc_VoiceInit 17 | - svc_VoiceData 18 | - svc_Print 19 | - svc_Sounds 20 | - svc_SetView 21 | - svc_FixAngle 22 | - svc_CrosshairAngle 23 | - svc_BSPDecal 24 | - svc_SplitScreen 25 | - svc_UserMessage 26 | - svc_EntityMessage 27 | - svc_GameEvent 28 | - svc_PacketEntities 29 | - svc_TempEntities 30 | - svc_Prefetch 31 | - svc_Menu 32 | - svc_GameEventList 33 | - svc_GetCvarValue 34 | - svc_PaintmapData 35 | - svc_CmdKeyValues 36 | - svc_EncryptedData 37 | - svc_HltvReplay 38 | - svc_Broadcast_Command 39 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = demoparser 8 | SOURCEDIR = source 9 | BUILDDIR = ../../demoparser-docs 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | html: 20 | @cd ..; python setup.py build_ext --inplace 21 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 22 | 23 | %: Makefile 24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | from setuptools.extension import Extension 5 | 6 | from Cython.Build import cythonize 7 | 8 | 9 | extensions = [ 10 | Extension( 11 | "demoparser.util", 12 | ["demoparser/util.pyx"] 13 | ), 14 | Extension( 15 | "demoparser.props", 16 | ["demoparser/props.pyx"], 17 | ), 18 | Extension( 19 | "demoparser.bitbuffer", 20 | ["demoparser/bitbuffer.pyx"] 21 | ), 22 | Extension( 23 | "demoparser.demofile", 24 | ["demoparser/demofile.pyx"] 25 | ), 26 | ] 27 | 28 | setup( 29 | setup_requires=['pbr>=1.9', 'setuptools>=17.1'], 30 | pbr=True, 31 | ext_modules=cythonize( 32 | extensions, compiler_directives={ 33 | "embedsignature": True, 34 | "linetrace": True, 35 | "language_level": 3 36 | } 37 | ) 38 | ) 39 | -------------------------------------------------------------------------------- /demoparser/entities/game_rules.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from demoparser.entities import BaseEntity 3 | 4 | 5 | class GameRules(BaseEntity): 6 | 7 | def __init__(self, parser, index, class_id, serial, props): 8 | self.parser = parser 9 | self.index = index 10 | self.class_id = class_id 11 | self.serial = serial 12 | self.props = props 13 | 14 | def properties(self): 15 | result = { 16 | 'index': self.index, 17 | 'class_id': self.class_id, 18 | 'serial': self.serial, 19 | } 20 | 21 | for name, value in inspect.getmembers(self): 22 | prop_attr = getattr(self.__class__, name, None) 23 | if inspect.isdatadescriptor(prop_attr): 24 | attr = getattr(self, name, None) 25 | if not isinstance(attr, BaseEntity): 26 | result[name] = value 27 | 28 | return result 29 | -------------------------------------------------------------------------------- /demoparser/consts.py: -------------------------------------------------------------------------------- 1 | MAX_CUSTOM_FILES = 4 2 | MAX_EDICT_BITS = 11 3 | MAX_PATH = 260 4 | MAX_SPLITSCREEN_CLIENTS = 2 5 | MAX_PLAYER_NAME_LENGTH = 128 6 | MAX_USERDATA_BITS = 14 7 | NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS = 10 8 | SIGNED_GUID_LEN = 33 9 | SUBSTRING_BITS = 5 10 | 11 | CW_None = 0 12 | CW_LowPrecision = 1 13 | CW_Integral = 2 14 | 15 | COORD_INTEGER_BITS = 14 16 | COORD_FRACTIONAL_BITS = 5 17 | COORD_DENOMINATOR = (1 << COORD_FRACTIONAL_BITS) 18 | COORD_RESOLUTION = (1.0 / COORD_DENOMINATOR) 19 | 20 | COORD_INTEGER_BITS_MP = 11 21 | COORD_FRACTIONAL_BITS_MP_LOWPRECISION = 3 22 | COORD_DENOMINATOR_LOWPRECISION = (1 << COORD_FRACTIONAL_BITS_MP_LOWPRECISION) 23 | COORD_RESOLUTION_LOWPRECISION = (1.0 / COORD_DENOMINATOR_LOWPRECISION) 24 | 25 | NORMAL_FRACTIONAL_BITS = 11 26 | NORMAL_DENOMINATOR = (1 << NORMAL_FRACTIONAL_BITS) - 1 27 | NORMAL_RESOLUTION = (1.0 / NORMAL_DENOMINATOR) 28 | 29 | MAX_EDICT_BITS = 11 30 | NETWORKED_EHANDLE_ENT_ENTRY_MASK = (1 << MAX_EDICT_BITS) - 1 31 | NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS = 10 32 | NUM_NETWORKED_EHANDLE_BITS = \ 33 | MAX_EDICT_BITS + NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS 34 | INVALID_NETWORKED_EHANDLE_VALUE = (1 << NUM_NETWORKED_EHANDLE_BITS) - 1 35 | -------------------------------------------------------------------------------- /demoparser/protobufs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Valve Software 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /demoparser/examples/player_death.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from demoparser.demofile import DemoFile 4 | 5 | 6 | def death(event, msg): 7 | for idx, key in enumerate(event['event'].keys): 8 | if key.name == 'attacker': 9 | user_id = msg.keys[idx].val_short 10 | attacker = d.entities.get_by_user_id(user_id) 11 | elif key.name == 'userid': 12 | user_id = msg.keys[idx].val_short 13 | victim = d.entities.get_by_user_id(user_id) 14 | elif key.name == 'weapon': 15 | weapon = msg.keys[idx].val_string 16 | elif key.name == 'headshot': 17 | headshot = msg.keys[idx].val_bool 18 | 19 | if attacker and victim: 20 | print("\n --- Player Death at tick {}---".format(d.current_tick)) 21 | print("{} killed by {} with {}. Headshot? {}.\n" 22 | "Attacker: health = {} position = {}\n" 23 | "Victim: position = {}".format( 24 | victim.name, 25 | attacker.name, 26 | weapon, 27 | 'Yes' if headshot else 'No', 28 | attacker.health, 29 | attacker.position, 30 | victim.position)) 31 | 32 | 33 | if __name__ == "__main__": 34 | data = open(sys.argv[1], 'rb').read() 35 | d = DemoFile(data) 36 | d.add_callback('player_death', death) 37 | d.parse() 38 | -------------------------------------------------------------------------------- /demoparser/props.pxd: -------------------------------------------------------------------------------- 1 | from demoparser.bitbuffer cimport Bitbuffer 2 | 3 | cdef class Decoder: 4 | cdef Bitbuffer buf 5 | cdef dict fprop 6 | cdef object prop 7 | cdef long flags 8 | 9 | # Methods 10 | cpdef object decode(self) 11 | cpdef long decode_int(self) 12 | cpdef float decode_float(self) 13 | cpdef dict decode_vector(self) 14 | cpdef dict decode_vector_xy(self) 15 | cpdef str decode_string(self) 16 | cpdef list decode_array(self) 17 | cpdef float decode_special_float(self) 18 | 19 | cdef enum PropTypes: 20 | DPT_Int = 0 21 | DPT_Float = 1 22 | DPT_Vector = 2 23 | DPT_VectorXY = 3 24 | DPT_String = 4 25 | DPT_Array = 5 26 | DPT_DataTable = 6 27 | DPT_Int64 = 7 28 | DT_MAX_STRING_BITS = 9 29 | 30 | cdef enum PropFlags: 31 | SPROP_UNSIGNED = (1 << 0) 32 | SPROP_COORD = (1 << 1) 33 | SPROP_NOSCALE = (1 << 2) 34 | SPROP_ROUNDDOWN = (1 << 3) 35 | SPROP_ROUNDUP = (1 << 4) 36 | SPROP_NORMAL = (1 << 5) 37 | SPROP_EXCLUDE = (1 << 6) 38 | SPROP_XYZE = (1 << 7) 39 | SPROP_INSIDEARRAY = (1 << 8) 40 | SPROP_PROXY_ALWAYS_YES = (1 << 9) 41 | SPROP_IS_A_VECTOR_ELEM = (1 << 10) 42 | SPROP_COLLAPSIBLE = (1 << 11) 43 | SPROP_COORD_MP = (1 << 12) 44 | SPROP_COORD_MP_LOWPRECISION = (1 << 13) 45 | SPROP_COORD_MP_INTEGRAL = (1 << 14) 46 | SPROP_CELL_COORD = (1 << 15) 47 | SPROP_CELL_COORD_LOWPRECISION = (1 << 16) 48 | SPROP_CELL_COORD_INTEGRAL = (1 << 17) 49 | SPROP_CHANGES_OFTEN = (1 << 18) 50 | SPROP_VARINT = (1 << 19) 51 | -------------------------------------------------------------------------------- /doc/source/api/demoparser.entities.rst: -------------------------------------------------------------------------------- 1 | demoparser\.entities package 2 | ============================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | demoparser\.entities\.base module 8 | --------------------------------- 9 | 10 | .. automodule:: demoparser.entities.base 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | demoparser\.entities\.entity\_list module 16 | ----------------------------------------- 17 | 18 | .. automodule:: demoparser.entities.entity_list 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | demoparser\.entities\.game\_rules module 24 | ---------------------------------------- 25 | 26 | .. automodule:: demoparser.entities.game_rules 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | demoparser\.entities\.player module 32 | ----------------------------------- 33 | 34 | .. automodule:: demoparser.entities.player 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | demoparser\.entities\.team module 40 | --------------------------------- 41 | 42 | .. automodule:: demoparser.entities.team 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | demoparser\.entities\.weapon module 48 | ----------------------------------- 49 | 50 | .. automodule:: demoparser.entities.weapon 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | 56 | Module contents 57 | --------------- 58 | 59 | .. automodule:: demoparser.entities 60 | :members: 61 | :undoc-members: 62 | :show-inheritance: 63 | -------------------------------------------------------------------------------- /demoparser/examples/chat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from demoparser.demofile import DemoFile 4 | 5 | 6 | color_map = { 7 | "\001": "\x1b[0m", 8 | "\002": "\x1b[0;31m", 9 | "\003": "\x1b[0;34m", 10 | "\004": "\x1b[0;32m", 11 | "\005": "\x1b[1;32m", 12 | "\006": "\x1b[1;33m", 13 | "\007": "\x1b[1;31m" 14 | } 15 | 16 | ansi_colors = [ 17 | '\x1b[0;34m', 18 | '\x1b[0;32m', 19 | '\x1b[0;36m', 20 | '\x1b[0;35m', 21 | '\x1b[0;33m', 22 | '\x1b[0;37m', 23 | '\x1b[1;30m', 24 | '\x1b[1;34m', 25 | '\x1b[1;32m', 26 | '\x1b[1;36m', 27 | '\x1b[1;31m', 28 | '\x1b[1;35m', 29 | '\x1b[1;33m', 30 | '\x1b[1;37m' 31 | ] 32 | 33 | 34 | def print_color(text): 35 | t = text 36 | for k, v in color_map.items(): 37 | t = t.replace(k, v) 38 | 39 | print(t) 40 | 41 | 42 | def server_text(msg): 43 | print_color(msg.text) 44 | 45 | 46 | def game_chat(msg): 47 | params = {} 48 | 49 | if msg.msg_name.endswith('AllDead'): 50 | params['dead'] = '\x1b[0;31m* DEAD *\x1b[0m ' 51 | else: 52 | params['dead'] = '' 53 | 54 | params['name'] = '{}{}\x1b[0m'.format( 55 | ansi_colors[msg.ent_idx % 14], msg.params[0] 56 | ) 57 | params['message'] = msg.params[1] 58 | 59 | fmt = "{dead}{name}: {message}".format(**params) 60 | print(fmt) 61 | 62 | 63 | if __name__ == "__main__": 64 | data = open(sys.argv[1], 'rb').read() 65 | d = DemoFile(data) 66 | d.add_callback('SayText', server_text) 67 | d.add_callback('SayText2', game_chat) 68 | d.parse() 69 | -------------------------------------------------------------------------------- /demoparser/fields.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from suitcase.fields import BaseField 4 | from suitcase.fields import BaseStructField 5 | from suitcase.fields import BaseFixedByteSequence 6 | 7 | 8 | class SLFloat32(BaseStructField): 9 | """Signed Little Endian 32-bit float field.""" 10 | PACK_FORMAT = UNPACK_FORMAT = b"" + "I" * l, length, **kwargs) 25 | self.bytes_required = length * 4 26 | 27 | 28 | class FixedLengthString(BaseField): 29 | """A string of a fixed number of bytes. 30 | 31 | The specified number of bytes are read and then any null 32 | bytes are stripped from the result. 33 | 34 | :param length: Number of bytes to read. 35 | :type length: Integer 36 | """ 37 | 38 | def __init__(self, length, **kwargs): 39 | super().__init__(**kwargs) 40 | self.length = length 41 | 42 | @property 43 | def bytes_required(self): 44 | """Number of bytes to read from stream.""" 45 | return self.length 46 | 47 | def pack(self, stream): 48 | stream.write(self._value.strip(b'\0')) 49 | 50 | def unpack(self, data): 51 | self._value = data.strip(b'\0') 52 | -------------------------------------------------------------------------------- /doc/source/api/demoparser.rst: -------------------------------------------------------------------------------- 1 | demoparser package 2 | ================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | demoparser.entities 10 | 11 | Submodules 12 | ---------- 13 | 14 | demoparser\.bitbuffer module 15 | ---------------------------- 16 | 17 | .. automodule:: demoparser.bitbuffer 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | demoparser\.bytebuffer module 23 | ----------------------------- 24 | 25 | .. automodule:: demoparser.bytebuffer 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | demoparser\.consts module 31 | ------------------------- 32 | 33 | .. automodule:: demoparser.consts 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | demoparser\.demofile module 39 | --------------------------- 40 | 41 | .. automodule:: demoparser.demofile 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | demoparser\.fields module 47 | ------------------------- 48 | 49 | .. automodule:: demoparser.fields 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | 54 | demoparser\.props module 55 | ------------------------ 56 | 57 | .. automodule:: demoparser.props 58 | :members: 59 | :undoc-members: 60 | :show-inheritance: 61 | 62 | demoparser\.structures module 63 | ----------------------------- 64 | 65 | .. automodule:: demoparser.structures 66 | :members: 67 | :undoc-members: 68 | :show-inheritance: 69 | 70 | demoparser\.util module 71 | ----------------------- 72 | 73 | .. automodule:: demoparser.util 74 | :members: 75 | :undoc-members: 76 | :show-inheritance: 77 | 78 | 79 | Module contents 80 | --------------- 81 | 82 | .. automodule:: demoparser 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | -------------------------------------------------------------------------------- /demoparser/entities/team.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from demoparser.entities import BaseEntity 3 | 4 | 5 | class Team(BaseEntity): 6 | 7 | def __init__(self, parser, index, class_id, serial, props): 8 | self.parser = parser 9 | self.index = index 10 | self.class_id = class_id 11 | self.serial = serial 12 | self.props = props 13 | 14 | @property 15 | def tid(self): 16 | """Team id.""" 17 | return self.get_prop('DT_Team', 'm_iTeamNum') 18 | 19 | @property 20 | def name(self): 21 | """Team name. Either 'T' or 'CT'.""" 22 | return self.get_prop('DT_Team', 'm_szTeamname') 23 | 24 | @property 25 | def clan(self): 26 | """Clan name.""" 27 | return self.get_prop('DT_Team', 'm_szClanTeamname') 28 | 29 | @property 30 | def score(self): 31 | """Final team score.""" 32 | return self.get_prop('DT_Team', 'm_scoreTotal') 33 | 34 | @property 35 | def score_first_half(self): 36 | """Score for first half of match.""" 37 | return self.get_prop('DT_Team', 'm_scoreFirstHalf') 38 | 39 | @property 40 | def score_second_half(self): 41 | """Score for second half of match.""" 42 | return self.get_prop('DT_Team', 'm_scoreSecondHalf') 43 | 44 | def properties(self): 45 | result = { 46 | 'index': self.index, 47 | 'class_id': self.class_id, 48 | 'serial': self.serial, 49 | } 50 | 51 | for name, value in inspect.getmembers(self): 52 | prop_attr = getattr(self.__class__, name, None) 53 | if inspect.isdatadescriptor(prop_attr): 54 | attr = getattr(self, name, None) 55 | if not isinstance(attr, BaseEntity): 56 | result[name] = value 57 | 58 | return result 59 | -------------------------------------------------------------------------------- /tests/test_bitbuffer.py: -------------------------------------------------------------------------------- 1 | from demoparser.bitbuffer import Bitbuffer 2 | 3 | 4 | def test_read_bit(): 5 | b = Bitbuffer(b'\xff\xff\xff\xff\x00') 6 | 7 | # Bitbuffer reads 32-bits at a time so 32 bits must be 8 | # read to check that it moves to the next word correctly. 9 | bits = [b.read_bit() for i in range(32)] 10 | assert bits == [1] * 32 11 | 12 | # Next word is 0 so next bit should be 0 13 | assert b.read_bit() == 0 14 | 15 | 16 | def test_read_uint_bits(): 17 | # Buffer is shown below and the word boundary is marked by ||. 18 | # 00100001000111111111000000000011 || 00110010 19 | b = Bitbuffer(b'\x00\xff\x11\x22\x33') 20 | 21 | # Read 0010000100011111111100000000 22 | num = b.read_uint_bits(28) 23 | assert num == 34733824 24 | 25 | # Test reading integers across word bundary 26 | # This will read the final 0011 from the first word 27 | # followed by 00110010 for a total of 12 bits. This value 28 | # should equal 001100110010. 29 | num = b.read_uint_bits(12) 30 | assert num == 818 31 | 32 | 33 | def test_read_sint_bits(): 34 | # 10000010 35 | b = Bitbuffer(b'\x82') 36 | num = b.read_sint_bits(8) 37 | assert num == -126 38 | 39 | # Unsigned this time 40 | # 00000010 41 | b = Bitbuffer(b'\x02') 42 | num = b.read_sint_bits(8) 43 | assert num == 2 44 | 45 | 46 | def test_read_string(): 47 | 48 | # Read until \0 49 | b = Bitbuffer(b'test\x00more\xff') 50 | assert b.read_string() == 'test' 51 | 52 | # Next value will be 'm' 53 | assert chr(b.read_uint_bits(8)) == 'm' 54 | 55 | # Read a fixed length 56 | # \x00more has been read but not returned 57 | b = Bitbuffer(b'test\x00more\xff') 58 | assert b.read_string(9) == 'test' 59 | 60 | # All that sould be left is \xff which is 255 61 | assert b.read_uint_bits(8) == 255 62 | -------------------------------------------------------------------------------- /demoparser/entities/base.py: -------------------------------------------------------------------------------- 1 | class BaseEntity: 2 | 3 | def __init__(self, parser, index, class_id, serial, props): 4 | self.parser = parser 5 | self.index = index 6 | self.class_id = class_id 7 | self.serial = serial 8 | self.props = props 9 | 10 | def update_prop(self, table, key, value): 11 | """Update entity property.""" 12 | if table in self.props and key in self.props[table]: 13 | self.props[table][key] = value 14 | 15 | def get_prop(self, table, var): 16 | return self.props.get(table, {}).get(var) 17 | 18 | @property 19 | def position(self): 20 | """Get the position of this entity.""" 21 | return self.get_prop('DT_BaseEntity', 'm_vecOrigin') 22 | 23 | @property 24 | def server_class(self): 25 | """Server class to which this entity belongs.""" 26 | return self.parser.server_classes[self.class_id] 27 | 28 | @property 29 | def team_num(self): 30 | """Entity team number. 31 | 32 | :returns: Team number of entity. 33 | """ 34 | return self.get_prop('DT_BaseEntity', 'm_iTeamNum') 35 | 36 | @property 37 | def team(self): 38 | """Team to which entity belongs. 39 | 40 | :returns: Team entity 41 | """ 42 | team_num = self.team_num 43 | 44 | if not team_num: 45 | return 46 | 47 | return self.parser.entities.teams[team_num] 48 | 49 | @property 50 | def owner(self): 51 | """Find entity which owns this entity. 52 | 53 | For example, a weapon entity belongs to a player entity. 54 | Calling this method on a weapon entity would return a 55 | player. 56 | 57 | :returns: Owning entity. 58 | """ 59 | return self.parser.entities.get_by_handle( 60 | self.get_prop('DT_BaseEntity', 'm_hOwnerEntity') 61 | ) 62 | 63 | def properties(self): 64 | result = { 65 | 'index': self.index, 66 | 'class_id': self.class_id, 67 | 'serial': self.serial, 68 | 'position': self.position, 69 | } 70 | 71 | return result 72 | -------------------------------------------------------------------------------- /demoparser/util.pyx: -------------------------------------------------------------------------------- 1 | from demoparser.bitbuffer cimport Bitbuffer 2 | from demoparser.props cimport Decoder 3 | from demoparser.props cimport PropFlags 4 | from demoparser.props cimport PropTypes 5 | 6 | cpdef int read_field_index(Bitbuffer buf, int last_index, bint new_way): 7 | """Read index. 8 | 9 | This method determines the next index into the property 10 | list for a server class. 11 | 12 | :returns: Next index or -1 if no more indices 13 | """ 14 | cdef int ret = 0 15 | cdef unsigned int val = 0 16 | 17 | if new_way and buf.read_bit(): 18 | return last_index + 1 19 | 20 | if new_way and buf.read_bit(): 21 | ret = buf.read_uint_bits(3) 22 | else: 23 | ret = buf.read_uint_bits(7) 24 | val = ret & (32 | 64) 25 | 26 | if val == 32: 27 | ret = (ret & ~96) | (buf.read_uint_bits(2) << 5) 28 | assert ret >= 32 29 | elif val == 64: 30 | ret = (ret & ~96) | (buf.read_uint_bits(4) << 5) 31 | assert ret >= 128 32 | elif val == 96: 33 | ret = (ret & ~96) | (buf.read_uint_bits(7) << 5) 34 | assert ret >= 512 35 | 36 | if ret == 0xfff: 37 | return -1 38 | 39 | return last_index + 1 + ret 40 | 41 | 42 | cpdef list parse_entity_update(Bitbuffer buf, object server_class): 43 | """Parse entity updates. 44 | 45 | First a list of all property indices is generated. For each 46 | property referenced by those indices the property's value is 47 | decoded. The list of properties and their decoded values are 48 | collected and returned. 49 | 50 | :returns: List of updated properties 51 | """ 52 | cdef bint new_way 53 | cdef int val = -1 54 | cdef Decoder decoder 55 | 56 | updated_props = [] 57 | field_indices = [] 58 | 59 | new_way = buf.read_bit() 60 | 61 | while True: 62 | val = read_field_index(buf, val, new_way) 63 | 64 | if val == -1: 65 | break 66 | 67 | field_indices.append(val) 68 | 69 | for index in field_indices: 70 | flattened_prop = server_class['props'][index] 71 | 72 | decoder = Decoder.__new__(Decoder, buf, flattened_prop) 73 | 74 | updated_props.append({ 75 | 'prop': flattened_prop, 76 | 'value': decoder.decode() 77 | }) 78 | 79 | return updated_props 80 | -------------------------------------------------------------------------------- /demoparser/protobufs/fatdemo.proto: -------------------------------------------------------------------------------- 1 | import "demoparser/protobufs/netmessages.proto"; 2 | 3 | enum EHitGroup { 4 | EHG_Generic = 0; 5 | EHG_Head = 1; 6 | EHG_Chest = 2; 7 | EHG_Stomach = 3; 8 | EHG_LeftArm = 4; 9 | EHG_RightArm = 5; 10 | EHG_LeftLeg = 6; 11 | EHG_RightLeg = 7; 12 | EHG_Gear = 8; 13 | EHG_Miss = 9; 14 | } 15 | 16 | enum ETeam { 17 | ET_Unknown = 0; 18 | ET_Spectator = 1; 19 | ET_Terrorist = 2; 20 | ET_CT = 3; 21 | } 22 | 23 | enum EWeaponType { 24 | EWT_Knife = 0; 25 | EWT_Pistol = 1; 26 | EWT_SubMachineGun = 2; 27 | EWT_Rifle = 3; 28 | EWT_Shotgun = 4; 29 | EWT_SniperRifle = 5; 30 | EWT_MachineGun = 6; 31 | EWT_C4 = 7; 32 | EWT_Grenade = 8; 33 | EWT_Equipment = 9; 34 | EWT_StackableItem = 10; 35 | EWT_Unknown = 11; 36 | } 37 | 38 | message MLDict { 39 | optional string key = 1; 40 | optional string val_string = 2; 41 | optional int32 val_int = 3; 42 | optional float val_float = 4; 43 | } 44 | 45 | message MLEvent { 46 | optional string event_name = 1; 47 | repeated .MLDict data = 2; 48 | } 49 | 50 | message MLMatchState { 51 | optional string game_mode = 1; 52 | optional string phase = 2; 53 | optional int32 round = 3; 54 | optional int32 score_ct = 4; 55 | optional int32 score_t = 5; 56 | } 57 | 58 | message MLRoundState { 59 | optional string phase = 1; 60 | optional .ETeam win_team = 2 [default = ET_Unknown]; 61 | optional string bomb_state = 3; 62 | } 63 | 64 | message MLWeaponState { 65 | optional int32 index = 1; 66 | optional string name = 2; 67 | optional .EWeaponType type = 3 [default = EWT_Knife]; 68 | optional int32 ammo_clip = 4; 69 | optional int32 ammo_clip_max = 5; 70 | optional int32 ammo_reserve = 6; 71 | optional string state = 7; 72 | optional float recoil_index = 8; 73 | } 74 | 75 | message MLPlayerState { 76 | optional int32 account_id = 1; 77 | optional int32 user_id = 2; 78 | optional int32 entindex = 3; 79 | optional string name = 4; 80 | optional string clan = 5; 81 | optional .ETeam team = 6 [default = ET_Unknown]; 82 | optional .CMsgVector abspos = 7; 83 | optional .CMsgQAngle eyeangle = 8; 84 | optional .CMsgVector eyeangle_fwd = 9; 85 | optional int32 health = 10; 86 | optional int32 armor = 11; 87 | optional float flashed = 12; 88 | optional float smoked = 13; 89 | optional int32 money = 14; 90 | optional int32 round_kills = 15; 91 | optional int32 round_killhs = 16; 92 | optional float burning = 17; 93 | optional bool helmet = 18; 94 | optional bool defuse_kit = 19; 95 | repeated .MLWeaponState weapons = 20; 96 | } 97 | 98 | message MLGameState { 99 | optional .MLMatchState match = 1; 100 | optional .MLRoundState round = 2; 101 | repeated .MLPlayerState players = 3; 102 | } 103 | 104 | message MLDemoHeader { 105 | optional string map_name = 1; 106 | optional int32 tick_rate = 2; 107 | optional uint32 version = 3; 108 | optional uint32 steam_universe = 4; 109 | } 110 | 111 | message MLTick { 112 | optional int32 tick_count = 1; 113 | optional .MLGameState state = 2; 114 | repeated .MLEvent events = 3; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /doc/source/events.rst: -------------------------------------------------------------------------------- 1 | Events 2 | ------ 3 | 4 | As demo files are parsed events are emitted. Callbacks can be 5 | registered for each event. The arguments passed to the callbacks 6 | are described for each event below. 7 | 8 | ----- 9 | 10 | .. _event_baseline_create: 11 | 12 | ``baseline_create`` 13 | An instance baseline has been created. 14 | 15 | Callback arguments: 16 | :class_id: Server class ID. 17 | :table: Data table for this server class. 18 | :baseline: Instance baseline object. 19 | 20 | .. _event_change: 21 | 22 | ``change`` 23 | An entity's property has been changed. 24 | 25 | Callback arguments: 26 | :entity: Entity being created or updated. 27 | :table_name: Table which contains the property being updated. 28 | :var_name: Name of property being updated. 29 | :value: New value of property. 30 | 31 | .. _event_datatable_ready: 32 | 33 | ``datatable_ready`` 34 | Data table has been created and all pending baselines have been handled. 35 | 36 | Callback arguments: 37 | :table: Data table instance 38 | 39 | .. _event_demo_packet: 40 | 41 | ``demo_packet`` 42 | An event for each type of demo packet will be emitted. Demo packets 43 | are instances of SVC and NET classes. 44 | 45 | Callback arguments: 46 | :class_name: Name of packet. 47 | :packet: Instance of approprite NET\_ or SVC\_ class. 48 | 49 | 50 | The following is a list of all demo packet types: 51 | 52 | .. include:: demopacket.rst 53 | 54 | 55 | .. _event_end: 56 | 57 | ``end`` 58 | Processing has finished. 59 | 60 | 61 | .. _event_game_event: 62 | 63 | ``game_event`` 64 | An event for the specific type of game event will be emitted. 65 | 66 | Callback arguments: 67 | :event: Game event object. 68 | :msg: The original message of type SVC_GameEvent which triggered 69 | this event. 70 | 71 | The following is a list of game events for CS:GO: 72 | 73 | .. include:: game_events.rst 74 | 75 | .. _event_string_table_update: 76 | 77 | ``string_table_update`` 78 | An entry in a string table has been updated. 79 | 80 | Callback arguments: 81 | :table: String table instance. 82 | :index: Index of entry being updated. 83 | :entry: The updated entry. 84 | :user_data: User data for the updated entry. 85 | 86 | .. _event_tick_start: 87 | 88 | ``tick_start`` 89 | Start of new game tick. 90 | 91 | Callback arguments: 92 | :current_tick: Tick which has just started. 93 | 94 | .. _event_tick_end: 95 | 96 | ``tick_end`` 97 | End of game tick. 98 | 99 | Callback arguments: 100 | :current_tick: Tick which has just ended. 101 | 102 | .. _event_user_msg: 103 | 104 | ``user_message`` 105 | An event for the specific type of user message will be emitted for each 106 | user message command. A callback can be added for each specific type of 107 | user message. 108 | 109 | .. code-block:: python 110 | 111 | def um_chat(msg_type, msg): 112 | assert msg_type == 'SayText2' 113 | 114 | d = demofile(...) 115 | d.add_callback('SayText2', um_chat) 116 | 117 | 118 | Callback arguments: 119 | :message_type: One of types listed below. 120 | :message: Instance of the specied user message class. 121 | 122 | 123 | The following is a list of all user message types: 124 | 125 | .. include:: user_messages.rst 126 | -------------------------------------------------------------------------------- /doc/source/usage.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Detailed Usage 3 | ============== 4 | 5 | This example shows how you can collect information from a demo file. In 6 | this case it's gathering data about every player death. 7 | 8 | In order to do this you need to know which event to add a callback for. 9 | The first step is to check the list of `game events`_. From the above documentation you 10 | can see the structure of the ``player_death`` event. 11 | 12 | You need an instance of :py:class:`demoparser.demofile.DemoFile` to parse 13 | the demo:: 14 | 15 | from demoparser.demofile import DemoFile 16 | 17 | and a callback function for the ``player_death`` event: 18 | 19 | .. literalinclude:: ../../demoparser/examples/player_death.py 20 | :lines: 6-30 21 | 22 | 23 | .. note:: 24 | The DemoFile instance should generally be in-scope for callback functions. 25 | Often a callback will need to refer to the list of active entities or 26 | other parser state. 27 | 28 | 29 | Retrieving the event data by name requires an extra step and some background 30 | information. 31 | 32 | At the beginning of the demo a message containing all game events is 33 | sent (svc_GameEventList). Each item in the list contains an event ID and 34 | a list of keys with their type and name. 35 | 36 | When an event occurs during the game the message from the server only contains 37 | the type and value. To understand what those values refer to you consult 38 | the game event from the list of events (this event is the first argument 39 | to the callback). The list of keys from the server message and the list of 40 | keys in the game event are in the same order. 41 | 42 | Here's a portion of what's in the message passed to the callback:: 43 | 44 | eventid: 23 45 | keys { 46 | type: 4 47 | val_short: 5 48 | } 49 | keys { 50 | type: 4 51 | val_short: 28 52 | } 53 | keys { 54 | type: 4 55 | val_short: 6 56 | } 57 | keys { 58 | type: 1 59 | val_string: "knife_default_ct" 60 | } 61 | keys { 62 | type: 6 63 | val_bool: false 64 | } 65 | 66 | and here's what's in the event:: 67 | 68 | eventid: 23 69 | name: "player_death" 70 | keys { 71 | type: 4 72 | name: "userid" 73 | } 74 | keys { 75 | type: 4 76 | name: "attacker" 77 | } 78 | keys { 79 | type: 4 80 | name: "assister" 81 | } 82 | keys { 83 | type: 1 84 | name: "weapon" 85 | } 86 | keys { 87 | type: 6 88 | name: "headshot" 89 | } 90 | 91 | 92 | Therefore, the value of ``userid`` is ``msg.keys[0].val_short`` because 93 | ``userid`` is the zeroth element in the list of game event keys. 94 | 95 | 96 | Now that a callback is defined the demo file can be parsed. 97 | 98 | .. literalinclude:: ../../demoparser/examples/player_death.py 99 | :lines: 33-37 100 | 101 | 102 | Output:: 103 | 104 | --- Player Death at tick 4156--- 105 | b0RUP killed by duMzyy * ringwald with knife_default_ct. Headshot? No. 106 | Attacker: health = 100 position = {'x': -420.1773681640625, 'y': -186.3490753173828, 'z': -215.97097778320312} 107 | Victim: position = {'x': -454.6003112792969, 'y': -176.74295043945312, 'z': -219.0527801513672} 108 | 109 | --- Player Death at tick 4200--- 110 | duMzyy * ringwald killed by smF with knife_t. Headshot? No. 111 | Attacker: health = 100 position = {'x': -454.4964599609375, 'y': -157.48977661132812, 'z': -219.96875} 112 | Victim: position = {'x': -451.5458679199219, 'y': -189.49244689941406, 'z': -215.96875} 113 | 114 | 115 | .. _game events: https://wiki.alliedmods.net/Counter-Strike:_Global_Offensive_Events 116 | -------------------------------------------------------------------------------- /demoparser/entities/entity_list.py: -------------------------------------------------------------------------------- 1 | import _pickle as pickle 2 | from collections import MutableSequence 3 | 4 | from demoparser import consts 5 | from demoparser.entities import BaseEntity 6 | from demoparser.entities import GameRules 7 | from demoparser.entities import Player 8 | from demoparser.entities import Team 9 | from demoparser.entities import Weapon 10 | 11 | 12 | class EntityList(MutableSequence): 13 | 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self._entities = [None] * (1 << consts.MAX_EDICT_BITS) 17 | self.class_map = { 18 | 'DT_BasePlayer': Player, 19 | 'DT_Team': Team, 20 | 'DT_WeaponCSBase': Weapon, 21 | 'DT_CSGameRules': GameRules 22 | } 23 | self._cache = {} 24 | 25 | def __getitem__(self, index): 26 | return self._entities.__getitem__(index) 27 | 28 | def __setitem__(self, index, value): 29 | self._entities.__setitem__(index, value) 30 | 31 | def __len__(self): 32 | return len(self._entities) 33 | 34 | def __delitem__(self, index): 35 | self._entities.__delitem__(index) 36 | 37 | def insert(self, index, value): 38 | self._entities.insert(index, value) 39 | 40 | def new_entity(self, index, class_id, serial): 41 | """Create new entity. 42 | 43 | :returns: Created entity. 44 | """ 45 | if self._entities[index]: 46 | self._entities[index] = None 47 | 48 | baseline = self.parser.instance_baselines[class_id] 49 | 50 | cls = BaseEntity 51 | # Find an entity class based on which tables are in its baseline. 52 | # Class IDs don't seem to be consistent across demo files so all 53 | # you can do is assume if an entity baseline has 'DT_Team' it must 54 | # be a Team. 55 | for table in baseline: 56 | if table in self.class_map: 57 | cls = self.class_map[table] 58 | break 59 | 60 | new_baseline = pickle.loads(pickle.dumps(baseline)) 61 | assert baseline == new_baseline 62 | entity = cls(self.parser, index, class_id, serial, new_baseline) 63 | 64 | self._entities[index] = entity 65 | return entity 66 | 67 | def _get_by_class(self, cls): 68 | return [x for x in self._entities if isinstance(x, cls)] 69 | 70 | @property 71 | def players(self): 72 | """Get all Players in the entity list.""" 73 | return self._get_by_class(Player) 74 | 75 | @property 76 | def teams(self): 77 | """Get all Teams in the entity list.""" 78 | return self._get_by_class(Team) 79 | 80 | @property 81 | def weapons(self): 82 | """Get all Weapons in the entity list.""" 83 | return self._get_by_class(Weapon) 84 | 85 | def get_by_user_id(self, user_id): 86 | users = self.parser.table_by_name('userinfo')['entries'] 87 | 88 | for idx, user in enumerate(users): 89 | if getattr(user['user_data'], 'user_id', None) == user_id: 90 | return self._entities[idx + 1] 91 | 92 | def get_by_handle(self, handle): 93 | if not handle: 94 | return 95 | ent = self._entities[handle & consts.NETWORKED_EHANDLE_ENT_ENTRY_MASK] 96 | if ent is None or ent.serial != (handle >> consts.MAX_EDICT_BITS): 97 | return 98 | 99 | return ent 100 | 101 | def get_one(self, table): 102 | if table in self._cache: 103 | return self._cache[table] 104 | 105 | for e in self._entities: 106 | if e and table in e.props: 107 | self._cache[table] = e 108 | return e 109 | -------------------------------------------------------------------------------- /demoparser/entities/player.py: -------------------------------------------------------------------------------- 1 | from demoparser.entities import BaseEntity 2 | import inspect 3 | 4 | 5 | class Player(BaseEntity): 6 | 7 | def __init__(self, parser, index, class_id, serial, props): 8 | self.parser = parser 9 | self.index = index 10 | self.slot = self.index - 1 11 | self.class_id = class_id 12 | self.serial = serial 13 | self.props = props 14 | self.user_info = self._get_user_info() 15 | self._serialize_props = [ 16 | 17 | ] 18 | 19 | def _get_user_info(self): 20 | users = self.parser.table_by_name('userinfo')['entries'] 21 | return users[self.slot]['user_data'] 22 | 23 | @property 24 | def health(self): 25 | """Get current health.""" 26 | return self.get_prop('DT_BasePlayer', 'm_iHealth') 27 | 28 | @property 29 | def name(self): 30 | """Get player's name.""" 31 | if isinstance(self.user_info, bytes): 32 | return "" 33 | return self.user_info.name.decode('utf-8') 34 | 35 | @property 36 | def steam_id(self): 37 | """Get Steam ID.""" 38 | return self.user_info.guid.decode('utf-8') 39 | 40 | @property 41 | def position(self): 42 | """Get current position. 43 | 44 | :returns: Position vector. 45 | """ 46 | vec = self.get_prop('DT_CSLocalPlayerExclusive', 'm_vecOrigin') 47 | z = self.get_prop('DT_CSLocalPlayerExclusive', 'm_vecOrigin[2]') 48 | 49 | return { 50 | 'x': vec['x'], 51 | 'y': vec['y'], 52 | 'z': z 53 | } 54 | 55 | @property 56 | def view_angle(self): 57 | """Get current view angle. 58 | 59 | :returns: Tuple of pitch and yaw. 60 | """ 61 | pitch = self.get_prop('DT_CSPlayer', 'm_angEyeAngles[0]') 62 | yaw = self.get_prop('DT_CSPlayer', 'm_angEyeAngles[1]') 63 | 64 | return {'pitch': pitch, 'yaw': yaw} 65 | 66 | @property 67 | def cash(self): 68 | """Get Cash 69 | 70 | :returns: available cash to spend 71 | """ 72 | return self.get_prop('DT_CSPlayer', 'm_iAccount') 73 | 74 | @property 75 | def equipment_value(self): 76 | """Get current equipment value 77 | 78 | :returns: current equipment value 79 | """ 80 | return self.get_prop('DT_CSPlayer', 'm_unCurrentEquipmentValue') 81 | 82 | @property 83 | def is_in_buy_zone(self): 84 | """Check if player is in buy zone 85 | 86 | :returns: boolean 87 | """ 88 | return self.get_prop('DT_CSPlayer', 'm_bInBuyZone') 89 | 90 | @property 91 | def life_state(self): 92 | """Get life state. 93 | 94 | 0 95 | Alive. 96 | 97 | 1 98 | Dying. Either the death animation is still playing 99 | or the player is falling and waiting to hit the ground. 100 | 101 | 2 102 | Dead. Not moving. 103 | 104 | :returns: Life state. 105 | """ 106 | return self.get_prop('DT_BasePlayer', 'm_lifeState') 107 | 108 | @property 109 | def armor(self): 110 | """Get armor value.""" 111 | return self.get_prop('DT_CSPlayer', 'm_ArmorValue') 112 | 113 | @property 114 | def place(self): 115 | """Get last place player occupied. 116 | 117 | A place refers a named navigation mesh. A navigation mesh 118 | represents the walkable areas of a map. 119 | 120 | :returns: Name of last nav mesh occupied. 121 | """ 122 | return self.get_prop('DT_BasePlayer', 'm_szLastPlaceName') 123 | 124 | @property 125 | def kills(self): 126 | """Number of kills for this player.""" 127 | if isinstance(self.user_info, bytes): 128 | return 0 129 | prop = self.parser.entities.get_one('DT_CSPlayerResource') 130 | # This property has a list of kills for all players 131 | kills = prop.props['m_iKills'] 132 | player_id = str(self.user_info.user_id).zfill(3) 133 | return kills[player_id] 134 | 135 | @property 136 | def weapon(self): 137 | """Get current weapon.""" 138 | return self.parser.entities.get_by_handle( 139 | self.get_prop('DT_BaseCombatCharacter', 'm_hActiveWeapon') 140 | ) 141 | 142 | def properties(self): 143 | result = { 144 | 'index': self.index, 145 | 'class_id': self.class_id, 146 | 'serial': self.serial, 147 | } 148 | 149 | for name, value in inspect.getmembers(self): 150 | prop_attr = getattr(self.__class__, name, None) 151 | if inspect.isdatadescriptor(prop_attr): 152 | attr = getattr(self, name, None) 153 | if not isinstance(attr, BaseEntity): 154 | result[name] = value 155 | 156 | return result 157 | -------------------------------------------------------------------------------- /demoparser/bytebuffer.py: -------------------------------------------------------------------------------- 1 | from demoparser.bitbuffer import Bitbuffer 2 | import struct 3 | 4 | from demoparser.structures import CommandHeader 5 | 6 | 7 | class Bytebuffer: 8 | r"""Parse a stream of bytes from a .DEM file. 9 | 10 | This class provdes convenience methods for parsing 11 | .DEM files. It handles unpacking bytes to different 12 | data types, reading variable-length integers, reading 13 | strings, and creating Bitbuffers. 14 | 15 | :param data: Buffer data 16 | :type data: bytes 17 | 18 | :Example: 19 | 20 | >>> b = Bytebuffer(b'\x00\xf1\xaa') 21 | >>> b.read(1) 22 | 0 23 | >>> b.read_short() 24 | 43761 25 | """ 26 | 27 | def __init__(self, data): 28 | self.data = data 29 | self.index = 0 30 | 31 | def read(self, num_bytes): 32 | """Read `num_bytes` bytes from buffer.""" 33 | d = self.data[self.index:self.index + num_bytes] 34 | self.index += num_bytes 35 | return d 36 | 37 | def read_command_header(self): 38 | """Read the 6 byte command header. 39 | 40 | See :ref:`CommandHeader description ` 41 | for more details. 42 | 43 | :returns: CommandHeader instance 44 | """ 45 | return CommandHeader.from_data(self.read(6)) 46 | 47 | def read_command_data(self): 48 | """Read command info structure. 49 | 50 | This is not used by the parser. 51 | 52 | :returns: bytes 53 | """ 54 | # This data fits the structure described in 55 | # structures.py:CommandInfo. This data seems to always 56 | # be all 0s though. It doesn't appear to be very useful 57 | # and it is very expensive to create one of these structures 58 | # for each message. 59 | self.read(152) 60 | 61 | def read_sequence_data(self): 62 | """Read two integers. 63 | 64 | This data is not used by the parser. 65 | 66 | :returns: Tuple of integers 67 | """ 68 | return struct.unpack("ceil(log2(max_elements))) + 1 172 | num_elements = self.buf.read_uint_bits(bits) 173 | 174 | elements = [] 175 | for idx in range(num_elements): 176 | prop = {'prop': self.fprop['array_element_prop']} 177 | val = Decoder(self.buf, prop).decode() 178 | elements.append(val) 179 | 180 | return elements 181 | 182 | cpdef float decode_special_float(self): 183 | """Decode a float 184 | 185 | A special float is a float which is interpreted in 186 | some special way. The treatement is determined by 187 | the property's flags. 188 | 189 | +-------------------------+--------------------------------------+ 190 | | Flag | Explanation | 191 | +=========================+======================================+ 192 | | COORD | Treat the float or vector as a world | 193 | | | coordinate. | 194 | +-------------------------+--------------------------------------+ 195 | | COORD_MP | Like COORD but special handling for | 196 | | | multi-player games. | 197 | +-------------------------+--------------------------------------+ 198 | | COORD_MP_LOWPRECISION | Like COORD_MP but the fractional | 199 | | | component uses 3 bits instead of 5. | 200 | +-------------------------+--------------------------------------+ 201 | | COORD_MP_INTEGRAL | Like COORD_MP but coordinates are | 202 | | | rounded to integral boundaries. | 203 | +-------------------------+--------------------------------------+ 204 | | NOSCALE | Don't scale floating-point value to | 205 | | | a range. | 206 | +-------------------------+--------------------------------------+ 207 | | NORMAL | Treat vector as a normal. | 208 | +-------------------------+--------------------------------------+ 209 | | CELL_COORD | Like COORD but has special encoding | 210 | | | for cell coordinates which can't be | 211 | | | negative. | 212 | +-------------------------+--------------------------------------+ 213 | | CELL_COORD_LOWPRECISION | Like CELL_COORD but fractional part | 214 | | | uses 3 bits instead of 5. | 215 | +-------------------------+--------------------------------------+ 216 | | CELL_COORD_INTEGRAL | Like CELL_COORD but coordinates are | 217 | | | rounded to integral boundaries. | 218 | +-------------------------+--------------------------------------+ 219 | """ 220 | cdef float val = NAN 221 | cdef unsigned int f 222 | cdef unsigned int flags = self.flags 223 | 224 | if flags & PropFlags.SPROP_COORD: 225 | val = self.buf.read_bit_coord() 226 | elif flags & PropFlags.SPROP_COORD_MP: 227 | val = self.buf.read_bit_coord_mp(consts.CW_None) 228 | elif flags & PropFlags.SPROP_COORD_MP_LOWPRECISION: 229 | val = self.buf.read_bit_coord_mp(consts.CW_LowPrecision) 230 | elif flags & PropFlags.SPROP_COORD_MP_INTEGRAL: 231 | val = self.buf.read_bit_coord_mp(consts.CW_Integral) 232 | elif flags & PropFlags.SPROP_NOSCALE: 233 | f = self.buf.read_uint_bits(32) 234 | val = dereference(&f) 235 | elif flags & PropFlags.SPROP_NORMAL: 236 | val = self.buf.read_bit_normal() 237 | elif flags & PropFlags.SPROP_CELL_COORD: 238 | val = self.buf.read_bit_cell_coord( 239 | self.prop.num_bits, consts.CW_None 240 | ) 241 | elif flags & PropFlags.SPROP_CELL_COORD_LOWPRECISION: 242 | val = self.buf.read_bit_cell_coord( 243 | self.prop.num_bits, consts.CW_LowPrecision 244 | ) 245 | elif flags & PropFlags.SPROP_CELL_COORD_INTEGRAL: 246 | val = self.buf.read_bit_cell_coord( 247 | self.prop.num_bits, consts.CW_Integral 248 | ) 249 | 250 | return val 251 | -------------------------------------------------------------------------------- /demoparser/bitbuffer.pyx: -------------------------------------------------------------------------------- 1 | from cython.operator cimport dereference 2 | from cpython cimport array 3 | import array 4 | from libc.math cimport ceil 5 | 6 | from demoparser import consts 7 | 8 | 9 | cdef unsigned int[33] mask_table = [ 10 | 0, 11 | ( 1 << 1 ) - 1, 12 | ( 1 << 2 ) - 1, 13 | ( 1 << 3 ) - 1, 14 | ( 1 << 4 ) - 1, 15 | ( 1 << 5 ) - 1, 16 | ( 1 << 6 ) - 1, 17 | ( 1 << 7 ) - 1, 18 | ( 1 << 8 ) - 1, 19 | ( 1 << 9 ) - 1, 20 | ( 1 << 10 ) - 1, 21 | ( 1 << 11 ) - 1, 22 | ( 1 << 12 ) - 1, 23 | ( 1 << 13 ) - 1, 24 | ( 1 << 14 ) - 1, 25 | ( 1 << 15 ) - 1, 26 | ( 1 << 16 ) - 1, 27 | ( 1 << 17 ) - 1, 28 | ( 1 << 18 ) - 1, 29 | ( 1 << 19 ) - 1, 30 | ( 1 << 20 ) - 1, 31 | ( 1 << 21 ) - 1, 32 | ( 1 << 22 ) - 1, 33 | ( 1 << 23 ) - 1, 34 | ( 1 << 24 ) - 1, 35 | ( 1 << 25 ) - 1, 36 | ( 1 << 26 ) - 1, 37 | ( 1 << 27 ) - 1, 38 | ( 1 << 28 ) - 1, 39 | ( 1 << 29 ) - 1, 40 | ( 1 << 30 ) - 1, 41 | 0x7fffffff, 42 | 0xffffffff, 43 | ] 44 | 45 | 46 | cdef class Bitbuffer: 47 | r"""Parse a stream of bits from a series of bytes. 48 | 49 | 50 | Data is cast to unsigned int* and read one unsigned int 51 | at a time. As an unsigned int is 32 bits this has the 52 | effect of reading 4 bytes at once. Reads can span integer 53 | boundaries and the consecutive integers will be merged 54 | correctly. 55 | 56 | :param data: Data to parse bit-by-bit 57 | :type data: bytes 58 | 59 | :Example: 60 | 61 | >>> buf = Bitbuffer(b'\x11\x22') 62 | >>> buf.read_uint_bits(8) 63 | 17 64 | >>> buf.read_uint_bits(8) 65 | 34 66 | """ 67 | 68 | def __init__(self, data): 69 | self.num_bytes = len(data) 70 | 71 | # We have to keep a reference to the Python object around. 72 | # It will be garbage-collected when __init__ exits otherwise. 73 | # This doesn't segfault, and we can keep reading from the pointer. 74 | # Parsing will fail far away from here though because the data 75 | # being read isn't a buffer from a demo file. 76 | self.orig_data = data 77 | 78 | # Cython does not let you cast Python objects directly so 79 | # ` data` will not work. It has to go through the 80 | # intermediate cast. 81 | self.data = data 82 | 83 | # Length in words where 1 word = 32 bits = 4 bytes 84 | self.length = int(ceil(self.num_bytes/4.0)) 85 | self.index = 0 86 | self.bits_avail = 32 87 | self.next_dword() 88 | 89 | cpdef unsigned int next_dword(self): 90 | """Move to the next 32-bit integer in the stream. 91 | 92 | :returns: unsigned int 93 | """ 94 | if self.index == self.length: 95 | self.bits_avail = 1 96 | self.in_buf_word = 0 97 | self.index += 1 98 | else: 99 | if self.index > self.length: 100 | self.in_buf_word = 0 101 | else: 102 | self.in_buf_word = self.data[self.index] 103 | self.index += 1 104 | 105 | cpdef unsigned char read_bit(self): 106 | """Read a single bit. 107 | 108 | :returns: one bit 109 | """ 110 | cdef unsigned char ret = self.in_buf_word & 1 111 | self.bits_avail -= 1 112 | 113 | if self.bits_avail == 0: 114 | self.bits_avail = 32 115 | self.next_dword() 116 | else: 117 | self.in_buf_word >>= 1 118 | 119 | return ret 120 | 121 | cpdef unsigned int read_uint_bits(self, unsigned int bits): 122 | """Read the unsigned integer represented by `bits` bits. 123 | 124 | If the number of bits remaining in the current word is 125 | not enough then the next word will be read. 126 | 127 | :param bits: Number of bits to read 128 | :type bits: unsigned int 129 | :returns: unsigned int 130 | """ 131 | cdef unsigned int ret = self.in_buf_word & mask_table[bits] 132 | 133 | if self.bits_avail >= bits: 134 | ret = self.in_buf_word & mask_table[bits] 135 | 136 | self.bits_avail -= bits 137 | if self.bits_avail: 138 | self.in_buf_word >>= bits 139 | else: 140 | self.bits_avail = 32 141 | self.next_dword() 142 | 143 | return ret 144 | else: 145 | # Merge words 146 | ret = self.in_buf_word 147 | bits -= self.bits_avail 148 | self.next_dword() 149 | 150 | ret |= ((self.in_buf_word & mask_table[bits]) << self.bits_avail) 151 | self.bits_avail = 32 - bits 152 | self.in_buf_word >>= bits 153 | 154 | return ret 155 | 156 | cpdef int read_sint_bits(self, unsigned int bits): 157 | """Read a signed integer of `bits` bits. 158 | 159 | First an unsigned integer is read then a two's complement 160 | integer is computed. 161 | 162 | :param bits: Number of bits to read 163 | :type bits: unsigned int 164 | :returns: int 165 | """ 166 | cdef unsigned int ret = self.read_uint_bits(bits) 167 | cdef unsigned int mask = 2 << (bits - 2) 168 | return -(ret & mask) + (ret & ~mask) 169 | 170 | cpdef unsigned int read_var_int(self): 171 | """Read a variable length integer. 172 | 173 | :returns: unsigned int 174 | """ 175 | cdef unsigned int num = self.read_uint_bits(6) 176 | cdef unsigned char bits = num & (16 | 32) 177 | 178 | if bits == 16: 179 | num = (num & 15) | (self.read_uint_bits(4) << 4) 180 | assert num >= 16 181 | elif bits == 32: 182 | num = (num & 15) | (self.read_uint_bits(8) << 4) 183 | assert num >= 256 184 | elif bits == 48: 185 | num = (num & 15) | (self.read_uint_bits(28) << 4) 186 | assert num >= 4096 187 | 188 | return num 189 | 190 | cpdef str read_string(self, int length=-1): 191 | r"""Read a string. 192 | 193 | If length is not provided characters are read until 194 | \\0 is reached. If length is provided then exactly 195 | length bytes will be read and the string may not be 196 | zero-terminated. 197 | 198 | :returns: str 199 | """ 200 | cdef char c 201 | cdef bint append = True 202 | cdef int index = 1 203 | ret = [] 204 | 205 | while True: 206 | c = self.read_uint_bits(8) 207 | if c == 0: 208 | append = False 209 | if length == -1: 210 | break 211 | 212 | if append: 213 | ret.append(c) 214 | if index == length: 215 | break 216 | 217 | index += 1 218 | 219 | return array.array('b', ret).tobytes().decode('utf-8') 220 | 221 | cpdef float read_bit_normal(self): 222 | cdef bint sign_bit = self.read_bit() 223 | """Read a float normal. 224 | 225 | :returns: float value between -1.0 and 1.0 226 | """ 227 | cdef int frac = self.read_uint_bits(consts.NORMAL_FRACTIONAL_BITS) 228 | cdef float value = frac * consts.NORMAL_RESOLUTION 229 | 230 | return -value if sign_bit else value 231 | 232 | cpdef float read_bit_coord(self): 233 | """Read a float and treat as a world coordinate. 234 | 235 | :returns: float 236 | """ 237 | cdef bint integer = self.read_bit() 238 | cdef bint fraction = self.read_bit() 239 | cdef bint sign_bit = 0 240 | cdef unsigned int int_val = 0 241 | cdef unsigned int frac_val = 0 242 | cdef float ret = 0.0 243 | 244 | if not integer and not fraction: 245 | return 0.0 246 | 247 | sign_bit = self.read_bit() 248 | 249 | if integer: 250 | int_val = self.read_uint_bits(consts.COORD_INTEGER_BITS) + 1 251 | 252 | if fraction: 253 | frac_val = self.read_uint_bits(consts.COORD_FRACTIONAL_BITS) 254 | 255 | value = int_val + (frac_val * consts.COORD_RESOLUTION) 256 | 257 | return -value if sign_bit else value 258 | 259 | cpdef float read_bit_cell_coord(self, unsigned int bits, 260 | unsigned int coord_type): 261 | """Read a cell coordinate. 262 | 263 | A cell coordinate is a float which has been 264 | compressed. The number of bits indicates maximum 265 | value. 266 | 267 | :param bits: number of bits to read 268 | :type bits: unsigned int 269 | :param coord_type: level of precision 270 | :type coord_type: unsigned int 271 | :returns: float 272 | """ 273 | 274 | cdef bint low_precision = (coord_type == consts.CW_LowPrecision) 275 | cdef float value = 0.0 276 | cdef float resolution = 0.0 277 | cdef unsigned int frac_bits = 0 278 | cdef unsigned int int_val, frac_val 279 | 280 | if coord_type == consts.CW_Integral: 281 | value = self.read_uint_bits(bits) 282 | else: 283 | if coord_type == consts.COORD_FRACTIONAL_BITS_MP_LOWPRECISION: 284 | frac_bits = low_precision 285 | else: 286 | frac_bits = consts.COORD_FRACTIONAL_BITS 287 | 288 | if low_precision: 289 | resolution = consts.COORD_RESOLUTION_LOWPRECISION 290 | else: 291 | resolution = consts.COORD_RESOLUTION 292 | 293 | int_val = self.read_uint_bits(bits) 294 | frac_val = self.read_uint_bits(frac_bits) 295 | 296 | value = int_val + (frac_val * resolution) 297 | 298 | return value 299 | 300 | cpdef bytes read_user_data(self, unsigned int bits): 301 | """Read user data. 302 | 303 | :param bits: Number of bits to read 304 | :type bits: unsigned int 305 | :returns: bytes 306 | """ 307 | cdef unsigned int entries = int(ceil(bits / 8.0)) 308 | cdef array.array[unsigned char] ret = array.array('B', [0] * entries) 309 | cdef unsigned int arr_idx = 0 310 | 311 | if bits % 8 == 0: 312 | while bits != 0: 313 | ret[arr_idx] = self.read_uint_bits(8) 314 | bits -= 8 315 | arr_idx += 1 316 | return bytes(ret) 317 | 318 | arr_idx = 0 319 | while bits >= 8: 320 | ret[arr_idx] = self.read_uint_bits(8) 321 | arr_idx += 1 322 | bits -= 8 323 | 324 | if bits > 0: 325 | ret[arr_idx] = self.read_uint_bits(bits) 326 | 327 | return bytes(ret) 328 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /demoparser/protobufs/cstrike15_usermessages.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | import "demoparser/protobufs/netmessages.proto"; 3 | import "demoparser/protobufs/cstrike15_gcmessages.proto"; 4 | 5 | option optimize_for = SPEED; 6 | option cc_generic_services = false; 7 | 8 | enum ECstrike15UserMessages { 9 | CS_UM_VGUIMenu = 1; 10 | CS_UM_Geiger = 2; 11 | CS_UM_Train = 3; 12 | CS_UM_HudText = 4; 13 | CS_UM_SayText = 5; 14 | CS_UM_SayText2 = 6; 15 | CS_UM_TextMsg = 7; 16 | CS_UM_HudMsg = 8; 17 | CS_UM_ResetHud = 9; 18 | CS_UM_GameTitle = 10; 19 | CS_UM_Shake = 12; 20 | CS_UM_Fade = 13; 21 | CS_UM_Rumble = 14; 22 | CS_UM_CloseCaption = 15; 23 | CS_UM_CloseCaptionDirect = 16; 24 | CS_UM_SendAudio = 17; 25 | CS_UM_RawAudio = 18; 26 | CS_UM_VoiceMask = 19; 27 | CS_UM_RequestState = 20; 28 | CS_UM_Damage = 21; 29 | CS_UM_RadioText = 22; 30 | CS_UM_HintText = 23; 31 | CS_UM_KeyHintText = 24; 32 | CS_UM_ProcessSpottedEntityUpdate = 25; 33 | CS_UM_ReloadEffect = 26; 34 | CS_UM_AdjustMoney = 27; 35 | CS_UM_UpdateTeamMoney = 28; 36 | CS_UM_StopSpectatorMode = 29; 37 | CS_UM_KillCam = 30; 38 | CS_UM_DesiredTimescale = 31; 39 | CS_UM_CurrentTimescale = 32; 40 | CS_UM_AchievementEvent = 33; 41 | CS_UM_MatchEndConditions = 34; 42 | CS_UM_DisconnectToLobby = 35; 43 | CS_UM_PlayerStatsUpdate = 36; 44 | CS_UM_DisplayInventory = 37; 45 | CS_UM_WarmupHasEnded = 38; 46 | CS_UM_ClientInfo = 39; 47 | CS_UM_XRankGet = 40; 48 | CS_UM_XRankUpd = 41; 49 | CS_UM_CallVoteFailed = 45; 50 | CS_UM_VoteStart = 46; 51 | CS_UM_VotePass = 47; 52 | CS_UM_VoteFailed = 48; 53 | CS_UM_VoteSetup = 49; 54 | CS_UM_ServerRankRevealAll = 50; 55 | CS_UM_SendLastKillerDamageToClient = 51; 56 | CS_UM_ServerRankUpdate = 52; 57 | CS_UM_ItemPickup = 53; 58 | CS_UM_ShowMenu = 54; 59 | CS_UM_BarTime = 55; 60 | CS_UM_AmmoDenied = 56; 61 | CS_UM_MarkAchievement = 57; 62 | CS_UM_MatchStatsUpdate = 58; 63 | CS_UM_ItemDrop = 59; 64 | CS_UM_GlowPropTurnOff = 60; 65 | CS_UM_SendPlayerItemDrops = 61; 66 | CS_UM_RoundBackupFilenames = 62; 67 | CS_UM_SendPlayerItemFound = 63; 68 | CS_UM_ReportHit = 64; 69 | CS_UM_XpUpdate = 65; 70 | CS_UM_QuestProgress = 66; 71 | CS_UM_ScoreLeaderboardData = 67; 72 | CS_UM_PlayerDecalDigitalSignature = 68; 73 | CS_UM_WeaponSound = 69; 74 | } 75 | 76 | message CCSUsrMsg_VGUIMenu { 77 | message Subkey { 78 | optional string name = 1; 79 | optional string str = 2; 80 | } 81 | 82 | optional string name = 1; 83 | optional bool show = 2; 84 | repeated .CCSUsrMsg_VGUIMenu.Subkey subkeys = 3; 85 | } 86 | 87 | message CCSUsrMsg_Geiger { 88 | optional int32 range = 1; 89 | } 90 | 91 | message CCSUsrMsg_Train { 92 | optional int32 train = 1; 93 | } 94 | 95 | message CCSUsrMsg_HudText { 96 | optional string text = 1; 97 | } 98 | 99 | message CCSUsrMsg_SayText { 100 | optional int32 ent_idx = 1; 101 | optional string text = 2; 102 | optional bool chat = 3; 103 | optional bool textallchat = 4; 104 | } 105 | 106 | message CCSUsrMsg_SayText2 { 107 | optional int32 ent_idx = 1; 108 | optional bool chat = 2; 109 | optional string msg_name = 3; 110 | repeated string params = 4; 111 | optional bool textallchat = 5; 112 | } 113 | 114 | message CCSUsrMsg_TextMsg { 115 | optional int32 msg_dst = 1; 116 | repeated string params = 3; 117 | } 118 | 119 | message CCSUsrMsg_HudMsg { 120 | optional int32 channel = 1; 121 | optional .CMsgVector2D pos = 2; 122 | optional .CMsgRGBA clr1 = 3; 123 | optional .CMsgRGBA clr2 = 4; 124 | optional int32 effect = 5; 125 | optional float fade_in_time = 6; 126 | optional float fade_out_time = 7; 127 | optional float hold_time = 9; 128 | optional float fx_time = 10; 129 | optional string text = 11; 130 | } 131 | 132 | message CCSUsrMsg_Shake { 133 | optional int32 command = 1; 134 | optional float local_amplitude = 2; 135 | optional float frequency = 3; 136 | optional float duration = 4; 137 | } 138 | 139 | message CCSUsrMsg_Fade { 140 | optional int32 duration = 1; 141 | optional int32 hold_time = 2; 142 | optional int32 flags = 3; 143 | optional .CMsgRGBA clr = 4; 144 | } 145 | 146 | message CCSUsrMsg_Rumble { 147 | optional int32 index = 1; 148 | optional int32 data = 2; 149 | optional int32 flags = 3; 150 | } 151 | 152 | message CCSUsrMsg_CloseCaption { 153 | optional uint32 hash = 1; 154 | optional int32 duration = 2; 155 | optional bool from_player = 3; 156 | } 157 | 158 | message CCSUsrMsg_CloseCaptionDirect { 159 | optional uint32 hash = 1; 160 | optional int32 duration = 2; 161 | optional bool from_player = 3; 162 | } 163 | 164 | message CCSUsrMsg_SendAudio { 165 | optional string radio_sound = 1; 166 | } 167 | 168 | message CCSUsrMsg_RawAudio { 169 | optional int32 pitch = 1; 170 | optional int32 entidx = 2; 171 | optional float duration = 3; 172 | optional string voice_filename = 4; 173 | } 174 | 175 | message CCSUsrMsg_VoiceMask { 176 | message PlayerMask { 177 | optional int32 game_rules_mask = 1; 178 | optional int32 ban_masks = 2; 179 | } 180 | 181 | repeated .CCSUsrMsg_VoiceMask.PlayerMask player_masks = 1; 182 | optional bool player_mod_enable = 2; 183 | } 184 | 185 | message CCSUsrMsg_Damage { 186 | optional int32 amount = 1; 187 | optional .CMsgVector inflictor_world_pos = 2; 188 | optional int32 victim_entindex = 3; 189 | } 190 | 191 | message CCSUsrMsg_RadioText { 192 | optional int32 msg_dst = 1; 193 | optional int32 client = 2; 194 | optional string msg_name = 3; 195 | repeated string params = 4; 196 | } 197 | 198 | message CCSUsrMsg_HintText { 199 | optional string text = 1; 200 | } 201 | 202 | message CCSUsrMsg_KeyHintText { 203 | repeated string hints = 1; 204 | } 205 | 206 | message CCSUsrMsg_ProcessSpottedEntityUpdate { 207 | message SpottedEntityUpdate { 208 | optional int32 entity_idx = 1; 209 | optional int32 class_id = 2; 210 | optional int32 origin_x = 3; 211 | optional int32 origin_y = 4; 212 | optional int32 origin_z = 5; 213 | optional int32 angle_y = 6; 214 | optional bool defuser = 7; 215 | optional bool player_has_defuser = 8; 216 | optional bool player_has_c4 = 9; 217 | } 218 | 219 | optional bool new_update = 1; 220 | repeated .CCSUsrMsg_ProcessSpottedEntityUpdate.SpottedEntityUpdate entity_updates = 2; 221 | } 222 | 223 | message CCSUsrMsg_SendPlayerItemDrops { 224 | repeated .CEconItemPreviewDataBlock entity_updates = 1; 225 | } 226 | 227 | message CCSUsrMsg_SendPlayerItemFound { 228 | optional .CEconItemPreviewDataBlock iteminfo = 1; 229 | optional int32 entindex = 2; 230 | } 231 | 232 | message CCSUsrMsg_ReloadEffect { 233 | optional int32 entidx = 1; 234 | optional int32 actanim = 2; 235 | optional float origin_x = 3; 236 | optional float origin_y = 4; 237 | optional float origin_z = 5; 238 | } 239 | 240 | message CCSUsrMsg_WeaponSound { 241 | optional int32 entidx = 1; 242 | optional float origin_x = 2; 243 | optional float origin_y = 3; 244 | optional float origin_z = 4; 245 | optional string sound = 5; 246 | optional float timestamp = 6; 247 | } 248 | 249 | message CCSUsrMsg_AdjustMoney { 250 | optional int32 amount = 1; 251 | } 252 | 253 | message CCSUsrMsg_ReportHit { 254 | optional float pos_x = 1; 255 | optional float pos_y = 2; 256 | optional float timestamp = 4; 257 | optional float pos_z = 3; 258 | } 259 | 260 | message CCSUsrMsg_KillCam { 261 | optional int32 obs_mode = 1; 262 | optional int32 first_target = 2; 263 | optional int32 second_target = 3; 264 | } 265 | 266 | message CCSUsrMsg_DesiredTimescale { 267 | optional float desired_timescale = 1; 268 | optional float duration_realtime_sec = 2; 269 | optional int32 interpolator_type = 3; 270 | optional float start_blend_time = 4; 271 | } 272 | 273 | message CCSUsrMsg_CurrentTimescale { 274 | optional float cur_timescale = 1; 275 | } 276 | 277 | message CCSUsrMsg_AchievementEvent { 278 | optional int32 achievement = 1; 279 | optional int32 count = 2; 280 | optional int32 user_id = 3; 281 | } 282 | 283 | message CCSUsrMsg_MatchEndConditions { 284 | optional int32 fraglimit = 1; 285 | optional int32 mp_maxrounds = 2; 286 | optional int32 mp_winlimit = 3; 287 | optional int32 mp_timelimit = 4; 288 | } 289 | 290 | message CCSUsrMsg_PlayerStatsUpdate { 291 | message Stat { 292 | optional int32 idx = 1; 293 | optional int32 delta = 2; 294 | } 295 | 296 | optional int32 version = 1; 297 | repeated .CCSUsrMsg_PlayerStatsUpdate.Stat stats = 4; 298 | optional int32 user_id = 5; 299 | optional int32 crc = 6; 300 | } 301 | 302 | message CCSUsrMsg_DisplayInventory { 303 | optional bool display = 1; 304 | optional int32 user_id = 2; 305 | } 306 | 307 | message CCSUsrMsg_QuestProgress { 308 | optional uint32 quest_id = 1; 309 | optional uint32 normal_points = 2; 310 | optional uint32 bonus_points = 3; 311 | optional bool is_event_quest = 4; 312 | } 313 | 314 | message CCSUsrMsg_ScoreLeaderboardData { 315 | optional .ScoreLeaderboardData data = 1; 316 | } 317 | 318 | message CCSUsrMsg_PlayerDecalDigitalSignature { 319 | optional .PlayerDecalDigitalSignature data = 1; 320 | } 321 | 322 | message CCSUsrMsg_XRankGet { 323 | optional int32 mode_idx = 1; 324 | optional int32 controller = 2; 325 | } 326 | 327 | message CCSUsrMsg_XRankUpd { 328 | optional int32 mode_idx = 1; 329 | optional int32 controller = 2; 330 | optional int32 ranking = 3; 331 | } 332 | 333 | message CCSUsrMsg_CallVoteFailed { 334 | optional int32 reason = 1; 335 | optional int32 time = 2; 336 | } 337 | 338 | message CCSUsrMsg_VoteStart { 339 | optional int32 team = 1; 340 | optional int32 ent_idx = 2; 341 | optional int32 vote_type = 3; 342 | optional string disp_str = 4; 343 | optional string details_str = 5; 344 | optional string other_team_str = 6; 345 | optional bool is_yes_no_vote = 7; 346 | } 347 | 348 | message CCSUsrMsg_VotePass { 349 | optional int32 team = 1; 350 | optional int32 vote_type = 2; 351 | optional string disp_str = 3; 352 | optional string details_str = 4; 353 | } 354 | 355 | message CCSUsrMsg_VoteFailed { 356 | optional int32 team = 1; 357 | optional int32 reason = 2; 358 | } 359 | 360 | message CCSUsrMsg_VoteSetup { 361 | repeated string potential_issues = 1; 362 | } 363 | 364 | message CCSUsrMsg_SendLastKillerDamageToClient { 365 | optional int32 num_hits_given = 1; 366 | optional int32 damage_given = 2; 367 | optional int32 num_hits_taken = 3; 368 | optional int32 damage_taken = 4; 369 | } 370 | 371 | message CCSUsrMsg_ServerRankUpdate { 372 | message RankUpdate { 373 | optional int32 account_id = 1; 374 | optional int32 rank_old = 2; 375 | optional int32 rank_new = 3; 376 | optional int32 num_wins = 4; 377 | optional float rank_change = 5; 378 | optional int32 rank_type_id = 6; 379 | } 380 | 381 | repeated .CCSUsrMsg_ServerRankUpdate.RankUpdate rank_update = 1; 382 | } 383 | 384 | message CCSUsrMsg_XpUpdate { 385 | optional .CMsgGCCstrike15_v2_GC2ServerNotifyXPRewarded data = 1; 386 | } 387 | 388 | message CCSUsrMsg_ItemPickup { 389 | optional string item = 1; 390 | } 391 | 392 | message CCSUsrMsg_ShowMenu { 393 | optional int32 bits_valid_slots = 1; 394 | optional int32 display_time = 2; 395 | optional string menu_string = 3; 396 | } 397 | 398 | message CCSUsrMsg_BarTime { 399 | optional string time = 1; 400 | } 401 | 402 | message CCSUsrMsg_AmmoDenied { 403 | optional int32 ammoIdx = 1; 404 | } 405 | 406 | message CCSUsrMsg_MarkAchievement { 407 | optional string achievement = 1; 408 | } 409 | 410 | message CCSUsrMsg_MatchStatsUpdate { 411 | optional string update = 1; 412 | } 413 | 414 | message CCSUsrMsg_ItemDrop { 415 | optional int64 itemid = 1; 416 | optional bool death = 2; 417 | } 418 | 419 | message CCSUsrMsg_GlowPropTurnOff { 420 | optional int32 entidx = 1; 421 | } 422 | 423 | message CCSUsrMsg_RoundBackupFilenames { 424 | optional int32 count = 1; 425 | optional int32 index = 2; 426 | optional string filename = 3; 427 | optional string nicename = 4; 428 | } 429 | 430 | message CCSUsrMsg_ResetHud { 431 | optional bool reset = 1; 432 | } 433 | 434 | message CCSUsrMsg_GameTitle { 435 | optional int32 dummy = 1; 436 | } 437 | 438 | message CCSUsrMsg_RequestState { 439 | optional int32 dummy = 1; 440 | } 441 | 442 | message CCSUsrMsg_StopSpectatorMode { 443 | optional int32 dummy = 1; 444 | } 445 | 446 | message CCSUsrMsg_DisconnectToLobby { 447 | optional int32 dummy = 1; 448 | } 449 | 450 | message CCSUsrMsg_WarmupHasEnded { 451 | optional int32 dummy = 1; 452 | } 453 | 454 | message CCSUsrMsg_ClientInfo { 455 | optional int32 dummy = 1; 456 | } 457 | 458 | message CCSUsrMsg_ServerRankRevealAll { 459 | optional int32 seconds_till_shutdown = 1; 460 | } 461 | 462 | -------------------------------------------------------------------------------- /demoparser/protobufs/netmessages.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | option cc_generic_services = false; 4 | 5 | enum NET_Messages { 6 | net_NOP = 0; 7 | net_Disconnect = 1; 8 | net_File = 2; 9 | net_SplitScreenUser = 3; 10 | net_Tick = 4; 11 | net_StringCmd = 5; 12 | net_SetConVar = 6; 13 | net_SignonState = 7; 14 | net_PlayerAvatarData = 100; 15 | } 16 | 17 | enum CLC_Messages { 18 | clc_ClientInfo = 8; 19 | clc_Move = 9; 20 | clc_VoiceData = 10; 21 | clc_BaselineAck = 11; 22 | clc_ListenEvents = 12; 23 | clc_RespondCvarValue = 13; 24 | clc_FileCRCCheck = 14; 25 | clc_LoadingProgress = 15; 26 | clc_SplitPlayerConnect = 16; 27 | clc_ClientMessage = 17; 28 | clc_CmdKeyValues = 18; 29 | clc_HltvReplay = 20; 30 | } 31 | 32 | enum VoiceDataFormat_t { 33 | VOICEDATA_FORMAT_STEAM = 0; 34 | VOICEDATA_FORMAT_ENGINE = 1; 35 | } 36 | 37 | enum ESplitScreenMessageType { 38 | option allow_alias = true; 39 | MSG_SPLITSCREEN_ADDUSER = 0; 40 | MSG_SPLITSCREEN_REMOVEUSER = 1; 41 | MSG_SPLITSCREEN_TYPE_BITS = 1; 42 | } 43 | 44 | enum SVC_Messages { 45 | svc_ServerInfo = 8; 46 | svc_SendTable = 9; 47 | svc_ClassInfo = 10; 48 | svc_SetPause = 11; 49 | svc_CreateStringTable = 12; 50 | svc_UpdateStringTable = 13; 51 | svc_VoiceInit = 14; 52 | svc_VoiceData = 15; 53 | svc_Print = 16; 54 | svc_Sounds = 17; 55 | svc_SetView = 18; 56 | svc_FixAngle = 19; 57 | svc_CrosshairAngle = 20; 58 | svc_BSPDecal = 21; 59 | svc_SplitScreen = 22; 60 | svc_UserMessage = 23; 61 | svc_EntityMessage = 24; 62 | svc_GameEvent = 25; 63 | svc_PacketEntities = 26; 64 | svc_TempEntities = 27; 65 | svc_Prefetch = 28; 66 | svc_Menu = 29; 67 | svc_GameEventList = 30; 68 | svc_GetCvarValue = 31; 69 | svc_PaintmapData = 33; 70 | svc_CmdKeyValues = 34; 71 | svc_EncryptedData = 35; 72 | svc_HltvReplay = 36; 73 | svc_Broadcast_Command = 38; 74 | } 75 | 76 | enum ReplayEventType_t { 77 | REPLAY_EVENT_CANCEL = 0; 78 | REPLAY_EVENT_DEATH = 1; 79 | REPLAY_EVENT_GENERIC = 2; 80 | REPLAY_EVENT_STUCK_NEED_FULL_UPDATE = 3; 81 | } 82 | 83 | message CMsgVector { 84 | optional float x = 1; 85 | optional float y = 2; 86 | optional float z = 3; 87 | } 88 | 89 | message CMsgVector2D { 90 | optional float x = 1; 91 | optional float y = 2; 92 | } 93 | 94 | message CMsgQAngle { 95 | optional float x = 1; 96 | optional float y = 2; 97 | optional float z = 3; 98 | } 99 | 100 | message CMsgRGBA { 101 | optional int32 r = 1; 102 | optional int32 g = 2; 103 | optional int32 b = 3; 104 | optional int32 a = 4; 105 | } 106 | 107 | message CNETMsg_Tick { 108 | optional uint32 tick = 1; 109 | optional uint32 host_computationtime = 4; 110 | optional uint32 host_computationtime_std_deviation = 5; 111 | optional uint32 host_framestarttime_std_deviation = 6; 112 | optional uint32 hltv_replay_flags = 7; 113 | } 114 | 115 | message CNETMsg_StringCmd { 116 | optional string command = 1; 117 | } 118 | 119 | message CNETMsg_SignonState { 120 | optional uint32 signon_state = 1; 121 | optional uint32 spawn_count = 2; 122 | optional uint32 num_server_players = 3; 123 | repeated string players_networkids = 4; 124 | optional string map_name = 5; 125 | } 126 | 127 | message CMsg_CVars { 128 | message CVar { 129 | optional string name = 1; 130 | optional string value = 2; 131 | optional uint32 dictionary_name = 3; 132 | } 133 | 134 | repeated .CMsg_CVars.CVar cvars = 1; 135 | } 136 | 137 | message CNETMsg_SetConVar { 138 | optional .CMsg_CVars convars = 1; 139 | } 140 | 141 | message CNETMsg_NOP { 142 | } 143 | 144 | message CNETMsg_Disconnect { 145 | optional string text = 1; 146 | } 147 | 148 | message CNETMsg_File { 149 | optional int32 transfer_id = 1; 150 | optional string file_name = 2; 151 | optional bool is_replay_demo_file = 3; 152 | optional bool deny = 4; 153 | } 154 | 155 | message CNETMsg_SplitScreenUser { 156 | optional int32 slot = 1; 157 | } 158 | 159 | message CNETMsg_PlayerAvatarData { 160 | optional uint32 accountid = 1; 161 | optional bytes rgb = 2; 162 | } 163 | 164 | message CCLCMsg_ClientInfo { 165 | optional fixed32 send_table_crc = 1; 166 | optional uint32 server_count = 2; 167 | optional bool is_hltv = 3; 168 | optional bool is_replay = 4; 169 | optional uint32 friends_id = 5; 170 | optional string friends_name = 6; 171 | repeated fixed32 custom_files = 7; 172 | } 173 | 174 | message CCLCMsg_Move { 175 | optional uint32 num_backup_commands = 1; 176 | optional uint32 num_new_commands = 2; 177 | optional bytes data = 3; 178 | } 179 | 180 | message CCLCMsg_VoiceData { 181 | optional bytes data = 1; 182 | optional fixed64 xuid = 2; 183 | optional .VoiceDataFormat_t format = 3 [default = VOICEDATA_FORMAT_ENGINE]; 184 | optional int32 sequence_bytes = 4; 185 | optional uint32 section_number = 5; 186 | optional uint32 uncompressed_sample_offset = 6; 187 | } 188 | 189 | message CCLCMsg_BaselineAck { 190 | optional int32 baseline_tick = 1; 191 | optional int32 baseline_nr = 2; 192 | } 193 | 194 | message CCLCMsg_ListenEvents { 195 | repeated fixed32 event_mask = 1; 196 | } 197 | 198 | message CCLCMsg_RespondCvarValue { 199 | optional int32 cookie = 1; 200 | optional int32 status_code = 2; 201 | optional string name = 3; 202 | optional string value = 4; 203 | } 204 | 205 | message CCLCMsg_FileCRCCheck { 206 | optional int32 code_path = 1; 207 | optional string path = 2; 208 | optional int32 code_filename = 3; 209 | optional string filename = 4; 210 | optional int32 file_fraction = 5; 211 | optional bytes md5 = 6; 212 | optional uint32 crc = 7; 213 | optional int32 file_hash_type = 8; 214 | optional int32 file_len = 9; 215 | optional int32 pack_file_id = 10; 216 | optional int32 pack_file_number = 11; 217 | } 218 | 219 | message CCLCMsg_LoadingProgress { 220 | optional int32 progress = 1; 221 | } 222 | 223 | message CCLCMsg_SplitPlayerConnect { 224 | optional .CMsg_CVars convars = 1; 225 | } 226 | 227 | message CCLCMsg_CmdKeyValues { 228 | optional bytes keyvalues = 1; 229 | } 230 | 231 | message CSVCMsg_ServerInfo { 232 | optional int32 protocol = 1; 233 | optional int32 server_count = 2; 234 | optional bool is_dedicated = 3; 235 | optional bool is_official_valve_server = 4; 236 | optional bool is_hltv = 5; 237 | optional bool is_replay = 6; 238 | optional bool is_redirecting_to_proxy_relay = 21; 239 | optional int32 c_os = 7; 240 | optional fixed32 map_crc = 8; 241 | optional fixed32 client_crc = 9; 242 | optional fixed32 string_table_crc = 10; 243 | optional int32 max_clients = 11; 244 | optional int32 max_classes = 12; 245 | optional int32 player_slot = 13; 246 | optional float tick_interval = 14; 247 | optional string game_dir = 15; 248 | optional string map_name = 16; 249 | optional string map_group_name = 17; 250 | optional string sky_name = 18; 251 | optional string host_name = 19; 252 | optional uint32 public_ip = 20; 253 | optional uint64 ugc_map_id = 22; 254 | } 255 | 256 | message CSVCMsg_ClassInfo { 257 | message class_t { 258 | optional int32 class_id = 1; 259 | optional string data_table_name = 2; 260 | optional string class_name = 3; 261 | } 262 | 263 | optional bool create_on_client = 1; 264 | repeated .CSVCMsg_ClassInfo.class_t classes = 2; 265 | } 266 | 267 | message CSVCMsg_SendTable { 268 | message sendprop_t { 269 | optional int32 type = 1; 270 | optional string var_name = 2; 271 | optional int32 flags = 3; 272 | optional int32 priority = 4; 273 | optional string dt_name = 5; 274 | optional int32 num_elements = 6; 275 | optional float low_value = 7; 276 | optional float high_value = 8; 277 | optional int32 num_bits = 9; 278 | } 279 | 280 | optional bool is_end = 1; 281 | optional string net_table_name = 2; 282 | optional bool needs_decoder = 3; 283 | repeated .CSVCMsg_SendTable.sendprop_t props = 4; 284 | } 285 | 286 | message CSVCMsg_Print { 287 | optional string text = 1; 288 | } 289 | 290 | message CSVCMsg_SetPause { 291 | optional bool paused = 1; 292 | } 293 | 294 | message CSVCMsg_SetView { 295 | optional int32 entity_index = 1; 296 | } 297 | 298 | message CSVCMsg_CreateStringTable { 299 | optional string name = 1; 300 | optional int32 max_entries = 2; 301 | optional int32 num_entries = 3; 302 | optional bool user_data_fixed_size = 4; 303 | optional int32 user_data_size = 5; 304 | optional int32 user_data_size_bits = 6; 305 | optional int32 flags = 7; 306 | optional bytes string_data = 8; 307 | } 308 | 309 | message CSVCMsg_UpdateStringTable { 310 | optional int32 table_id = 1; 311 | optional int32 num_changed_entries = 2; 312 | optional bytes string_data = 3; 313 | } 314 | 315 | message CSVCMsg_VoiceInit { 316 | optional int32 quality = 1; 317 | optional string codec = 2; 318 | optional int32 version = 3 [default = 0]; 319 | } 320 | 321 | message CSVCMsg_VoiceData { 322 | optional int32 client = 1; 323 | optional bool proximity = 2; 324 | optional fixed64 xuid = 3; 325 | optional int32 audible_mask = 4; 326 | optional bytes voice_data = 5; 327 | optional bool caster = 6; 328 | optional .VoiceDataFormat_t format = 7 [default = VOICEDATA_FORMAT_ENGINE]; 329 | optional int32 sequence_bytes = 8; 330 | optional uint32 section_number = 9; 331 | optional uint32 uncompressed_sample_offset = 10; 332 | } 333 | 334 | message CSVCMsg_FixAngle { 335 | optional bool relative = 1; 336 | optional .CMsgQAngle angle = 2; 337 | } 338 | 339 | message CSVCMsg_CrosshairAngle { 340 | optional .CMsgQAngle angle = 1; 341 | } 342 | 343 | message CSVCMsg_Prefetch { 344 | optional int32 sound_index = 1; 345 | } 346 | 347 | message CSVCMsg_BSPDecal { 348 | optional .CMsgVector pos = 1; 349 | optional int32 decal_texture_index = 2; 350 | optional int32 entity_index = 3; 351 | optional int32 model_index = 4; 352 | optional bool low_priority = 5; 353 | } 354 | 355 | message CSVCMsg_SplitScreen { 356 | optional .ESplitScreenMessageType type = 1 [default = MSG_SPLITSCREEN_ADDUSER]; 357 | optional int32 slot = 2; 358 | optional int32 player_index = 3; 359 | } 360 | 361 | message CSVCMsg_GetCvarValue { 362 | optional int32 cookie = 1; 363 | optional string cvar_name = 2; 364 | } 365 | 366 | message CSVCMsg_Menu { 367 | optional int32 dialog_type = 1; 368 | optional bytes menu_key_values = 2; 369 | } 370 | 371 | message CSVCMsg_UserMessage { 372 | optional int32 msg_type = 1; 373 | optional bytes msg_data = 2; 374 | optional int32 passthrough = 3; 375 | } 376 | 377 | message CSVCMsg_PaintmapData { 378 | optional bytes paintmap = 1; 379 | } 380 | 381 | message CSVCMsg_GameEvent { 382 | message key_t { 383 | optional int32 type = 1; 384 | optional string val_string = 2; 385 | optional float val_float = 3; 386 | optional int32 val_long = 4; 387 | optional int32 val_short = 5; 388 | optional int32 val_byte = 6; 389 | optional bool val_bool = 7; 390 | optional uint64 val_uint64 = 8; 391 | optional bytes val_wstring = 9; 392 | } 393 | 394 | optional string event_name = 1; 395 | optional int32 eventid = 2; 396 | repeated .CSVCMsg_GameEvent.key_t keys = 3; 397 | optional int32 passthrough = 4; 398 | } 399 | 400 | message CSVCMsg_GameEventList { 401 | message key_t { 402 | optional int32 type = 1; 403 | optional string name = 2; 404 | } 405 | 406 | message descriptor_t { 407 | optional int32 eventid = 1; 408 | optional string name = 2; 409 | repeated .CSVCMsg_GameEventList.key_t keys = 3; 410 | } 411 | 412 | repeated .CSVCMsg_GameEventList.descriptor_t descriptors = 1; 413 | } 414 | 415 | message CSVCMsg_TempEntities { 416 | optional bool reliable = 1; 417 | optional int32 num_entries = 2; 418 | optional bytes entity_data = 3; 419 | } 420 | 421 | message CSVCMsg_PacketEntities { 422 | optional int32 max_entries = 1; 423 | optional int32 updated_entries = 2; 424 | optional bool is_delta = 3; 425 | optional bool update_baseline = 4; 426 | optional int32 baseline = 5; 427 | optional int32 delta_from = 6; 428 | optional bytes entity_data = 7; 429 | } 430 | 431 | message CSVCMsg_Sounds { 432 | message sounddata_t { 433 | optional sint32 origin_x = 1; 434 | optional sint32 origin_y = 2; 435 | optional sint32 origin_z = 3; 436 | optional uint32 volume = 4; 437 | optional float delay_value = 5; 438 | optional int32 sequence_number = 6; 439 | optional int32 entity_index = 7; 440 | optional int32 channel = 8; 441 | optional int32 pitch = 9; 442 | optional int32 flags = 10; 443 | optional uint32 sound_num = 11; 444 | optional fixed32 sound_num_handle = 12; 445 | optional int32 speaker_entity = 13; 446 | optional int32 random_seed = 14; 447 | optional int32 sound_level = 15; 448 | optional bool is_sentence = 16; 449 | optional bool is_ambient = 17; 450 | } 451 | 452 | optional bool reliable_sound = 1; 453 | repeated .CSVCMsg_Sounds.sounddata_t sounds = 2; 454 | } 455 | 456 | message CSVCMsg_EntityMsg { 457 | optional int32 ent_index = 1; 458 | optional int32 class_id = 2; 459 | optional bytes ent_data = 3; 460 | } 461 | 462 | message CSVCMsg_CmdKeyValues { 463 | optional bytes keyvalues = 1; 464 | } 465 | 466 | message CSVCMsg_EncryptedData { 467 | optional bytes encrypted = 1; 468 | optional int32 key_type = 2; 469 | } 470 | 471 | message CSVCMsg_HltvReplay { 472 | optional int32 delay = 1; 473 | optional int32 primary_target = 2; 474 | optional int32 replay_stop_at = 3; 475 | optional int32 replay_start_at = 4; 476 | optional int32 replay_slowdown_begin = 5; 477 | optional int32 replay_slowdown_end = 6; 478 | optional float replay_slowdown_rate = 7; 479 | } 480 | 481 | message CCLCMsg_HltvReplay { 482 | optional int32 request = 1; 483 | optional float slowdown_length = 2; 484 | optional float slowdown_rate = 3; 485 | optional int32 primary_target_ent_index = 4; 486 | optional float event_time = 5; 487 | } 488 | 489 | message CSVCMsg_Broadcast_Command { 490 | optional string cmd = 1; 491 | } 492 | 493 | -------------------------------------------------------------------------------- /demoparser/protobufs/base_gcmessages.proto: -------------------------------------------------------------------------------- 1 | import "demoparser/protobufs/steammessages.proto"; 2 | 3 | option optimize_for = SPEED; 4 | option cc_generic_services = false; 5 | 6 | enum EGCBaseMsg { 7 | k_EMsgGCSystemMessage = 4001; 8 | k_EMsgGCReplicateConVars = 4002; 9 | k_EMsgGCConVarUpdated = 4003; 10 | k_EMsgGCInQueue = 4008; 11 | k_EMsgGCInviteToParty = 4501; 12 | k_EMsgGCInvitationCreated = 4502; 13 | k_EMsgGCPartyInviteResponse = 4503; 14 | k_EMsgGCKickFromParty = 4504; 15 | k_EMsgGCLeaveParty = 4505; 16 | k_EMsgGCServerAvailable = 4506; 17 | k_EMsgGCClientConnectToServer = 4507; 18 | k_EMsgGCGameServerInfo = 4508; 19 | k_EMsgGCError = 4509; 20 | k_EMsgGCReplay_UploadedToYouTube = 4510; 21 | k_EMsgGCLANServerAvailable = 4511; 22 | } 23 | 24 | enum EGCBaseProtoObjectTypes { 25 | k_EProtoObjectPartyInvite = 1001; 26 | k_EProtoObjectLobbyInvite = 1002; 27 | } 28 | 29 | enum GC_BannedWordType { 30 | GC_BANNED_WORD_DISABLE_WORD = 0; 31 | GC_BANNED_WORD_ENABLE_WORD = 1; 32 | } 33 | 34 | message CGCStorePurchaseInit_LineItem { 35 | optional uint32 item_def_id = 1; 36 | optional uint32 quantity = 2; 37 | optional uint32 cost_in_local_currency = 3; 38 | optional uint32 purchase_type = 4; 39 | } 40 | 41 | message CMsgGCStorePurchaseInit { 42 | optional string country = 1; 43 | optional int32 language = 2; 44 | optional int32 currency = 3; 45 | repeated .CGCStorePurchaseInit_LineItem line_items = 4; 46 | } 47 | 48 | message CMsgGCStorePurchaseInitResponse { 49 | optional int32 result = 1; 50 | optional uint64 txn_id = 2; 51 | optional string url = 3; 52 | repeated uint64 item_ids = 4; 53 | } 54 | 55 | message CSOPartyInvite { 56 | optional uint64 group_id = 1 [(key_field) = true]; 57 | optional fixed64 sender_id = 2; 58 | optional string sender_name = 3; 59 | } 60 | 61 | message CSOLobbyInvite { 62 | optional uint64 group_id = 1 [(key_field) = true]; 63 | optional fixed64 sender_id = 2; 64 | optional string sender_name = 3; 65 | } 66 | 67 | message CMsgSystemBroadcast { 68 | optional string message = 1; 69 | } 70 | 71 | message CMsgInviteToParty { 72 | optional fixed64 steam_id = 1; 73 | optional uint32 client_version = 2; 74 | optional uint32 team_invite = 3; 75 | } 76 | 77 | message CMsgInvitationCreated { 78 | optional uint64 group_id = 1; 79 | optional fixed64 steam_id = 2; 80 | } 81 | 82 | message CMsgPartyInviteResponse { 83 | optional uint64 party_id = 1; 84 | optional bool accept = 2; 85 | optional uint32 client_version = 3; 86 | optional uint32 team_invite = 4; 87 | } 88 | 89 | message CMsgKickFromParty { 90 | optional fixed64 steam_id = 1; 91 | } 92 | 93 | message CMsgLeaveParty { 94 | } 95 | 96 | message CMsgServerAvailable { 97 | } 98 | 99 | message CMsgLANServerAvailable { 100 | optional fixed64 lobby_id = 1; 101 | } 102 | 103 | message CSOEconGameAccountClient { 104 | optional uint32 additional_backpack_slots = 1 [default = 0]; 105 | optional fixed32 bonus_xp_timestamp_refresh = 12; 106 | optional uint32 bonus_xp_usedflags = 13; 107 | optional uint32 elevated_state = 14; 108 | optional uint32 elevated_timestamp = 15; 109 | } 110 | 111 | message CSOItemCriteriaCondition { 112 | optional int32 op = 1; 113 | optional string field = 2; 114 | optional bool required = 3; 115 | optional float float_value = 4; 116 | optional string string_value = 5; 117 | } 118 | 119 | message CSOItemCriteria { 120 | optional uint32 item_level = 1; 121 | optional int32 item_quality = 2; 122 | optional bool item_level_set = 3; 123 | optional bool item_quality_set = 4; 124 | optional uint32 initial_inventory = 5; 125 | optional uint32 initial_quantity = 6; 126 | optional bool ignore_enabled_flag = 8; 127 | repeated .CSOItemCriteriaCondition conditions = 9; 128 | optional int32 item_rarity = 10; 129 | optional bool item_rarity_set = 11; 130 | optional bool recent_only = 12; 131 | } 132 | 133 | message CSOItemRecipe { 134 | optional uint32 def_index = 1; 135 | optional string name = 2; 136 | optional string n_a = 3; 137 | optional string desc_inputs = 4; 138 | optional string desc_outputs = 5; 139 | optional string di_a = 6; 140 | optional string di_b = 7; 141 | optional string di_c = 8; 142 | optional string do_a = 9; 143 | optional string do_b = 10; 144 | optional string do_c = 11; 145 | optional bool requires_all_same_class = 12; 146 | optional bool requires_all_same_slot = 13; 147 | optional int32 class_usage_for_output = 14; 148 | optional int32 slot_usage_for_output = 15; 149 | optional int32 set_for_output = 16; 150 | repeated .CSOItemCriteria input_items_criteria = 20; 151 | repeated .CSOItemCriteria output_items_criteria = 21; 152 | repeated uint32 input_item_dupe_counts = 22; 153 | } 154 | 155 | message CMsgDevNewItemRequest { 156 | optional fixed64 receiver = 1; 157 | optional .CSOItemCriteria criteria = 2; 158 | } 159 | 160 | message CMsgIncrementKillCountAttribute { 161 | optional fixed32 killer_account_id = 1; 162 | optional fixed32 victim_account_id = 2; 163 | optional uint64 item_id = 3; 164 | optional uint32 event_type = 4; 165 | optional uint32 amount = 5; 166 | } 167 | 168 | message CMsgApplySticker { 169 | optional uint64 sticker_item_id = 1; 170 | optional uint64 item_item_id = 2; 171 | optional uint32 sticker_slot = 3; 172 | optional uint32 baseitem_defidx = 4; 173 | optional float sticker_wear = 5; 174 | } 175 | 176 | message CMsgApplyStatTrakSwap { 177 | optional uint64 tool_item_id = 1; 178 | optional uint64 item_1_item_id = 2; 179 | optional uint64 item_2_item_id = 3; 180 | } 181 | 182 | message CMsgApplyStrangePart { 183 | optional uint64 strange_part_item_id = 1; 184 | optional uint64 item_item_id = 2; 185 | } 186 | 187 | message CMsgApplyPennantUpgrade { 188 | optional uint64 upgrade_item_id = 1; 189 | optional uint64 pennant_item_id = 2; 190 | } 191 | 192 | message CMsgApplyEggEssence { 193 | optional uint64 essence_item_id = 1; 194 | optional uint64 egg_item_id = 2; 195 | } 196 | 197 | message CSOEconItemAttribute { 198 | optional uint32 def_index = 1; 199 | optional uint32 value = 2; 200 | optional bytes value_bytes = 3; 201 | } 202 | 203 | message CSOEconItemEquipped { 204 | optional uint32 new_class = 1; 205 | optional uint32 new_slot = 2; 206 | } 207 | 208 | message CSOEconItem { 209 | optional uint64 id = 1; 210 | optional uint32 account_id = 2; 211 | optional uint32 inventory = 3; 212 | optional uint32 def_index = 4; 213 | optional uint32 quantity = 5; 214 | optional uint32 level = 6; 215 | optional uint32 quality = 7; 216 | optional uint32 flags = 8 [default = 0]; 217 | optional uint32 origin = 9; 218 | optional string custom_name = 10; 219 | optional string custom_desc = 11; 220 | repeated .CSOEconItemAttribute attribute = 12; 221 | optional .CSOEconItem interior_item = 13; 222 | optional bool in_use = 14 [default = false]; 223 | optional uint32 style = 15 [default = 0]; 224 | optional uint64 original_id = 16 [default = 0]; 225 | repeated .CSOEconItemEquipped equipped_state = 18; 226 | optional uint32 rarity = 19; 227 | } 228 | 229 | message CMsgAdjustItemEquippedState { 230 | optional uint64 item_id = 1; 231 | optional uint32 new_class = 2; 232 | optional uint32 new_slot = 3; 233 | optional bool swap = 4; 234 | } 235 | 236 | message CMsgSortItems { 237 | optional uint32 sort_type = 1; 238 | } 239 | 240 | message CSOEconClaimCode { 241 | optional uint32 account_id = 1; 242 | optional uint32 code_type = 2; 243 | optional uint32 time_acquired = 3; 244 | optional string code = 4; 245 | } 246 | 247 | message CMsgStoreGetUserData { 248 | optional fixed32 price_sheet_version = 1; 249 | optional int32 currency = 2; 250 | } 251 | 252 | message CMsgStoreGetUserDataResponse { 253 | optional int32 result = 1; 254 | optional int32 currency_deprecated = 2; 255 | optional string country_deprecated = 3; 256 | optional fixed32 price_sheet_version = 4; 257 | optional bytes price_sheet = 8; 258 | } 259 | 260 | message CMsgUpdateItemSchema { 261 | optional bytes items_game = 1; 262 | optional fixed32 item_schema_version = 2; 263 | optional string items_game_url_DEPRECATED2013 = 3; 264 | optional string items_game_url = 4; 265 | } 266 | 267 | message CMsgGCError { 268 | optional string error_text = 1; 269 | } 270 | 271 | message CMsgRequestInventoryRefresh { 272 | } 273 | 274 | message CMsgConVarValue { 275 | optional string name = 1; 276 | optional string value = 2; 277 | } 278 | 279 | message CMsgReplicateConVars { 280 | repeated .CMsgConVarValue convars = 1; 281 | } 282 | 283 | message CMsgUseItem { 284 | optional uint64 item_id = 1; 285 | optional fixed64 target_steam_id = 2; 286 | repeated uint32 gift__potential_targets = 3; 287 | optional uint32 duel__class_lock = 4; 288 | optional fixed64 initiator_steam_id = 5; 289 | } 290 | 291 | message CMsgReplayUploadedToYouTube { 292 | optional string youtube_url = 1; 293 | optional string youtube_account_name = 2; 294 | optional uint64 session_id = 3; 295 | } 296 | 297 | message CMsgConsumableExhausted { 298 | optional int32 item_def_id = 1; 299 | } 300 | 301 | message CMsgItemAcknowledged__DEPRECATED { 302 | optional uint32 account_id = 1; 303 | optional uint32 inventory = 2; 304 | optional uint32 def_index = 3; 305 | optional uint32 quality = 4; 306 | optional uint32 rarity = 5; 307 | optional uint32 origin = 6; 308 | optional uint64 item_id = 7; 309 | } 310 | 311 | message CMsgSetItemPositions { 312 | message ItemPosition { 313 | optional uint32 legacy_item_id = 1; 314 | optional uint32 position = 2; 315 | optional uint64 item_id = 3; 316 | } 317 | 318 | repeated .CMsgSetItemPositions.ItemPosition item_positions = 1; 319 | } 320 | 321 | message CMsgGCReportAbuse { 322 | optional fixed64 target_steam_id = 1; 323 | optional string description = 4; 324 | optional uint64 gid = 5; 325 | optional uint32 abuse_type = 2; 326 | optional uint32 content_type = 3; 327 | optional fixed32 target_game_server_ip = 6; 328 | optional uint32 target_game_server_port = 7; 329 | } 330 | 331 | message CMsgGCReportAbuseResponse { 332 | optional fixed64 target_steam_id = 1; 333 | optional uint32 result = 2; 334 | optional string error_message = 3; 335 | } 336 | 337 | message CMsgGCNameItemNotification { 338 | optional fixed64 player_steamid = 1; 339 | optional uint32 item_def_index = 2; 340 | optional string item_name_custom = 3; 341 | } 342 | 343 | message CMsgGCClientDisplayNotification { 344 | optional string notification_title_localization_key = 1; 345 | optional string notification_body_localization_key = 2; 346 | repeated string body_substring_keys = 3; 347 | repeated string body_substring_values = 4; 348 | } 349 | 350 | message CMsgGCShowItemsPickedUp { 351 | optional fixed64 player_steamid = 1; 352 | } 353 | 354 | message CMsgGCIncrementKillCountResponse { 355 | optional uint32 killer_account_id = 1 [(key_field) = true]; 356 | optional uint32 num_kills = 2; 357 | optional uint32 item_def = 3; 358 | optional uint32 level_type = 4; 359 | } 360 | 361 | message CSOEconItemDropRateBonus { 362 | optional uint32 account_id = 1; 363 | optional fixed32 expiration_date = 2; 364 | optional float bonus = 3; 365 | optional uint32 bonus_count = 4; 366 | optional uint64 item_id = 5; 367 | optional uint32 def_index = 6; 368 | } 369 | 370 | message CSOEconItemLeagueViewPass { 371 | optional uint32 account_id = 1 [(key_field) = true]; 372 | optional uint32 league_id = 2 [(key_field) = true]; 373 | optional uint32 admin = 3; 374 | optional uint32 itemindex = 4; 375 | } 376 | 377 | message CSOEconItemEventTicket { 378 | optional uint32 account_id = 1; 379 | optional uint32 event_id = 2; 380 | optional uint64 item_id = 3; 381 | } 382 | 383 | message CMsgGCItemPreviewItemBoughtNotification { 384 | optional uint32 item_def_index = 1; 385 | } 386 | 387 | message CMsgGCStorePurchaseCancel { 388 | optional uint64 txn_id = 1; 389 | } 390 | 391 | message CMsgGCStorePurchaseCancelResponse { 392 | optional uint32 result = 1; 393 | } 394 | 395 | message CMsgGCStorePurchaseFinalize { 396 | optional uint64 txn_id = 1; 397 | } 398 | 399 | message CMsgGCStorePurchaseFinalizeResponse { 400 | optional uint32 result = 1; 401 | repeated uint64 item_ids = 2; 402 | } 403 | 404 | message CMsgGCBannedWordListRequest { 405 | optional uint32 ban_list_group_id = 1; 406 | optional uint32 word_id = 2; 407 | } 408 | 409 | message CMsgGCRequestAnnouncements { 410 | } 411 | 412 | message CMsgGCRequestAnnouncementsResponse { 413 | optional string announcement_title = 1; 414 | optional string announcement = 2; 415 | optional string nextmatch_title = 3; 416 | optional string nextmatch = 4; 417 | } 418 | 419 | message CMsgGCBannedWord { 420 | optional uint32 word_id = 1; 421 | optional .GC_BannedWordType word_type = 2 [default = GC_BANNED_WORD_DISABLE_WORD]; 422 | optional string word = 3; 423 | } 424 | 425 | message CMsgGCBannedWordListResponse { 426 | optional uint32 ban_list_group_id = 1; 427 | repeated .CMsgGCBannedWord word_list = 2; 428 | } 429 | 430 | message CMsgGCToGCBannedWordListBroadcast { 431 | optional .CMsgGCBannedWordListResponse broadcast = 1; 432 | } 433 | 434 | message CMsgGCToGCBannedWordListUpdated { 435 | optional uint32 group_id = 1; 436 | } 437 | 438 | message CSOEconDefaultEquippedDefinitionInstanceClient { 439 | optional uint32 account_id = 1 [(key_field) = true]; 440 | optional uint32 item_definition = 2; 441 | optional uint32 class_id = 3 [(key_field) = true]; 442 | optional uint32 slot_id = 4 [(key_field) = true]; 443 | } 444 | 445 | message CMsgGCToGCDirtySDOCache { 446 | optional uint32 sdo_type = 1; 447 | optional uint64 key_uint64 = 2; 448 | } 449 | 450 | message CMsgGCToGCDirtyMultipleSDOCache { 451 | optional uint32 sdo_type = 1; 452 | repeated uint64 key_uint64 = 2; 453 | } 454 | 455 | message CMsgGCCollectItem { 456 | optional uint64 collection_item_id = 1; 457 | optional uint64 subject_item_id = 2; 458 | } 459 | 460 | message CMsgSDONoMemcached { 461 | } 462 | 463 | message CMsgGCToGCUpdateSQLKeyValue { 464 | optional string key_name = 1; 465 | } 466 | 467 | message CMsgGCToGCIsTrustedServer { 468 | optional fixed64 steam_id = 1; 469 | } 470 | 471 | message CMsgGCToGCIsTrustedServerResponse { 472 | optional bool is_trusted = 1; 473 | } 474 | 475 | message CMsgGCToGCBroadcastConsoleCommand { 476 | optional string con_command = 1; 477 | } 478 | 479 | message CMsgGCServerVersionUpdated { 480 | optional uint32 server_version = 1; 481 | } 482 | 483 | message CMsgGCClientVersionUpdated { 484 | optional uint32 client_version = 1; 485 | } 486 | 487 | message CMsgGCToGCWebAPIAccountChanged { 488 | } 489 | 490 | message CMsgGCToGCRequestPassportItemGrant { 491 | optional fixed64 steam_id = 1; 492 | optional uint32 league_id = 2; 493 | optional int32 reward_flag = 3; 494 | } 495 | 496 | message CMsgGameServerInfo { 497 | enum ServerType { 498 | UNSPECIFIED = 0; 499 | GAME = 1; 500 | PROXY = 2; 501 | } 502 | 503 | optional fixed32 server_public_ip_addr = 1; 504 | optional fixed32 server_private_ip_addr = 2; 505 | optional uint32 server_port = 3; 506 | optional uint32 server_tv_port = 4; 507 | optional string server_key = 5; 508 | optional bool server_hibernation = 6; 509 | optional .CMsgGameServerInfo.ServerType server_type = 7 [default = UNSPECIFIED]; 510 | optional uint32 server_region = 8; 511 | optional float server_loadavg = 9; 512 | optional float server_tv_broadcast_time = 10; 513 | optional float server_game_time = 11; 514 | optional fixed64 server_relay_connected_steam_id = 12; 515 | optional uint32 relay_slots_max = 13; 516 | optional int32 relays_connected = 14; 517 | optional int32 relay_clients_connected = 15; 518 | optional fixed64 relayed_game_server_steam_id = 16; 519 | optional uint32 parent_relay_count = 17; 520 | optional fixed64 tv_secret_code = 18; 521 | } 522 | 523 | -------------------------------------------------------------------------------- /demoparser/protobufs/steammessages.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | option optimize_for = SPEED; 4 | option cc_generic_services = false; 5 | 6 | extend .google.protobuf.FieldOptions { 7 | optional bool key_field = 60000 [default = false]; 8 | } 9 | 10 | extend .google.protobuf.MessageOptions { 11 | optional int32 msgpool_soft_limit = 60000 [default = 32]; 12 | optional int32 msgpool_hard_limit = 60001 [default = 384]; 13 | } 14 | 15 | enum GCProtoBufMsgSrc { 16 | GCProtoBufMsgSrc_Unspecified = 0; 17 | GCProtoBufMsgSrc_FromSystem = 1; 18 | GCProtoBufMsgSrc_FromSteamID = 2; 19 | GCProtoBufMsgSrc_FromGC = 3; 20 | GCProtoBufMsgSrc_ReplySystem = 4; 21 | } 22 | 23 | message CMsgProtoBufHeader { 24 | option (msgpool_soft_limit) = 256; 25 | option (msgpool_hard_limit) = 1024; 26 | optional fixed64 client_steam_id = 1; 27 | optional int32 client_session_id = 2; 28 | optional uint32 source_app_id = 3; 29 | optional fixed64 job_id_source = 10 [default = 18446744073709551615]; 30 | optional fixed64 job_id_target = 11 [default = 18446744073709551615]; 31 | optional string target_job_name = 12; 32 | optional int32 eresult = 13 [default = 2]; 33 | optional string error_message = 14; 34 | optional uint32 ip = 15; 35 | optional .GCProtoBufMsgSrc gc_msg_src = 200 [default = GCProtoBufMsgSrc_Unspecified]; 36 | optional uint32 gc_dir_index_source = 201; 37 | } 38 | 39 | message CMsgWebAPIKey { 40 | optional uint32 status = 1 [default = 255]; 41 | optional uint32 account_id = 2 [default = 0]; 42 | optional uint32 publisher_group_id = 3 [default = 0]; 43 | optional uint32 key_id = 4; 44 | optional string domain = 5; 45 | } 46 | 47 | message CMsgHttpRequest { 48 | message RequestHeader { 49 | optional string name = 1; 50 | optional string value = 2; 51 | } 52 | 53 | message QueryParam { 54 | optional string name = 1; 55 | optional bytes value = 2; 56 | } 57 | 58 | optional uint32 request_method = 1; 59 | optional string hostname = 2; 60 | optional string url = 3; 61 | repeated .CMsgHttpRequest.RequestHeader headers = 4; 62 | repeated .CMsgHttpRequest.QueryParam get_params = 5; 63 | repeated .CMsgHttpRequest.QueryParam post_params = 6; 64 | optional bytes body = 7; 65 | optional uint32 absolute_timeout = 8; 66 | } 67 | 68 | message CMsgWebAPIRequest { 69 | optional string UNUSED_job_name = 1; 70 | optional string interface_name = 2; 71 | optional string method_name = 3; 72 | optional uint32 version = 4; 73 | optional .CMsgWebAPIKey api_key = 5; 74 | optional .CMsgHttpRequest request = 6; 75 | optional uint32 routing_app_id = 7; 76 | } 77 | 78 | message CMsgHttpResponse { 79 | message ResponseHeader { 80 | optional string name = 1; 81 | optional string value = 2; 82 | } 83 | 84 | optional uint32 status_code = 1; 85 | repeated .CMsgHttpResponse.ResponseHeader headers = 2; 86 | optional bytes body = 3; 87 | } 88 | 89 | message CMsgAMFindAccounts { 90 | optional uint32 search_type = 1; 91 | optional string search_string = 2; 92 | } 93 | 94 | message CMsgAMFindAccountsResponse { 95 | repeated fixed64 steam_id = 1; 96 | } 97 | 98 | message CMsgNotifyWatchdog { 99 | optional uint32 source = 1; 100 | optional uint32 alert_type = 2; 101 | optional uint32 alert_destination = 3; 102 | optional bool critical = 4; 103 | optional uint32 time = 5; 104 | optional uint32 appid = 6; 105 | optional string text = 7; 106 | } 107 | 108 | message CMsgAMGetLicenses { 109 | optional fixed64 steamid = 1; 110 | } 111 | 112 | message CMsgPackageLicense { 113 | optional uint32 package_id = 1; 114 | optional uint32 time_created = 2; 115 | optional uint32 owner_id = 3; 116 | } 117 | 118 | message CMsgAMGetLicensesResponse { 119 | repeated .CMsgPackageLicense license = 1; 120 | optional uint32 result = 2; 121 | } 122 | 123 | message CMsgAMGetUserGameStats { 124 | optional fixed64 steam_id = 1; 125 | optional fixed64 game_id = 2; 126 | repeated uint32 stats = 3; 127 | } 128 | 129 | message CMsgAMGetUserGameStatsResponse { 130 | message Stats { 131 | optional uint32 stat_id = 1; 132 | optional uint32 stat_value = 2; 133 | } 134 | 135 | message Achievement_Blocks { 136 | optional uint32 achievement_id = 1; 137 | optional uint32 achievement_bit_id = 2; 138 | optional fixed32 unlock_time = 3; 139 | } 140 | 141 | optional fixed64 steam_id = 1; 142 | optional fixed64 game_id = 2; 143 | optional int32 eresult = 3 [default = 2]; 144 | repeated .CMsgAMGetUserGameStatsResponse.Stats stats = 4; 145 | repeated .CMsgAMGetUserGameStatsResponse.Achievement_Blocks achievement_blocks = 5; 146 | } 147 | 148 | message CMsgGCGetCommandList { 149 | optional uint32 app_id = 1; 150 | optional string command_prefix = 2; 151 | } 152 | 153 | message CMsgGCGetCommandListResponse { 154 | repeated string command_name = 1; 155 | } 156 | 157 | message CGCMsgMemCachedGet { 158 | repeated string keys = 1; 159 | } 160 | 161 | message CGCMsgMemCachedGetResponse { 162 | message ValueTag { 163 | optional bool found = 1; 164 | optional bytes value = 2; 165 | } 166 | 167 | repeated .CGCMsgMemCachedGetResponse.ValueTag values = 1; 168 | } 169 | 170 | message CGCMsgMemCachedSet { 171 | message KeyPair { 172 | optional string name = 1; 173 | optional bytes value = 2; 174 | } 175 | 176 | repeated .CGCMsgMemCachedSet.KeyPair keys = 1; 177 | } 178 | 179 | message CGCMsgMemCachedDelete { 180 | repeated string keys = 1; 181 | } 182 | 183 | message CGCMsgMemCachedStats { 184 | } 185 | 186 | message CGCMsgMemCachedStatsResponse { 187 | optional uint64 curr_connections = 1; 188 | optional uint64 cmd_get = 2; 189 | optional uint64 cmd_set = 3; 190 | optional uint64 cmd_flush = 4; 191 | optional uint64 get_hits = 5; 192 | optional uint64 get_misses = 6; 193 | optional uint64 delete_hits = 7; 194 | optional uint64 delete_misses = 8; 195 | optional uint64 bytes_read = 9; 196 | optional uint64 bytes_written = 10; 197 | optional uint64 limit_maxbytes = 11; 198 | optional uint64 curr_items = 12; 199 | optional uint64 evictions = 13; 200 | optional uint64 bytes = 14; 201 | } 202 | 203 | message CGCMsgSQLStats { 204 | optional uint32 schema_catalog = 1; 205 | } 206 | 207 | message CGCMsgSQLStatsResponse { 208 | optional uint32 threads = 1; 209 | optional uint32 threads_connected = 2; 210 | optional uint32 threads_active = 3; 211 | optional uint32 operations_submitted = 4; 212 | optional uint32 prepared_statements_executed = 5; 213 | optional uint32 non_prepared_statements_executed = 6; 214 | optional uint32 deadlock_retries = 7; 215 | optional uint32 operations_timed_out_in_queue = 8; 216 | optional uint32 errors = 9; 217 | } 218 | 219 | message CMsgAMAddFreeLicense { 220 | optional fixed64 steamid = 1; 221 | optional uint32 ip_public = 2; 222 | optional uint32 packageid = 3; 223 | optional string store_country_code = 4; 224 | } 225 | 226 | message CMsgAMAddFreeLicenseResponse { 227 | optional int32 eresult = 1 [default = 2]; 228 | optional int32 purchase_result_detail = 2; 229 | optional fixed64 transid = 3; 230 | } 231 | 232 | message CGCMsgGetIPLocation { 233 | repeated fixed32 ips = 1; 234 | } 235 | 236 | message CIPLocationInfo { 237 | optional uint32 ip = 1; 238 | optional float latitude = 2; 239 | optional float longitude = 3; 240 | optional string country = 4; 241 | optional string state = 5; 242 | optional string city = 6; 243 | } 244 | 245 | message CGCMsgGetIPLocationResponse { 246 | repeated .CIPLocationInfo infos = 1; 247 | } 248 | 249 | message CGCMsgSystemStatsSchema { 250 | optional uint32 gc_app_id = 1; 251 | optional bytes schema_kv = 2; 252 | } 253 | 254 | message CGCMsgGetSystemStats { 255 | } 256 | 257 | message CGCMsgGetSystemStatsResponse { 258 | optional uint32 gc_app_id = 1; 259 | optional bytes stats_kv = 2; 260 | optional uint32 active_jobs = 3; 261 | optional uint32 yielding_jobs = 4; 262 | optional uint32 user_sessions = 5; 263 | optional uint32 game_server_sessions = 6; 264 | optional uint32 socaches = 7; 265 | optional uint32 socaches_to_unload = 8; 266 | optional uint32 socaches_loading = 9; 267 | optional uint32 writeback_queue = 10; 268 | optional uint32 steamid_locks = 11; 269 | optional uint32 logon_queue = 12; 270 | optional uint32 logon_jobs = 13; 271 | } 272 | 273 | message CMsgAMSendEmail { 274 | message ReplacementToken { 275 | optional string token_name = 1; 276 | optional string token_value = 2; 277 | } 278 | 279 | message PersonaNameReplacementToken { 280 | optional fixed64 steamid = 1; 281 | optional string token_name = 2; 282 | } 283 | 284 | optional fixed64 steamid = 1; 285 | optional uint32 email_msg_type = 2; 286 | optional uint32 email_format = 3; 287 | repeated .CMsgAMSendEmail.PersonaNameReplacementToken persona_name_tokens = 5; 288 | optional uint32 source_gc = 6; 289 | repeated .CMsgAMSendEmail.ReplacementToken tokens = 7; 290 | } 291 | 292 | message CMsgAMSendEmailResponse { 293 | optional uint32 eresult = 1 [default = 2]; 294 | } 295 | 296 | message CMsgGCGetEmailTemplate { 297 | optional uint32 app_id = 1; 298 | optional uint32 email_msg_type = 2; 299 | optional int32 email_lang = 3; 300 | optional int32 email_format = 4; 301 | } 302 | 303 | message CMsgGCGetEmailTemplateResponse { 304 | optional uint32 eresult = 1 [default = 2]; 305 | optional bool template_exists = 2; 306 | optional string template = 3; 307 | } 308 | 309 | message CMsgAMGrantGuestPasses2 { 310 | optional fixed64 steam_id = 1; 311 | optional uint32 package_id = 2; 312 | optional int32 passes_to_grant = 3; 313 | optional int32 days_to_expiration = 4; 314 | optional int32 action = 5; 315 | } 316 | 317 | message CMsgAMGrantGuestPasses2Response { 318 | optional int32 eresult = 1 [default = 2]; 319 | optional int32 passes_granted = 2 [default = 0]; 320 | } 321 | 322 | message CGCSystemMsg_GetAccountDetails { 323 | option (msgpool_soft_limit) = 128; 324 | option (msgpool_hard_limit) = 512; 325 | optional fixed64 steamid = 1; 326 | optional uint32 appid = 2; 327 | } 328 | 329 | message CGCSystemMsg_GetAccountDetails_Response { 330 | option (msgpool_soft_limit) = 128; 331 | option (msgpool_hard_limit) = 512; 332 | optional uint32 eresult_deprecated = 1 [default = 2]; 333 | optional string account_name = 2; 334 | optional string persona_name = 3; 335 | optional bool is_profile_public = 4; 336 | optional bool is_inventory_public = 5; 337 | optional bool is_vac_banned = 7; 338 | optional bool is_cyber_cafe = 8; 339 | optional bool is_school_account = 9; 340 | optional bool is_limited = 10; 341 | optional bool is_subscribed = 11; 342 | optional uint32 package = 12; 343 | optional bool is_free_trial_account = 13; 344 | optional uint32 free_trial_expiration = 14; 345 | optional bool is_low_violence = 15; 346 | optional bool is_account_locked_down = 16; 347 | optional bool is_community_banned = 17; 348 | optional bool is_trade_banned = 18; 349 | optional uint32 trade_ban_expiration = 19; 350 | optional uint32 accountid = 20; 351 | optional uint32 suspension_end_time = 21; 352 | optional string currency = 22; 353 | optional uint32 steam_level = 23; 354 | optional uint32 friend_count = 24; 355 | optional uint32 account_creation_time = 25; 356 | optional bool is_steamguard_enabled = 27; 357 | optional bool is_phone_verified = 28; 358 | optional bool is_two_factor_auth_enabled = 29; 359 | optional uint32 two_factor_enabled_time = 30; 360 | optional uint32 phone_verification_time = 31; 361 | optional uint64 phone_id = 33; 362 | optional bool is_phone_identifying = 34; 363 | optional uint32 rt_identity_linked = 35; 364 | optional uint32 rt_birth_date = 36; 365 | optional string txn_country_code = 37; 366 | } 367 | 368 | message CMsgGCGetPersonaNames { 369 | repeated fixed64 steamids = 1; 370 | } 371 | 372 | message CMsgGCGetPersonaNames_Response { 373 | message PersonaName { 374 | optional fixed64 steamid = 1; 375 | optional string persona_name = 2; 376 | } 377 | 378 | repeated .CMsgGCGetPersonaNames_Response.PersonaName succeeded_lookups = 1; 379 | repeated fixed64 failed_lookup_steamids = 2; 380 | } 381 | 382 | message CMsgGCCheckFriendship { 383 | optional fixed64 steamid_left = 1; 384 | optional fixed64 steamid_right = 2; 385 | } 386 | 387 | message CMsgGCCheckFriendship_Response { 388 | optional bool success = 1; 389 | optional bool found_friendship = 2; 390 | } 391 | 392 | message CMsgGCMsgMasterSetDirectory { 393 | message SubGC { 394 | optional uint32 dir_index = 1; 395 | optional string name = 2; 396 | optional string box = 3; 397 | optional string command_line = 4; 398 | optional string gc_binary = 5; 399 | } 400 | 401 | optional uint32 master_dir_index = 1; 402 | repeated .CMsgGCMsgMasterSetDirectory.SubGC dir = 2; 403 | } 404 | 405 | message CMsgGCMsgMasterSetDirectory_Response { 406 | optional int32 eresult = 1 [default = 2]; 407 | } 408 | 409 | message CMsgGCMsgWebAPIJobRequestForwardResponse { 410 | optional uint32 dir_index = 1; 411 | } 412 | 413 | message CGCSystemMsg_GetPurchaseTrust_Request { 414 | optional fixed64 steamid = 1; 415 | } 416 | 417 | message CGCSystemMsg_GetPurchaseTrust_Response { 418 | optional bool has_prior_purchase_history = 1; 419 | optional bool has_no_recent_password_resets = 2; 420 | optional bool is_wallet_cash_trusted = 3; 421 | optional uint32 time_all_trusted = 4; 422 | } 423 | 424 | message CMsgGCHAccountVacStatusChange { 425 | optional fixed64 steam_id = 1; 426 | optional uint32 app_id = 2; 427 | optional uint32 rtime_vacban_starts = 3; 428 | optional bool is_banned_now = 4; 429 | optional bool is_banned_future = 5; 430 | } 431 | 432 | message CMsgGCGetPartnerAccountLink { 433 | optional fixed64 steamid = 1; 434 | } 435 | 436 | message CMsgGCGetPartnerAccountLink_Response { 437 | optional uint32 pwid = 1; 438 | optional uint32 nexonid = 2; 439 | } 440 | 441 | message CMsgGCRoutingInfo { 442 | enum RoutingMethod { 443 | RANDOM = 0; 444 | DISCARD = 1; 445 | CLIENT_STEAMID = 2; 446 | PROTOBUF_FIELD_UINT64 = 3; 447 | WEBAPI_PARAM_UINT64 = 4; 448 | } 449 | 450 | repeated uint32 dir_index = 1; 451 | optional .CMsgGCRoutingInfo.RoutingMethod method = 2 [default = RANDOM]; 452 | optional .CMsgGCRoutingInfo.RoutingMethod fallback = 3 [default = DISCARD]; 453 | optional uint32 protobuf_field = 4; 454 | optional string webapi_param = 5; 455 | } 456 | 457 | message CMsgGCMsgMasterSetWebAPIRouting { 458 | message Entry { 459 | optional string interface_name = 1; 460 | optional string method_name = 2; 461 | optional .CMsgGCRoutingInfo routing = 3; 462 | } 463 | 464 | repeated .CMsgGCMsgMasterSetWebAPIRouting.Entry entries = 1; 465 | } 466 | 467 | message CMsgGCMsgMasterSetClientMsgRouting { 468 | message Entry { 469 | optional uint32 msg_type = 1; 470 | optional .CMsgGCRoutingInfo routing = 2; 471 | } 472 | 473 | repeated .CMsgGCMsgMasterSetClientMsgRouting.Entry entries = 1; 474 | } 475 | 476 | message CMsgGCMsgMasterSetWebAPIRouting_Response { 477 | optional int32 eresult = 1 [default = 2]; 478 | } 479 | 480 | message CMsgGCMsgMasterSetClientMsgRouting_Response { 481 | optional int32 eresult = 1 [default = 2]; 482 | } 483 | 484 | message CMsgGCMsgSetOptions { 485 | message MessageRange { 486 | required uint32 low = 1; 487 | required uint32 high = 2; 488 | } 489 | 490 | enum Option { 491 | NOTIFY_USER_SESSIONS = 0; 492 | NOTIFY_SERVER_SESSIONS = 1; 493 | NOTIFY_ACHIEVEMENTS = 2; 494 | NOTIFY_VAC_ACTION = 3; 495 | } 496 | 497 | repeated .CMsgGCMsgSetOptions.Option options = 1; 498 | repeated .CMsgGCMsgSetOptions.MessageRange client_msg_ranges = 2; 499 | } 500 | 501 | message CMsgGCHUpdateSession { 502 | message ExtraField { 503 | optional string name = 1; 504 | optional string value = 2; 505 | } 506 | 507 | optional fixed64 steam_id = 1; 508 | optional uint32 app_id = 2; 509 | optional bool online = 3; 510 | optional fixed64 server_steam_id = 4; 511 | optional uint32 server_addr = 5; 512 | optional uint32 server_port = 6; 513 | optional uint32 os_type = 7; 514 | optional uint32 client_addr = 8; 515 | repeated .CMsgGCHUpdateSession.ExtraField extra_fields = 9; 516 | optional fixed64 owner_id = 10; 517 | optional uint32 cm_session_sysid = 11; 518 | optional uint32 cm_session_identifier = 12; 519 | repeated uint32 depot_ids = 13; 520 | } 521 | 522 | message CMsgNotificationOfSuspiciousActivity { 523 | message MultipleGameInstances { 524 | optional uint32 app_instance_count = 1; 525 | repeated fixed64 other_steamids = 2; 526 | } 527 | 528 | optional fixed64 steamid = 1; 529 | optional uint32 appid = 2; 530 | optional .CMsgNotificationOfSuspiciousActivity.MultipleGameInstances multiple_instances = 3; 531 | } 532 | 533 | message CMsgDPPartnerMicroTxns { 534 | message PartnerMicroTxn { 535 | optional uint32 init_time = 1; 536 | optional uint32 last_update_time = 2; 537 | optional uint64 txn_id = 3; 538 | optional uint32 account_id = 4; 539 | optional uint32 line_item = 5; 540 | optional uint64 item_id = 6; 541 | optional uint32 def_index = 7; 542 | optional uint64 price = 8; 543 | optional uint64 tax = 9; 544 | optional uint64 price_usd = 10; 545 | optional uint64 tax_usd = 11; 546 | optional uint32 purchase_type = 12; 547 | optional uint32 steam_txn_type = 13; 548 | optional string country_code = 14; 549 | optional string region_code = 15; 550 | optional int32 quantity = 16; 551 | optional uint64 ref_trans_id = 17; 552 | } 553 | 554 | message PartnerInfo { 555 | optional uint32 partner_id = 1; 556 | optional string partner_name = 2; 557 | optional string currency_code = 3; 558 | optional string currency_name = 4; 559 | } 560 | 561 | optional uint32 appid = 1; 562 | optional string gc_name = 2; 563 | optional .CMsgDPPartnerMicroTxns.PartnerInfo partner = 3; 564 | repeated .CMsgDPPartnerMicroTxns.PartnerMicroTxn transactions = 4; 565 | } 566 | 567 | message CMsgDPPartnerMicroTxnsResponse { 568 | enum EErrorCode { 569 | k_MsgValid = 0; 570 | k_MsgInvalidAppID = 1; 571 | k_MsgInvalidPartnerInfo = 2; 572 | k_MsgNoTransactions = 3; 573 | k_MsgSQLFailure = 4; 574 | k_MsgPartnerInfoDiscrepancy = 5; 575 | k_MsgTransactionInsertFailed = 7; 576 | k_MsgAlreadyRunning = 8; 577 | k_MsgInvalidTransactionData = 9; 578 | } 579 | 580 | optional uint32 eresult = 1 [default = 2]; 581 | optional .CMsgDPPartnerMicroTxnsResponse.EErrorCode eerrorcode = 2 [default = k_MsgValid]; 582 | } 583 | 584 | --------------------------------------------------------------------------------