UNPKG

slp-enforcer

Version:

Finds violations of the Melee Controller Ruleset by inspecting SLP files

168 lines (167 loc) 6.7 kB
"use strict"; // import type { WritableOptions } from "stream"; // import { Writable } from "stream"; Object.defineProperty(exports, "__esModule", { value: true }); var SlpStreamMode; (function (SlpStreamMode) { SlpStreamMode["AUTO"] = "AUTO"; SlpStreamMode["MANUAL"] = "MANUAL"; })(SlpStreamMode = exports.SlpStreamMode || (exports.SlpStreamMode = {})); const defaultSettings = { suppressErrors: false, mode: SlpStreamMode.AUTO, }; var SlpStreamEvent; (function (SlpStreamEvent) { SlpStreamEvent["RAW"] = "slp-raw"; SlpStreamEvent["COMMAND"] = "slp-command"; })(SlpStreamEvent = exports.SlpStreamEvent || (exports.SlpStreamEvent = {})); /** * SlpStream is a writable stream of Slippi data. It passes the data being written in * and emits an event based on what kind of Slippi messages were processed. * * SlpStream emits two events: "slp-raw" and "slp-command". The "slp-raw" event emits the raw buffer * bytes whenever it processes each command. You can manually parse this or write it to a * file. The "slp-command" event returns the parsed payload which you can access the attributes. * * @class SlpStream * @extends {Writable} */ // export class SlpStream extends Writable { // private gameEnded = false; // True only if in manual mode and the game has completed // private settings: SlpStreamSettings; // private payloadSizes: MessageSizes | null = null; // private previousBuffer: Uint8Array = Buffer.from([]); // /** // *Creates an instance of SlpStream. // * @param {Partial<SlpStreamSettings>} [slpOptions] // * @param {WritableOptions} [opts] // * @memberof SlpStream // */ // public constructor(slpOptions?: Partial<SlpStreamSettings>, opts?: WritableOptions) { // super(opts); // this.settings = Object.assign({}, defaultSettings, slpOptions); // } // public restart(): void { // this.gameEnded = false; // this.payloadSizes = null; // } // // eslint-disable-next-line @typescript-eslint/no-explicit-any // public _write(newData: Buffer, encoding: string, callback: (error?: Error | null, data?: any) => void): void { // if (encoding !== "buffer") { // throw new Error(`Unsupported stream encoding. Expected 'buffer' got '${encoding}'.`); // } // // Join the current data with the old data // const data = Uint8Array.from(Buffer.concat([this.previousBuffer, newData])); // // Clear previous data // this.previousBuffer = Buffer.from([]); // const dataView = new DataView(data.buffer); // // Iterate through the data // let index = 0; // while (index < data.length) { // // // We want to filter out the network messages // // if (Buffer.from(data.slice(index, index + 5)).toString() === NETWORK_MESSAGE) { // // index += 5; // // continue; // // } // // Make sure we have enough data to read a full payload // const command = dataView.getUint8(index); // let payloadSize = 0; // if (this.payloadSizes) { // payloadSize = this.payloadSizes.get(command) ?? 0; // } // const remainingLen = data.length - index; // if (remainingLen < payloadSize + 1) { // // If remaining length is not long enough for full payload, save the remaining // // data until we receive more data. The data has been split up. // this.previousBuffer = data.slice(index); // break; // } // // Only process if the game is still going // if (this.settings.mode === SlpStreamMode.MANUAL && this.gameEnded) { // break; // } // // Increment by one for the command byte // index += 1; // const payloadPtr = data.slice(index); // const payloadDataView = new DataView(data.buffer, index); // let payloadLen = 0; // try { // payloadLen = this._processCommand(command, payloadPtr, payloadDataView); // } catch (err) { // // Only throw the error if we're not suppressing the errors // if (!this.settings.suppressErrors) { // throw err; // } // payloadLen = 0; // } // index += payloadLen; // } // callback(); // } // private _writeCommand(command: Command, entirePayload: Uint8Array, payloadSize: number): Uint8Array { // const payloadBuf = entirePayload.slice(0, payloadSize); // const bufToWrite = Buffer.concat([Buffer.from([command]), payloadBuf]); // // Forward the raw buffer onwards // const event: SlpRawEventPayload = { // command: command, // payload: bufToWrite, // }; // this.emit(SlpStreamEvent.RAW, event); // return new Uint8Array(bufToWrite); // } // private _processCommand(command: Command, entirePayload: Uint8Array, dataView: DataView): number { // // Handle the message size command // if (command === Command.MESSAGE_SIZES) { // const payloadSize = dataView.getUint8(0); // // Set the payload sizes // this.payloadSizes = processReceiveCommands(dataView); // // Emit the raw command event // this._writeCommand(command, entirePayload, payloadSize); // const eventPayload: SlpCommandEventPayload = { // command: command, // payload: this.payloadSizes, // }; // this.emit(SlpStreamEvent.COMMAND, eventPayload); // return payloadSize; // } // let payloadSize = 0; // if (this.payloadSizes) { // payloadSize = this.payloadSizes.get(command) ?? 0; // } // // Fetch the payload and parse it // let payload: Uint8Array; // let parsedPayload: EventPayloadTypes | null = null; // if (payloadSize > 0) { // payload = this._writeCommand(command, entirePayload, payloadSize); // parsedPayload = parseMessage(command, payload); // } // if (!parsedPayload) { // return payloadSize; // } // switch (command) { // case Command.GAME_END: // // Stop parsing data until we manually restart the stream // if (this.settings.mode === SlpStreamMode.MANUAL) { // this.gameEnded = true; // } // break; // } // const eventPayload: SlpCommandEventPayload = { // command: command, // payload: parsedPayload, // }; // this.emit(SlpStreamEvent.COMMAND, eventPayload); // return payloadSize; // } // } const processReceiveCommands = (dataView) => { const payloadSizes = new Map(); const payloadLen = dataView.getUint8(0); for (let i = 1; i < payloadLen; i += 3) { const commandByte = dataView.getUint8(i); const payloadSize = dataView.getUint16(i + 1); payloadSizes.set(commandByte, payloadSize); } return payloadSizes; };