mirror of
https://github.com/demostf/demo.js
synced 2026-06-03 16:44:12 +02:00
initial version
This commit is contained in:
commit
011625257d
10 changed files with 576 additions and 0 deletions
14
consolecmd.js
Normal file
14
consolecmd.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
var ConsoleCmd = function (type, tick, stream, length) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
};
|
||||
|
||||
ConsoleCmd.prototype.parse = function () {
|
||||
var cmd = this.stream.readASCIIString();
|
||||
console.log("cmd " + cmd);
|
||||
return cmd;
|
||||
};
|
||||
|
||||
module.exports = ConsoleCmd;
|
||||
30
demo.js
Normal file
30
demo.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
var BitStream = require('bit-buffer').BitStream;
|
||||
var Parser = require('./parser');
|
||||
|
||||
var Demo = function (arrayBuffer) {
|
||||
this.stream = new BitStream(arrayBuffer);
|
||||
};
|
||||
|
||||
Demo.prototype.getParser = function () {
|
||||
return new Parser(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.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;
|
||||
BIN
gully.dem
Normal file
BIN
gully.dem
Normal file
Binary file not shown.
6
package.json
Normal file
6
package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"bit-buffer": "0.0.3",
|
||||
"buffer": "^3.0.1"
|
||||
}
|
||||
}
|
||||
334
packet.js
Normal file
334
packet.js
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
var ParserGenerator = require('./parsergenerator');
|
||||
|
||||
function logBase2(num) {
|
||||
var result = 0;
|
||||
while ((num >>= 1) != 0) {
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var Packet = function (type, tick, stream, length) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
};
|
||||
|
||||
Packet.gameEventMap = {};
|
||||
|
||||
Object.defineProperty(Packet.prototype, 'bitsLeft', {
|
||||
get: function () {
|
||||
return (this.length * 8) - this.stream._index;
|
||||
}
|
||||
});
|
||||
|
||||
Packet.prototype.parse = function () {
|
||||
var packets = [];
|
||||
while (this.bitsLeft > 6) { // last 6 bits for NOOP
|
||||
var type = this.stream.readBits(6);
|
||||
if (Packet.parsers[type]) {
|
||||
var packet = Packet.parsers[type](this.stream);
|
||||
console.log(packet);
|
||||
packets.push(packet);
|
||||
} else {
|
||||
throw 'Unknown packet type ' + type;
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
};
|
||||
|
||||
Packet.parseGameEvent = function (eventId, stream) {
|
||||
if (!Packet.gameEventMap[eventId]) {
|
||||
return 'unknown';
|
||||
}
|
||||
var eventDescription = this.gameEventMap[eventId];
|
||||
//console.log(eventDescription);
|
||||
var values = {};
|
||||
for (var i = 0; i < eventDescription.entries.length; i++) {
|
||||
var entry = eventDescription.entries[i];
|
||||
values[entry.name] = Packet.getGameEventValue(stream, entry);
|
||||
}
|
||||
return {
|
||||
name : eventDescription.name,
|
||||
type : eventDescription.type,
|
||||
values: values
|
||||
};
|
||||
};
|
||||
|
||||
Packet.getGameEventValue = function (stream, entry) {
|
||||
switch (entry.type) {
|
||||
case 1:
|
||||
return stream.readASCIIString();
|
||||
case 2:
|
||||
return stream.readFloat32();
|
||||
case 3:
|
||||
return stream.readInt32();
|
||||
case 4:
|
||||
return stream.readBits(16);
|
||||
case 5:
|
||||
return stream.readBits(8);
|
||||
case 6:
|
||||
return !!stream.readBits(1);
|
||||
case 7:
|
||||
return 'local value'
|
||||
}
|
||||
};
|
||||
|
||||
Packet.parsers = {
|
||||
0 : function () {
|
||||
},
|
||||
3 : ParserGenerator.make('netTick', 'tick{32}frameTime{16}stdDev{16}'),
|
||||
6 : ParserGenerator.make('sigOnState', 'state{8}count{32}'),
|
||||
8 : ParserGenerator.make('serverInfo',
|
||||
'version{16}serverCount{32}stv{b}dedicated{b}maxCrc{32}maxClasses{16}' +
|
||||
'mapHash{128}playerCount{8}maxPlayerCount{8}intervalPerTick{f32}platform{s1}' +
|
||||
'game{s}map{s}skybox{s}serverName{s}replay{b}'),
|
||||
10: function (stream) {
|
||||
var number = stream.readBits(16);
|
||||
var create = !!stream.readBits(1);
|
||||
var entries = [];
|
||||
if (!create) {
|
||||
var bits = logBase2(number) + 1;
|
||||
for (var i = 0; i < number; i++) {
|
||||
var entry = {
|
||||
'classId' : stream.readBits(bits),
|
||||
'className' : stream.readASCIIString(),
|
||||
'dataTableName': stream.readASCIIString()
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
return {
|
||||
'packetType': 'classInfo',
|
||||
number : number,
|
||||
create : create,
|
||||
entries : entries
|
||||
}
|
||||
},
|
||||
11: ParserGenerator.make('setPause', 'paused{b}'),
|
||||
12: function (stream) {
|
||||
//todo
|
||||
// https://coldemoplayer.googlecode.com/svn/branches/2.0/code/plugins/CDP.Source/Messages/SvcCreateStringTable.cs
|
||||
var name = stream.readASCIIString();
|
||||
var maxEntries = stream.readBits(16);
|
||||
var bits = Math.log(maxEntries) / Math.LN2;
|
||||
var numEntries = stream.readBits(bits + 1);
|
||||
var length = stream.readBits(20);
|
||||
var userDataFixedSize = stream.readBits(1);
|
||||
if (userDataFixedSize) {
|
||||
var userSize = stream.readBits(12);
|
||||
var userDataBits = stream.readBits(4);
|
||||
}
|
||||
var data = [];
|
||||
var end = stream._index + length;
|
||||
|
||||
|
||||
for (var i = 0; i < numEntries; i++) {
|
||||
//data.push(stream.readASCIIString());
|
||||
//stream.readBits(7);
|
||||
//data.push(stream.readASCIIString());
|
||||
//console.log(stream.readBits(bits + 1));
|
||||
//stream.readBits(1);
|
||||
//data.push(stream.readASCIIString());
|
||||
}
|
||||
stream._index = end;
|
||||
return {packetType: 'createStringTable'};
|
||||
//return {
|
||||
// packetType: 'createStringTable',
|
||||
// name : name,
|
||||
// entries : numEntries,
|
||||
// data : data
|
||||
//}
|
||||
},
|
||||
13: function (stream) {
|
||||
var tableId = stream.readBits(5);
|
||||
var changeEntries = stream.readBits(1) ? stream.readBits(16) : 1;
|
||||
var length = stream.readBits(20);
|
||||
var end = stream._index + length;
|
||||
stream.readBits(7);
|
||||
var strings = [];
|
||||
//for (var i = 0; i < changeEntries; i++) {
|
||||
// // todo cleanup the 8/16 bits that get read in the string here
|
||||
// strings.push(stream.readASCIIString());
|
||||
//}
|
||||
stream._index = end;
|
||||
//throw false;
|
||||
return {
|
||||
packetType : 'updateStringTables',
|
||||
tableId : tableId,
|
||||
changedEntries: changeEntries,
|
||||
length : length,
|
||||
strings : strings
|
||||
}
|
||||
},
|
||||
14: ParserGenerator.make('voiceInit', 'coded{s}quality{8}'),
|
||||
15: ParserGenerator.make('voiceData', 'client{8}proximity{8}length{16}data{$length}'),
|
||||
17: function (stream) {
|
||||
var reliable = !!stream.readBits(1);
|
||||
var num = (reliable) ? 1 : stream.readBits(8);
|
||||
var length = (reliable) ? stream.readBits(8) : stream.readBits(16);
|
||||
stream._index += length;
|
||||
return {
|
||||
packetType: 'parseSounds',
|
||||
reliable : reliable,
|
||||
num : num,
|
||||
length : length
|
||||
}
|
||||
},
|
||||
18: ParserGenerator.make('setView', 'index{11}'),
|
||||
19: ParserGenerator.make('fixAngle', 'relative{b}x{16}y{16}z{16}'),
|
||||
23: function (stream) {
|
||||
// user message
|
||||
var type = stream.readBits(8);
|
||||
var length = stream.readBits(11);
|
||||
var pos = stream._index;
|
||||
if (Packet.userMessageParsers[type]) {
|
||||
var result = Packet.userMessageParsers[type](stream);
|
||||
} else {
|
||||
result = {
|
||||
packetType: 'unknownUserMessage',
|
||||
type : type
|
||||
}
|
||||
}
|
||||
stream._index = pos + length;
|
||||
return result;
|
||||
},
|
||||
24: ParserGenerator.make('entityMessage', 'index{11}id{9}length{11}data{$length}'),
|
||||
25: function (stream) {
|
||||
var length = stream.readBits(11);
|
||||
var end = stream._index + length;
|
||||
var eventId = stream.readBits(9);
|
||||
var event = Packet.parseGameEvent(eventId, stream);
|
||||
stream._index = end;
|
||||
return {
|
||||
packetType: 'gameEvent',
|
||||
event : event
|
||||
}
|
||||
},
|
||||
26: function (stream) {
|
||||
var maxEntries = stream.readBits(11);
|
||||
var isDelta = !!stream.readBits(1);
|
||||
if (isDelta) {
|
||||
var delta = stream.readInt32();
|
||||
} else {
|
||||
delta = null;
|
||||
}
|
||||
var baseLink = !!stream.readBits(1);
|
||||
var updatedEntries = stream.readBits(11);
|
||||
var length = stream.readBits(20);
|
||||
var updatedBaseLink = !!stream.readBits(1);
|
||||
stream._index += length;
|
||||
return {
|
||||
packetType : 'packetEntities',
|
||||
maxEntries : maxEntries,
|
||||
isDelta : isDelta,
|
||||
delta : delta,
|
||||
baseLink : baseLink,
|
||||
updatedEntries : updatedEntries,
|
||||
length : length,
|
||||
updatedBaseLink: updatedBaseLink
|
||||
}
|
||||
},
|
||||
27: ParserGenerator.make('tempEntities', 'count{8}length{17}data{$length}'),
|
||||
28: ParserGenerator.make('preFetch', 'index{14}'),
|
||||
30: function (stream) {
|
||||
// list of game events and parameters
|
||||
var numEvents = stream.readBits(9);
|
||||
var length = stream.readBits(20);
|
||||
var events = {};
|
||||
for (var i = 0; i < numEvents; i++) {
|
||||
var id = stream.readBits(9);
|
||||
var name = stream.readASCIIString();
|
||||
var type = stream.readBits(3);
|
||||
var entries = [];
|
||||
while (type !== 0) {
|
||||
var entryName = stream.readASCIIString();
|
||||
entries.push({
|
||||
type: type,
|
||||
name: entryName
|
||||
});
|
||||
type = stream.readBits(3);
|
||||
}
|
||||
events[id] = {
|
||||
id : id,
|
||||
name : name,
|
||||
type : type,
|
||||
entries: entries
|
||||
};
|
||||
}
|
||||
Packet.gameEventMap = events;
|
||||
return {
|
||||
packetType: 'gameEventList',
|
||||
events : events
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Packet.userMessageParsers = {
|
||||
4: ParserGenerator.make('sayText2', 'client{8}raw{8}kind{s}from{s}text{s}'),
|
||||
5: ParserGenerator.make('textMsg', 'destType{8}text{s}')
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
module.exports = Packet;
|
||||
90
parser.js
Normal file
90
parser.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
var Packet = require('./packet');
|
||||
var ConsoleCmd = require('./consolecmd');
|
||||
var StringTable = require('./stringtable');
|
||||
var BitStream = require('bit-buffer').BitStream;
|
||||
|
||||
var Parser = function (steam) {
|
||||
this.stream = steam;
|
||||
};
|
||||
|
||||
Parser.MessageType = {
|
||||
Sigon : 1,
|
||||
Packet : 2,
|
||||
SyncTick : 3,
|
||||
ConsoleCmd : 4,
|
||||
UserCmd : 5,
|
||||
DataTables : 6,
|
||||
Stop : 7,
|
||||
StringTables: 8
|
||||
};
|
||||
|
||||
Parser.prototype.readHeader = function () {
|
||||
return {
|
||||
'type' : this.stream.readASCIIString(8),
|
||||
'version' : this.stream.readInt32(),
|
||||
'protocol': this.stream.readInt32(),
|
||||
'server' : this.stream.readASCIIString(260),
|
||||
'nick' : this.stream.readASCIIString(260),
|
||||
'map' : this.stream.readASCIIString(260),
|
||||
'game' : this.stream.readASCIIString(260),
|
||||
'duration': this.stream.readFloat32(),
|
||||
'ticks' : this.stream.readInt32(),
|
||||
'frames' : this.stream.readInt32(),
|
||||
'sigon' : this.stream.readInt32()
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.readMessage = function () {
|
||||
console.log();
|
||||
console.log('start message');
|
||||
console.log(this.stream.byteIndex);
|
||||
var type = this.stream.readBits(8);
|
||||
console.log(type);
|
||||
if (type === Parser.MessageType.Stop) {
|
||||
return null;
|
||||
}
|
||||
var tick = this.stream.readInt32();
|
||||
var data, start, length, buffer;
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
this.stream.byteIndex += 0x54; // command/sequence info
|
||||
break;
|
||||
case Parser.MessageType.UserCmd:
|
||||
this.stream.byteIndex += 0x04; // unknown / outgoing sequence
|
||||
break;
|
||||
case Parser.MessageType.Stop:
|
||||
return false;
|
||||
case Parser.MessageType.SyncTick:
|
||||
return true;
|
||||
}
|
||||
|
||||
length = this.stream.readInt32();
|
||||
console.log('message length: ' + length + ' byte');
|
||||
start = this.stream.byteIndex;
|
||||
buffer = this.stream.buffer.slice(start, start + length);
|
||||
this.stream.byteIndex += length;
|
||||
data = new BitStream(buffer);
|
||||
//console.log(this.stream.buffer);
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
return new Packet(type, tick, data, length);
|
||||
case Parser.MessageType.ConsoleCmd:
|
||||
return new ConsoleCmd(type, tick, data, length);
|
||||
case Parser.MessageType.UserCmd:
|
||||
return true;
|
||||
case Parser.MessageType.DataTables:
|
||||
console.log('datatable');
|
||||
return true;
|
||||
case Parser.MessageType.StringTables:
|
||||
return new StringTable(type, tick, data, length);
|
||||
default:
|
||||
return true;
|
||||
//throw 'Unknown message type: ' + type;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Parser;
|
||||
47
parsergenerator.js
Normal file
47
parsergenerator.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
var Generator = {};
|
||||
|
||||
Generator.make = function (name, string) {
|
||||
var parts = string.substr(0, string.length - 1).split('}');//remove leading } to prevent empty part
|
||||
var items = parts.map(function (part) {
|
||||
return part.split('{');
|
||||
});
|
||||
return function (stream) {
|
||||
var result = {
|
||||
'packetType': name
|
||||
};
|
||||
try {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
result[items[i][0]] = Generator.readItem(stream, items[i][1], result);
|
||||
}
|
||||
} catch (e) {
|
||||
throw 'Failed reading pattern ' + string;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
Generator.readItem = function (stream, description, data) {
|
||||
var length;
|
||||
if (description[0] === 'b') {
|
||||
return !!stream.readBits(1);
|
||||
} else if (description[0] === 's') {
|
||||
if (description.length === 1) {
|
||||
return stream.readASCIIString();
|
||||
} else {
|
||||
length = parseInt(description.substr(1), 10);
|
||||
return stream.readASCIIString(length);
|
||||
}
|
||||
} else if (description === 'f32') {
|
||||
return stream.readFloat32();
|
||||
} else if (description[0] === 'u') {
|
||||
length = parseInt(description.substr(1), 10);
|
||||
return stream.readBits(length);
|
||||
} else if (description[0] === '$') {
|
||||
var variable = description.substr(1);
|
||||
return stream.readBits(variable);
|
||||
} else {
|
||||
return stream.readBits(parseInt(description, 10), true);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Generator;
|
||||
BIN
snakewater.dem
Normal file
BIN
snakewater.dem
Normal file
Binary file not shown.
39
stringtable.js
Normal file
39
stringtable.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
var StringTable = function (type, tick, stream, length) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
};
|
||||
|
||||
StringTable.prototype.parse = function () {
|
||||
var tableCount = this.stream.readBits(8);
|
||||
var tables = {};
|
||||
for (var i = 0; i < tableCount; i++) {
|
||||
var entries = [];
|
||||
var tableName = this.stream.readASCIIString();
|
||||
var entryCount = this.stream.readBits(16);
|
||||
for (var j = 0; j < entryCount; j++) {
|
||||
var entry = {
|
||||
text: this.stream.readASCIIString()
|
||||
};
|
||||
if (this.stream.readBits(1)) {
|
||||
var extraDataLength = this.stream.readBits(16);
|
||||
entry.extraData = this.stream.readASCIIString(extraDataLength);
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
tables[tableName] = entries;
|
||||
if (this.stream.readBits(1)) {
|
||||
console.log(this.stream.readASCIIString());
|
||||
if (this.stream.readBits(1)) {
|
||||
throw 'more extra data not implemted';
|
||||
var extraDataLength = this.stream.readBits(16);
|
||||
this.stream.readBits(extraDataLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
//console.log(tables);
|
||||
return tables;
|
||||
};
|
||||
|
||||
module.exports = StringTable;
|
||||
16
test.js
Normal file
16
test.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
var Demo = require('./demo');
|
||||
var fs = require('fs');
|
||||
|
||||
fs.readFile("snakewater.dem", function (err, data) {
|
||||
if (err) throw err;
|
||||
var demo = Demo.fromNodeBuffer(data);
|
||||
var parser = demo.getParser();
|
||||
var head = parser.readHeader();
|
||||
//console.log(parser.readHeader());
|
||||
var message;
|
||||
while (message = parser.readMessage()) {
|
||||
if (message.parse) {
|
||||
message.parse();
|
||||
}
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue