import type { WritableOptions } from "stream" ; import { Writable } from "stream" ; import { NETWORK_MESSAGE } from "../console" ; import type { EventPayloadTypes } from "../types" ; import { Command } from "../types" ; import { parseMessage } from "./slpReader" ; export enum SlpStreamMode { AUTO = "AUTO" , MANUAL = "MANUAL" } const defaultSettings = { suppressErrors : false , mode : SlpStreamMode . AUTO , } ; export type SlpStreamSettings = typeof defaultSettings ; export type MessageSizes = Map < Command , number > ; export interface SlpCommandEventPayload { command : Command ; payload : EventPayloadTypes | MessageSizes ; } export interface SlpRawEventPayload { command : Command ; payload : Buffer ; } export enum SlpStreamEvent { RAW = "slp-raw" , COMMAND = "slp-command" } class extends Writable export class SlpStream extends Writable { private gameEnded = false ; private settings : SlpStreamSettings ; private payloadSizes : MessageSizes | null = null ; private previousBuffer : Uint8Array = Buffer . from ( [ ] ) ; param { Partial < SlpStreamSettings > } slpOptions param { WritableOptions } opts memberof public constructor ( slpOptions ? , opts ? ) { super ( opts ) ; this . settings = Object . assign ( { } , defaultSettings , slpOptions ) ; } public restart ( ) { this . gameEnded = false ; this . payloadSizes = null ; } public _write ( newData , encoding , callback ) { if ( encoding !== "buffer" ) { throw new Error ( `Unsupported stream encoding. Expected 'buffer' got '${ encoding }'.` ) ; } const data = Uint8Array . from ( Buffer . concat ( [ this . previousBuffer , newData ] ) ) ; this . previousBuffer = Buffer . from ( [ ] ) ; const dataView = new DataView ( data . buffer ) ; let index = 0 ; while ( index < data . length ) { if ( Buffer . from ( data . slice ( index , index + 5 ) ) . toString ( ) === NETWORK_MESSAGE ) { index += 5 ; continue ; } 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 ) { this . previousBuffer = data . slice ( index ) ; break ; } if ( this . settings . mode === SlpStreamMode . MANUAL && this . gameEnded ) { break ; } 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 ) { if ( ! this . settings . suppressErrors ) { throw err ; } payloadLen = 0 ; } index += payloadLen ; } callback ( ) ; } private _writeCommand ( command , entirePayload , payloadSize ) { const payloadBuf = entirePayload . slice ( 0 , payloadSize ) ; const bufToWrite = Buffer . concat ( [ Buffer . from ( [ command ] ) , payloadBuf ] ) ; this . emit ( SlpStreamEvent . RAW , { command : command , payload : bufToWrite , } as SlpRawEventPayload ) ; return new Uint8Array ( bufToWrite ) ; } private _processCommand ( command , entirePayload , dataView ) { if ( command === Command . MESSAGE_SIZES ) { const payloadSize = dataView . getUint8 ( 0 ) ; this . payloadSizes = processReceiveCommands ( dataView ) ; this . _writeCommand ( command , entirePayload , payloadSize ) ; this . emit ( SlpStreamEvent . COMMAND , { command : command , payload : this . payloadSizes , } as SlpCommandEventPayload ) ; return payloadSize ; } let payloadSize = 0 ; if ( this . payloadSizes ) { payloadSize = this . payloadSizes . get ( command ) ?? 0 ; } let payload ; let parsedPayload = null ; if ( payloadSize > 0 ) { payload = this . _writeCommand ( command , entirePayload , payloadSize ) ; parsedPayload = parseMessage ( command , payload ) ; } if ( ! parsedPayload ) { return payloadSize ; } switch ( command ) { case Command . GAME_END : if ( this . settings . mode === SlpStreamMode . MANUAL ) { this . gameEnded = true ; } break ; } this . emit ( SlpStreamEvent . COMMAND , { command : command , payload : parsedPayload , } as SlpCommandEventPayload ) ; return payloadSize ; } } const processReceiveCommands = ( dataView ) : MessageSizes => { const payloadSizes = new Map < Command , number > ( ) ; 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 ; } ;