mirror of
https://github.com/demostf/demo.js
synced 2026-06-03 16:44:12 +02:00
final typescript conversion
This commit is contained in:
parent
94383f447f
commit
3b805be013
19 changed files with 440 additions and 518 deletions
2
index.js
2
index.js
|
|
@ -1 +1 @@
|
|||
module.exports = require('./build/demo');
|
||||
module.exports = require('./build/Demo').Demo;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {Entity} from "./Entity";
|
|||
import {ServerClass} from "./ServerClass";
|
||||
import {SendTable} from "./SendTable";
|
||||
import {StringTable} from "./StringTable";
|
||||
import {SendProp} from "./SendProp";
|
||||
export class Match {
|
||||
tick: number;
|
||||
chat: any[];
|
||||
|
|
@ -10,11 +11,11 @@ export class Match {
|
|||
rounds: any[];
|
||||
startTick: number;
|
||||
intervalPerTick: number;
|
||||
entities: Entity[];
|
||||
entities: (Entity|null)[];
|
||||
stringTables: StringTable[];
|
||||
serverClasses: ServerClass[];
|
||||
sendTables: SendTable[];
|
||||
instanceBaselines: any[][];
|
||||
instanceBaselines: SendProp[][][];
|
||||
staticBaseLines: any[];
|
||||
|
||||
constructor() {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class SendProp {
|
|||
this.value = null;
|
||||
}
|
||||
|
||||
clone() {
|
||||
clone():SendProp {
|
||||
const prop = new SendProp(this.definition);
|
||||
prop.value = clone(this.value);
|
||||
return prop;
|
||||
|
|
|
|||
41
src/Demo.ts
Normal file
41
src/Demo.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import {Stream} from "stream";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Parser} from './Parser';
|
||||
import {StreamParser} from './StreamParser';
|
||||
|
||||
export class Demo {
|
||||
stream: BitStream;
|
||||
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
this.stream = new BitStream(arrayBuffer);
|
||||
}
|
||||
|
||||
getParser() {
|
||||
return new Parser(this.stream);
|
||||
}
|
||||
|
||||
static fromNodeBuffer(nodeBuffer) {
|
||||
const arrayBuffer = new ArrayBuffer(nodeBuffer.length);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < nodeBuffer.length; ++i) {
|
||||
view[i] = nodeBuffer[i];
|
||||
}
|
||||
return new Demo(arrayBuffer);
|
||||
}
|
||||
|
||||
static fromNodeStream(nodeStream) {
|
||||
return new StreamDemo(nodeStream);
|
||||
}
|
||||
}
|
||||
|
||||
class StreamDemo {
|
||||
stream: Stream;
|
||||
|
||||
constructor(nodeStream: Stream) {
|
||||
this.stream = nodeStream;
|
||||
}
|
||||
|
||||
getParser() {
|
||||
return new StreamParser(this.stream);
|
||||
}
|
||||
}
|
||||
147
src/Parser.ts
Normal file
147
src/Parser.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import toBuffer = require('typedarray-to-buffer');
|
||||
import {Packet} from './Parser/Message/Packet';
|
||||
import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
|
||||
import {StringTable} from './Parser/Message/StringTable';
|
||||
import {DataTable} from './Parser/Message/DataTable';
|
||||
import {UserCmd} from './Parser/Message/UserCmd';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {EventEmitter} from 'events';
|
||||
import {Match} from './Data/Match';
|
||||
import {Packet as IPacket} from "./Data/Packet";
|
||||
import {Parser as MessageParser} from './Parser/Message/Parser';
|
||||
|
||||
export class Parser extends EventEmitter {
|
||||
stream: BitStream;
|
||||
packets: IPacket[];
|
||||
match: Match;
|
||||
|
||||
constructor(stream: BitStream) {
|
||||
super();
|
||||
this.stream = stream;
|
||||
this.packets = [];
|
||||
this.match = new Match();
|
||||
this.on('packet', this.match.handlePacket.bind(this.match));
|
||||
this.on('packet', function (packet) {
|
||||
this.packets.push(packet);
|
||||
});
|
||||
}
|
||||
|
||||
readHeader() {
|
||||
return this.parseHeader(this.stream);
|
||||
}
|
||||
|
||||
parseHeader(stream) {
|
||||
return {
|
||||
'type': stream.readASCIIString(8),
|
||||
'version': stream.readInt32(),
|
||||
'protocol': stream.readInt32(),
|
||||
'server': stream.readASCIIString(260),
|
||||
'nick': stream.readASCIIString(260),
|
||||
'map': stream.readASCIIString(260),
|
||||
'game': stream.readASCIIString(260),
|
||||
'duration': stream.readFloat32(),
|
||||
'ticks': stream.readInt32(),
|
||||
'frames': stream.readInt32(),
|
||||
'sigon': stream.readInt32()
|
||||
}
|
||||
}
|
||||
|
||||
parseBody() {
|
||||
let message;
|
||||
while (message = this.readMessage(this.stream, this.match)) {
|
||||
if (message instanceof MessageParser) {
|
||||
this.handleMessage(message);
|
||||
}
|
||||
}
|
||||
this.emit('done', this.match);
|
||||
return this.match;
|
||||
}
|
||||
|
||||
parseMessage(buffer: ArrayBuffer, type: MessageType, tick: number, length: number, match: Match): MessageParser {
|
||||
const data = new BitStream(buffer);
|
||||
|
||||
switch (type) {
|
||||
case MessageType.Sigon:
|
||||
case MessageType.Packet:
|
||||
return new Packet(type, tick, data, length, match);
|
||||
case MessageType.ConsoleCmd:
|
||||
return new ConsoleCmd(type, tick, data, length, match);
|
||||
case MessageType.UserCmd:
|
||||
return new UserCmd(type, tick, data, length, match);
|
||||
case MessageType.DataTables:
|
||||
return new DataTable(type, tick, data, length, match);
|
||||
case MessageType.StringTables:
|
||||
return new StringTable(type, tick, data, length, match);
|
||||
default:
|
||||
throw new Error("unknown message type");
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(message: MessageParser) {
|
||||
if (message.parse) {
|
||||
const packets = message.parse();
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
if (packet) {
|
||||
this.emit('packet', packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readMessage(stream, match): MessageParser|boolean {
|
||||
const type: MessageType = stream.readBits(8);
|
||||
if (type === MessageType.Stop) {
|
||||
return false;
|
||||
}
|
||||
const tick = stream.readInt32();
|
||||
let start, length, buffer;
|
||||
|
||||
let viewOrigin: number[][] = [];
|
||||
let viewAngles: number[][] = [];
|
||||
|
||||
switch (type) {
|
||||
case MessageType.Sigon:
|
||||
case MessageType.Packet:
|
||||
this.stream.readInt32(); // flags
|
||||
for (let j = 0; j < 2; j++) {
|
||||
viewOrigin[j] = [];
|
||||
viewAngles[j] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
viewOrigin[j][i] = this.stream.readInt32();
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
viewAngles[j][i] = this.stream.readInt32();
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
this.stream.readInt32(); // local viewAngles
|
||||
}
|
||||
}
|
||||
this.stream.readInt32(); // sequence in
|
||||
this.stream.readInt32(); // sequence out
|
||||
break;
|
||||
case MessageType.UserCmd:
|
||||
stream.byteIndex += 0x04; // unknown / outgoing sequence
|
||||
break;
|
||||
case MessageType.SyncTick:
|
||||
return true;
|
||||
}
|
||||
|
||||
length = stream.readInt32();
|
||||
start = stream.byteIndex;
|
||||
buffer = toBuffer(stream._view._view.slice(start, start + length));
|
||||
stream.byteIndex += length;
|
||||
return this.parseMessage(buffer, type, tick, length, match);
|
||||
}
|
||||
}
|
||||
|
||||
export enum MessageType {
|
||||
Sigon = 1,
|
||||
Packet = 2,
|
||||
SyncTick = 3,
|
||||
ConsoleCmd = 4,
|
||||
UserCmd = 5,
|
||||
DataTables = 6,
|
||||
Stop = 7,
|
||||
StringTables = 8
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {Packet} from "../../Data/Packet";
|
||||
import {SendTable} from "../../Data/SendTable";
|
||||
|
||||
export abstract class Parser {
|
||||
type: any;
|
||||
|
|
@ -16,5 +18,5 @@ export abstract class Parser {
|
|||
this.match = match;
|
||||
}
|
||||
|
||||
abstract parse();
|
||||
abstract parse():Packet[]|string|SendTable[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import {PacketStringTable} from '../../packetstringtable';
|
||||
import {PacketStringTable} from './PacketStringTable';
|
||||
|
||||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export function CreateStringTable(stream: BitStream): Packet { // 12: createStringTable
|
||||
const stringTable = new PacketStringTable(stream);
|
||||
const tables = stringTable.parse();
|
||||
const tables = PacketStringTable(stream);
|
||||
return {
|
||||
packetType: 'createStringTable',
|
||||
table: tables
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@ import {BitStream} from 'bit-buffer';
|
|||
import {GameEventDefinition} from "../../Data/GameEvent";
|
||||
import {Match} from "../../Data/Match";
|
||||
|
||||
var PVS = {
|
||||
PRESERVE: 0,
|
||||
ENTER: 1,
|
||||
LEAVE: 2,
|
||||
DELETE: 4
|
||||
};
|
||||
enum PVS {
|
||||
PRESERVE = 0,
|
||||
ENTER = 1,
|
||||
LEAVE = 2,
|
||||
DELETE = 4
|
||||
}
|
||||
|
||||
function readPVSType(stream) {
|
||||
function readPVSType(stream: BitStream): number {
|
||||
// https://github.com/skadistats/smoke/blob/a2954fbe2fa3936d64aee5b5567be294fef228e6/smoke/io/stream/entity.pyx#L24
|
||||
var pvs;
|
||||
var hi = stream.readBoolean();
|
||||
var low = stream.readBoolean();
|
||||
let pvs;
|
||||
const hi = stream.readBoolean();
|
||||
const low = stream.readBoolean();
|
||||
if (low && !hi) {
|
||||
pvs = PVS.ENTER;
|
||||
} else if (!(hi || low)) {
|
||||
|
|
@ -30,27 +30,33 @@ function readPVSType(stream) {
|
|||
return pvs;
|
||||
}
|
||||
|
||||
function readEnterPVS(stream, entityId, match, baseLine) {
|
||||
function readEnterPVS(stream: BitStream, entityId: number, match: Match, baseLine: number):Entity {
|
||||
// https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs#L198
|
||||
var serverClass = match.serverClasses[stream.readBits(match.classBits)];
|
||||
const serverClass = match.serverClasses[stream.readBits(match.classBits)];
|
||||
console.log(serverClass);
|
||||
var sendTable = match.getSendTable(serverClass.dataTable);
|
||||
var serialNumber = stream.readBits(10);
|
||||
const sendTable = match.getSendTable(serverClass.dataTable);
|
||||
const serialNumber = stream.readBits(10);
|
||||
if (!sendTable) {
|
||||
throw new Error('Unknown SendTable for serverclass');
|
||||
}
|
||||
|
||||
var entity = (match.entities[entityId]) ? match.entities[entityId] : new Entity(serverClass, sendTable, entityId, serialNumber);
|
||||
let entity = match.entities[entityId];
|
||||
if (!entity) {
|
||||
entity = new Entity(serverClass, sendTable, entityId, serialNumber);
|
||||
}
|
||||
|
||||
var decodedBaseLine = match.instanceBaselines[baseLine][entityId];
|
||||
const decodedBaseLine = match.instanceBaselines[baseLine][entityId];
|
||||
if (decodedBaseLine) {
|
||||
for (var i = 0; i < decodedBaseLine.length; i++) {
|
||||
var newProp = decodedBaseLine[i];
|
||||
for (let i = 0; i < decodedBaseLine.length; i++) {
|
||||
const newProp = decodedBaseLine[i];
|
||||
if (!entity.getPropByDefinition(newProp.definition)) {
|
||||
entity.props.push(newProp.clone(entity));
|
||||
entity.props.push(newProp.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var staticBaseLine = match.staticBaseLines[serverClass.id];
|
||||
const staticBaseLine = match.staticBaseLines[serverClass.id];
|
||||
if (staticBaseLine) {
|
||||
var streamStart = staticBaseLine._index;
|
||||
const streamStart = staticBaseLine._index;
|
||||
applyEntityUpdate(entity, staticBaseLine);
|
||||
staticBaseLine._index = streamStart;
|
||||
}
|
||||
|
|
@ -70,24 +76,20 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
|
|||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Entity.cs
|
||||
// https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs
|
||||
// todo
|
||||
var maxEntries = stream.readBits(11);
|
||||
var isDelta = !!stream.readBits(1);
|
||||
if (isDelta) {
|
||||
var delta = stream.readInt32();
|
||||
} else {
|
||||
delta = null;
|
||||
}
|
||||
var baseLine = stream.readBits(1);
|
||||
var updatedEntries = stream.readBits(11);
|
||||
var length = stream.readBits(20);
|
||||
var updatedBaseLine = stream.readBoolean();
|
||||
var end = stream._index + length;
|
||||
var entityId = -1;
|
||||
const maxEntries = stream.readBits(11);
|
||||
const isDelta = !!stream.readBits(1);
|
||||
const delta = (isDelta) ? stream.readInt32() : null;
|
||||
const baseLine = stream.readBits(1);
|
||||
const updatedEntries = stream.readBits(11);
|
||||
const length = stream.readBits(20);
|
||||
const updatedBaseLine = stream.readBoolean();
|
||||
const end = stream._index + length;
|
||||
let entityId = -1
|
||||
|
||||
stream._index = end;
|
||||
return {
|
||||
packetType: 'packetEntities',
|
||||
entities: entities
|
||||
entities: entities
|
||||
};
|
||||
|
||||
if (updatedBaseLine) {
|
||||
|
|
@ -100,29 +102,31 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
|
|||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < updatedEntries; i++) {
|
||||
var diff = readUBitVar(stream);
|
||||
console.log(diff);
|
||||
for (let i = 0; i < updatedEntries; i++) {
|
||||
const diff = readUBitVar(stream);
|
||||
entityId += 1 + diff;
|
||||
var pvs = readPVSType(stream);
|
||||
const pvs = readPVSType(stream);
|
||||
if (pvs === PVS.ENTER) {
|
||||
var entity = readEnterPVS(stream, entityId, match, baseLine);
|
||||
const entity = readEnterPVS(stream, entityId, match, baseLine);
|
||||
applyEntityUpdate(entity, stream);
|
||||
match.entities[entityId] = entity;
|
||||
|
||||
if (updatedBaseLine) {
|
||||
match.instanceBaselines[baseLine][entityId] = [].concat(entity.props);
|
||||
const newBaseLine:SendProp[] = [];
|
||||
newBaseLine.concat(entity.props);
|
||||
match.instanceBaselines[baseLine][entityId] = newBaseLine;
|
||||
}
|
||||
entity.inPVS = true;
|
||||
} else if (pvs === PVS.PRESERVE) {
|
||||
entity = match.entities[entityId];
|
||||
if (!entity) {
|
||||
const entity = match.entities[entityId];
|
||||
if (entity) {
|
||||
applyEntityUpdate(entity, stream);
|
||||
} else {
|
||||
console.log(entityId, match.entities.length);
|
||||
throw new Error("unknown entity");
|
||||
}
|
||||
applyEntityUpdate(entity, stream);
|
||||
} else {
|
||||
entity = match.entities[entityId];
|
||||
const entity = match.entities[entityId];
|
||||
if (entity) {
|
||||
entity.inPVS = false;
|
||||
}
|
||||
|
|
@ -132,7 +136,7 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
|
|||
|
||||
if (isDelta) {
|
||||
while (stream.readBoolean()) {
|
||||
var ent = stream.readBits(11);
|
||||
const ent = stream.readBits(11);
|
||||
match.entities[ent] = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -140,29 +144,29 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
|
|||
stream._index = end;
|
||||
return {
|
||||
packetType: 'packetEntities',
|
||||
entities: entities
|
||||
entities: entities
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
var readFieldIndex = function (stream, lastIndex) {
|
||||
const readFieldIndex = function (stream: BitStream, lastIndex: number): number {
|
||||
if (!stream.readBoolean()) {
|
||||
return -1;
|
||||
}
|
||||
var diff = readUBitVar(stream);
|
||||
const diff = readUBitVar(stream);
|
||||
return lastIndex + diff + 1;
|
||||
};
|
||||
|
||||
var applyEntityUpdate = function (entity, stream) {
|
||||
var index = -1;
|
||||
var allProps = entity.sendTable.flattenedProps;
|
||||
const applyEntityUpdate = function (entity: Entity, stream: BitStream): Entity {
|
||||
let index = -1;
|
||||
const allProps = entity.sendTable.flattenedProps;
|
||||
while ((index = readFieldIndex(stream, index)) != -1) {
|
||||
if (index > 4096) {
|
||||
throw new Error('prop index out of bounds');
|
||||
}
|
||||
console.log(index);
|
||||
var propDefinition = allProps[index];
|
||||
var existingProp = entity.getPropByDefinition(propDefinition);
|
||||
var prop;
|
||||
const propDefinition = allProps[index];
|
||||
const existingProp = entity.getPropByDefinition(propDefinition);
|
||||
let prop;
|
||||
if (existingProp) {
|
||||
prop = existingProp;
|
||||
} else {
|
||||
|
|
@ -178,7 +182,7 @@ var applyEntityUpdate = function (entity, stream) {
|
|||
return entity;
|
||||
};
|
||||
|
||||
var readUBitVar = function (stream) {
|
||||
const readUBitVar = function (stream: BitStream): number {
|
||||
switch (stream.readBits(2)) {
|
||||
case 0:
|
||||
return stream.readBits(4);
|
||||
|
|
@ -189,4 +193,5 @@ var readUBitVar = function (stream) {
|
|||
case 3:
|
||||
return stream.readBits(32);
|
||||
}
|
||||
throw new Error('Invalid var bit');
|
||||
};
|
||||
|
|
|
|||
11
src/Parser/Packet/PacketStringTable.ts
Normal file
11
src/Parser/Packet/PacketStringTable.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import {BitStream} from "bit-buffer";
|
||||
import {Packet} from "../../Data/Packet";
|
||||
|
||||
export function PacketStringTable(stream: BitStream):Packet {
|
||||
//todo
|
||||
// https://coldemoplayer.googlecode.com/svn/branches/2.0/code/plugins/CDP.Source/Messages/SvcCreateStringTable.cs
|
||||
stream._index = stream._view._view.byteLength * 8;
|
||||
return {
|
||||
packetType: 'stringTableTODO'
|
||||
};
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import {PacketStringTable} from '../../packetstringtable';
|
||||
import {PacketStringTable} from './PacketStringTable';
|
||||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export function UpdateStringTable(stream: BitStream): Packet { // 12: updateStringTable
|
||||
const stringTable = new PacketStringTable(stream);
|
||||
const tables = stringTable.parse();
|
||||
const tables = PacketStringTable(stream);
|
||||
return {
|
||||
packetType: 'updateStringTable',
|
||||
table: tables
|
||||
|
|
|
|||
|
|
@ -1,86 +1,87 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {make} from './ParserGenerator';
|
||||
import {SayText2} from '../UserMessage/SayText2';
|
||||
|
||||
enum UserMessageType{
|
||||
Geiger = 0,
|
||||
Train = 1,
|
||||
HudText = 2,
|
||||
SayText = 3,
|
||||
SayText2 = 4,
|
||||
TextMsg = 5,
|
||||
ResetHUD = 6,
|
||||
GameTitle = 7,
|
||||
ItemPickup = 8,
|
||||
ShowMenu = 9,
|
||||
Shake = 10,
|
||||
Fade = 11,
|
||||
VGUIMenu = 12,
|
||||
Rumble = 13,
|
||||
CloseCaption = 14,
|
||||
SendAudio = 15,
|
||||
VoiceMask = 16,
|
||||
RequestState = 17,
|
||||
Damage = 18,
|
||||
HintText = 19,
|
||||
KeyHintText = 20,
|
||||
HudMsg = 21,
|
||||
AmmoDenied = 22,
|
||||
AchievementEvent = 23,
|
||||
UpdateRadar = 24,
|
||||
VoiceSubtitle = 25,
|
||||
HudNotify = 26,
|
||||
HudNotifyCustom = 27,
|
||||
PlayerStatsUpdate = 28,
|
||||
PlayerIgnited = 29,
|
||||
PlayerIgnitedInv = 30,
|
||||
HudArenaNotify = 31,
|
||||
UpdateAchievement = 32,
|
||||
TrainingMsg = 33,
|
||||
TrainingObjective = 34,
|
||||
DamageDodged = 35,
|
||||
PlayerJarated = 36,
|
||||
PlayerExtinguished = 37,
|
||||
PlayerJaratedFade = 38,
|
||||
PlayerShieldBlocked = 39,
|
||||
BreakModel = 40,
|
||||
CheapBreakModel = 41,
|
||||
BreakModel_Pumpkin = 42,
|
||||
BreakModelRocketDud = 43,
|
||||
CallVoteFailed = 44,
|
||||
VoteStart = 45,
|
||||
VotePass = 46,
|
||||
VoteFailed = 47,
|
||||
VoteSetup = 48,
|
||||
PlayerBonusPoints = 49,
|
||||
SpawnFlyingBird = 50,
|
||||
PlayerGodRayEffect = 51,
|
||||
SPHapWeapEvent = 52,
|
||||
HapDmg = 53,
|
||||
HapPunch = 54,
|
||||
HapSetDrag = 55,
|
||||
HapSet = 56,
|
||||
HapMeleeContact = 57
|
||||
}
|
||||
|
||||
const userMessageParsers = {
|
||||
4: require('../../handlers/userMessage/SayText2'),
|
||||
4: SayText2,
|
||||
5: make('textMsg', 'destType{8}text{s}')
|
||||
};
|
||||
|
||||
export function UserMessage(stream: BitStream): Packet { // 23: user message
|
||||
const type = stream.readBits(8);
|
||||
const type = stream.readBits(8);
|
||||
const length = stream.readBits(11);
|
||||
const pos = stream._index;
|
||||
const pos = stream._index;
|
||||
let result;
|
||||
if (userMessageParsers[type]) {
|
||||
result = userMessageParsers[type](stream);
|
||||
} else {
|
||||
result = {
|
||||
packetType: 'unknownUserMessage',
|
||||
type: type
|
||||
type: type
|
||||
}
|
||||
}
|
||||
stream._index = pos + length;
|
||||
return result;
|
||||
}
|
||||
|
||||
var UserMessageType = {
|
||||
Geiger: 0,
|
||||
Train: 1,
|
||||
HudText: 2,
|
||||
SayText: 3,
|
||||
SayText2: 4,
|
||||
TextMsg: 5,
|
||||
ResetHUD: 6,
|
||||
GameTitle: 7,
|
||||
ItemPickup: 8,
|
||||
ShowMenu: 9,
|
||||
Shake: 10,
|
||||
Fade: 11,
|
||||
VGUIMenu: 12,
|
||||
Rumble: 13,
|
||||
CloseCaption: 14,
|
||||
SendAudio: 15,
|
||||
VoiceMask: 16,
|
||||
RequestState: 17,
|
||||
Damage: 18,
|
||||
HintText: 19,
|
||||
KeyHintText: 20,
|
||||
HudMsg: 21,
|
||||
AmmoDenied: 22,
|
||||
AchievementEvent: 23,
|
||||
UpdateRadar: 24,
|
||||
VoiceSubtitle: 25,
|
||||
HudNotify: 26,
|
||||
HudNotifyCustom: 27,
|
||||
PlayerStatsUpdate: 28,
|
||||
PlayerIgnited: 29,
|
||||
PlayerIgnitedInv: 30,
|
||||
HudArenaNotify: 31,
|
||||
UpdateAchievement: 32,
|
||||
TrainingMsg: 33,
|
||||
TrainingObjective: 34,
|
||||
DamageDodged: 35,
|
||||
PlayerJarated: 36,
|
||||
PlayerExtinguished: 37,
|
||||
PlayerJaratedFade: 38,
|
||||
PlayerShieldBlocked: 39,
|
||||
BreakModel: 40,
|
||||
CheapBreakModel: 41,
|
||||
BreakModel_Pumpkin: 42,
|
||||
BreakModelRocketDud: 43,
|
||||
CallVoteFailed: 44,
|
||||
VoteStart: 45,
|
||||
VotePass: 46,
|
||||
VoteFailed: 47,
|
||||
VoteSetup: 48,
|
||||
PlayerBonusPoints: 49,
|
||||
SpawnFlyingBird: 50,
|
||||
PlayerGodRayEffect: 51,
|
||||
SPHapWeapEvent: 52,
|
||||
HapDmg: 53,
|
||||
HapPunch: 54,
|
||||
HapSetDrag: 55,
|
||||
HapSet: 56,
|
||||
HapMeleeContact: 57
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export class SendPropParser {
|
|||
case SendPropType.DPT_Array:
|
||||
return SendPropParser.readArray(propDefinition, stream);
|
||||
}
|
||||
throw new Error('Unknown property type');
|
||||
}
|
||||
|
||||
static readInt(propDefinition: SendPropDefinition, stream: BitStream) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
module.exports = function (stream) { // 4: SayText2
|
||||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export function SayText2(stream: BitStream): Packet { // 4: SayText2
|
||||
var client = stream.readBits(8);
|
||||
var raw = stream.readBits(8);
|
||||
var pos = stream._index;
|
||||
var raw = stream.readBits(8);
|
||||
var pos = stream._index;
|
||||
var from, text, kind, arg1, arg2;
|
||||
if (stream.readBits(8) === 1) {
|
||||
var first = stream.readBits(8);
|
||||
|
|
@ -14,16 +17,16 @@ module.exports = function (stream) { // 4: SayText2
|
|||
if (text.substr(0, 6) === '*DEAD*') {
|
||||
// grave talk is in the format '*DEAD* \u0003$from\u0001: $text'
|
||||
var start = text.indexOf('\u0003');
|
||||
var end = text.indexOf('\u0001');
|
||||
from = text.substr(start + 1, end - start - 1);
|
||||
text = text.substr(end + 5);
|
||||
kind = 'TF_Chat_AllDead';
|
||||
var end = text.indexOf('\u0001');
|
||||
from = text.substr(start + 1, end - start - 1);
|
||||
text = text.substr(end + 5);
|
||||
kind = 'TF_Chat_AllDead';
|
||||
}
|
||||
} else {
|
||||
stream._index = pos;
|
||||
kind = stream.readUTF8String();
|
||||
from = stream.readUTF8String();
|
||||
text = stream.readUTF8String();
|
||||
kind = stream.readUTF8String();
|
||||
from = stream.readUTF8String();
|
||||
text = stream.readUTF8String();
|
||||
stream.readASCIIString();
|
||||
stream.readASCIIString();
|
||||
}
|
||||
|
|
@ -35,10 +38,10 @@ module.exports = function (stream) { // 4: SayText2
|
|||
}
|
||||
return {
|
||||
packetType: 'sayText2',
|
||||
client : client,
|
||||
raw : raw,
|
||||
kind : kind,
|
||||
from : from,
|
||||
text : text
|
||||
client: client,
|
||||
raw: raw,
|
||||
kind: kind,
|
||||
from: from,
|
||||
text: text
|
||||
}
|
||||
};
|
||||
|
|
@ -1,16 +1,88 @@
|
|||
var util = require('util');
|
||||
var Parser = require('./parser');
|
||||
var BitStream = require('bit-buffer').BitStream;
|
||||
import {Match} from "./Data/Match";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Parser, MessageType} from './Parser';
|
||||
import {Stream} from "stream";
|
||||
|
||||
var StreamParser = function (stream) {
|
||||
this.stream = stream;
|
||||
this.match = new Match();
|
||||
this.on('packet', this, match.handlePacket.bind(this.match));
|
||||
this.header = null;
|
||||
this.buffer = new Buffer(0);
|
||||
};
|
||||
export class StreamParser extends Parser {
|
||||
buffer: Buffer;
|
||||
header: any;
|
||||
sourceStream: Stream;
|
||||
|
||||
util.inherits(StreamParser, Parser);
|
||||
constructor(stream) {
|
||||
super(new BitStream(new ArrayBuffer(0)));
|
||||
this.sourceStream = stream;
|
||||
this.on('packet', this.match.handlePacket.bind(this.match));
|
||||
this.header = null;
|
||||
this.buffer = new Buffer(0);
|
||||
}
|
||||
|
||||
eatBuffer(length) {
|
||||
this.buffer = shrinkBuffer(this.buffer, length);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.sourceStream.on('data', this.handleData.bind(this));
|
||||
this.sourceStream.on('end', function () {
|
||||
this.emit('done', this.match);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
handleData(data) {
|
||||
this.buffer = Buffer.concat([this.buffer, data]);
|
||||
if (this.header === null) {
|
||||
if (this.buffer.length > 1072) {
|
||||
this.header = this.parseHeader(new BitStream(this.buffer));
|
||||
this.eatBuffer(1072);
|
||||
}
|
||||
} else {
|
||||
this.readStreamMessage();
|
||||
}
|
||||
}
|
||||
|
||||
readStreamMessage() {
|
||||
if (this.buffer.length < 9) { // 9 byte minimum message header (type, tick, length)
|
||||
return;
|
||||
}
|
||||
const stream = new BitStream(this.buffer);
|
||||
const type = stream.readBits(8);
|
||||
if (type === MessageType.Stop) {
|
||||
console.log('stop');
|
||||
return;
|
||||
}
|
||||
const tick = stream.readInt32();
|
||||
|
||||
let headerSize = 5;
|
||||
let extraHeader = 0;
|
||||
|
||||
switch (type) {
|
||||
case MessageType.Sigon:
|
||||
case MessageType.Packet:
|
||||
extraHeader += 0x54; // command/sequence info
|
||||
break;
|
||||
case MessageType.UserCmd:
|
||||
extraHeader += 0x04; // unknown / outgoing sequence
|
||||
break;
|
||||
case MessageType.Stop:
|
||||
case MessageType.SyncTick:
|
||||
this.eatBuffer(headerSize);
|
||||
return;
|
||||
}
|
||||
stream.byteIndex += extraHeader;
|
||||
const length = stream.readInt32();
|
||||
headerSize += extraHeader + 4;
|
||||
|
||||
if (this.buffer.length < (headerSize + length)) {
|
||||
console.log('wants ' + length);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('got message ' + tick);
|
||||
const messageBuffer = this.buffer.slice(headerSize, headerSize + length);
|
||||
this.eatBuffer(headerSize + length);
|
||||
const message = this.parseMessage(messageBuffer, type, tick, length, this.match);
|
||||
this.handleMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
function shrinkBuffer(buffer, length) {
|
||||
if (length < 0) {
|
||||
|
|
@ -18,72 +90,3 @@ function shrinkBuffer(buffer, length) {
|
|||
}
|
||||
return buffer.slice(length, buffer.length);
|
||||
}
|
||||
|
||||
StreamParser.prototype.eatBuffer = function (length) {
|
||||
this.buffer = shrinkBuffer(this.buffer, length);
|
||||
};
|
||||
|
||||
StreamParser.prototype.start = function () {
|
||||
this.stream.on('data', this.handleData.bind(this));
|
||||
this.stream.on('end', function () {
|
||||
this.emit('done', this.match);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
StreamParser.prototype.handleData = function (data) {
|
||||
this.buffer = Buffer.concat([this.buffer, data]);
|
||||
if (this.header === null) {
|
||||
if (this.buffer.length > 1072) {
|
||||
this.header = this.parseHeader(new BitStream(this.buffer));
|
||||
this.eatBuffer(1072);
|
||||
}
|
||||
} else {
|
||||
this.readMessage();
|
||||
}
|
||||
};
|
||||
|
||||
StreamParser.prototype.readMessage = function () {
|
||||
if (this.buffer.length < 9) { // 9 byte minimum message header (type, tick, length)
|
||||
return;
|
||||
}
|
||||
var stream = new BitStream(this.buffer);
|
||||
var type = stream.readBits(8);
|
||||
if (type === Parser.MessageType.Stop) {
|
||||
console.log('stop');
|
||||
return;
|
||||
}
|
||||
var tick = stream.readInt32();
|
||||
|
||||
var headerSize = 5;
|
||||
var extraHeader = 0;
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
extraHeader += 0x54; // command/sequence info
|
||||
break;
|
||||
case Parser.MessageType.UserCmd:
|
||||
extraHeader += 0x04; // unknown / outgoing sequence
|
||||
break;
|
||||
case Parser.MessageType.Stop:
|
||||
case Parser.MessageType.SyncTick:
|
||||
this.eatBuffer(headerSize);
|
||||
return;
|
||||
}
|
||||
stream.byteIndex += extraHeader;
|
||||
var length = stream.readInt32();
|
||||
headerSize += extraHeader + 4;
|
||||
|
||||
if (this.buffer.length < (headerSize + length)) {
|
||||
console.log('wants ' + length);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('got message ' + tick);
|
||||
var messageBuffer = this.buffer.slice(headerSize, headerSize + length);
|
||||
this.eatBuffer(headerSize + length);
|
||||
var message = this.parseMessage(messageBuffer, type, tick, length);
|
||||
this.handleMessage(message);
|
||||
};
|
||||
|
||||
module.exports = StreamParser;
|
||||
|
|
|
|||
43
src/demo.ts
43
src/demo.ts
|
|
@ -1,43 +0,0 @@
|
|||
var BitStream = require('bit-buffer').BitStream;
|
||||
var Parser = require('./parser');
|
||||
var StreamParser = require('./StreamParser');
|
||||
|
||||
var Demo = function (arrayBuffer) {
|
||||
this.stream = new BitStream(arrayBuffer);
|
||||
};
|
||||
|
||||
Demo.prototype.getParser = function () {
|
||||
return new Parser(this.stream);
|
||||
};
|
||||
|
||||
var StreamDemo = function (nodeStream) {
|
||||
this.stream = nodeStream;
|
||||
};
|
||||
|
||||
StreamDemo.prototype.getParser = function () {
|
||||
return new StreamParser(this.stream);
|
||||
};
|
||||
|
||||
Demo.fromNodeBuffer = function (nodeBuffer) {
|
||||
var arrayBuffer = new ArrayBuffer(nodeBuffer.length);
|
||||
var view = new Uint8Array(arrayBuffer);
|
||||
for (var i = 0; i < nodeBuffer.length; ++i) {
|
||||
view[i] = nodeBuffer[i];
|
||||
}
|
||||
return new Demo(arrayBuffer);
|
||||
};
|
||||
|
||||
Demo.fromNodeStream = function (nodeStream) {
|
||||
return new StreamDemo(nodeStream);
|
||||
};
|
||||
|
||||
Demo.fromPath = function (path) {
|
||||
var arrayBuffer = new ArrayBuffer(nodeBuffer.length);
|
||||
var view = new Uint8Array(arrayBuffer);
|
||||
for (var i = 0; i < nodeBuffer.length; ++i) {
|
||||
view[i] = nodeBuffer[i];
|
||||
}
|
||||
return new Demo(arrayBuffer);
|
||||
};
|
||||
|
||||
module.exports = Demo;
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
export class PacketStringTable {
|
||||
constructor(stream) {
|
||||
this.stream = stream;
|
||||
this.id = PacketStringTable.tables.length;
|
||||
this.strings = [];
|
||||
this.numEntries = 0;
|
||||
this.name = '';
|
||||
PacketStringTable.tables.push(this);
|
||||
}
|
||||
|
||||
parse() {
|
||||
//todo
|
||||
// https://coldemoplayer.googlecode.com/svn/branches/2.0/code/plugins/CDP.Source/Messages/SvcCreateStringTable.cs
|
||||
this.stream._index = this.stream._view._view.length * 8;
|
||||
return {
|
||||
packetType: 'stringTableTODO'
|
||||
};
|
||||
//return this.searchIds();
|
||||
}
|
||||
|
||||
parsePlayerInfo() {
|
||||
console.log('name: ' + this.stream.readUTF8String());
|
||||
}
|
||||
|
||||
// "fuckit" parsing, look for anything that looks like a steam id, user id is the 32 bit before that
|
||||
searchIds() {
|
||||
var validChar = function (charCode) {
|
||||
return charCode === 91 || charCode === 93 || charCode === 58 || (charCode > 47 && charCode < 58); // [ ] : 0-9
|
||||
};
|
||||
var users = {};
|
||||
var numFound = 0;
|
||||
while (true) {
|
||||
var found = false;
|
||||
while (this.stream._index < ((this.stream._view._view.length - 1) * 8)) {
|
||||
var startPos = this.stream._index;
|
||||
try {
|
||||
if (this.stream.readASCIIString(3) === '[U:') {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
this.stream._index = startPos + 1;
|
||||
} catch (e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (numFound) {
|
||||
//console.log(users);
|
||||
}
|
||||
this.stream._index = this.stream._view._view.length * 8;
|
||||
return users;
|
||||
}
|
||||
while (validChar(this.stream.readBits(8))) {
|
||||
// seek
|
||||
}
|
||||
var endPos = this.stream._index - 8;
|
||||
var length = (endPos / 8) - (startPos / 8);
|
||||
this.stream._index = startPos - 32;
|
||||
var userId = this.stream.readBits(32);
|
||||
var steamId = this.stream.readASCIIString(length);
|
||||
if (steamId[steamId.length - 1] !== ']') {
|
||||
steamId += ']';
|
||||
}
|
||||
users[userId] = steamId;
|
||||
numFound++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PacketStringTable.tables = [];
|
||||
143
src/parser.ts
143
src/parser.ts
|
|
@ -1,143 +0,0 @@
|
|||
import * as toBuffer from 'typedarray-to-buffer'
|
||||
import {Packet} from './Parser/Message/Packet';
|
||||
import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
|
||||
import {StringTable} from './Parser/Message/StringTable';
|
||||
import {DataTable} from './Parser/Message/DataTable';
|
||||
import {UserCmd} from './Parser/Message/UserCmd';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {EventEmitter} from 'events';
|
||||
import {Match} from './Data/Match';
|
||||
|
||||
class Parser extends EventEmitter {
|
||||
constructor(stream) {
|
||||
super();
|
||||
this.stream = stream;
|
||||
this.packets = [];
|
||||
this.match = new Match();
|
||||
this.on('packet', this.match.handlePacket.bind(this.match));
|
||||
this.on('packet', function (packet) {
|
||||
this.packets.push(packet);
|
||||
});
|
||||
}
|
||||
|
||||
readHeader() {
|
||||
return this.parseHeader(this.stream);
|
||||
}
|
||||
|
||||
parseHeader(stream) {
|
||||
return {
|
||||
'type' : stream.readASCIIString(8),
|
||||
'version' : stream.readInt32(),
|
||||
'protocol': stream.readInt32(),
|
||||
'server' : stream.readASCIIString(260),
|
||||
'nick' : stream.readASCIIString(260),
|
||||
'map' : stream.readASCIIString(260),
|
||||
'game' : stream.readASCIIString(260),
|
||||
'duration': stream.readFloat32(),
|
||||
'ticks' : stream.readInt32(),
|
||||
'frames' : stream.readInt32(),
|
||||
'sigon' : stream.readInt32()
|
||||
}
|
||||
}
|
||||
|
||||
parseBody() {
|
||||
var message;
|
||||
while (message = this.readMessage(this.stream, this.match)) {
|
||||
this.handleMessage(message);
|
||||
}
|
||||
this.emit('done', this.match);
|
||||
return this.match;
|
||||
}
|
||||
|
||||
parseMessage(buffer, type, tick, length, viewOrigin, match) {
|
||||
var data = new BitStream(buffer);
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
return new Packet(type, tick, data, length, viewOrigin, match);
|
||||
case Parser.MessageType.ConsoleCmd:
|
||||
return new ConsoleCmd(type, tick, data, length, match);
|
||||
case Parser.MessageType.UserCmd:
|
||||
return new UserCmd(type, tick, data, length, match);
|
||||
case Parser.MessageType.DataTables:
|
||||
return new DataTable(type, tick, data, length, match);
|
||||
case Parser.MessageType.StringTables:
|
||||
return new StringTable(type, tick, data, length, match);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
if (message.parse) {
|
||||
var packets = message.parse();
|
||||
for (var i = 0; i < packets.length; i++) {
|
||||
var packet = packets[i];
|
||||
if (packet) {
|
||||
this.emit('packet', packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readMessage(stream, match) {
|
||||
var type = stream.readBits(8);
|
||||
if (type === Parser.MessageType.Stop) {
|
||||
return null;
|
||||
}
|
||||
var tick = stream.readInt32();
|
||||
var start, length, buffer;
|
||||
|
||||
var viewOrigin = [];
|
||||
var viewAngles = [];
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
this.stream.readInt32(); // flags
|
||||
for (var j = 0; j < 2; j++) {
|
||||
viewOrigin[j] = [];
|
||||
viewAngles[j] = [];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
viewOrigin[j][i] = this.stream.readInt32();
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
viewAngles[j][i] = this.stream.readInt32();
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
this.stream.readInt32(); // local viewAngles
|
||||
}
|
||||
}
|
||||
this.stream.readInt32(); // sequence in
|
||||
this.stream.readInt32(); // sequence out
|
||||
break;
|
||||
case Parser.MessageType.UserCmd:
|
||||
stream.byteIndex += 0x04; // unknown / outgoing sequence
|
||||
break;
|
||||
case Parser.MessageType.Stop:
|
||||
return false;
|
||||
case Parser.MessageType.SyncTick:
|
||||
return true;
|
||||
}
|
||||
|
||||
length = stream.readInt32();
|
||||
start = stream.byteIndex;
|
||||
buffer = toBuffer(stream._view._view.slice(start, start + length));
|
||||
stream.byteIndex += length;
|
||||
return this.parseMessage(buffer, type, tick, length, viewOrigin, match);
|
||||
}
|
||||
}
|
||||
|
||||
Parser.MessageType = {
|
||||
Sigon : 1,
|
||||
Packet : 2,
|
||||
SyncTick : 3,
|
||||
ConsoleCmd : 4,
|
||||
UserCmd : 5,
|
||||
DataTables : 6,
|
||||
Stop : 7,
|
||||
StringTables: 8
|
||||
};
|
||||
|
||||
module.exports = Parser;
|
||||
40
typings/modules/bit-buffer/index.d.ts
vendored
40
typings/modules/bit-buffer/index.d.ts
vendored
|
|
@ -1,40 +0,0 @@
|
|||
export interface BitView {
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface BitStream {
|
||||
byteIndex: number;
|
||||
buffer: Buffer;
|
||||
_view: BitView;
|
||||
_index: number;
|
||||
readBits(bits: number, signed?: boolean): number;
|
||||
readWrite(value: number, bits: number);
|
||||
|
||||
readBoolean(): boolean;
|
||||
readInt8(): number;
|
||||
readUint8(): number;
|
||||
readInt16(): number;
|
||||
readUint16(): number;
|
||||
readInt32(): number;
|
||||
readUint32(): number;
|
||||
readFloat32(): number;
|
||||
readFloat64(): number;
|
||||
|
||||
writeBoolean(value: number);
|
||||
writeInt8(value: number);
|
||||
writeUint8(value: number);
|
||||
writeInt16(value: number);
|
||||
writeUint16(value: number);
|
||||
writeInt32(value: number);
|
||||
writeUint32(value: number);
|
||||
writeFloat32(value: number);
|
||||
writeFloat64(value: number);
|
||||
|
||||
readASCIIString(length?: number): string;
|
||||
readUTF8String(length?: number): string;
|
||||
|
||||
writeASCIIString(data: string, length?: number);
|
||||
writeUTF8String(data: string, length?: number);
|
||||
|
||||
readBitStream(length: number): BitStream;
|
||||
}
|
||||
5
typings/modules/typedarray-to-buffer/index.d.ts
vendored
Normal file
5
typings/modules/typedarray-to-buffer/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
declare module 'typedarray-to-buffer' {
|
||||
function toBuffer(typedArray:Uint8Array):ArrayBuffer;
|
||||
|
||||
export = toBuffer
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue