slp-enforcer
Version:
Finds violations of the Melee Controller Ruleset by inspecting SLP files
168 lines (167 loc) • 6.7 kB
JavaScript
;
// 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;
};