deadem
Version:
JavaScript (Node.js & Browsers) parser for Deadlock (Valve Source 2 Engine) demo/replay files
319 lines (236 loc) • 9.37 kB
JavaScript
import SnappyDecompressor from '#core/SnappyDecompressor.instance.js';
import Demo from '#data/Demo.js';
import Entity from '#data/entity/Entity.js';
import DemoPacketType from '#data/enums/DemoPacketType.js';
import DemoSource from '#data/enums/DemoSource.js';
import MessagePacketType from '#data/enums/MessagePacketType.js';
import WorkerMessageType from '#data/enums/WorkerMessageType.js';
import MessagePacketRawExtractor from '#extractors/MessagePacketRawExtractor.js';
import DemoMessageHandler from '#handlers/DemoMessageHandler.js';
import DemoPacketHandler from '#handlers/DemoPacketHandler.js';
import WorkerResponseDHPParse from '#workers/responses/WorkerResponseDHPParse.js';
import WorkerResponseDPacketSync from '#workers/responses/WorkerResponseDPacketSync.js';
import WorkerResponseMPacketSync from '#workers/responses/WorkerResponseMPacketSync.js';
import WorkerResponseSvcCreatedEntities from '#workers/responses/WorkerResponseSvcCreatedEntities.js';
import WorkerResponseSvcUpdatedEntities from '#workers/responses/WorkerResponseSvcUpdatedEntities.js';
import WorkerMessageBridge from '#workers/WorkerMessageBridge.instance.js';
class Worker {
/**
* @abstract
* @constructor
*/
constructor() {
this._demo = new Demo();
this._demoMessageHandler = new DemoMessageHandler(this._demo);
this._demoPacketHandler = new DemoPacketHandler(this._demo);
}
/**
* @protected
*/
_respond() {
throw new Error('Worker._respond() is not implemented');
}
/**
* @protected
* @param {*} requestRaw
*/
_route(requestRaw) {
const workerRequestClass = WorkerMessageBridge.resolveRequestClass(requestRaw);
const request = workerRequestClass.deserialize(requestRaw.payload);
switch (request.type) {
case WorkerMessageType.DEMO_HEAVY_PACKET_PARSE: {
this._handleHeavyPacketParse(request);
break;
}
case WorkerMessageType.DEMO_PACKET_SYNC: {
const demoPacket = request.payload;
switch (demoPacket.type) {
case DemoPacketType.DEM_CLASS_INFO:
this._handleClassInfo(request);
break;
case DemoPacketType.DEM_SEND_TABLES:
this._handleSendTables(request);
break;
case DemoPacketType.DEM_STRING_TABLES:
this._handleStringTables(request);
break;
default:
throw new Error(`Unhandled message [ ${WorkerMessageType.DEMO_PACKET_SYNC.code} ] [ ${demoPacket.type.code} ]`);
}
break;
}
case WorkerMessageType.MESSAGE_PACKET_SYNC: {
const messagePacket = request.payload;
switch (messagePacket.type) {
case MessagePacketType.SVC_CLEAR_ALL_STRING_TABLES:
this._handleSvcClearAllStringTables(request);
break;
case MessagePacketType.SVC_CREATE_STRING_TABLE:
this._handleSvcCreateStringTable(request);
break;
case MessagePacketType.SVC_UPDATE_STRING_TABLE:
this._handleSvcUpdateStringTable(request);
break;
case MessagePacketType.SVC_SERVER_INFO:
this._handleSvcServerInfo(request);
break;
default:
throw new Error(`Unhandled message [ ${WorkerMessageType.MESSAGE_PACKET_SYNC.code} ] [ ${messagePacket.type.code} ]`);
}
break;
}
case WorkerMessageType.SVC_CREATED_ENTITIES: {
this._handleSvcCreatedEntities(request);
break;
}
case WorkerMessageType.SVC_UPDATED_ENTITIES: {
this._handleSvcUpdatedEntities(request);
break;
}
default:
throw new Error(`Unhandled request [ ${request.type.code} ]`);
}
}
/**
* @protected
* @param {WorkerRequestDHPParse} request
*/
_handleHeavyPacketParse(request) {
const batches = [ ];
const stringTables = [ ];
request.payload.forEach((data) => {
const [ typeId, sourceId, compressed, buffer ] = data;
const demoPacketType = DemoPacketType.parseById(typeId);
const demoSource = DemoSource.parseById(sourceId);
let decompressed;
if (compressed) {
decompressed = SnappyDecompressor.decompress(buffer);
} else {
decompressed = buffer;
}
let decoded;
if (demoSource === DemoSource.HTTP_BROADCAST) {
decoded = { data: decompressed };
} else {
decoded = demoPacketType.proto.decode(decompressed);
}
let extractor;
if (demoPacketType === DemoPacketType.DEM_FULL_PACKET) {
extractor = new MessagePacketRawExtractor(decoded.packet.data);
stringTables.push(decoded.stringTable);
} else {
extractor = new MessagePacketRawExtractor(decoded.data);
stringTables.push(null);
}
const packed = extractor.allPacked();
batches.push(packed);
});
const response = new WorkerResponseDHPParse(batches, stringTables);
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestDPacketSync} request
*/
_handleClassInfo(request) {
const demoPacket = request.payload;
this._demoPacketHandler.handleDemClassInfo(demoPacket);
const response = new WorkerResponseDPacketSync();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestDPacketSync} request
*/
_handleSendTables(request) {
const demoPacket = request.payload;
this._demoPacketHandler.handleDemSendTables(demoPacket);
const response = new WorkerResponseDPacketSync();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestDPacketSync} request
*/
_handleStringTables(request) {
const demoPacket = request.payload;
this._demoPacketHandler.handleDemStringTables(demoPacket);
const response = new WorkerResponseDPacketSync();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestMPacketSync} request
*/
_handleSvcClearAllStringTables(request) {
const messagePacket = request.payload;
this._demoMessageHandler.handleSvcClearAllStringTables(messagePacket);
const response = new WorkerResponseMPacketSync();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestMPacketSync} request
*/
_handleSvcCreateStringTable(request) {
const messagePacket = request.payload;
this._demoMessageHandler.handleSvcCreateStringTable(messagePacket);
const response = new WorkerResponseMPacketSync();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestMPacketSync} request
*/
_handleSvcUpdateStringTable(request) {
const messagePacket = request.payload;
this._demoMessageHandler.handleSvcUpdateStringTable(messagePacket);
const response = new WorkerResponseMPacketSync();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestMPacketSync} request
*/
_handleSvcServerInfo(request) {
const messagePacket = request.payload;
this._demoMessageHandler.handleSvcServerInfo(messagePacket);
const response = new WorkerResponseMPacketSync();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestMPacketSync} request
*/
_handleSvcCreatedEntities(request) {
const length = request.payload.length;
for (let i = 0; i < length; i += 3) {
const index = request.payload[i];
const serial = request.payload[i + 1];
const clazzId = request.payload[i + 2];
const clazz = this._demo.getClassById(clazzId);
if (clazz === null) {
throw new Error(`Couldn't find class [ ${clazzId} ]`);
}
this._demo.registerEntity(new Entity(index, serial, clazz));
}
const response = new WorkerResponseSvcCreatedEntities();
this._respond(response);
}
/**
* @protected
* @param {WorkerRequestMPacketSync} request
*/
_handleSvcUpdatedEntities(request) {
const messagePacket = request.payload;
let events;
try {
events = this._demoMessageHandler.handleSvcPacketEntitiesPartial(messagePacket);
} catch {
events = [ ];
}
const response = new WorkerResponseSvcUpdatedEntities(events);
this._respond(response);
}
}
export default Worker;