1
0
Fork 0
mirror of https://github.com/demostf/demo.js synced 2026-06-04 09:04:13 +02:00

propper skipping of TempEntities

This commit is contained in:
Robin Appelman 2016-12-19 00:21:52 +01:00
commit 1fbb61f252
11 changed files with 255 additions and 163 deletions

View file

@ -17,22 +17,23 @@ export class Match {
sendTables: SendTable[]; sendTables: SendTable[];
instanceBaselines: SendProp[][][]; instanceBaselines: SendProp[][][];
staticBaseLines: any[]; staticBaseLines: any[];
_classBits: number = 0
constructor() { constructor() {
this.tick = 0; this.tick = 0;
this.chat = []; this.chat = [];
this.users = {}; this.users = {};
this.deaths = []; this.deaths = [];
this.rounds = []; this.rounds = [];
this.startTick = 0; this.startTick = 0;
this.intervalPerTick = 0; this.intervalPerTick = 0;
this.entities = []; this.entities = [];
this.stringTables = []; this.stringTables = [];
this.sendTables = []; this.sendTables = [];
this.serverClasses = []; this.serverClasses = [];
this.entities = []; this.entities = [];
this.instanceBaselines = [[], []]; this.instanceBaselines = [[], []];
this.staticBaseLines = []; this.staticBaseLines = [];
} }
getSendTable(name) { getSendTable(name) {
@ -55,11 +56,11 @@ export class Match {
getState() { getState() {
return { return {
'chat': this.chat, 'chat': this.chat,
'users': this.users, 'users': this.users,
'deaths': this.deaths, 'deaths': this.deaths,
'rounds': this.rounds, 'rounds': this.rounds,
'startTick': this.startTick, 'startTick': this.startTick,
'intervalPerTick': this.intervalPerTick 'intervalPerTick': this.intervalPerTick
}; };
} }
@ -88,11 +89,11 @@ export class Match {
if (packet.tables.userinfo) { if (packet.tables.userinfo) {
for (var j = 0; j < packet.tables.userinfo.length; j++) { for (var j = 0; j < packet.tables.userinfo.length; j++) {
if (packet.tables.userinfo[j].extraData) { if (packet.tables.userinfo[j].extraData) {
var name = packet.tables.userinfo[j].extraData[0]; var name = packet.tables.userinfo[j].extraData[0];
var steamId = packet.tables.userinfo[j].extraData[2]; var steamId = packet.tables.userinfo[j].extraData[2];
var userId = packet.tables.userinfo[j].extraData[1].charCodeAt(0); var userId = packet.tables.userinfo[j].extraData[1].charCodeAt(0);
userState = this.getUserState(userId); userState = this.getUserState(userId);
userState.name = name; userState.name = name;
userState.steamId = steamId; userState.steamId = steamId;
} }
} }
@ -113,24 +114,24 @@ export class Match {
packet.event.values.userid -= 256; packet.event.values.userid -= 256;
} }
this.deaths.push({ this.deaths.push({
killer: packet.event.values.attacker, killer: packet.event.values.attacker,
assister: assister, assister: assister,
victim: packet.event.values.userid, victim: packet.event.values.userid,
weapon: packet.event.values.weapon, weapon: packet.event.values.weapon,
tick: this.tick tick: this.tick
}); });
break; break;
case 'teamplay_round_win': case 'teamplay_round_win':
if (packet.event.values.winreason !== 6) {// 6 = timelimit if (packet.event.values.winreason !== 6) {// 6 = timelimit
this.rounds.push({ this.rounds.push({
winner: packet.event.values.team === 2 ? 'red' : 'blue', winner: packet.event.values.team === 2 ? 'red' : 'blue',
length: packet.event.values.round_time, length: packet.event.values.round_time,
end_tick: this.tick end_tick: this.tick
}); });
} }
break; break;
case 'player_spawn': case 'player_spawn':
userId = packet.event.values.userid; userId = packet.event.values.userid;
userState = this.getUserState(userId); userState = this.getUserState(userId);
if (!userState.team) { //only register first spawn if (!userState.team) { //only register first spawn
userState.team = packet.event.values.team === 2 ? 'red' : 'blue' userState.team = packet.event.values.team === 2 ? 'red' : 'blue'
@ -154,8 +155,8 @@ export class Match {
} }
if (!this.users[userId]) { if (!this.users[userId]) {
this.users[userId] = { this.users[userId] = {
name: null, name: null,
userId: userId, userId: userId,
steamId: null, steamId: null,
classes: {} classes: {}
} }

View file

@ -10,18 +10,20 @@ export class SendPropDefinition {
table: SendTable|null; table: SendTable|null;
numElements: number|null; numElements: number|null;
arrayProperty: SendPropDefinition|null; arrayProperty: SendPropDefinition|null;
ownerTableName: string;
constructor(type, name, flags) { constructor(type, name, flags, ownerTableName) {
this.type = type; this.type = type;
this.name = name; this.name = name;
this.flags = flags; this.flags = flags;
this.excludeDTName = null; this.excludeDTName = null;
this.lowValue = 0; this.lowValue = 0;
this.highValue = 0; this.highValue = 0;
this.bitCount = 0; this.bitCount = 0;
this.table = null; this.table = null;
this.numElements = null; this.numElements = null;
this.arrayProperty = null; this.arrayProperty = null;
this.ownerTableName = ownerTableName;
} }
hasFlag(flag: SendPropFlag) { hasFlag(flag: SendPropFlag) {
@ -33,11 +35,22 @@ export class SendPropDefinition {
} }
inspect() { inspect() {
return { let data: any = {
name: this.name, fromTable: this.ownerTableName,
type: SendPropType[this.type], name: this.name,
flags: SendPropDefinition.formatFlags(this.flags) type: SendPropType[this.type],
flags: SendPropDefinition.formatFlags(this.flags),
bitCount: this.bitCount
};
if (this.type === SendPropType.DPT_Float) {
data.lowValue = this.lowValue;
data.highValue = this.highValue;
} }
if (this.type === SendPropType.DPT_DataTable && this.table) {
data.tableName = this.table.name;
}
return data;
} }
static formatFlags(flags: number) { static formatFlags(flags: number) {
@ -67,27 +80,27 @@ export enum SendPropType {
export enum SendPropFlag { export enum SendPropFlag {
SPROP_UNSIGNED = (1 << 0),// Unsigned integer data. SPROP_UNSIGNED = (1 << 0),// Unsigned integer data.
SPROP_COORD = (1 << 1),// If this is set, the float/vector is treated like a world coordinate. SPROP_COORD = (1 << 1),// If this is set, the float/vector is treated like a world coordinate.
// Note that the bit count is ignored in this case. // Note that the bit count is ignored in this case.
SPROP_NOSCALE = (1 << 2),// For floating point, don't scale into range, just take value as is. SPROP_NOSCALE = (1 << 2),// For floating point, don't scale into range, just take value as is.
SPROP_ROUNDDOWN = (1 << 3),// For floating point, limit high value to range minus one bit unit SPROP_ROUNDDOWN = (1 << 3),// For floating point, limit high value to range minus one bit unit
SPROP_ROUNDUP = (1 << 4),// For floating point, limit low value to range minus one bit unit SPROP_ROUNDUP = (1 << 4),// For floating point, limit low value to range minus one bit unit
SPROP_NORMAL = (1 << 5),// If this is set, the vector is treated like a normal (only valid for vectors) SPROP_NORMAL = (1 << 5),// If this is set, the vector is treated like a normal (only valid for vectors)
SPROP_EXCLUDE = (1 << 6),// This is an exclude prop (not excludED, but it points at another prop to be excluded). SPROP_EXCLUDE = (1 << 6),// This is an exclude prop (not excludED, but it points at another prop to be excluded).
SPROP_XYZE = (1 << 7),// Use XYZ/Exponent encoding for vectors. SPROP_XYZE = (1 << 7),// Use XYZ/Exponent encoding for vectors.
SPROP_INSIDEARRAY = (1 << 8),// This tells us that the property is inside an array, so it shouldn't be put into the SPROP_INSIDEARRAY = (1 << 8),// This tells us that the property is inside an array, so it shouldn't be put into the
// flattened property list. Its array will point at it when it needs to. // flattened property list. Its array will point at it when it needs to.
SPROP_PROXY_ALWAYS_YES = (1 << 9),// Set for datatable props using one of the default datatable proxies like SPROP_PROXY_ALWAYS_YES = (1 << 9),// Set for datatable props using one of the default datatable proxies like
// SendProxy_DataTableToDataTable that always send the data to all clients. // SendProxy_DataTableToDataTable that always send the data to all clients.
SPROP_CHANGES_OFTEN = (1 << 10),// this is an often changed field, moved to head of sendtable so it gets a small index SPROP_CHANGES_OFTEN = (1 << 10),// this is an often changed field, moved to head of sendtable so it gets a small index
SPROP_IS_A_VECTOR_ELEM = (1 << 11),// Set automatically if SPROP_VECTORELEM is used. SPROP_IS_A_VECTOR_ELEM = (1 << 11),// Set automatically if SPROP_VECTORELEM is used.
SPROP_COLLAPSIBLE = (1 << 12),// Set automatically if it's a datatable with an offset of 0 that doesn't change the pointer SPROP_COLLAPSIBLE = (1 << 12),// Set automatically if it's a datatable with an offset of 0 that doesn't change the pointer
// (ie: for all automatically-chained base classes). // (ie: for all automatically-chained base classes).
// In this case, it can get rid of this SendPropDataTable altogether and spare the // In this case, it can get rid of this SendPropDataTable altogether and spare the
// trouble of walking the hierarchy more than necessary. // trouble of walking the hierarchy more than necessary.
SPROP_COORD_MP = (1 << 13),// Like SPROP_COORD, but special handling for multiplayer games SPROP_COORD_MP = (1 << 13),// Like SPROP_COORD, but special handling for multiplayer games
SPROP_COORD_MP_LOWPRECISION = (1 << 14),// Like SPROP_COORD, but special handling for multiplayer games where the fractional component only gets a 3 bits instead of 5 SPROP_COORD_MP_LOWPRECISION = (1 << 14),// Like SPROP_COORD, but special handling for multiplayer games where the fractional component only gets a 3 bits instead of 5
SPROP_COORD_MP_INTEGRAL = (1 << 15),// SPROP_COORD_MP, but coordinates are rounded to integral boundaries SPROP_COORD_MP_INTEGRAL = (1 << 15),// SPROP_COORD_MP, but coordinates are rounded to integral boundaries
SPROP_VARINT = (1 << 5) SPROP_VARINT = (1 << 5)
} }

View file

@ -6,8 +6,8 @@ export class SendTable {
private _flattenedProps: SendPropDefinition[]; private _flattenedProps: SendPropDefinition[];
constructor(name) { constructor(name) {
this.name = name; this.name = name;
this.props = []; this.props = [];
this._flattenedProps = []; this._flattenedProps = [];
} }
@ -16,8 +16,8 @@ export class SendTable {
} }
flatten() { flatten() {
let excludes = []; let excludes: SendPropDefinition[] = [];
let props: SendPropDefinition[] = []; let props: SendPropDefinition[] = [];
this.getAllProps(excludes, props); this.getAllProps(excludes, props);
// sort often changed props before the others // sort often changed props before the others
@ -25,8 +25,8 @@ export class SendTable {
for (let i = 0; i < props.length; i++) { for (let i = 0; i < props.length; i++) {
if (props[i].hasFlag(SendPropFlag.SPROP_CHANGES_OFTEN)) { if (props[i].hasFlag(SendPropFlag.SPROP_CHANGES_OFTEN)) {
if (i != start) { if (i != start) {
const temp = props[i]; const temp = props[i];
props[i] = props[start]; props[i] = props[start];
props[start] = temp; props[start] = temp;
} }
start++; start++;
@ -35,7 +35,7 @@ export class SendTable {
this._flattenedProps = props; this._flattenedProps = props;
} }
getAllProps(excludes: SendTable[], props: SendPropDefinition[]) { getAllProps(excludes: SendPropDefinition[], props: SendPropDefinition[]) {
let localProps = []; let localProps = [];
this.getAllPropsIteratorProps(excludes, localProps, props); this.getAllPropsIteratorProps(excludes, localProps, props);
for (let i = 0; i < localProps.length; i++) { for (let i = 0; i < localProps.length; i++) {
@ -43,18 +43,23 @@ export class SendTable {
} }
} }
getAllPropsIteratorProps(excludes: SendTable[], props: SendPropDefinition[], childProps: SendPropDefinition[]) { getAllPropsIteratorProps(excludes: SendPropDefinition[], props: SendPropDefinition[], childProps: SendPropDefinition[]) {
for (let i = 0; i < this.props.length; i++) { for (let i = 0; i < this.props.length; i++) {
const prop = this.props[i]; const prop = this.props[i];
if (prop.hasFlag(SendPropFlag.SPROP_EXCLUDE) || excludes.indexOf(prop) !== -1) {
continue;
}
if (excludes.filter((exclude) => {
return prop.table && exclude.name == prop.name && exclude.excludeDTName == prop.table.name;
}).length > 0) {
continue;
}
if (prop.type === SendPropType.DPT_DataTable && prop.table) { if (prop.type === SendPropType.DPT_DataTable && prop.table) {
if (prop.hasFlag(SendPropFlag.SPROP_EXCLUDE)) { if (prop.hasFlag(SendPropFlag.SPROP_COLLAPSIBLE)) {
excludes.push(prop.table); prop.table.getAllPropsIteratorProps(excludes, props, childProps);
} else if (excludes.indexOf(this) === -1) { } else {
if (prop.hasFlag(SendPropFlag.SPROP_COLLAPSIBLE)) { prop.table.getAllProps(excludes, childProps);
prop.table.getAllPropsIteratorProps(excludes, props, childProps);
} else {
prop.table.getAllProps(excludes, childProps);
}
} }
} else if (!prop.hasFlag(SendPropFlag.SPROP_EXCLUDE)) { } else if (!prop.hasFlag(SendPropFlag.SPROP_EXCLUDE)) {
props.push(prop); props.push(prop);

View file

@ -8,22 +8,22 @@ export class DataTable extends Parser {
// https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_common_eng.cpp#L356 // https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_common_eng.cpp#L356
// https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_recv_eng.cpp#L310 // https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_recv_eng.cpp#L310
// https://github.com/PazerOP/DemoLib/blob/master/DemoLib/Commands/DemoDataTablesCommand.cs // https://github.com/PazerOP/DemoLib/blob/master/DemoLib/Commands/DemoDataTablesCommand.cs
var tables:SendTable[] = []; let tables:SendTable[] = [];
var i, j; let i, j;
while (this.stream.readBoolean()) { while (this.stream.readBoolean()) {
var needsDecoder = this.stream.readBoolean(); const needsDecoder = this.stream.readBoolean();
var tableName = this.stream.readASCIIString(); const tableName = this.stream.readASCIIString();
var numProps = this.stream.readBits(10); const numProps = this.stream.readBits(10);
var table = new SendTable(tableName); const table = new SendTable(tableName);
// get props metadata // get props metadata
var arrayElementProp; let arrayElementProp;
for (i = 0; i < numProps; i++) { for (i = 0; i < numProps; i++) {
var propType = this.stream.readBits(5); const propType = this.stream.readBits(5);
var propName = this.stream.readASCIIString(); const propName = this.stream.readASCIIString();
var nFlagsBits = 16; // might be 11 (old?), 13 (new?), 16(networked) or 17(??) const nFlagsBits = 16; // might be 11 (old?), 13 (new?), 16(networked) or 17(??)
var flags = this.stream.readBits(nFlagsBits); const flags = this.stream.readBits(nFlagsBits);
var prop = new SendPropDefinition(propType, propName, flags); const prop = new SendPropDefinition(propType, propName, flags, tableName);
if (propType === SendPropType.DPT_DataTable) { if (propType === SendPropType.DPT_DataTable) {
prop.excludeDTName = this.stream.readASCIIString(); prop.excludeDTName = this.stream.readASCIIString();
} else { } else {
@ -57,6 +57,12 @@ export class DataTable extends Parser {
} }
if (prop.hasFlag(SendPropFlag.SPROP_INSIDEARRAY)) { if (prop.hasFlag(SendPropFlag.SPROP_INSIDEARRAY)) {
if (arrayElementProp) {
throw new Error("array element already set");
}
if (prop.hasFlag(SendPropFlag.SPROP_CHANGES_OFTEN)) {
throw new Error("unexpected CHANGES_OFTEN prop in array");
}
arrayElementProp = prop; arrayElementProp = prop;
} else { } else {
table.addProp(prop); table.addProp(prop);
@ -76,23 +82,23 @@ export class DataTable extends Parser {
} }
} }
var serverClasses = this.stream.readUint16(); // short const serverClasses = this.stream.readUint16(); // short
if (serverClasses <= 0) { if (serverClasses <= 0) {
throw "expected one or more serverclasses"; throw "expected one or more serverclasses";
} }
for (i = 0; i < serverClasses; i++) { for (i = 0; i < serverClasses; i++) {
var classId = this.stream.readUint16(); const classId = this.stream.readUint16();
if (classId > serverClasses) { if (classId > serverClasses) {
throw "invalid class id"; throw "invalid class id";
} }
var className = this.stream.readASCIIString(); const className = this.stream.readASCIIString();
var dataTable = this.stream.readASCIIString(); const dataTable = this.stream.readASCIIString();
this.match.serverClasses.push(new ServerClass(classId, className, dataTable)); this.match.serverClasses.push(new ServerClass(classId, className, dataTable));
} }
var bitsLeft = (this.length * 8) - this.stream._index; const bitsLeft = (this.length * 8) - this.stream._index;
if (bitsLeft > 7) { if (bitsLeft > 7 || bitsLeft < 0) {
throw "unexpected remaining data in datatable (" + bitsLeft + " bits)"; throw "unexpected remaining data in datatable (" + bitsLeft + " bits)";
} }

View file

@ -13,8 +13,11 @@ import {SetConVar} from '../Packet/SetConVar';
import {UpdateStringTable} from '../Packet/UpdateStringTable'; import {UpdateStringTable} from '../Packet/UpdateStringTable';
import {UserMessage} from '../Packet/UserMessage'; import {UserMessage} from '../Packet/UserMessage';
import {PacketParserMap} from '../Packet/Parser' import {PacketParserMap} from '../Packet/Parser'
import {TempEntities} from '../Packet/TempEntities'
import {GameEventDefinitionMap} from "../../Data/GameEvent"; import {GameEventDefinitionMap} from "../../Data/GameEvent";
import {Packet as IPacket} from '../../Data/Packet'; import {Packet as IPacket} from '../../Data/Packet';
// https://code.google.com/p/coldemoplayer/source/browse/branches/2.0/compLexity+Demo+Player/CDP.Source/Messages/?r=219 // https://code.google.com/p/coldemoplayer/source/browse/branches/2.0/compLexity+Demo+Player/CDP.Source/Messages/?r=219
@ -23,15 +26,10 @@ import {Packet as IPacket} from '../../Data/Packet';
// https://github.com/LestaD/SourceEngine2007/blob/master/src_main/common/netmessages.cpp // https://github.com/LestaD/SourceEngine2007/blob/master/src_main/common/netmessages.cpp
export class Packet extends Parser { export class Packet extends Parser {
viewOrigin: any;
parse() { parse() {
//var table = new PacketStringTable(this.stream);
//table.searchIds();
//return [];
let packets: IPacket[] = []; let packets: IPacket[] = [];
let entities = []; let entities = [];
let lastPacketType = 0;
while (this.bitsLeft > 6) { // last 6 bits for NOOP while (this.bitsLeft > 6) { // last 6 bits for NOOP
const type = this.stream.readBits(6); const type = this.stream.readBits(6);
if (type !== 0) { if (type !== 0) {
@ -39,8 +37,9 @@ export class Packet extends Parser {
let packet = Packet.parsers[type].call(this, this.stream, Packet.gameEventMap, entities, this.match); let packet = Packet.parsers[type].call(this, this.stream, Packet.gameEventMap, entities, this.match);
packets.push(packet); packets.push(packet);
} else { } else {
throw 'Unknown packet type ' + type; throw new Error('Unknown packet type ' + type + " just parsed a " + PacketType[lastPacketType]);
} }
lastPacketType = type;
} }
} }
return packets; return packets;
@ -51,13 +50,13 @@ export class Packet extends Parser {
} }
static parsers: PacketParserMap = { static parsers: PacketParserMap = {
2: ParserGenerator.make('file', 'transferId{32}fileName{s}requested{b}'), 2: ParserGenerator.make('file', 'transferId{32}fileName{s}requested{b}'),
3: ParserGenerator.make('netTick', 'tick{32}frameTime{16}stdDev{16}'), 3: ParserGenerator.make('netTick', 'tick{32}frameTime{16}stdDev{16}'),
4: ParserGenerator.make('stringCmd', 'command{s}'), 4: ParserGenerator.make('stringCmd', 'command{s}'),
5: SetConVar, 5: SetConVar,
6: ParserGenerator.make('sigOnState', 'state{8}count{32}'), 6: ParserGenerator.make('sigOnState', 'state{8}count{32}'),
7: ParserGenerator.make('print', 'value{s}'), 7: ParserGenerator.make('print', 'value{s}'),
8: ParserGenerator.make('serverInfo', 8: ParserGenerator.make('serverInfo',
'version{16}serverCount{32}stv{b}dedicated{b}maxCrc{32}maxClasses{16}' + 'version{16}serverCount{32}stv{b}dedicated{b}maxCrc{32}maxClasses{16}' +
'mapHash{128}playerCount{8}maxPlayerCount{8}intervalPerTick{f32}platform{s1}' + 'mapHash{128}playerCount{8}maxPlayerCount{8}intervalPerTick{f32}platform{s1}' +
'game{s}map{s}skybox{s}serverName{s}replay{b}'), 'game{s}map{s}skybox{s}serverName{s}replay{b}'),
@ -75,7 +74,7 @@ export class Packet extends Parser {
24: EntityMessage, 24: EntityMessage,
25: GameEvent, 25: GameEvent,
26: PacketEntities, 26: PacketEntities,
27: ParserGenerator.make('tempEntities', 'count{8}length{17}_{$length}'), 27: TempEntities,
28: ParserGenerator.make('preFetch', 'index{14}'), 28: ParserGenerator.make('preFetch', 'index{14}'),
29: ParserGenerator.make('menu', 'type{16}length{16}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}'),//length*8 29: ParserGenerator.make('menu', 'type{16}length{16}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}'),//length*8
30: GameEventList, 30: GameEventList,
@ -85,3 +84,33 @@ export class Packet extends Parser {
static gameEventMap: GameEventDefinitionMap = {}; static gameEventMap: GameEventDefinitionMap = {};
} }
enum PacketType {
file = 2,
netTick = 3,
stringSmd = 4,
setConVar = 5,
sigOnState = 6,
print = 7,
serverInfo = 8,
classInfo = 10,
setPause = 11,
createStringTable = 12,
updateStringTable = 13,
voiceInit = 14,
voiceData = 15,
parseSounds = 17,
setView = 18,
fixAngle = 19,
bspDecal = 21,
userMessage = 23,
entityMessage = 24,
gameEvent = 25,
packetEntities = 26,
tempEntities = 27,
preFetch = 28,
menu = 29,
gameEventList = 30,
getCvarValue = 30,
cmdKeyValues = 32
}

View file

@ -41,7 +41,7 @@ export function BSPDecal(stream: BitStream): Packet { // 21: BSPDecal
} }
const lowPriority = !!stream.readBits(1); const lowPriority = !!stream.readBits(1);
return { return {
packetType: 'BSPDecal', packetType: 'bspDecal',
position: position, position: position,
textureIndex: textureIndex, textureIndex: textureIndex,
entIndex: entIndex, entIndex: entIndex,

View file

@ -5,6 +5,7 @@ import {Packet} from "../../Data/Packet";
import {BitStream} from 'bit-buffer'; import {BitStream} from 'bit-buffer';
import {GameEventDefinition} from "../../Data/GameEvent"; import {GameEventDefinition} from "../../Data/GameEvent";
import {Match} from "../../Data/Match"; import {Match} from "../../Data/Match";
import {readUBitVar} from "../readBitVar";
enum PVS { enum PVS {
PRESERVE = 0, PRESERVE = 0,
@ -13,7 +14,7 @@ enum PVS {
DELETE = 4 DELETE = 4
} }
function readPVSType(stream: BitStream): number { function readPVSType(stream: BitStream): PVS {
// https://github.com/skadistats/smoke/blob/a2954fbe2fa3936d64aee5b5567be294fef228e6/smoke/io/stream/entity.pyx#L24 // https://github.com/skadistats/smoke/blob/a2954fbe2fa3936d64aee5b5567be294fef228e6/smoke/io/stream/entity.pyx#L24
let pvs; let pvs;
const hi = stream.readBoolean(); const hi = stream.readBoolean();
@ -30,7 +31,7 @@ function readPVSType(stream: BitStream): number {
return pvs; return pvs;
} }
function readEnterPVS(stream: BitStream, entityId: number, match: Match, baseLine: number):Entity { function readEnterPVS(stream: BitStream, entityId: number, match: Match, baseLine: number): Entity {
// https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs#L198 // https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs#L198
const serverClass = match.serverClasses[stream.readBits(match.classBits)]; const serverClass = match.serverClasses[stream.readBits(match.classBits)];
console.log(serverClass); console.log(serverClass);
@ -84,7 +85,7 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
const length = stream.readBits(20); const length = stream.readBits(20);
const updatedBaseLine = stream.readBoolean(); const updatedBaseLine = stream.readBoolean();
const end = stream._index + length; const end = stream._index + length;
let entityId = -1 let entityId = -1;
stream._index = end; stream._index = end;
return { return {
@ -106,23 +107,25 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
const diff = readUBitVar(stream); const diff = readUBitVar(stream);
entityId += 1 + diff; entityId += 1 + diff;
const pvs = readPVSType(stream); const pvs = readPVSType(stream);
console.log("entity: " + entityId, ", pvs " + PVS[pvs]);
if (pvs === PVS.ENTER) { if (pvs === PVS.ENTER) {
const entity = readEnterPVS(stream, entityId, match, baseLine); const entity = readEnterPVS(stream, entityId, match, baseLine);
applyEntityUpdate(entity, stream); applyEntityUpdate(entity, stream);
match.entities[entityId] = entity; match.entities[entityId] = entity;
if (updatedBaseLine) { if (updatedBaseLine) {
const newBaseLine:SendProp[] = []; const newBaseLine: SendProp[] = [];
newBaseLine.concat(entity.props); newBaseLine.concat(entity.props);
match.instanceBaselines[baseLine][entityId] = newBaseLine; match.instanceBaselines[baseLine][entityId] = newBaseLine;
} }
entity.inPVS = true; entity.inPVS = true;
// stream.readBits(1);
} else if (pvs === PVS.PRESERVE) { } else if (pvs === PVS.PRESERVE) {
const entity = match.entities[entityId]; const entity = match.entities[entityId];
if (entity) { if (entity) {
applyEntityUpdate(entity, stream); applyEntityUpdate(entity, stream);
} else { } else {
console.log(entityId, match.entities.length); console.log( entityId, match.entities.length);
throw new Error("unknown entity"); throw new Error("unknown entity");
} }
} else { } else {
@ -157,13 +160,13 @@ const readFieldIndex = function (stream: BitStream, lastIndex: number): number {
}; };
const applyEntityUpdate = function (entity: Entity, stream: BitStream): Entity { const applyEntityUpdate = function (entity: Entity, stream: BitStream): Entity {
let index = -1; let index = -1;
const allProps = entity.sendTable.flattenedProps; const allProps = entity.sendTable.flattenedProps;
let changedProps: SendProp[] = [];
while ((index = readFieldIndex(stream, index)) != -1) { while ((index = readFieldIndex(stream, index)) != -1) {
if (index > 4096) { if (index > 4096) {
throw new Error('prop index out of bounds'); throw new Error('prop index out of bounds');
} }
console.log(index);
const propDefinition = allProps[index]; const propDefinition = allProps[index];
const existingProp = entity.getPropByDefinition(propDefinition); const existingProp = entity.getPropByDefinition(propDefinition);
let prop; let prop;
@ -172,26 +175,18 @@ const applyEntityUpdate = function (entity: Entity, stream: BitStream): Entity {
} else { } else {
prop = new SendProp(propDefinition); prop = new SendProp(propDefinition);
} }
prop.value = SendPropParser.decode(propDefinition, stream); // prop.value = SendPropParser.decode(propDefinition, stream);
console.log(prop); // console.log(prop);
changedProps.push(prop);
if (!existingProp) { if (!existingProp) {
entity.props.push(prop); entity.props.push(prop);
} }
} }
for (let i = 0; i < changedProps.length; i++) {
const prop = changedProps[i];
prop.value = SendPropParser.decode(prop.definition, stream);
console.log(prop);
}
return entity; return entity;
}; };
const readUBitVar = function (stream: BitStream): number {
switch (stream.readBits(2)) {
case 0:
return stream.readBits(4);
case 1:
return stream.readBits(8);
case 2:
return stream.readBits(12);
case 3:
return stream.readBits(32);
}
throw new Error('Invalid var bit');
};

View file

@ -1,32 +1,32 @@
import {Parser} from './Parser'; import {Parser} from './Parser';
export function make(name: string, definition: string): Parser { export function make(name: string, definition: string): Parser {
var parts = definition.substr(0, definition.length - 1).split('}');//remove leading } to prevent empty part const parts = definition.substr(0, definition.length - 1).split('}');//remove leading } to prevent empty part
var items = parts.map(function (part) { const items = parts.map(function (part) {
return part.split('{'); return part.split('{');
}); });
return function (stream) { return function (stream) {
var result = { let result = {
'packetType': name 'packetType': name
}; };
try { try {
for (var i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
var value = readItem(stream, items[i][1], result); const value = readItem(stream, items[i][1], result);
if (items[i][0] !== '_') { if (items[i][0] !== '_') {
result[items[i][0]] = value; result[items[i][0]] = value;
} }
} }
} catch (e) { } catch (e) {
throw 'Failed reading pattern ' + definition + '. ' + e; throw new Error('Failed reading pattern ' + definition + '. ' + e);
} }
return result; return result;
} }
} }
const readItem = function (stream, description, data) { const readItem = function (stream, description, data) {
var length; let length;
if (description[0] === 'b') { if (description[0] === 'b') {
return !!stream.readBits(1); return stream.readBoolean();
} else if (description[0] === 's') { } else if (description[0] === 's') {
if (description.length === 1) { if (description.length === 1) {
return stream.readUTF8String(); return stream.readUTF8String();
@ -40,8 +40,8 @@ const readItem = function (stream, description, data) {
length = parseInt(description.substr(1), 10); length = parseInt(description.substr(1), 10);
return stream.readBits(length); return stream.readBits(length);
} else if (description[0] === '$') { } else if (description[0] === '$') {
var variable = description.substr(1); const variable = description.substr(1);
return stream.readBits(variable); return stream.readBits(data[variable]);
} else { } else {
return stream.readBits(parseInt(description, 10), true); return stream.readBits(parseInt(description, 10), true);
} }

View file

@ -0,0 +1,26 @@
import {Packet} from "../../Data/Packet";
import {BitStream} from 'bit-buffer';
export function TempEntities(stream: BitStream): Packet { // 10: classInfo
const entityCount = stream.readBits(8);
const length = readVarInt(stream);
console.log(length);
stream._index += length;
return {
'packetType': 'tempEntities'
}
}
function readVarInt(stream: BitStream) {
let result = 0;
for (let run = 0; run < 35; run += 7) {
const byte = stream.readUint8();
result |= ((byte & 0x7F) << run);
if ((byte >> 7) == 0) {
return result;
}
}
return result;
}

View file

@ -47,14 +47,14 @@ export class SendPropParser {
static readArray(propDefinition: SendPropDefinition, stream: BitStream): SendPropArrayValue[] { static readArray(propDefinition: SendPropDefinition, stream: BitStream): SendPropArrayValue[] {
let maxElements = propDefinition.numElements; let maxElements = propDefinition.numElements;
let numBits = 1; let numBits = 1;
while ((maxElements >>= 1) != 0) while ((maxElements >>= 1) != 0)
numBits++; numBits++;
const count = stream.readBits(numBits); const count = stream.readBits(numBits);
const values: SendPropArrayValue[] = []; const values: SendPropArrayValue[] = [];
if (!propDefinition.arrayProperty) { if (!propDefinition.arrayProperty) {
throw new Error('Array of undefniend type'); throw new Error('Array of undefined type');
} }
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const value = SendPropParser.decode(propDefinition.arrayProperty, stream); const value = SendPropParser.decode(propDefinition.arrayProperty, stream);
@ -88,24 +88,24 @@ export class SendPropParser {
if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD)) { if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD)) {
throw new Error("not implemented"); throw new Error("not implemented");
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP)) { } else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP)) {
return SendPropParser.readBitCoord(stream, false, false); return SendPropParser.readBitCoord(propDefinition, stream, false, false);
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP_LOWPRECISION)) { } else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP_LOWPRECISION)) {
return SendPropParser.readBitCoord(stream, false, true); return SendPropParser.readBitCoord(propDefinition, stream, false, true);
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP_INTEGRAL)) { } else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP_INTEGRAL)) {
return SendPropParser.readBitCoord(stream, true, false); return SendPropParser.readBitCoord(propDefinition, stream, true, false);
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_NOSCALE)) { } else if (propDefinition.hasFlag(SendPropFlag.SPROP_NOSCALE)) {
return stream.readFloat32(); return stream.readFloat32();
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_NORMAL)) { } else if (propDefinition.hasFlag(SendPropFlag.SPROP_NORMAL)) {
throw new Error("not implemented"); throw new Error("not implemented");
} else { } else {
const raw = stream.readBits(propDefinition.bitCount); const raw = stream.readBits(propDefinition.bitCount);
const percentage = raw / ((1 << propDefinition.bitCount) - 1); const percentage = raw / ((1 << propDefinition.bitCount) - 1);
return propDefinition.lowValue + (propDefinition.highValue - propDefinition.lowValue) * percentage; return propDefinition.lowValue + (propDefinition.highValue - propDefinition.lowValue) * percentage;
} }
} }
static readBitCoord(stream: BitStream, isIntegral: boolean, isLowPrecision: boolean): number { static readBitCoord(propDefinition: SendPropDefinition, stream: BitStream, isIntegral: boolean, isLowPrecision: boolean): number {
let value = 0; let value = 0;
let isNegative = false; let isNegative = false;
const inBounds = stream.readBoolean(); const inBounds = stream.readBoolean();
@ -131,6 +131,7 @@ export class SendPropParser {
} else { } else {
value = stream.readBits(14) + 1; value = stream.readBits(14) + 1;
if (value < (1 << 11)) { if (value < (1 << 11)) {
console.log(propDefinition, value);
throw new Error("Something's fishy..."); throw new Error("Something's fishy...");
} }
} }

16
src/Parser/readBitVar.ts Normal file
View file

@ -0,0 +1,16 @@
import {BitStream} from "bit-buffer";
export function readBitVar(stream: BitStream, signed?: boolean): number {
switch (stream.readBits(2)) {
case 0:
return stream.readBits(4, signed);
case 1:
return stream.readBits(8, signed);
case 2:
return stream.readBits(12, signed);
case 3:
return stream.readBits(32, signed);
}
throw new Error('Invalid var bit');
}
export const readUBitVar = readBitVar;