├── enums ├── kCW.js ├── PacketEntities.js ├── Teams.js ├── Commands.js ├── WeaponTypes.js ├── SPROP.js ├── NetMessages.js └── UserMessages.js ├── src ├── Entities │ ├── Weapon.js │ ├── Grenade.js │ ├── HEGrenade.js │ ├── Flashbang.js │ ├── MolotovGrenade.js │ ├── DecoyGrenade.js │ ├── SmokeGrenade.js │ ├── Team.js │ ├── Player.js │ └── Entity.js ├── MathHelpers.js ├── Vector3.js ├── Protobuf.js ├── BitStream.js └── Reader.js ├── protos ├── index.js ├── GCMessages.js ├── Common.js ├── NetMessages.js └── UserMessages.js ├── index.js ├── package.json ├── LICENSE ├── .gitignore └── README.md /enums/kCW.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | None: 0, 3 | LowPrecision: 1, 4 | Integral: 2 5 | }); -------------------------------------------------------------------------------- /src/Entities/Weapon.js: -------------------------------------------------------------------------------- 1 | const Entity = require(__dirname + '/Entity'); 2 | 3 | class Weapon extends Entity { 4 | 5 | 6 | 7 | } 8 | 9 | module.exports = Weapon; -------------------------------------------------------------------------------- /src/Entities/Grenade.js: -------------------------------------------------------------------------------- 1 | const Entity = require(__dirname + '/Entity'); 2 | 3 | class Grenade extends Entity { 4 | 5 | 6 | 7 | } 8 | 9 | module.exports = Grenade; -------------------------------------------------------------------------------- /src/Entities/HEGrenade.js: -------------------------------------------------------------------------------- 1 | const Grenade = require(__dirname + '/Grenade'); 2 | 3 | class HEGrenade extends Grenade { 4 | 5 | } 6 | 7 | module.exports = HEGrenade; -------------------------------------------------------------------------------- /enums/PacketEntities.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | PVS_ENTER: 0, 3 | PVS_LEAVE: 1, 4 | DELTA_ENT: 2, 5 | PRESERVE_ENT: 3, 6 | FINISHED: 4, 7 | FAILED: 5 8 | }); -------------------------------------------------------------------------------- /src/Entities/Flashbang.js: -------------------------------------------------------------------------------- 1 | const Grenade = require(__dirname + '/Grenade'); 2 | 3 | class Flashbang extends Grenade { 4 | 5 | 6 | 7 | } 8 | 9 | module.exports = Flashbang; -------------------------------------------------------------------------------- /src/Entities/MolotovGrenade.js: -------------------------------------------------------------------------------- 1 | const Grenade = require(__dirname + '/Grenade'); 2 | 3 | class MolotovGrenade extends Grenade { 4 | 5 | } 6 | 7 | module.exports = MolotovGrenade; -------------------------------------------------------------------------------- /src/Entities/DecoyGrenade.js: -------------------------------------------------------------------------------- 1 | const Grenade = require(__dirname + '/Grenade'); 2 | 3 | class DecoyGrenade extends Grenade { 4 | 5 | 6 | 7 | } 8 | 9 | module.exports = DecoyGrenade; -------------------------------------------------------------------------------- /src/Entities/SmokeGrenade.js: -------------------------------------------------------------------------------- 1 | const Grenade = require(__dirname + '/Grenade'); 2 | 3 | class SmokeGrenade extends Grenade { 4 | 5 | 6 | 7 | } 8 | 9 | module.exports = SmokeGrenade; -------------------------------------------------------------------------------- /enums/Teams.js: -------------------------------------------------------------------------------- 1 | let teams = { 2 | NONE: 0, 3 | SPECTATOR: 1, 4 | TERRORIST: 2, 5 | CT: 3 6 | }; 7 | 8 | module.exports = Object.freeze(Object.assign(teams, Object.entries(teams).map(([a,b]) => (a)))); -------------------------------------------------------------------------------- /src/MathHelpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | toRadians: (num) => { 3 | return num * (Math.PI / 180); 4 | }, 5 | 6 | makeAnglePositive: (angle) => { 7 | return (angle % 360 + 360) % 360; 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /enums/Commands.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | SIGNON: 1, 3 | PACKET: 2, 4 | SYNC_TICK: 3, 5 | CONSOLE_CMD: 4, 6 | USER_CMD: 5, 7 | DATA_TABLES: 6, 8 | STOP: 7, 9 | CUSTOM: 8, 10 | STRING_TABLES: 9 11 | }); -------------------------------------------------------------------------------- /enums/WeaponTypes.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | UNKNOWN: -1, 3 | KNIFE: 0, 4 | PISTOL: 1, 5 | SUBMACHINEGUN: 2, 6 | RIFLE: 3, 7 | SHOTGUN: 4, 8 | SNIPER_RIFLE: 5, 9 | MACHINEGUN: 6, 10 | C4: 7, 11 | TASER: 8, 12 | GRENADE: 9, 13 | HEALTHSHOT: 11 14 | }); -------------------------------------------------------------------------------- /enums/SPROP.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | COORD: (1 << 1), 3 | NOSCALE: (1 << 2), 4 | NORMAL: (1 << 5), 5 | COORD_MP: (1 << 12), 6 | COORD_MP_LOWPRECISION: (1 << 13), 7 | COORD_MP_INTEGRAL: (1 << 14), 8 | CELL_COORD: (1 << 15), 9 | CELL_COORD_LOWPRECISION: (1 << 16), 10 | CELL_COORD_INTEGRAL: (1 << 17) 11 | }); -------------------------------------------------------------------------------- /protos/index.js: -------------------------------------------------------------------------------- 1 | const ProtoBuf = require(__dirname + '/../src/Protobuf'); 2 | let proto = new ProtoBuf(); 3 | 4 | proto.require(__dirname + '/Common', 'Common'); 5 | proto.require(__dirname + '/GCMessages', 'GCMessages'); 6 | proto.require(__dirname + '/NetMessages', 'NetMessages'); 7 | proto.require(__dirname + '/UserMessages', 'UserMessages'); 8 | 9 | module.exports = proto; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Reader = require(`${__dirname}/src/Reader`); 2 | const Vector3 = require(`${__dirname}/src/Vector3`); 3 | const MathHelpers = require(`${__dirname}/src/MathHelpers`); 4 | 5 | const UserMessages = require(`${__dirname}/enums/UserMessages`); 6 | const Teams = require(`${__dirname}/enums/Teams`); 7 | const WeaponTypes = require(`${__dirname}/enums/WeaponTypes`); 8 | 9 | module.exports = { 10 | Reader, 11 | Vector3, 12 | MathHelpers, 13 | UserMessages, 14 | Teams, 15 | WeaponTypes, 16 | }; 17 | -------------------------------------------------------------------------------- /protos/GCMessages.js: -------------------------------------------------------------------------------- 1 | let Messages = {}; 2 | module.exports = Messages; 3 | 4 | Messages.XpProgressData = { 5 | fields: [ 6 | [1, 'xp_points', 'int32'], 7 | [2, 'xp_category', 'varInt32'] 8 | ] 9 | } 10 | 11 | Messages.GC2ServerNotifyXPRewarded = { 12 | fields: [ 13 | [1, 'xp_progress_data', 'GCMessages/XpProgressData'], 14 | [2, 'account_id', 'int32'], 15 | [3, 'current_xp', 'int32'], 16 | [4, 'current_level', 'int32'], 17 | [5, 'upgraded_defidx', 'int32'], 18 | [6, 'operation_points_awarded', 'int32'] 19 | ] 20 | }; -------------------------------------------------------------------------------- /protos/Common.js: -------------------------------------------------------------------------------- 1 | let Messages = {}; 2 | module.exports = Messages; 3 | 4 | Messages.Vector = { 5 | fields: [ 6 | [1, 'x', 'float'], 7 | [2, 'y', 'float'], 8 | [3, 'z', 'float'] 9 | ] 10 | }; 11 | 12 | Messages.Vector2D = { 13 | fields: [ 14 | [1, 'x', 'float'], 15 | [2, 'y', 'float'] 16 | ] 17 | }; 18 | 19 | Messages.QAngle = { 20 | fields: [ 21 | [1, 'x', 'float'], 22 | [2, 'y', 'float'], 23 | [3, 'z', 'float'] 24 | ] 25 | }; 26 | 27 | Messages.RGBA = { 28 | fields: [ 29 | [1, 'r', 'varInt32'], 30 | [2, 'g', 'varInt32'], 31 | [3, 'b', 'varInt32'], 32 | [4, 'a', 'varInt32'] 33 | ] 34 | }; -------------------------------------------------------------------------------- /enums/NetMessages.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | ServerInfo: 8, 3 | SendTable: 9, 4 | ClassInfo: 10, 5 | SetPause: 11, 6 | CreateStringTable: 12, 7 | UpdateStringTable: 13, 8 | VoiceInit: 14, 9 | VoiceData: 15, 10 | Print: 16, 11 | Sounds: 17, 12 | SetView: 18, 13 | FixAngle: 19, 14 | CrosshairAngle: 20, 15 | BSPDecal: 21, 16 | SplitScreen: 22, 17 | UserMessage: 23, 18 | EntityMessage: 24, 19 | GameEvent: 25, 20 | PacketEntities: 26, 21 | TempEntities: 27, 22 | Prefetch: 28, 23 | Menu: 29, 24 | GameEventList: 30, 25 | GetCvarValue: 31, 26 | PaintmapData: 33, 27 | CmdKeyValues: 34, 28 | EncryptedData: 35, 29 | HltvReplay: 36, 30 | Broadcast_Command: 38 31 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csgodemoreader", 3 | "version": "1.0.22", 4 | "description": "Library to reading demo files of Counter-Strike: Global Offensive", 5 | "keywords": [ 6 | "csgo", 7 | "demo", 8 | "file", 9 | "reader", 10 | "match", 11 | "counter", 12 | "strike" 13 | ], 14 | "main": "index.js", 15 | "author": { 16 | "name": "Szymon Lisowiec", 17 | "url": "https://kysune.me" 18 | }, 19 | "license": "MIT", 20 | "homepage": "https://github.com/SzymonLisowiec/node-CSGODemoReader", 21 | "bugs": { 22 | "url": "https://github.com/SzymonLisowiec/node-CSGODemoReader/issues" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/SzymonLisowiec/node-CSGODemoReader.git" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Entities/Team.js: -------------------------------------------------------------------------------- 1 | const Entity = require(__dirname + '/Entity'); 2 | 3 | class Team extends Entity { 4 | 5 | getTeamNumber() { 6 | return this.getValue('m_iTeamNum'); 7 | } 8 | 9 | getSide() { 10 | return (this.getValue('m_szTeamname') || '').toUpperCase(); 11 | } 12 | 13 | getClanName() { 14 | return this.getValue('m_szClanTeamname'); 15 | } 16 | 17 | getFlag() { 18 | return this.getValue('m_szTeamFlagImage'); 19 | } 20 | 21 | getScore() { 22 | return this.getValue('m_scoreTotal') || 0; 23 | } 24 | 25 | getScoreFirstHalf() { 26 | return this.getValue('m_scoreFirstHalf') || 0; 27 | } 28 | 29 | getScoreSecondHalf() { 30 | return this.getValue('m_scoreSecondHalf') || 0; 31 | } 32 | 33 | getPlayers() { 34 | const d = this.getValue('"player_array"') || {}; 35 | const players = []; 36 | for (const k in d) { 37 | players.push(this.demo.findEntityById(d[k])); 38 | } 39 | return players; 40 | } 41 | } 42 | 43 | module.exports = Team; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kysune 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | demos/ 2 | examples/ 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | package-lock.json -------------------------------------------------------------------------------- /enums/UserMessages.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | VGUIMenu: 1, 3 | Geiger: 2, 4 | Train: 3, 5 | HudText: 4, 6 | SayText: 5, 7 | SayText2: 6, 8 | TextMsg: 7, 9 | HudMsg: 8, 10 | ResetHud: 9, 11 | GameTitle: 10, 12 | Shake: 12, 13 | Fade: 13, 14 | Rumble: 14, 15 | CloseCaption: 15, 16 | CloseCaptionDirect: 16, 17 | SendAudio: 17, 18 | RawAudio: 18, 19 | VoiceMask: 19, 20 | RequestState: 20, 21 | Damage: 21, 22 | RadioText: 22, 23 | HintText: 23, 24 | KeyHintText: 24, 25 | ProcessSpottedEntityUpdate: 25, 26 | ReloadEffect: 26, 27 | AdjustMoney: 27, 28 | UpdateTeamMoney: 28, 29 | StopSpectatorMode: 29, 30 | KillCam: 30, 31 | DesiredTimescale: 31, 32 | CurrentTimescale: 32, 33 | AchievementEvent: 33, 34 | MatchEndConditions: 34, 35 | DisconnectToLobby: 35, 36 | PlayerStatsUpdate: 36, 37 | DisplayInventory: 37, 38 | WarmupHasEnded: 38, 39 | ClientInfo: 39, 40 | XRankGet: 40, 41 | XRankUpd: 41, 42 | CallVoteFailed: 45, 43 | VoteStart: 46, 44 | VotePass: 47, 45 | VoteFailed: 48, 46 | VoteSetup: 49, 47 | ServerRankRevealAll: 50, 48 | SendLastKillerDamageToClient: 51, 49 | ServerRankUpdate: 52, 50 | ItemPickup: 53, 51 | ShowMenu: 54, 52 | BarTime: 55, 53 | AmmoDenied: 56, 54 | MarkAchievement: 57, 55 | MatchStatsUpdate: 58, 56 | ItemDrop: 59, 57 | GlowPropTurnOff: 60, 58 | SendPlayerItemDrops: 61, 59 | RoundBackupFilenames: 62, 60 | SendPlayerItemFound: 63, 61 | ReportHit: 64, 62 | XpUpdate: 65, 63 | QuestProgress: 66, 64 | ScoreLeaderboardData: 67, 65 | PlayerDecalDigitalSignature: 68 66 | }); -------------------------------------------------------------------------------- /src/Vector3.js: -------------------------------------------------------------------------------- 1 | class Vector3 { 2 | constructor (x, y, z) { 3 | this.x = x || 0; 4 | this.y = y || 0; 5 | this.z = z || 0; 6 | } 7 | 8 | add (vector3) { 9 | return new this(this.x + vector3.x, this.y + vector3.y, this.z + vector3.z); 10 | } 11 | 12 | subtract (vector3) { 13 | return new this(this.x - vector3.x, this.y - vector3.y, this.z - vector3.z); 14 | } 15 | 16 | multiply (multiplier) { 17 | return new this(this.x * multiplier, this.y * multiplier, this.z * multiplier); 18 | } 19 | 20 | scale (vector3) { 21 | return new this(this.x * vector3.x, this.y * vector3.y, this.z * vector3.z); 22 | } 23 | 24 | length () { 25 | return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); 26 | } 27 | 28 | lengthSquared () { 29 | return this.x * this.x + this.y * this.y + this.z * this.z; 30 | } 31 | 32 | normalize () { 33 | const length = this.length(); 34 | this.x /= l; 35 | this.y /= l; 36 | this.z /= l; 37 | return this; 38 | } 39 | 40 | dot (vector3) { 41 | return this.x * vector3.x + this.y * vector3.y + this.z * vector3.z; 42 | } 43 | 44 | cross (vector3) { 45 | return new this( 46 | this.y * vector3.z - vector3.y * this.z, 47 | vector3.x * this.z - this.x * vector3.z, 48 | this.x * vector3.y - vector3.x * this.y, 49 | ); 50 | } 51 | 52 | clone () { 53 | return new this(this.x, this.y, this.z); 54 | } 55 | 56 | distance (vector3) { 57 | const dx = vector3.x - this.x; 58 | const dy = vector3.y - this.y; 59 | const dz = vector3.z - this.z; 60 | return Math.sqrt(dx * dx + dy * dy + dz * dz); 61 | } 62 | } 63 | 64 | module.exports = Vector3; 65 | -------------------------------------------------------------------------------- /protos/NetMessages.js: -------------------------------------------------------------------------------- 1 | const MSG = require(__dirname + '/../enums/NetMessages'); 2 | 3 | let Messages = {}; 4 | module.exports = Messages; 5 | 6 | Messages[MSG.ServerInfo] = { 7 | fields: [ 8 | [1, 'protocol', 'varInt32'], 9 | [2, 'serverCount', 'varInt32'], 10 | [3, 'isDedicated', 'varInt32Bool'], 11 | [4, 'isOfficialValveServer', 'varInt32Bool'], 12 | [5, 'isHLTV', 'varInt32Bool'], 13 | [6, 'isReplay', 'varInt32Bool'], 14 | [7, 'cOs', 'varInt32'], 15 | [8, 'mapCrc', 'int32'], 16 | [9, 'clientCrc', 'int32'], 17 | [10, 'stringTableCrc', 'int32'], 18 | [11, 'maxClients', 'varInt32'], 19 | [12, 'maxClasses', 'varInt32'], 20 | [13, 'playerSlot', 'varInt32'], 21 | [14, 'tickInterval', 'float'], 22 | [15, 'gameDirectory', 'vString'], 23 | [16, 'mapName', 'vString'], 24 | [17, 'mapGroupName', 'vString'], 25 | [18, 'skyName', 'vString'], 26 | [19, 'hostName', 'vString'], 27 | [21, 'isRedirectingToProxyRelay', 'varInt32Bool'], 28 | [22, 'ugcMapId', 'bytes', [8]] 29 | ] 30 | }; 31 | 32 | Messages[MSG.SendTable] = { 33 | messages: { 34 | sendprop_t: { 35 | messages: {}, 36 | fields: [ 37 | [1, 'type', 'varInt32'], 38 | [2, 'varName', 'vString'], 39 | [3, 'flags', 'varInt32'], 40 | [4, 'priority', 'varInt32'], 41 | [5, 'dataTableName', 'vString'], 42 | [6, 'numElements', 'varInt32'], 43 | [7, 'lowValue', 'float'], 44 | [8, 'highValue', 'float'], 45 | [9, 'numBits', 'varInt32'] 46 | ] 47 | } 48 | }, 49 | fields: [ 50 | [1, 'isEnd', 'varInt32Bool'], 51 | [2, 'netTableName', 'vString'], 52 | [3, 'needsDecoder', 'varInt32Bool'], 53 | [4, 'props', 'sendprop_t', null, 'array'] 54 | ] 55 | }; 56 | 57 | Messages[MSG.CreateStringTable] = { 58 | fields: [ 59 | [1, 'name', 'vString'], 60 | [2, 'maxEntries', 'varInt32'], 61 | [3, 'numEntries', 'varInt32'], 62 | [4, 'userDataFixedSize', 'varInt32Bool'], 63 | [5, 'userDataSize', 'varInt32'], 64 | [6, 'userDataSizeBits', 'varInt32'], 65 | [7, 'flags', 'varInt32'], 66 | [8, 'data', 'vChunk'] 67 | ] 68 | }; 69 | 70 | Messages[MSG.UpdateStringTable] = { 71 | fields: [ 72 | [1, 'tableId', 'varInt32'], 73 | [2, 'numChangedEntries', 'varInt32'], 74 | [3, 'data', 'vChunk'] 75 | ] 76 | }; 77 | 78 | Messages[MSG.PacketEntities] = { 79 | fields: [ 80 | [1, 'maxEntities', 'varInt32'], 81 | [2, 'updatedEntities', 'varInt32'], 82 | [3, 'isDelta', 'varInt32Bool'], 83 | [4, 'updateBaseline', 'varInt32Bool'], 84 | [5, 'baseline', 'varInt32'], 85 | [6, 'deltaFrom', 'varInt32'], 86 | [7, 'entityData', 'vChunk'] 87 | ] 88 | }; 89 | 90 | Messages[MSG.UserMessage] = { 91 | fields: [ 92 | [1, 'userMessageType', 'varInt32'], 93 | [2, 'data', 'vChunk'] 94 | ] 95 | }; 96 | 97 | Messages[MSG.GameEvent] = { 98 | messages: { 99 | key_t: { 100 | messages: {}, 101 | fields: [ 102 | [1, 'type', 'varInt32'], 103 | [2, 'value', 'vString'], 104 | [3, 'value', 'float'], 105 | [4, 'value', 'varInt32'], 106 | [5, 'value', 'varInt32'], 107 | [6, 'value', 'varInt32'], 108 | [7, 'value', 'varInt32Bool'], 109 | [8, 'value', 'varInt64'], 110 | [9, 'value', 'vChunk'] 111 | ] 112 | } 113 | }, 114 | fields: [ 115 | [1, 'eventName', 'vString'], 116 | [2, 'eventId', 'varInt32'], 117 | [3, 'values', 'key_t', null, 'array'] 118 | ] 119 | }; 120 | 121 | Messages[MSG.GameEventList] = { 122 | messages: { 123 | descriptor_t: { 124 | messages: { 125 | key_t: { 126 | messages: {}, 127 | fields: [ 128 | [1, 'type', 'varInt32'], 129 | [2, 'name', 'vString'] 130 | ] 131 | } 132 | }, 133 | fields: [ 134 | [1, 'eventId', 'varInt32'], 135 | [2, 'name', 'vString'], 136 | [3, 'keys', 'key_t', null, 'array'] 137 | ] 138 | } 139 | }, 140 | fields: [ 141 | [1, 'descriptors', 'descriptor_t', null, 'array'] 142 | ] 143 | }; -------------------------------------------------------------------------------- /src/Protobuf.js: -------------------------------------------------------------------------------- 1 | class Protobuf { 2 | 3 | constructor () { 4 | this.enums = {}; 5 | this.messages = {}; 6 | } 7 | 8 | require (path, namespace) { 9 | let scheme = require(path); 10 | 11 | for(let msg_name in scheme){ 12 | 13 | let msg_scheme = scheme[msg_name]; 14 | 15 | if(typeof namespace != 'undefined') 16 | msg_name = namespace + '/' + msg_name; 17 | 18 | this.require_msg(this, msg_name, msg_scheme); 19 | 20 | } 21 | } 22 | 23 | require_msg (parent, msg_name, msg_scheme) { 24 | 25 | let msg = parent.addMessage(msg_name); 26 | 27 | if(typeof msg_scheme.messages != 'undefined'){ 28 | 29 | for(let sub_message_name in msg_scheme.messages){ 30 | this.require_msg(msg, sub_message_name, msg_scheme.messages[sub_message_name]); 31 | } 32 | 33 | } 34 | 35 | for(let field in msg_scheme.fields){ 36 | 37 | field = msg_scheme.fields[field]; 38 | 39 | if(typeof msg.messages[field[2]] != 'undefined'){ 40 | field[2] = msg.messages[field[2]]; 41 | }else if(typeof this.getMessage(field[2] ) != 'undefined'){ 42 | field[2] = this.getMessage(field[2]); 43 | }else if(field[2].indexOf('.') > -1){ 44 | 45 | let s = field[2].split('.'); 46 | let m = this.getMessage(s[0]); 47 | if(typeof m != 'undefined'){ 48 | 49 | m = m.getMessage(s[1]); 50 | if(typeof m != 'undefined'){ 51 | field[2] = m; 52 | } 53 | 54 | } 55 | 56 | } 57 | 58 | msg.addField(field[0], field[1], field[2], field[3], field[4]); 59 | 60 | } 61 | 62 | } 63 | 64 | addEnums (name, enums) { 65 | this.enums[name] = Object.freeze(enums); 66 | return this.enums[name]; 67 | } 68 | 69 | getEnums (name) { 70 | return this.enums[name]; 71 | } 72 | 73 | addMessage (msg) { 74 | this.messages[msg] = new ProtobufMessage(); 75 | return this.messages[msg]; 76 | } 77 | 78 | getMessage (msg){ 79 | return this.messages[msg]; 80 | } 81 | 82 | } 83 | 84 | class ProtobufMessage { 85 | 86 | constructor () { 87 | this.messages = {}; 88 | this.fields = {}; 89 | } 90 | 91 | addMessage (msg) { 92 | this.messages[msg] = new ProtobufMessage(); 93 | return this.messages[msg]; 94 | } 95 | 96 | getMessage (msg){ 97 | return this.messages[msg]; 98 | } 99 | 100 | addField (id, name, method, args, type) { 101 | this.fields[id] = { 102 | 'name': name, 103 | 'method': method, 104 | 'type': type || 'value', 105 | 'args': args || [] 106 | }; 107 | return this; 108 | } 109 | 110 | getField (id) { 111 | return this.fields[id]; 112 | } 113 | 114 | decode (stream, size) { 115 | 116 | let data = {}; 117 | size = size || stream.varInt32(); 118 | let offset = stream.tell(); 119 | 120 | while (stream.tell() < offset + size) { 121 | let tag = stream.varInt32(); 122 | let id = tag >> 3; 123 | let field = this.getField(id); 124 | 125 | if (field != null) { 126 | let val = null; 127 | if (typeof field.method == 'string') { 128 | if (stream[field.method] == null) { 129 | throw Error('Unable to find method ' + field.method); 130 | } 131 | val = stream[field.method].apply(stream, field.args); 132 | } else { 133 | val = field.method.decode(stream); 134 | } 135 | 136 | if (field.type == 'array') { 137 | 138 | if (data[field.name] == null) { 139 | data[field.name] = []; 140 | } 141 | data[field.name].push(val); 142 | } else { 143 | data[field.name] = val; 144 | } 145 | } 146 | 147 | } 148 | 149 | stream.seek(offset + size); 150 | 151 | return data; 152 | } 153 | 154 | } 155 | 156 | module.exports = Protobuf; -------------------------------------------------------------------------------- /src/Entities/Player.js: -------------------------------------------------------------------------------- 1 | const Entity = require(`${__dirname}/Entity`); 2 | const Vector3 = require(`${__dirname}/../Vector3`); 3 | const MathHelpers = require(`${__dirname}/../MathHelpers`); 4 | 5 | class Player extends Entity { 6 | 7 | getName() { 8 | return this.info.name || 'Unknown'; 9 | } 10 | 11 | isHLTV() { 12 | return this.info.isHLTV || false; 13 | } 14 | 15 | isFakePlayer() { 16 | return this.info.fakePlayer || false; 17 | } 18 | 19 | getGuid() { 20 | return this.info.guid || ''; 21 | } 22 | 23 | getUserId() { 24 | return this.info.userId || null; 25 | } 26 | 27 | getHealth() { 28 | return this.getValue('m_iHealth') || 100; 29 | } 30 | 31 | getTeamNumber () { 32 | return this.getValue('m_iTeamNum'); 33 | } 34 | 35 | getTeam() { 36 | 37 | let teams = this.demo.getTeams(); 38 | 39 | for (let team of teams) { 40 | if (team.getTeamNumber() == this.getValue('m_iTeamNum')) { 41 | return team; 42 | } 43 | } 44 | } 45 | 46 | getEyeAngle() { 47 | let angle0 = this.getValue('m_angEyeAngles[0]'); 48 | let angle1 = this.getValue('m_angEyeAngles[1]'); 49 | return { 50 | pitch: MathHelpers.makeAnglePositive(-(angle0 || 0)), 51 | yaw: MathHelpers.makeAnglePositive(angle1 || 0) 52 | }; 53 | } 54 | 55 | getEyeAngleVector() { 56 | const angles = this.getEyeAngle(); 57 | const pitchRadians = MathHelpers.toRadians(90 - angles.pitch); 58 | const yawRadians = MathHelpers.toRadians(angles.yaw); 59 | return new Vector3( 60 | Math.sin(pitchRadians) * Math.cos(yawRadians), 61 | Math.sin(pitchRadians) * Math.sin(yawRadians), 62 | Math.cos(pitchRadians), 63 | ); 64 | } 65 | 66 | getAimPunchAngle() { 67 | return new Vector3(...this.getValue('localdata.m_Local.m_aimPunchAngle')); 68 | } 69 | 70 | getPosition() { 71 | let xy = this.getValue(`${this.latestPositionPath}.m_vecOrigin`); 72 | let z = this.getValue(`${this.latestPositionPath}.m_vecOrigin[2]`); 73 | if (xy != null && z !== null) { 74 | return new Vector3(xy.x, xy.y, z); 75 | } 76 | return new Vector3(); 77 | } 78 | 79 | getArmorValue() { 80 | return this.getValue('m_ArmorValue') || 0; 81 | } 82 | 83 | hasHelmet() { 84 | return this.getValue('m_bHasHelmet') == 1; 85 | } 86 | 87 | getCurrentEquipmentValue() { 88 | return this.getValue('m_unCurrentEquipmentValue') || 0; 89 | } 90 | 91 | isSpotted() { 92 | return this.getValue('m_bSpotted') == 1; 93 | } 94 | 95 | getRoundStartCash() { 96 | return this.getValue('m_iStartAccount') || 0; 97 | } 98 | 99 | getCurrentCash() { 100 | return this.getValue('m_iAccount') || 0; 101 | } 102 | 103 | getLastPlaceName() { 104 | return this.getValue('m_szLastPlaceName') || ''; 105 | } 106 | 107 | getRoundKills() { 108 | return this.getValue('m_iNumRoundKills') || 0; 109 | } 110 | 111 | getRoundHeadshotKills() { 112 | return this.getValue('m_iNumRoundKillsHeadshots') || 0; 113 | } 114 | 115 | isScoped() { 116 | return this.getValue('m_bIsScoped') == 1; 117 | } 118 | 119 | isInBuyzone() { 120 | return this.getValue('m_bInBuyZone') == 1; 121 | } 122 | 123 | isWalking() { 124 | return this.getValue('m_bIsWalking') == 1; 125 | } 126 | 127 | hasDefuser() { 128 | return this.getValue('m_bHasDefuser') == 1; 129 | } 130 | 131 | getActiveWeapon() { 132 | let active = this.getValue('m_hActiveWeapon') & 0x7FF; 133 | return this.demo.findEntityById(active); 134 | } 135 | 136 | getWeapons() { 137 | let weapons = []; 138 | for (let i = 0; i < 10; i++) { 139 | let weapon = this.getWeapon(i); 140 | if (weapon != null) { 141 | weapons.push(weapon); 142 | } 143 | } 144 | return weapons; 145 | } 146 | 147 | getWeapon(index) { 148 | let weaponId = this.getValue(`m_hMyWeapons.00${index}`); // bleh, whatever! 149 | if (weaponId == null) { 150 | return null; 151 | } 152 | return demo.findEntityById(weaponId & 0x7FF); 153 | } 154 | } 155 | 156 | module.exports = Player; 157 | -------------------------------------------------------------------------------- /src/BitStream.js: -------------------------------------------------------------------------------- 1 | const kCW = require(__dirname + '/../enums/kCW'); 2 | 3 | class BitStream { 4 | 5 | constructor (buffer) { 6 | 7 | this.buffer = buffer; 8 | this.index = 0; 9 | this.bufferedBits = null; 10 | this.bitsAvailable = 0; 11 | 12 | } 13 | 14 | tell () { 15 | return this.index; 16 | } 17 | 18 | tellBits () { 19 | return ((this.index - 1) << 3) + (8 - this.bitsAvailable); 20 | } 21 | 22 | skip (bytes) { 23 | this.index += bytes; 24 | if (this.bitsAvailable !== 0) { 25 | this.bufferedBits = this.takeByteAt(this.index - 1); 26 | } 27 | return this; 28 | } 29 | 30 | seek (dest) { 31 | this.index = dest; 32 | this.clearBufferedBits(); 33 | return this; 34 | } 35 | 36 | seekBits (dest) { 37 | this.index = dest >> 3; 38 | this.clearBufferedBits(); 39 | this.bits(dest % 8); 40 | } 41 | 42 | string (len, nongreedy) { 43 | let str = ''; 44 | for (let i = 0; i < len; i++) { 45 | let char = this.byte(); 46 | if (char == 0) { 47 | if (!nongreedy) { 48 | this.skip(len - i - 1); 49 | } 50 | break; 51 | } 52 | str += String.fromCharCode(char); 53 | } 54 | return str; 55 | } 56 | 57 | vString () { 58 | let length = this.varInt32(); 59 | if (length == 0) { 60 | return ''; 61 | } 62 | return this.string(length, true); 63 | } 64 | 65 | vChunk () { 66 | let size = this.varInt32(); 67 | let buffer = Buffer.alloc(size); 68 | for (let i = 0; i < size; i++) { 69 | buffer.writeUInt8(this.byte(), i); 70 | } 71 | return new BitStream(buffer); 72 | } 73 | 74 | int16 () { 75 | return this.flatten(2); 76 | } 77 | 78 | int32 () { 79 | return this.flatten(4); 80 | } 81 | 82 | flatten (bytes) { 83 | if (typeof bytes === 'number' && isFinite(bytes)) { 84 | bytes = this.bytes(bytes); 85 | } 86 | let ret = 0; 87 | for (let i = 0; i < bytes.length; i++) { 88 | ret |= bytes[i] << (i << 3); 89 | } 90 | return ret; 91 | } 92 | 93 | float () { 94 | let b = this.bytes(4), 95 | sign = 1 - (2 * (b[3] >> 7)), 96 | exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127, 97 | mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0]; 98 | 99 | if (exponent === 128) { 100 | if (mantissa !== 0) { 101 | return NaN; 102 | } else { 103 | return sign * Infinity; 104 | } 105 | } 106 | 107 | if (exponent === -127) { 108 | return sign * mantissa * this.pow2(-126 - 23); 109 | } 110 | 111 | return sign * (1 + mantissa * this.pow2(-23)) * this.pow2(exponent); 112 | } 113 | 114 | varInt32 () { 115 | return this.varInt(4); 116 | } 117 | 118 | varInt32Bool () { 119 | return !(!this.varInt32()); 120 | } 121 | 122 | varInt (maxLen) { 123 | let b = 0; 124 | let count = 0; 125 | let result = 0; 126 | do { 127 | if (count + 1 == maxLen) { 128 | return result; 129 | } 130 | b = this.byte(); 131 | result |= (b & 0x7F) << (7 * count); 132 | ++count; 133 | } while (b & 0x80); 134 | return result; 135 | } 136 | 137 | uBitVar () { 138 | var ret = this.bits(6); 139 | switch (ret & (16 | 32)) { 140 | case 16: 141 | ret = (ret & 15) | (this.bits(4) << 4); 142 | break; 143 | case 32: 144 | ret = (ret & 15) | (this.bits(8) << 4); 145 | break; 146 | case 48: 147 | ret = (ret & 15) | (this.bits(32 - 4) << 4); 148 | break; 149 | } 150 | return ret; 151 | } 152 | 153 | bitCoord () { 154 | var value = 0; 155 | var intval = this.bits(1); 156 | var fractval = this.bits(1); 157 | if (intval || fractval) { 158 | var signbit = this.bits(1); 159 | if (intval) { 160 | intval = this.bits(14) + 1; 161 | } 162 | if (fractval) { 163 | fractval = this.bits(5); 164 | } 165 | value = intval + (fractval * (1 / (1 << 5))); 166 | if (signbit) { 167 | value = -value; 168 | } 169 | } 170 | return value; 171 | } 172 | 173 | bitNormal () { 174 | 175 | var signbit = this.bits(1); 176 | var fractval = this.bits(11); 177 | 178 | var value = fractval * (1 / ((1 << 11) - 1)); 179 | 180 | if (signbit) { 181 | value = -value; 182 | } 183 | 184 | return value; 185 | } 186 | 187 | bitCellCoord (bits, coordType) { 188 | var bIntegral = coordType == kCW.Integral; 189 | var bLowPrecision = coordType == kCW.LowPrecision; 190 | var value = 0; 191 | if (bIntegral) { 192 | return this.bits(bits); 193 | } 194 | var intval = this.bits(bits); 195 | var fractval = this.bits(bLowPrecision ? 3 : 5); 196 | return value = intval + (fractval * (1 / (1 << (bLowPrecision ? 3 : 5)))); 197 | } 198 | 199 | pow2 (n) { 200 | return (n >= 0 && n < 31) ? (1 << n) : (this.pow2[n] || (this.pow2[n] = Math.pow(2, n))); 201 | } 202 | 203 | takeByteAt (index) { 204 | return this.buffer[index]; 205 | } 206 | 207 | takeByte () { 208 | return this.takeByteAt(this.index++); 209 | } 210 | 211 | bool () { 212 | return !(!this.byte()); 213 | } 214 | 215 | byte () { 216 | return this.bits(8); 217 | } 218 | 219 | bytes (bytes) { 220 | let result = []; 221 | for (let i = 0; i < bytes; i++) { 222 | result.push(this.byte()); 223 | } 224 | return result; 225 | } 226 | 227 | bits (bits) { 228 | if (bits == 8 && this.bitsAvailable == 0) { 229 | return this.takeByte(); 230 | } 231 | let ret = 0; 232 | for (let i = 0; i < bits; i++) { 233 | if (this.bitsAvailable == 0) { 234 | this.bufferedBits = this.takeByte(); 235 | this.bitsAvailable = 8; 236 | } 237 | ret |= ((this.bufferedBits >> (8 - this.bitsAvailable--)) & 1) << i; 238 | } 239 | return ret; 240 | } 241 | 242 | clearBufferedBits () { 243 | this.bufferedBits = null; 244 | this.bitsAvailable = 0; 245 | return this; 246 | } 247 | 248 | } 249 | 250 | module.exports = BitStream; 251 | -------------------------------------------------------------------------------- /src/Entities/Entity.js: -------------------------------------------------------------------------------- 1 | const kCW = require(__dirname + '/../../enums/kCW'); 2 | const SPROP = require(__dirname + '/../../enums/SPROP'); 3 | 4 | class Entity { 5 | 6 | constructor(demo) { 7 | this.demo = demo; 8 | this.data = {}; 9 | this.serialNumber = null; 10 | this.classInfo = null; 11 | this.latestPositionPath = 'csnonlocaldata'; 12 | } 13 | 14 | getData() { 15 | return this.data; 16 | } 17 | 18 | readFieldIndex(stream, lastIndex, newWay) { 19 | 20 | if (newWay && stream.bits(1)) { 21 | return lastIndex + 1; 22 | } 23 | 24 | let ret = 0; 25 | if (newWay && stream.bits(1)) { 26 | ret = stream.bits(3); 27 | } else { 28 | ret = stream.bits(7); 29 | switch (ret & (32 | 64)) { 30 | case 32: 31 | ret = (ret & ~96) | (stream.bits(2) << 5); 32 | break; 33 | case 64: 34 | ret = (ret & ~96) | (stream.bits(4) << 5); 35 | break; 36 | case 96: 37 | ret = (ret & ~96) | (stream.bits(7) << 5); 38 | break; 39 | } 40 | } 41 | if (ret == 0xFFF) { 42 | return -1; 43 | } 44 | return lastIndex + 1 + ret; 45 | } 46 | 47 | readFromStream(stream) { 48 | const fieldIndices = []; 49 | const newWay = stream.bits(1); 50 | let index = -1; 51 | do { 52 | index = this.readFieldIndex(stream, index, newWay); 53 | if (index != -1) { 54 | fieldIndices.push(index); 55 | } 56 | } while (index != -1); 57 | let paths = []; 58 | for (let i = 0; i < fieldIndices.length; i++) { 59 | paths = paths.concat(this.decodeProperty(stream, fieldIndices[i])); 60 | } 61 | return paths; 62 | } 63 | 64 | getValue(path) { 65 | const objPart = this.getObj(this.data, path); 66 | return objPart.obj[objPart.property]; 67 | } 68 | 69 | setValue(path, value) { 70 | const objPart = this.getObj(this.data, path); 71 | if (objPart.property == 'm_vecOrigin') { 72 | this.latestPositionPath = path.includes('nonlocal') ? 73 | 'csnonlocaldata' : 'cslocaldata'; 74 | } 75 | objPart.obj[objPart.property] = value; 76 | } 77 | 78 | getObj(data, path) { 79 | const parts = path.split('.'); 80 | const prop = parts[parts.length - 1]; 81 | for (let i = 0; i < parts.length - 1; i++) { 82 | if (parts[i] != 'baseclass') { 83 | if (data[parts[i]] == null) { 84 | data[parts[i]] = {}; 85 | } 86 | data = data[parts[i]]; 87 | } 88 | } 89 | return { 90 | obj: data, 91 | property: prop 92 | }; 93 | } 94 | 95 | decodeProperty(stream, fieldIndex, _property) { 96 | 97 | const flattenedProp = _property || this.classInfo.flattenedProps[fieldIndex]; 98 | if (flattenedProp == null) { 99 | return null; 100 | } 101 | const prop = flattenedProp.prop; 102 | let paths = []; 103 | paths.push(flattenedProp.path); 104 | if (prop) { 105 | var result = null; 106 | switch (prop.type) { 107 | case 0: 108 | this.setValue(flattenedProp.path, this.decodeInt(stream, prop)); 109 | break; 110 | case 1: 111 | this.setValue(flattenedProp.path, this.decodeFloat(stream, prop)); 112 | break; 113 | case 2: 114 | this.setValue(flattenedProp.path, this.decodeVector(stream, prop)); 115 | break; 116 | case 3: 117 | this.setValue(flattenedProp.path, this.decodeVectorXY(stream, prop)); 118 | break; 119 | case 4: 120 | this.setValue(flattenedProp.path, this.decodeString(stream, prop)); 121 | break; 122 | case 5: 123 | var result = []; 124 | let maxElements = prop.numElements; 125 | let numBits = 1; 126 | while ((maxElements >>= 1) != 0) { 127 | numBits++; 128 | } 129 | const numElements = stream.bits(numBits); 130 | for (let i = 0; i < numElements; i++) { 131 | const tmp = { 132 | 'prop': flattenedProp.elm, 133 | 'path': `${flattenedProp.path}.${i}`, 134 | 'elm': null 135 | }; 136 | paths = paths.concat(this.decodeProperty(stream, fieldIndex, tmp)); 137 | } 138 | break; 139 | default: 140 | console.log(`sendProp.type = ${sendProp.type}`); 141 | exit; 142 | break; 143 | } 144 | return paths; 145 | } 146 | } 147 | 148 | decodeInt(stream, prop) { 149 | if (prop.flags & (1 << 19)) { 150 | if (prop.flags & (1 << 0)) { 151 | return stream.varInt32(); 152 | } else { 153 | return stream.signedVarInt32(); 154 | } 155 | } else { 156 | if (prop.flags & (1 << 0)) { 157 | return stream.bits(prop.numBits); 158 | } else { 159 | return stream.bits(prop.numBits); // hmm 160 | } 161 | } 162 | } 163 | 164 | decodeFloat(stream, prop) { 165 | 166 | const flags = prop.flags; 167 | 168 | if (flags & SPROP.COORD) { 169 | return stream.bitCoord(); 170 | } else if (flags & SPROP.COORD_MP) { 171 | return stream.bitCoordMP(kCW.None); 172 | } else if (flags & SPROP.COORD_MP_LOWPRECISION) { 173 | return stream.bitCoordMP(kCW.LowPrecision); 174 | } else if (flags & SPROP.COORD_MP_INTEGRAL) { 175 | return stream.bitCoordMP(kCW.Integral); 176 | } else if (flags & SPROP.NOSCALE) { 177 | return stream.float(); 178 | } else if (flags & SPROP.NORMAL) { 179 | return stream.bitNormal(); 180 | } else if (flags & SPROP.CELL_COORD) { 181 | return stream.bitCellCoord(prop.numBits, kCW.None); 182 | } else if (flags & SPROP.CELL_COORD_LOWPRECISION) { 183 | return stream.bitCellCoord(prop.numBits, kCW.LowPrecision); 184 | } else if (flags & SPROP.CELL_COORD_INTEGRAL) { 185 | return stream.bitCellCoord(prop.numBits, kCW.Integral); 186 | } 187 | 188 | const dwInterp = stream.bits(prop.numBits); 189 | let fVal = 0; 190 | fVal = dwInterp / ((1 << prop.numBits) - 1); 191 | fVal = prop.lowValue + (prop.highValue - prop.lowValue) * fVal; 192 | return fVal; 193 | } 194 | 195 | decodeVector(stream, prop) { 196 | const v = { 197 | x: this.decodeFloat(stream, prop), 198 | y: this.decodeFloat(stream, prop) 199 | }; 200 | if ((prop.flags & (1 << 5)) == 0) { 201 | v.z = this.decodeFloat(stream, prop); 202 | } else { 203 | const v0v0v1v1 = v.x * v.x + v.y * v.y; 204 | if (v0v0v1v1 < 1) { 205 | v.z = Math.sqrt(1 - v0v0v1v1); 206 | } else { 207 | v.z = 0; 208 | } 209 | if (stream.bits(1)) { 210 | v.z *= -1; 211 | } 212 | } 213 | return v; 214 | } 215 | 216 | decodeVectorXY(stream, prop) { 217 | const vector = { 218 | x: this.decodeFloat(stream, prop), 219 | y: this.decodeFloat(stream, prop) 220 | }; 221 | return vector; 222 | } 223 | 224 | decodeString(stream, prop) { 225 | let len = stream.bits(9); 226 | if (len == 0) { 227 | return ''; 228 | } 229 | const maxBuffer = (1 << 9); 230 | if (len >= maxBuffer) { 231 | len = maxBuffer - 1; 232 | } 233 | return stream.string(len); 234 | } 235 | } 236 | 237 | module.exports = Entity; -------------------------------------------------------------------------------- /protos/UserMessages.js: -------------------------------------------------------------------------------- 1 | const UMSG = require(__dirname + '/../enums/UserMessages'); 2 | 3 | let Messages = {}; 4 | module.exports = Messages; 5 | 6 | Messages[UMSG.VGUIMenu] = { 7 | messages: { 8 | subkey: { 9 | messages: {}, 10 | fields: [ 11 | [1, 'name', 'vString'], 12 | [2, 'str', 'vString'] 13 | ] 14 | } 15 | }, 16 | fields: [ 17 | [1, 'name', 'vString'], 18 | [2, 'show', 'varInt32Bool'], 19 | [3, 'subkeys', 'subkey', null, 'array'] 20 | ] 21 | } 22 | 23 | Messages[UMSG.Geiger] = { 24 | fields: [ 25 | [1, 'range', 'varInt32'] 26 | ] 27 | } 28 | 29 | Messages[UMSG.Train] = { 30 | fields: [ 31 | [1, 'train', 'varInt32'] 32 | ] 33 | } 34 | 35 | Messages[UMSG.HudText] = { 36 | fields: [ 37 | [1, 'text', 'vString'] 38 | ] 39 | } 40 | 41 | Messages[UMSG.SayText] = { 42 | fields: [ 43 | [1, 'ent_idx', 'varInt32'], 44 | [2, 'range', 'vString'], 45 | [3, 'chat', 'varInt32Bool'], 46 | [4, 'textallchat', 'varInt32Bool'] 47 | ] 48 | } 49 | 50 | Messages[UMSG.SayText2] = { 51 | fields: [ 52 | [1, 'ent_idx', 'varInt32'], 53 | [2, 'chat', 'varInt32Bool'], 54 | [3, 'msg_name', 'vString'], 55 | [4, 'params', 'vString'], 56 | [5, 'textallchat', 'varInt32Bool'] 57 | ] 58 | } 59 | 60 | Messages[UMSG.TextMsg] = { 61 | fields: [ 62 | [1, 'msg_dst', 'varInt32'], 63 | [2, 'params', 'vString'] 64 | ] 65 | } 66 | 67 | Messages[UMSG.TextMsg] = { 68 | fields: [ 69 | [1, 'msg_dst', 'varInt32'], 70 | [2, 'params', 'vString'] 71 | ] 72 | } 73 | 74 | /* 75 | message CCSUsrMsg_HudMsg { 76 | optional int32 channel = 1; 77 | optional .CMsgVector2D pos = 2; 78 | optional .CMsgRGBA clr1 = 3; 79 | optional .CMsgRGBA clr2 = 4; 80 | optional int32 effect = 5; 81 | optional float fade_in_time = 6; 82 | optional float fade_out_time = 7; 83 | optional float hold_time = 9; 84 | optional float fx_time = 10; 85 | optional string text = 11; 86 | } 87 | */ 88 | 89 | Messages[UMSG.Shake] = { 90 | fields: [ 91 | [1, 'command', 'varInt32'], 92 | [2, 'local_amplitude', 'float'], 93 | [3, 'frequency', 'float'], 94 | [4, 'duration', 'float'] 95 | ] 96 | } 97 | 98 | Messages[UMSG.Fade] = { 99 | fields: [ 100 | [1, 'duration', 'varInt32'], 101 | [2, 'hold_time', 'varInt32'], 102 | [3, 'flags', 'varInt32'], 103 | [4, 'clr', 'Common/RGBA'] 104 | ] 105 | } 106 | 107 | /* 108 | 109 | message CCSUsrMsg_Rumble { 110 | optional int32 index = 1; 111 | optional int32 data = 2; 112 | optional int32 flags = 3; 113 | } 114 | 115 | message CCSUsrMsg_CloseCaption { 116 | optional uint32 hash = 1; 117 | optional int32 duration = 2; 118 | optional bool from_player = 3; 119 | } 120 | 121 | message CCSUsrMsg_CloseCaptionDirect { 122 | optional uint32 hash = 1; 123 | optional int32 duration = 2; 124 | optional bool from_player = 3; 125 | } 126 | 127 | message CCSUsrMsg_SendAudio { 128 | optional string radio_sound = 1; 129 | } 130 | 131 | message CCSUsrMsg_RawAudio { 132 | optional int32 pitch = 1; 133 | optional int32 entidx = 2; 134 | optional float duration = 3; 135 | optional string voice_filename = 4; 136 | } 137 | 138 | message CCSUsrMsg_VoiceMask { 139 | message PlayerMask { 140 | optional int32 game_rules_mask = 1; 141 | optional int32 ban_masks = 2; 142 | } 143 | 144 | repeated .CCSUsrMsg_VoiceMask.PlayerMask player_masks = 1; 145 | optional bool player_mod_enable = 2; 146 | } 147 | */ 148 | 149 | Messages[UMSG.Damage] = { 150 | fields: [ 151 | [1, 'amount', 'varInt32'], 152 | [2, 'inflictor_world_pos', 'Common/Vector'], 153 | [3, 'inflictovictim_entindexr_world_pos', 'varInt32'] 154 | ] 155 | } 156 | 157 | /* 158 | message CCSUsrMsg_RadioText { 159 | optional int32 msg_dst = 1; 160 | optional int32 client = 2; 161 | optional string msg_name = 3; 162 | repeated string params = 4; 163 | } 164 | 165 | message CCSUsrMsg_HintText { 166 | optional string text = 1; 167 | } 168 | 169 | message CCSUsrMsg_KeyHintText { 170 | repeated string hints = 1; 171 | } 172 | 173 | */ 174 | 175 | Messages[UMSG.ProcessSpottedEntityUpdate] = { 176 | messages: { 177 | spotted_entity_update: { 178 | fields: [ 179 | [1, 'entity_idx', 'varInt32'], 180 | [2, 'class_id', 'varInt32'], 181 | [3, 'origin_x', 'varInt32'], 182 | [4, 'origin_y', 'varInt32'], 183 | [5, 'origin_z', 'varInt32'], 184 | [6, 'angle_y', 'varInt32'], 185 | [7, 'defuser', 'varInt32Bool'], 186 | [8, 'player_has_defuser', 'varInt32Bool'], 187 | [9, 'player_has_c4', 'varInt32Bool'] 188 | ] 189 | } 190 | }, 191 | fields: [ 192 | [1, 'new_update', 'varInt32Bool'], 193 | [2, 'entity_updates', 'spotted_entity_update'] 194 | ] 195 | } 196 | 197 | /* 198 | Messages[UMSG.SendPlayerItemDrops] = { 199 | fields: [ 200 | [1, 'entity_updates', '.CEconItemPreviewDataBlock'] 201 | ] 202 | } 203 | */ 204 | 205 | /* 206 | 207 | 208 | 209 | message CCSUsrMsg_SendPlayerItemFound { 210 | optional .CEconItemPreviewDataBlock iteminfo = 1; 211 | optional int32 entindex = 2; 212 | } 213 | */ 214 | 215 | Messages[UMSG.ReloadEffect] = { 216 | fields: [ 217 | [1, 'entidx', 'varInt32'], 218 | [2, 'actanim', 'varInt32'], 219 | [3, 'origin_x', 'float'], 220 | [4, 'origin_y', 'float'], 221 | [5, 'origin_z', 'float'] 222 | ] 223 | } 224 | 225 | /* 226 | message CCSUsrMsg_AdjustMoney { 227 | optional int32 amount = 1; 228 | } 229 | 230 | message CCSUsrMsg_ReportHit { 231 | optional float pos_x = 1; 232 | optional float pos_y = 2; 233 | optional float timestamp = 4; 234 | optional float pos_z = 3; 235 | } 236 | 237 | message CCSUsrMsg_KillCam { 238 | optional int32 obs_mode = 1; 239 | optional int32 first_target = 2; 240 | optional int32 second_target = 3; 241 | } 242 | 243 | message CCSUsrMsg_DesiredTimescale { 244 | optional float desired_timescale = 1; 245 | optional float duration_realtime_sec = 2; 246 | optional int32 interpolator_type = 3; 247 | optional float start_blend_time = 4; 248 | } 249 | 250 | message CCSUsrMsg_CurrentTimescale { 251 | optional float cur_timescale = 1; 252 | } 253 | 254 | message CCSUsrMsg_AchievementEvent { 255 | optional int32 achievement = 1; 256 | optional int32 count = 2; 257 | optional int32 user_id = 3; 258 | } 259 | 260 | message CCSUsrMsg_MatchEndConditions { 261 | optional int32 fraglimit = 1; 262 | optional int32 mp_maxrounds = 2; 263 | optional int32 mp_winlimit = 3; 264 | optional int32 mp_timelimit = 4; 265 | } 266 | */ 267 | 268 | Messages[UMSG.PlayerStatsUpdate] = { 269 | messages: { 270 | stat: { 271 | fields: [ 272 | [1, 'idx', 'varInt32'], 273 | [2, 'delta', 'varInt32'] 274 | ] 275 | } 276 | }, 277 | fields: [ 278 | [1, 'version', 'varInt32'], 279 | [4, 'stats', 'stat'], 280 | [5, 'user_id', 'varInt32'], 281 | [6, 'crc', 'varInt32'] 282 | ] 283 | } 284 | 285 | /* 286 | message CCSUsrMsg_DisplayInventory { 287 | optional bool display = 1; 288 | optional int32 user_id = 2; 289 | } 290 | 291 | message CCSUsrMsg_QuestProgress { 292 | optional uint32 quest_id = 1; 293 | optional uint32 normal_points = 2; 294 | optional uint32 bonus_points = 3; 295 | optional bool is_event_quest = 4; 296 | } 297 | 298 | message CCSUsrMsg_ScoreLeaderboardData { 299 | optional .ScoreLeaderboardData data = 1; 300 | } 301 | 302 | message CCSUsrMsg_PlayerDecalDigitalSignature { 303 | optional .PlayerDecalDigitalSignature data = 1; 304 | } 305 | 306 | message CCSUsrMsg_XRankGet { 307 | optional int32 mode_idx = 1; 308 | optional int32 controller = 2; 309 | } 310 | 311 | message CCSUsrMsg_XRankUpd { 312 | optional int32 mode_idx = 1; 313 | optional int32 controller = 2; 314 | optional int32 ranking = 3; 315 | } 316 | 317 | message CCSUsrMsg_CallVoteFailed { 318 | optional int32 reason = 1; 319 | optional int32 time = 2; 320 | } 321 | */ 322 | 323 | Messages[UMSG.VoteStart] = { 324 | fields: [ 325 | [1, 'team', 'varInt32'], 326 | [2, 'ent_idx', 'varInt32'], 327 | [3, 'vote_type', 'varInt32'], 328 | [4, 'disp_str', 'vString'], 329 | [5, 'details_str', 'vString'], 330 | [6, 'other_team_str', 'vString'], 331 | [7, 'is_yes_no_vote', 'varInt32Bool'] 332 | ] 333 | } 334 | 335 | Messages[UMSG.VotePass] = { 336 | fields: [ 337 | [1, 'team', 'varInt32'], 338 | [2, 'vote_type', 'varInt32'], 339 | [3, 'disp_str', 'vString'], 340 | [4, 'details_str', 'vString'] 341 | ] 342 | } 343 | 344 | Messages[UMSG.VoteFailed] = { 345 | fields: [ 346 | [1, 'team', 'varInt32'], 347 | [4, 'reason', 'varInt32'] 348 | ] 349 | } 350 | 351 | Messages[UMSG.VoteSetup] = { 352 | fields: [ 353 | [4, 'potential_issues', 'vString'] 354 | ] 355 | } 356 | 357 | /* 358 | 359 | message CCSUsrMsg_SendLastKillerDamageToClient { 360 | optional int32 num_hits_given = 1; 361 | optional int32 damage_given = 2; 362 | optional int32 num_hits_taken = 3; 363 | optional int32 damage_taken = 4; 364 | } 365 | 366 | */ 367 | 368 | Messages[UMSG.ServerRankUpdate] = { 369 | messages: { 370 | RankUpdate: { 371 | fields: [ 372 | [1, 'account_id', 'varInt32'], 373 | [2, 'rank_old', 'varInt32'], 374 | [3, 'rank_new', 'varInt32'], 375 | [4, 'num_wins', 'varInt32'], 376 | [5, 'rank_change', 'float'], 377 | [6, 'rank_type_id', 'varInt32'] 378 | ] 379 | } 380 | }, 381 | fields: [ 382 | [1, 'rank_update', 'RankUpdate'] 383 | ] 384 | } 385 | 386 | Messages[UMSG.XpUpdate] = { 387 | fields: [ 388 | [1, 'data', 'GCMessages/GC2ServerNotifyXPRewarded'] 389 | ] 390 | } 391 | 392 | /* 393 | 394 | message CCSUsrMsg_ItemPickup { 395 | optional string item = 1; 396 | } 397 | 398 | message CCSUsrMsg_ShowMenu { 399 | optional int32 bits_valid_slots = 1; 400 | optional int32 display_time = 2; 401 | optional string menu_string = 3; 402 | } 403 | 404 | message CCSUsrMsg_BarTime { 405 | optional string time = 1; 406 | } 407 | 408 | message CCSUsrMsg_AmmoDenied { 409 | optional int32 ammoIdx = 1; 410 | } 411 | 412 | message CCSUsrMsg_MarkAchievement { 413 | optional string achievement = 1; 414 | } 415 | 416 | message CCSUsrMsg_MatchStatsUpdate { 417 | optional string update = 1; 418 | } 419 | 420 | message CCSUsrMsg_ItemDrop { 421 | optional int64 itemid = 1; 422 | optional bool death = 2; 423 | } 424 | 425 | message CCSUsrMsg_GlowPropTurnOff { 426 | optional int32 entidx = 1; 427 | } 428 | 429 | message CCSUsrMsg_RoundBackupFilenames { 430 | optional int32 count = 1; 431 | optional int32 index = 2; 432 | optional string filename = 3; 433 | optional string nicename = 4; 434 | } 435 | 436 | */ 437 | 438 | Messages[UMSG.ResetHud] = { 439 | fields: [ 440 | [1, 'reset', 'varInt32Bool'] 441 | ] 442 | } 443 | 444 | /* 445 | 446 | message CCSUsrMsg_GameTitle { 447 | optional int32 dummy = 1; 448 | } 449 | 450 | message CCSUsrMsg_RequestState { 451 | optional int32 dummy = 1; 452 | } 453 | 454 | message CCSUsrMsg_StopSpectatorMode { 455 | optional int32 dummy = 1; 456 | } 457 | 458 | message CCSUsrMsg_DisconnectToLobby { 459 | optional int32 dummy = 1; 460 | } 461 | 462 | */ 463 | 464 | Messages[UMSG.WarmupHasEnded] = { 465 | fields: [ 466 | [1, 'dummy', 'varInt32'] 467 | ] 468 | } 469 | 470 | /* 471 | 472 | message CCSUsrMsg_ClientInfo { 473 | optional int32 dummy = 1; 474 | } 475 | 476 | message CCSUsrMsg_ServerRankRevealAll { 477 | optional int32 seconds_till_shutdown = 1; 478 | } 479 | 480 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS:GO Demo Reader for Node.js 2 | [![npm version](https://img.shields.io/npm/v/csgodemoreader.svg)](https://npmjs.com/package/csgodemoreader) 3 | [![npm downloads](https://img.shields.io/npm/dm/csgodemoreader.svg)](https://npmjs.com/package/csgodemoreader) 4 | [![license](https://img.shields.io/npm/l/csgodemoreader.svg)](https://github.com/SzymonLisowiec/node-CSGODemoReader/blob/master/LICENSE.MD) 5 | [![paypal](https://img.shields.io/badge/paypal-donate-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=szymonlisowiec%40gmail.com¤cy_code=USD&source=url) 6 | 7 | A library for reading CS:GO demo files. 8 | 9 | ## Note 10 | This is a rewrited/modified version of [JSGO](https://github.com/mikeemoo/jsgo) originally created by @mikeemoo. 11 | 12 | ## Install 13 | ``` 14 | npm install csgodemoreader --save 15 | ``` 16 | 17 | ## Examples 18 | ```javascript 19 | const DemoReader = require('csgodemoreader').Reader; 20 | const UserMessages = require('csgodemoreader').UserMessages; 21 | const Teams = require('csgodemoreader').Teams; 22 | const fs = require('fs'); 23 | 24 | let buffer = fs.readFileSync(__dirname + '/../file.dem'); 25 | let demo = new DemoReader(buffer); 26 | 27 | //Round counter 28 | demo.on('csgo.round_start', _ => { 29 | console.log('Round ' + demo.getRound()); 30 | }); 31 | 32 | //Listening User Messages 33 | demo.on('umsg.' + UserMessages.WarmupHasEnded, _ => { 34 | console.log('Warmup has ended'); 35 | }); 36 | 37 | //End of reading demo 38 | demo.on('end', _ => { 39 | 40 | let scores = demo.getScores(); 41 | 42 | for(let team_number in scores){ 43 | console.log(Teams[team_number] + ': ' + scores[team_number]); 44 | } 45 | 46 | }); 47 | 48 | //Run reader 49 | demo.run(); 50 | ``` 51 | Output 52 | ``` 53 | Round 0 54 | Warmup has ended 55 | Round 1 56 | Round 2 57 | Round 3 58 | Round 4 59 | Round 5 60 | Round 6 61 | Round 7 62 | Round 8 63 | Round 9 64 | Round 10 65 | Round 11 66 | Round 12 67 | Round 13 68 | Round 14 69 | Round 15 70 | Round 16 71 | Round 17 72 | Round 18 73 | SPECTATOR: 0 74 | TERRORIST: 16 75 | CT: 2 76 | ``` 77 | 78 | ## All possible events 79 | ``` 80 | //Demo Reader events: 81 | tick 82 | tick_end 83 | server_info 84 | possible_events 85 | user_message 86 | end 87 | event 88 | entity_added 89 | entity_updated 90 | entity_removed 91 | 92 | //Game events: 93 | csgo.server_spawn 94 | csgo.server_pre_shutdown 95 | csgo.server_shutdown 96 | csgo.server_cvar 97 | csgo.server_message 98 | csgo.server_addban 99 | csgo.server_removeban 100 | csgo.player_connect 101 | csgo.player_info 102 | csgo.player_disconnect 103 | csgo.player_activate 104 | csgo.player_connect_full 105 | csgo.player_say 106 | csgo.cs_round_start_beep 107 | csgo.cs_round_final_beep 108 | csgo.round_time_warning 109 | csgo.team_info 110 | csgo.team_score 111 | csgo.teamplay_broadcast_audio 112 | csgo.gameui_hidden 113 | csgo.items_gifted 114 | csgo.player_team 115 | csgo.player_class 116 | csgo.player_death 117 | csgo.player_hurt 118 | csgo.player_chat 119 | csgo.player_score 120 | csgo.player_spawn 121 | csgo.player_shoot 122 | csgo.player_use 123 | csgo.player_changename 124 | csgo.player_hintmessage 125 | csgo.game_init 126 | csgo.game_newmap 127 | csgo.game_start 128 | csgo.game_end 129 | csgo.round_start 130 | csgo.round_announce_match_point 131 | csgo.round_announce_final 132 | csgo.round_announce_last_round_half 133 | csgo.round_announce_match_start 134 | csgo.round_announce_warmup 135 | csgo.round_end 136 | csgo.round_end_upload_stats 137 | csgo.round_officially_ended 138 | csgo.ugc_map_info_received 139 | csgo.ugc_map_unsubscribed 140 | csgo.ugc_map_download_error 141 | csgo.ugc_file_download_finished 142 | csgo.ugc_file_download_start 143 | csgo.begin_new_match 144 | csgo.round_start_pre_entity 145 | csgo.teamplay_round_start 146 | csgo.hostname_changed 147 | csgo.difficulty_changed 148 | csgo.finale_start 149 | csgo.game_message 150 | csgo.dm_bonus_weapon_start 151 | csgo.survival_announce_phase 152 | csgo.break_breakable 153 | csgo.break_prop 154 | csgo.player_decal 155 | csgo.entity_killed 156 | csgo.bonus_updated 157 | csgo.player_stats_updated 158 | csgo.achievement_event 159 | csgo.achievement_increment 160 | csgo.achievement_earned 161 | csgo.achievement_write_failed 162 | csgo.physgun_pickup 163 | csgo.flare_ignite_npc 164 | csgo.helicopter_grenade_punt_miss 165 | csgo.user_data_downloaded 166 | csgo.ragdoll_dissolved 167 | csgo.gameinstructor_draw 168 | csgo.gameinstructor_nodraw 169 | csgo.map_transition 170 | csgo.entity_visible 171 | csgo.set_instructor_group_enabled 172 | csgo.instructor_server_hint_create 173 | csgo.instructor_server_hint_stop 174 | csgo.read_game_titledata 175 | csgo.write_game_titledata 176 | csgo.reset_game_titledata 177 | csgo.weaponhud_selection 178 | csgo.vote_ended 179 | csgo.vote_started 180 | csgo.vote_changed 181 | csgo.vote_passed 182 | csgo.vote_failed 183 | csgo.vote_cast 184 | csgo.vote_options 185 | csgo.endmatch_mapvote_selecting_map 186 | csgo.endmatch_cmm_start_reveal_items 187 | csgo.inventory_updated 188 | csgo.cart_updated 189 | csgo.store_pricesheet_updated 190 | csgo.gc_connected 191 | csgo.item_schema_initialized 192 | csgo.client_loadout_changed 193 | csgo.add_player_sonar_icon 194 | csgo.add_bullet_hit_marker 195 | csgo.verify_client_hit 196 | csgo.other_death 197 | csgo.item_purchase 198 | csgo.bomb_beginplant 199 | csgo.bomb_abortplant 200 | csgo.bomb_planted 201 | csgo.bomb_defused 202 | csgo.bomb_exploded 203 | csgo.bomb_dropped 204 | csgo.bomb_pickup 205 | csgo.defuser_dropped 206 | csgo.defuser_pickup 207 | csgo.announce_phase_end 208 | csgo.cs_intermission 209 | csgo.bomb_begindefuse 210 | csgo.bomb_abortdefuse 211 | csgo.hostage_follows 212 | csgo.hostage_hurt 213 | csgo.hostage_killed 214 | csgo.hostage_rescued 215 | csgo.hostage_stops_following 216 | csgo.hostage_rescued_all 217 | csgo.hostage_call_for_help 218 | csgo.vip_escaped 219 | csgo.vip_killed 220 | csgo.player_radio 221 | csgo.bomb_beep 222 | csgo.weapon_fire 223 | csgo.weapon_fire_on_empty 224 | csgo.grenade_thrown 225 | csgo.weapon_outofammo 226 | csgo.weapon_reload 227 | csgo.weapon_zoom 228 | csgo.silencer_detach 229 | csgo.inspect_weapon 230 | csgo.weapon_zoom_rifle 231 | csgo.player_spawned 232 | csgo.item_pickup 233 | csgo.item_remove 234 | csgo.ammo_pickup 235 | csgo.item_equip 236 | csgo.enter_buyzone 237 | csgo.exit_buyzone 238 | csgo.buytime_ended 239 | csgo.enter_bombzone 240 | csgo.exit_bombzone 241 | csgo.enter_rescue_zone 242 | csgo.exit_rescue_zone 243 | csgo.silencer_off 244 | csgo.silencer_on 245 | csgo.buymenu_open 246 | csgo.buymenu_close 247 | csgo.round_prestart 248 | csgo.round_poststart 249 | csgo.grenade_bounce 250 | csgo.hegrenade_detonate 251 | csgo.flashbang_detonate 252 | csgo.smokegrenade_detonate 253 | csgo.smokegrenade_expired 254 | csgo.molotov_detonate 255 | csgo.decoy_detonate 256 | csgo.decoy_started 257 | csgo.tagrenade_detonate 258 | csgo.inferno_startburn 259 | csgo.inferno_expire 260 | csgo.inferno_extinguish 261 | csgo.decoy_firing 262 | csgo.bullet_impact 263 | csgo.player_footstep 264 | csgo.player_jump 265 | csgo.player_blind 266 | csgo.player_falldamage 267 | csgo.door_moving 268 | csgo.round_freeze_end 269 | csgo.mb_input_lock_success 270 | csgo.mb_input_lock_cancel 271 | csgo.nav_blocked 272 | csgo.nav_generate 273 | csgo.achievement_info_loaded 274 | csgo.spec_target_updated 275 | csgo.spec_mode_updated 276 | csgo.hltv_changed_mode 277 | csgo.cs_game_disconnected 278 | csgo.cs_win_panel_round 279 | csgo.cs_win_panel_match 280 | csgo.cs_match_end_restart 281 | csgo.cs_pre_restart 282 | csgo.show_freezepanel 283 | csgo.hide_freezepanel 284 | csgo.freezecam_started 285 | csgo.player_avenged_teammate 286 | csgo.achievement_earned_local 287 | csgo.item_found 288 | csgo.repost_xbox_achievements 289 | csgo.match_end_conditions 290 | csgo.round_mvp 291 | csgo.client_disconnect 292 | csgo.gg_player_levelup 293 | csgo.ggtr_player_levelup 294 | csgo.assassination_target_killed 295 | csgo.ggprogressive_player_levelup 296 | csgo.gg_killed_enemy 297 | csgo.gg_final_weapon_achieved 298 | csgo.gg_bonus_grenade_achieved 299 | csgo.switch_team 300 | csgo.gg_leader 301 | csgo.gg_team_leader 302 | csgo.gg_player_impending_upgrade 303 | csgo.write_profile_data 304 | csgo.trial_time_expired 305 | csgo.update_matchmaking_stats 306 | csgo.player_reset_vote 307 | csgo.enable_restart_voting 308 | csgo.sfuievent 309 | csgo.start_vote 310 | csgo.player_given_c4 311 | csgo.gg_reset_round_start_sounds 312 | csgo.tr_player_flashbanged 313 | csgo.tr_mark_complete 314 | csgo.tr_mark_best_time 315 | csgo.tr_exit_hint_trigger 316 | csgo.bot_takeover 317 | csgo.tr_show_finish_msgbox 318 | csgo.tr_show_exit_msgbox 319 | csgo.reset_player_controls 320 | csgo.jointeam_failed 321 | csgo.teamchange_pending 322 | csgo.material_default_complete 323 | csgo.cs_prev_next_spectator 324 | csgo.nextlevel_changed 325 | csgo.seasoncoin_levelup 326 | csgo.tournament_reward 327 | csgo.start_halftime 328 | csgo.hltv_status 329 | csgo.hltv_cameraman 330 | csgo.hltv_rank_camera 331 | csgo.hltv_rank_entity 332 | csgo.hltv_fixed 333 | csgo.hltv_chase 334 | csgo.hltv_message 335 | csgo.hltv_title 336 | csgo.hltv_chat 337 | csgo.hltv_changed_target 338 | 339 | //User Messages 340 | umsg.USERMESSAGE_CODE 341 | ``` 342 | 343 | ## Enums 344 | You have access to useful enums. 345 | ```javascript 346 | const DemoReader = require('csgodemoreader'); 347 | const UserMessages = DemoReader.UserMessages; 348 | const WeaponTypes = DemoReader.WeaponTypes; 349 | const Teams = DemoReader.Teams; 350 | ``` 351 | 352 | ## DemoReader 353 | ### Initialization 354 | ```javascript 355 | const DemoReader = require('csgodemoreader').Reader; 356 | 357 | let buffer = fs.readFileSync(__dirname + '/demos/file.dem'); 358 | let demo = new DemoReader(buffer); 359 | ``` 360 | ### Methods 361 | #### run() 362 | #### getTick() 363 | Returns current tick. 364 | #### getTeams() 365 | Returns array of teams. 366 | #### getPlayers() 367 | Returns array of players. 368 | #### getRound() 369 | Returns current round. 370 | #### getScores() 371 | Returns current scores. 372 | #### getEntities([entityClass]) 373 | * entityClass - class being instance of entity which we searching 374 | 375 | Returns array of entities. 376 | #### findEntityById(entityId) 377 | Returns array of found entities. 378 | 379 | ## Player 380 | The Player can be returned while event, which applies to him. 381 | ### Methods 382 | #### getName() 383 | #### isHLTV() 384 | #### isFakePlayer() 385 | #### getGuid() 386 | #### getUserId() 387 | #### getHealth() 388 | #### getTeamNumber() 389 | #### getTeam() 390 | #### getEyeAngle() 391 | #### getEyeAngleVector() 392 | #### getAimPunchAngle() 393 | #### getPosition() 394 | #### getArmorValue() 395 | #### hasHelmet() 396 | #### getCurrentEquipmentValue() 397 | #### isSpotted() 398 | #### getRoundStartCash() 399 | #### getCurrentCash() 400 | #### getLastPlaceName() 401 | #### getRoundKills() 402 | #### getRoundHeadshotKills() 403 | #### isScoped() 404 | #### isInBuyzone() 405 | #### isWalking() 406 | #### hasDefuser() 407 | #### getActiveWeapon() 408 | #### getWeapons() 409 | #### getWeapon(index) 410 | 411 | ## Team 412 | The Team is returned by `Player.getTeam()` and `DemoReader.getTeams()` 413 | ### Methods 414 | #### getTeamNumber() 415 | #### getSide() 416 | #### getClanName() 417 | #### getFlag() 418 | #### getScore() 419 | #### getScoreFirstHalf() 420 | #### getScoreSecondHalf() 421 | #### getPlayers() 422 | #### getWeapons() 423 | 424 | ## Vector3 425 | ### Properties 426 | #### x 427 | #### y 428 | #### z 429 | ### Methods 430 | #### clone() 431 | #### add(vector3) 432 | #### subtract(vector3) 433 | #### multiply(multiplier) 434 | #### scale(vector3) 435 | #### length() 436 | #### lengthSquared() 437 | #### normalize() 438 | #### dot(vector3) 439 | #### cross(vector3) 440 | #### distance(vector3) 441 | 442 | ## MathHelpers 443 | ### Methods 444 | #### toRadians(n) 445 | #### makeAnglePositive(angle) 446 | -------------------------------------------------------------------------------- /src/Reader.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const BitStream = require(__dirname + '/BitStream'); 3 | const Entity = require(__dirname + '/Entities/Entity'); 4 | 5 | const Protos = require(__dirname + '/../protos/'); 6 | let NetMessages = require(__dirname + '/../enums/NetMessages'); 7 | let Commands = require(__dirname + '/../enums/Commands'); 8 | let PacketEntities = require(__dirname + '/../enums/PacketEntities'); 9 | let UserMessages = require(__dirname + '/../enums/UserMessages'); 10 | 11 | const EntitiesMap ={ 12 | Player: require(__dirname + '/Entities/Player'), 13 | Team: require(__dirname + '/Entities/Team'), 14 | DecoyGrenade: require(__dirname + '/Entities/DecoyGrenade'), 15 | Flashbang: require(__dirname + '/Entities/Flashbang'), 16 | HEGrenade: require(__dirname + '/Entities/HEGrenade'), 17 | Grenade: require(__dirname + '/Entities/Grenade'), 18 | MolotovGrenade: require(__dirname + '/Entities/MolotovGrenade'), 19 | SmokeGrenade: require(__dirname + '/Entities/SmokeGrenade'), 20 | Weapon: require(__dirname + '/Entities/Weapon') 21 | } 22 | 23 | class Demo extends EventEmitter { 24 | 25 | constructor (buffer){ 26 | super(buffer); 27 | 28 | this.stream = new BitStream(buffer); 29 | 30 | this.match ={ 31 | round: -1 32 | }; 33 | this.commandHandlers = []; 34 | this.serverClasses = []; 35 | this.players = []; 36 | this.entities = []; 37 | this.serverClassesLoaded = false; 38 | this.classCachePath = null; 39 | this.dataTables = null; 40 | this.stringTables = []; 41 | this.gameEventDescriptors = []; 42 | this.tick = 0; 43 | } 44 | 45 | run () { 46 | 47 | this.running = 2; 48 | 49 | let demo_header = { 50 | 'filestamp': this.stream.string(8), 51 | 'demo_protocol': this.stream.int32(), 52 | 'network_protocol': this.stream.int32(), 53 | 'server_name': this.stream.string(260), 54 | 'client_name': this.stream.string(260), 55 | 'map_name': this.stream.string(260), 56 | 'game_directory': this.stream.string(260), 57 | 'playback_time': this.stream.float(), 58 | 'playback_ticks': this.stream.int32(), 59 | 'playback_frames': this.stream.int32(), 60 | 'signOnLength': this.stream.int32() 61 | }; 62 | 63 | while(this.running){ 64 | 65 | let command = this.stream.byte(); 66 | if (command === undefined){ 67 | this.running = false; 68 | this.emit('end'); 69 | } 70 | 71 | let tick = this.stream.int32(); 72 | this.tick = tick; 73 | 74 | if(command != Commands.PACKET){ 75 | this.tick = 0; 76 | } 77 | if(this.tick > 0){ 78 | this.emit('tick', this.tick); 79 | } 80 | 81 | this.stream.skip(1); 82 | 83 | switch(command){ 84 | 85 | case Commands.SIGNON: 86 | case Commands.PACKET: 87 | this.parsePacket(); 88 | break; 89 | 90 | case Commands.DATA_TABLES: 91 | this.parseDataTables(); 92 | break; 93 | 94 | case Commands.USER_CMD: 95 | this.stream.skip(4); 96 | this.stream.skip(this.stream.int32()); 97 | break; 98 | 99 | case Commands.STRING_TABLES: 100 | this.parseStringTables(); 101 | break; 102 | 103 | case Commands.STOP: 104 | this.running = false; 105 | this.emit('end'); 106 | break; 107 | 108 | case Commands.CUSTOM: 109 | this.stream.skip(this.stream.int32()); 110 | break; 111 | 112 | case Commands.CONSOLE_CMD: 113 | this.stream.skip(this.stream.int32()); 114 | break; 115 | 116 | } 117 | 118 | if(this.tick > 0){ 119 | this.emit('tick_end', this.tick); 120 | } 121 | 122 | } 123 | 124 | } 125 | 126 | getTick () { 127 | return this.tick; 128 | } 129 | 130 | getTeams () { 131 | return this.getEntities(EntitiesMap.Team); 132 | } 133 | 134 | getPlayers () { 135 | return this.getEntities(EntitiesMap.Player); 136 | } 137 | 138 | getGrenades(){ 139 | return this.getEntities(EntitiesMap.Grenade); 140 | } 141 | 142 | getFlashes(){ 143 | return this.getEntities(EntitiesMap.Flashbang); 144 | } 145 | 146 | getMolotovs(){ 147 | return this.getEntities(EntitiesMap.MolotovGrenade); 148 | } 149 | 150 | getHEGrenades(){ 151 | return this.getEntities(EntitiesMap.HEGrenade); 152 | } 153 | 154 | getSmokeGrenades(){ 155 | return this.getEntities(EntitiesMap.SmokeGrenade); 156 | } 157 | 158 | getRound () { 159 | return this.match.round; 160 | } 161 | 162 | getScores () { 163 | let scores ={}; 164 | this.getTeams().forEach(team =>{ 165 | 166 | let team_number = team.getTeamNumber(); 167 | 168 | if(team_number) 169 | scores[team_number] = team.getScore(); 170 | 171 | }); 172 | return scores; 173 | } 174 | 175 | parsePacket () { 176 | this.stream.skip(160); 177 | let chunkSize = this.stream.int32(); 178 | let offset = this.stream.tell(); 179 | 180 | while(this.stream.tell() < offset + chunkSize){ 181 | 182 | let message = this.parseProtobufMessage('NetMessages'); 183 | if(message != null){ 184 | 185 | switch(message.messageType){ 186 | 187 | case NetMessages.ServerInfo: 188 | this.emit('server_info', message); 189 | break; 190 | 191 | case NetMessages.CreateStringTable: 192 | if(message.name == 'userinfo'){ 193 | this.parseStringTableUpdate(message.data, 194 | message.numEntries, 195 | message.maxEntries, 196 | message.userDataSize, 197 | message.userDataSizeBits, 198 | message.userDataFixedSize); 199 | } 200 | this.stringTables.push({ 201 | name: message.name, 202 | maxEntries: message.maxEntries 203 | }); 204 | break; 205 | 206 | case NetMessages.UpdateStringTable: 207 | let stringTable = this.stringTables[message.tableId]; 208 | if(stringTable != null && stringTable.name == 'userinfo' && message.numChangedEntries < stringTable.maxEntries){ 209 | this.parseStringTableUpdate( 210 | message.data, 211 | message.numChangedEntries, 212 | stringTable.maxEntries, 213 | 0, 0, 0, true 214 | ); 215 | } 216 | break; 217 | 218 | case NetMessages.PacketEntities: 219 | this.handlePacketEntities(message); 220 | break; 221 | 222 | case NetMessages.UserMessage: 223 | 224 | this.emit('user_message', message); 225 | let msg = Protos.getMessage('UserMessages/' + message.userMessageType); 226 | 227 | if(msg){ 228 | 229 | msg = msg.decode(message.data); 230 | this.emit('umsg.' + message.userMessageType, msg); 231 | 232 | } 233 | 234 | break; 235 | 236 | case NetMessages.GameEvent: 237 | this.handleGameEvent(message); 238 | break; 239 | 240 | case NetMessages.GameEventList: 241 | this.handleGameEventsList(message); 242 | break; 243 | 244 | } 245 | 246 | } 247 | 248 | } 249 | 250 | } 251 | 252 | parseProtobufMessage (namespace) { 253 | 254 | let messageType = this.stream.varInt32(); 255 | let message = Protos.getMessage((namespace) ? namespace + '/' + messageType : messageType); 256 | 257 | if(message){ 258 | 259 | message = message.decode(this.stream); 260 | 261 | if(message){ 262 | message.messageType = messageType; 263 | return message; 264 | } 265 | 266 | }else{ 267 | 268 | this.stream.skip(this.stream.varInt32()); 269 | 270 | } 271 | 272 | return null; 273 | } 274 | 275 | findDataTable (name) { 276 | for(let j = 0; j < this.dataTables.length; j++){ 277 | if(this.dataTables[j].netTableName == name){ 278 | return this.dataTables[j]; 279 | } 280 | } 281 | } 282 | 283 | parseStringTableUpdate (stream, numEntries, maxEntries, userDataSize, userDataSizeBits, userDataFixedSize) { 284 | 285 | let lastEntry = -1; 286 | let lastDictionaryIndex = -1; 287 | let nTemp = maxEntries; 288 | let entryBits = 0; 289 | 290 | while(nTemp >>= 1){ 291 | ++entryBits; 292 | } 293 | 294 | if(stream.bits(1)){ 295 | return; 296 | } 297 | 298 | let bitDebug = stream.tellBits(); 299 | 300 | for(let i = 0; i < numEntries; i++){ 301 | 302 | let entryIndex = lastEntry + 1; 303 | 304 | if(!stream.bits(1)){ 305 | entryIndex = stream.bits(entryBits); 306 | } 307 | 308 | lastEntry = entryIndex; 309 | 310 | if(entryIndex < 0 || entryIndex > maxEntries){ 311 | return; 312 | } 313 | 314 | let entry = ''; 315 | if(stream.bits(1)){ 316 | if(stream.bits(1)){ 317 | let index = stream.bits(5); 318 | let bytestocopy = stream.bits(5); 319 | entry = stream.string(100000, true); 320 | } else{ 321 | entry = stream.string(100000, true); 322 | } 323 | } 324 | 325 | let userData = ''; 326 | let size = 0; 327 | 328 | if(stream.bits(1)){ 329 | if(userDataFixedSize){ 330 | size = userDataSizeBits; 331 | } else{ 332 | let sizeBytes = stream.bits(14); 333 | size = sizeBytes * 8; 334 | } 335 | } 336 | 337 | let currentBits = stream.tellBits(); 338 | 339 | if(size > 0){ 340 | let player = this.readPlayer(stream); 341 | this.addPlayer(player, entryIndex); 342 | } 343 | 344 | stream.seekBits(currentBits + size); 345 | 346 | } 347 | } 348 | 349 | parseStringTables () { 350 | let playerIndex = 0; 351 | let size = this.stream.int32(); 352 | let destination = this.stream.tell() + size; 353 | 354 | let numTables = this.stream.byte(); 355 | 356 | for(let i = 0; i < numTables; i++){ 357 | 358 | let tableName = this.stream.string(4096, true); 359 | let isUserTable = this.tableName == 'userinfo'; 360 | let numStrings = this.stream.int16(); 361 | 362 | if(isUserTable){ 363 | //this.players = []; 364 | } 365 | 366 | for(let j = 0; j < numStrings; j++){ 367 | 368 | let string = this.stream.string(4096, true); 369 | 370 | if(this.stream.bits(1)){ 371 | 372 | let userDataSize = this.stream.int16(); 373 | let postReadDest = this.stream.tellBits() + (userDataSize * 8); 374 | 375 | if(isUserTable){ 376 | let player = this.readPlayer(this.stream); 377 | //console.log('adding a player from string table'); 378 | this.addPlayer(player, playerIndex++); 379 | } 380 | this.stream.seekBits(postReadDest); 381 | } 382 | } 383 | if(isUserTable) break; 384 | this.stream.bits(1); 385 | } 386 | 387 | this.stream.seek(destination); 388 | } 389 | 390 | parseDataTables () { 391 | 392 | let size = this.stream.int32(); 393 | let destination = this.stream.tell() + size; 394 | let message = null; 395 | this.dataTables = []; 396 | 397 | while(true){ 398 | message = this.parseProtobufMessage('NetMessages'); 399 | if(message.isEnd){ 400 | break; 401 | } else{ 402 | this.dataTables.push(message); 403 | } 404 | } 405 | 406 | let numServerClasses = this.stream.int16(); 407 | 408 | for(let i = 0; i < numServerClasses; i++){ 409 | 410 | let serverClass ={ 411 | 'classId': this.stream.int16(), 412 | 'name': this.stream.string(256, true), 413 | 'dataTableName': this.stream.string(256, true), 414 | 'flattenedProps': [] 415 | }; 416 | 417 | serverClass.dataTable = this.findDataTable(serverClass.dataTableName); 418 | let excludes = this.gatherExcludes(serverClass.dataTable); 419 | this.gatherProps(serverClass, serverClass.dataTable, excludes); 420 | this.sortProps(serverClass.flattenedProps); 421 | this.serverClasses.push(serverClass); 422 | 423 | } 424 | 425 | this.serverClassBits = 0; 426 | while(numServerClasses >>= 1) ++this.serverClassBits; 427 | this.serverClassBits++; 428 | } 429 | 430 | gatherProps (serverClass, dataTable, excludes, path) { 431 | let tmp = []; 432 | this.iterateProps(serverClass, dataTable, tmp, excludes, path); 433 | for(let i = 0; i < tmp.length; i++){ 434 | serverClass.flattenedProps.push(tmp[i]); 435 | } 436 | } 437 | 438 | iterateProps (serverClass, dataTable, props, excludes, path) { 439 | 440 | path = path || ''; 441 | 442 | if(dataTable.props == null){ 443 | return; 444 | } 445 | 446 | for(let i = 0; i < dataTable.props.length; i++){ 447 | 448 | let prop = dataTable.props[i]; 449 | 450 | if((prop.flags & (1 << 8)) || 451 | (prop.flags & (1 << 6)) || 452 | this.isPropExcluded(dataTable, prop, excludes)){ 453 | continue; 454 | } 455 | 456 | let propPath = prop.varName == 'baseclass' ? 457 | '' : prop.varName; 458 | 459 | if(propPath != '' && path != ''){ 460 | propPath = path + '.' + propPath; 461 | } 462 | 463 | if(prop.type == 6){ 464 | 465 | let subTable = this.findDataTable(prop.dataTableName); 466 | 467 | if(prop.flags & (1 << 11)){ 468 | this.iterateProps(serverClass, subTable, props, excludes, propPath); 469 | } else{ 470 | this.gatherProps(serverClass, subTable, excludes, propPath); 471 | } 472 | 473 | } else if(prop.type == 5){ 474 | 475 | props.push({ 476 | path: propPath, 477 | prop: prop, 478 | elm: dataTable.props[i - 1] 479 | }); 480 | 481 | } else{ 482 | 483 | props.push({ 484 | path: propPath, 485 | prop: prop 486 | }); 487 | 488 | } 489 | } 490 | } 491 | 492 | isPropExcluded (dataTable, prop, excludes) { 493 | 494 | for(let i = 0; i < excludes.length; i++){ 495 | if(dataTable.netTableName == excludes[i].dataTableName && 496 | prop.varName == excludes[i].varName){ 497 | return true; 498 | } 499 | } 500 | 501 | return false; 502 | } 503 | 504 | gatherExcludes (dataTable, excludes) { 505 | 506 | excludes = excludes || []; 507 | 508 | if(dataTable.props != null){ 509 | for(let i = 0; i < dataTable.props.length; i++){ 510 | let prop = dataTable.props[i]; 511 | if(prop.flags & (1 << 6)){ 512 | excludes.push({ 513 | varName: prop.varName, 514 | dataTableName: prop.dataTableName, 515 | netTableName: dataTable.netTableName 516 | }); 517 | } 518 | if(prop.type == 6){ 519 | let subTable = this.findDataTable(prop.dataTableName); 520 | if(subTable != null){ 521 | this.gatherExcludes(subTable, excludes); 522 | } 523 | } 524 | } 525 | } 526 | 527 | return excludes; 528 | } 529 | 530 | sortProps (flattened) { 531 | let priorities = []; 532 | priorities.push(64); 533 | 534 | for(let i = 0; i < flattened.length; i++){ 535 | let prop = flattened[i].prop; 536 | if(priorities.indexOf(prop.priority) == -1){ 537 | priorities.push(prop.priority); 538 | } 539 | } 540 | 541 | priorities.sort(function(a, b){ 542 | return a - b 543 | }); 544 | 545 | let start = 0; 546 | for(let priority_index = 0; priority_index < priorities.length; priority_index++){ 547 | let priority = priorities[priority_index]; 548 | while(true){ 549 | let currentProp = start; 550 | while(currentProp < flattened.length){ 551 | let prop = flattened[currentProp].prop; 552 | if(prop.priority == priority || (priority == 64 && ((1 << 18) & prop.flags))){ 553 | if(start != currentProp){ 554 | let temp = flattened[start]; 555 | flattened[start] = flattened[currentProp]; 556 | flattened[currentProp] = temp; 557 | } 558 | start++; 559 | break; 560 | } 561 | currentProp++; 562 | } 563 | if(currentProp == flattened.length){ 564 | break; 565 | } 566 | } 567 | } 568 | } 569 | 570 | readPlayer (stream) { 571 | stream.skip(16); 572 | let player ={ 573 | 'name': stream.string(128), 574 | 'userId': (function(){ 575 | let val = stream.int32(); 576 | return ((val >> 24) & 0xff) | 577 | ((val << 8) & 0xff0000) | 578 | ((val >> 8) & 0xff00) | 579 | ((val << 24) & 0xff000000); 580 | })(), 581 | 'guid': stream.string(36), 582 | 'fakePlayer': stream.skip(132).bool(), 583 | 'isHLTV': stream.bool() 584 | }; 585 | stream.skip(22); 586 | return player; 587 | } 588 | 589 | addPlayer (player, index) { 590 | if(typeof index !== 'undefined'){ 591 | this.players[index] = player; 592 | }else{ 593 | this.players.push(player); 594 | } 595 | } 596 | 597 | findPlayerById (userId) { 598 | let index = this.findPlayerIndex(userId); 599 | if(index > -1){ 600 | return this.players[index]; 601 | } 602 | return null; 603 | } 604 | 605 | findPlayerIndex (userId) { 606 | for(let i = 0; i < this.players.length; i++){ 607 | if(this.players[i] != null && this.players[i].userId == userId){ 608 | return i; 609 | } 610 | } 611 | return -1; 612 | } 613 | 614 | findPlayerByName (name) { 615 | let players = this.getPlayers(); 616 | for(let i = 0; i < players.length; i++){ 617 | let player = players[i]; 618 | if(player.getName() == name){ 619 | return player; 620 | } 621 | } 622 | } 623 | 624 | handleGameEventsList (message) { 625 | let possible_events = []; 626 | 627 | for(let i = 0; i < message.descriptors.length; i++){ 628 | let descriptor = message.descriptors[i]; 629 | possible_events.push('csgo.' + descriptor.name); 630 | this.gameEventDescriptors[descriptor.eventId] = descriptor; 631 | } 632 | 633 | this.emit('possible_events', possible_events); 634 | } 635 | 636 | handleGameEvent (message) { 637 | 638 | if(this.gameEventDescriptors[message.eventId] != null){ 639 | 640 | let description = this.gameEventDescriptors[message.eventId]; 641 | 642 | let params ={}; 643 | if(description.keys != null){ 644 | for(let i = 0; i < description.keys.length; i++){ 645 | let key = description.keys[i]; 646 | params[key.name] = message.values[i].value; 647 | } 648 | } 649 | 650 | switch(description.name){ 651 | case 'player_connect': 652 | let player ={ 653 | 'name': params.name, 654 | 'userId': params.userid, 655 | 'guid': params.networkid, 656 | 'fakePlayer': params.networkid == 'BOT', 657 | 'isHLTV': false 658 | }; 659 | this.addPlayer(player, params.index); 660 | break; 661 | 662 | case 'player_disconnect': 663 | let entity = this.findEntityByUserId(params.userid); 664 | params ={ 665 | reason: params.reason, 666 | player: entity 667 | } 668 | break; 669 | 670 | case 'round_start': 671 | this.match.round = 1; 672 | this.getTeams().forEach(team =>{ 673 | 674 | let team_number = team.getTeamNumber(); 675 | 676 | if(team_number) 677 | this.match.round += team.getScore(); 678 | 679 | }); 680 | break; 681 | 682 | } 683 | 684 | if(params.userid){ 685 | params.player = this.findEntityByUserId(params.userid); 686 | delete params.userid; 687 | } 688 | 689 | if(params.attacker){ 690 | params.attacker = this.findEntityByUserId(params.attacker); 691 | } 692 | 693 | if(params.assister){ 694 | params.assister = this.findEntityByUserId(params.assister); 695 | } 696 | 697 | this.emit('event', 'csgo.' + description.name); 698 | this.emit('csgo.' + description.name, params); 699 | 700 | if(description.name == 'player_disconnect'){ 701 | if(typeof player != 'undefined'){ 702 | player.info.connected = false; 703 | player.info.userId = -1; 704 | } 705 | } 706 | } 707 | 708 | } 709 | 710 | handlePacketEntities (message) { 711 | let entity = null; 712 | let headerCount = message.updatedEntities; 713 | let updateFlags = 0; 714 | let headerBase = -1; 715 | let entityId = -1; 716 | let updateType = PacketEntities.PRESERVE_ENT; 717 | let data = message.entityData; 718 | while(updateType < 4){ 719 | let isEntity = --headerCount >= 0; 720 | if(isEntity){ 721 | updateFlags = 0; 722 | entityId = headerBase + 1 + data.uBitVar(); 723 | headerBase = entityId; 724 | if(!data.bits(1)){ 725 | if(data.bits(1)){ 726 | updateFlags |= 4; 727 | } 728 | } else{ 729 | updateFlags |= 1; 730 | if(data.bits(1)){ 731 | updateFlags |= 2; 732 | } 733 | } 734 | } 735 | for(updateType = PacketEntities.PRESERVE_ENT; updateType == PacketEntities.PRESERVE_ENT;){ 736 | if(!isEntity || entityId > 9999){ 737 | updateType = 4; 738 | } else{ 739 | if(updateFlags & 4){ 740 | updateType = PacketEntities.PVS_ENTER; 741 | } else if(updateFlags & 1){ 742 | updateType = PacketEntities.PVS_LEAVE; 743 | } else{ 744 | updateType = PacketEntities.DELTA_ENT; 745 | } 746 | } 747 | switch(updateType){ 748 | case PacketEntities.PVS_ENTER: 749 | let classIndex = data.bits(this.serverClassBits); 750 | let serialNum = data.bits(10); 751 | entity = this.addEntity(entityId, this.serverClasses[classIndex], serialNum); 752 | let paths = entity.readFromStream(data); 753 | this.emit('entity_added', entity); 754 | this.emit('entity_updated', entity, paths); 755 | break; 756 | case PacketEntities.PVS_LEAVE: 757 | let removed = this.removeEntity(entityId); 758 | this.emit('entity_removed', removed); 759 | break; 760 | case PacketEntities.DELTA_ENT: 761 | entity = this.findEntityById(entityId); 762 | if(entity != null){ 763 | let paths = entity.readFromStream(data); 764 | this.emit('entity_updated', entity, paths); 765 | } else{ 766 | console.log('cant find entity ' + entityId); 767 | return; 768 | } 769 | break; 770 | } 771 | } 772 | } 773 | } 774 | 775 | getEntities (entityClass) { 776 | let selectedEntities = []; 777 | for(let i = 0; i < this.entities.length; i++){ 778 | let entity = this.entities[i]; 779 | if(entityClass == null || 780 | entity.classInfo.dataTableName == entityClass || 781 | (typeof entityClass == 'function' && 782 | entity instanceof entityClass)){ 783 | 784 | selectedEntities.push(entity); 785 | } 786 | } 787 | return selectedEntities; 788 | } 789 | 790 | addEntity (entityId, classInfo, serialNumber) { 791 | let entity = this.findEntityById(entityId); 792 | if(entity == null){ 793 | 794 | if(typeof this.d == 'undefined') 795 | this.d ={}; 796 | 797 | if(typeof this.d[classInfo.dataTableName] == 'undefined') 798 | this.d[classInfo.dataTableName] = 1; 799 | else this.d[classInfo.dataTableName]++; 800 | 801 | let entity_class; 802 | if(classInfo.dataTableName.substr(0, 9) === 'DT_Weapon'){ 803 | entity_class = 'Weapon'; 804 | }else entity_class = classInfo.dataTableName.replace('DT_CS', '').replace('DT_', ''); 805 | 806 | if(typeof EntitiesMap[entity_class] != 'undefined'){ 807 | entity_class = EntitiesMap[entity_class]; 808 | }else{ 809 | entity_class = Entity; 810 | } 811 | 812 | entity = new entity_class(this); 813 | entity.entityId = entityId; 814 | this.entities.push(entity); 815 | } 816 | if(entity instanceof EntitiesMap.Player){ 817 | entity.info = this.players[entity.entityId - 1]; 818 | } 819 | entity.classInfo = classInfo; 820 | entity.serialNumber = serialNumber; 821 | return entity; 822 | } 823 | 824 | removeEntity (entityId) { 825 | let i = this.entities.length; 826 | while(i--){ 827 | if(this.entities[i].entityId == entityId){ 828 | return this.entities.splice(i, 1)[0]; 829 | } 830 | } 831 | } 832 | 833 | findEntityById (entityId) { 834 | for(let i = 0; i < this.entities.length; i++){ 835 | if(this.entities[i].entityId == entityId){ 836 | return this.entities[i]; 837 | } 838 | } 839 | } 840 | 841 | findEntityByUserId (userId) { 842 | let index = this.findPlayerIndex(userId); 843 | if(index > -1){ 844 | return this.findEntityById(index + 1); 845 | } 846 | return null; 847 | } 848 | 849 | } 850 | 851 | module.exports = Demo; --------------------------------------------------------------------------------