import { LogSocket } from '../../log' ; import { DzrpBufferRemote , CONNECTION_TIMEOUT } from './dzrpbufferremote' ; import { Socket } from 'net' ; import { Settings } from '../../settings' ; import { Utility } from '../../misc/utility' ; import { BREAK_REASON_NUMBER } from '../remotebase' ; import { GenericBreakpoint } from '../../genericwatchpoint' ; import { Opcode , OpcodeFlag } from '../../disassembler/opcode' ; import { Z80Registers } from '../z80registers' ; const MESSAGE_START_BYTE = 0xA5 ; /**
 * Structure to hold the opcode to restore and the address of
 * the breakpoint.
 */ interface RestorableBreakpoint { address : number ; opcode : number ; } /**
 * A ZX Next remote that is connected via a socket.
 * I.e. another program that converts socket to serial.
 */ export class ZxNextSocketRemote extends DzrpBufferRemote { public socket : Socket ; protected breakedAddress : number | undefined ; protected breakpointIdLastIndex ; protected breakpointsAndOpcodes : Array < RestorableBreakpoint > ; protected msgStartByteFound : boolean ; protected lastCmdContinueTime = 0 ; protected cmdContinueNoResponseErrorTime = 1000 ; constructor ( ) { super ( ) ; this . cmdRespTimeoutTime = Settings . launch . zxnext . socketTimeout * 1000 ; } public async doInitialization ( ) { this . socket = new Socket ( ) ; this . socket . unref ( ) ; this . socket . on ( 'connect' , async ( ) => { LogSocket . log ( 'ZxNextSocketRemote: Connected to server!' ) ; this . receivedData = Buffer . alloc ( 0 ) ; this . msgStartByteFound = false ; this . expectedLength = 4 ; this . receivingHeader = true ; this . stopChunkTimeout ( ) ; this . breakedAddress = undefined ; this . breakpointIdLastIndex = 0 ; this . onConnect ( ) ; } ) ; this . socket . on ( 'error' , err => { LogSocket . log ( 'ZxNextSocketRemote: Error: ' + err ) ; this . emit ( 'error' , err ) ; } ) ; this . socket . on ( 'data' , data => { this . dataReceived ( data ) ; } ) ; this . socket . setTimeout ( CONNECTION_TIMEOUT ) ; const port = Settings . launch . zxnext . port ; const hostname = Settings . launch . zxnext . hostname ; this . socket . connect ( port , hostname ) ; } /**
     * This will disconnect the socket.
     */ public async disconnect ( ) { if ( ! this . socket ) return ; return new Promise < void > ( async ( resolve ) => { await super . disconnect ( ) ; this . socket . end ( ( ) => { resolve ( ) ; } ) ; } ) ; } /**
     * Note:
     * This is like the super class implementation except that it suppresses a warning message.
     * If F5 (CONTINUE) or F10 etc. is pressed rapidly or held down it may happen that a request
     * (e.g. memory request) is done after CMD_CONTINUE has been sent. Due to some asynchronous
     * requests from vscode.
     * Normally this is not a problem, the remote would just answer the request.
     * For the ZXNext UART serial protocol this is different.
     * The UART is not accessible when the Z80 program is being run. This is because the 'dezogif'
     * program does not check the UART for new data when run and because the Joystick ports are
     * remapped to serve as joystick ports and not as UART ports when the program is being run.
     * Thus, the ZX Next is not able to receive and not able to respond.
     * Furthermore if the user now changes e.g. a register or memory content there should be
     * feedback that this is not possible.
     * On the other hand the "automatic" requests from vscode should be suppressed.
     * As there is no way to distinguish it is done with a time guardian.
     * I.e about one second after the CMD_CONTINUE was sent no warning is emitted.
     * Otherwise the warning is shown.
     */ protected startCmdRespTimeout ( respTimeoutTime ) { this . stopCmdRespTimeout ( ) ; this . cmdRespTimeout = setTimeout ( ( ) => { this . stopCmdRespTimeout ( ) ; const err = new Error ( 'No response received from remote. A simple reason for this message is that the ZX Next is running the debugged program and cannot answer. In that case press the yellow NMI button on the ZX Next to pause execution. It can, of course, be also any other connection problem.' ) ; LogSocket . log ( 'Warning: ' + err . message ) ; const timeSpan = ( Date . now ( ) - this . lastCmdContinueTime ) ; if ( timeSpan > this . cmdContinueNoResponseErrorTime ) this . emit ( 'warning' , err . message ) ; const msg = this . messageQueue . shift ( ) ! ; this . sendNextMessage ( ) ; msg . reject ( err ) ; } , respTimeoutTime ) ; } /**
     * Called when data has been received.
     * If not configured for UART the ZX Next emits zeros through the serial cable.
     * Therefore we wait until the first indication of a message is received.
     * I.e. all received messages start with 0xA5.
     */ protected dataReceived ( data ) { let nData = data ; if ( this . receivedData . length == 0 && ! this . msgStartByteFound ) { const len = data . length ; let i ; for ( i = 0 ; i < len ; i ++ ) { if ( data [ i ] == MESSAGE_START_BYTE ) { if ( len == 1 ) { this . msgStartByteFound = true ; return ; } break ; } } if ( i + 1 >= len ) return ; nData = data . subarray ( i + 1 ) ; } this . msgStartByteFound = false ; super . dataReceived ( nData ) ; } /**
     * Writes the buffer to the socket port.
     */ protected async sendBuffer ( buffer ) { return new Promise < void > ( ( resolve , reject ) => { const txt = this . dzrpCmdBufferToString ( buffer ) ; LogSocket . log ( '>>> ZxNextSocketRemote: Sending ' + txt ) ; let outerError ; try { this . socket . write ( buffer , ( error ) => { if ( ! outerError ) { if ( error ) throw error ; resolve ( ) ; } } ) ; } catch ( e ) { outerError = e ; reject ( e ) ; } } ) ; } param stepOver returns protected async calcStepBp ( stepOver ) { let [ opcode , bpAddr1 , bpAddr2 ] = await super . calcStepBp ( stepOver ) ; const pc = this . getPC ( ) ; if ( pc == bpAddr2 ) { bpAddr2 = undefined ; } const ocFlags = opcode . flags ; if ( ocFlags & OpcodeFlag . BRANCH_ADDRESS && ( ocFlags & OpcodeFlag . CONDITIONAL ) == 0 && opcode . code == 0xCF ) { bpAddr1 = pc + opcode . length ; bpAddr2 = undefined ; } return [ opcode , bpAddr1 , bpAddr2 ] ; } param bp1Address param bp2Address protected async sendDzrpCmdContinue ( bp1Address ? , bp2Address ? ) { if ( this . checkBreakpoint ( bp1Address ) || this . checkBreakpoint ( bp2Address ) ) { const breakAddress = this . getPC ( ) ; const breakReasonString = "Cannot step at address " + Utility . getHexString ( breakAddress , 4 ) + "h." ; this . emit ( 'warning' , breakReasonString ) ; const breakNumber = BREAK_REASON_NUMBER . STEPPING_NOT_ALLOWED ; this . funcContinueResolve ! ( { breakNumber , breakAddress , breakReasonString } ) ; return ; } const originalContinueResolve = this . funcContinueResolve ! ; const resolveWithBp = async ( { breakNumber , breakAddress , breakReasonString } ) => { this . breakedAddress = undefined ; if ( breakNumber == BREAK_REASON_NUMBER . BREAKPOINT_HIT ) this . breakedAddress = breakAddress ; if ( breakAddress == longBp1Address || breakAddress == longBp2Address ) breakNumber = BREAK_REASON_NUMBER . NO_REASON ; const count = this . breakpointsAndOpcodes . length ; let memCount = count ; if ( oldOpcode != undefined ) memCount + 1 ; const memValues = new Array < { address : number ; value : number ; } > ( memCount ) ; let k = 0 ; if ( oldOpcode != undefined ) { memValues [ k ++ ] = { address : oldBreakedAddress ! , value : oldOpcode [ 0 ] } ; } for ( let i = count - 1 ; i >= 0 ; i -- ) { const bp = this . breakpointsAndOpcodes [ i ] ; memValues [ k ++ ] = { address : bp . address , value : bp . opcode } ; } await this . sendDzrpCmdRestoreMem ( memValues ) ; this . breakpointsAndOpcodes = undefined as any ; originalContinueResolve ( { breakNumber , breakAddress , breakReasonString } ) ; } ; const bpAddresses = this . getBreakpointAddresses ( ) ; const opcodes = await this . sendDzrpCmdSetBreakpoints ( bpAddresses ) ; this . breakpointsAndOpcodes = new Array < RestorableBreakpoint > ( ) ; let len = bpAddresses . length ; for ( let i = 0 ; i < len ; i ++ ) { const address = bpAddresses [ i ] ; const opcode = opcodes [ i ] ; this . breakpointsAndOpcodes . push ( { address , opcode } ) ; } let longBp1Address = bp1Address ; let longBp2Address = bp2Address ; const slots = this . getSlots ( ) ; if ( bp1Address != undefined ) longBp1Address = Z80Registers . createLongAddress ( bp1Address , slots ) ; if ( bp2Address != undefined ) longBp2Address = Z80Registers . createLongAddress ( bp2Address , slots ) ; const oldBreakedAddress = this . breakedAddress ; let oldOpcode ; if ( oldBreakedAddress == undefined ) { Utility . assert ( this . funcContinueResolve ) ; this . funcContinueResolve = resolveWithBp ; this . lastCmdContinueTime = Date . now ( ) ; await super . sendDzrpCmdContinue ( bp1Address , bp2Address ) ; } else { this . funcContinueResolve = async ( { breakNumber , breakAddress , breakReasonString } ) => { this . breakedAddress = undefined ; if ( breakNumber == BREAK_REASON_NUMBER . BREAKPOINT_HIT ) this . breakedAddress = breakAddress ; if ( ( breakAddress != undefined && ( breakAddress == bp1Address || breakAddress == bp2Address ) ) || breakNumber == BREAK_REASON_NUMBER . BREAKPOINT_HIT ) { resolveWithBp ( { breakNumber , breakAddress , breakReasonString } ) ; } else { this . funcContinueResolve = resolveWithBp ; oldOpcode = await this . sendDzrpCmdSetBreakpoints ( [ oldBreakedAddress ] ) ; this . lastCmdContinueTime = Date . now ( ) ; await super . sendDzrpCmdContinue ( bp1Address , bp2Address ) ; } } ; let [ , tmpBp1Addr , tmpBp2Addr ] = await this . calcStepBp ( false ) ; this . lastCmdContinueTime = Date . now ( ) ; await super . sendDzrpCmdContinue ( tmpBp1Addr , tmpBp2Addr ) ; } } param bp protected async sendDzrpCmdAddBreakpoint ( bp ) { const bpAddress = bp . address ; const errText = this . checkBreakpoint ( bpAddress ) ; if ( errText ) { this . emit ( 'warning' , "On the ZXNext you cannot set breakpoints at " + errText + "." ) ; bp . bpId = 0 ; } this . breakpointIdLastIndex ++ ; bp . bpId = this . breakpointIdLastIndex ; if ( this . breakpointsAndOpcodes && ! this . pauseStep ) { const opcodes = await this . sendDzrpCmdSetBreakpoints ( [ bpAddress ] ) ; const opcode = opcodes [ 0 ] ; this . breakpointsAndOpcodes . push ( { address : bpAddress , opcode } ) ; } } param bp protected async sendDzrpCmdRemoveBreakpoint ( bp ) { const bpAddress = bp . address ; if ( this . breakedAddress == bpAddress ) this . breakedAddress = undefined ; if ( this . breakpointsAndOpcodes && ! this . pauseStep ) { const bpLen = this . breakpointsAndOpcodes . length ; for ( let i = bpLen - 1 ; i >= 0 ; i -- ) { const bp = this . breakpointsAndOpcodes [ i ] ; if ( bp . address == bpAddress ) { const opcode = bp . opcode ; await this . sendDzrpCmdRestoreMem ( [ { address : bpAddress , value : opcode } ] ) ; this . breakpointsAndOpcodes . splice ( i , 1 ) ; return ; } } } } returns protected getBreakpointAddresses ( ) { const bpFiltered = new Array < number > ( ) ; const tmpBps = this . tmpBreakpoints . keys ( ) ; for ( const addr of tmpBps ) { if ( addr != this . breakedAddress ) bpFiltered . push ( addr ) ; } return bpFiltered ; } returns protected checkBreakpoint ( addr ) { if ( addr != undefined ) { const bank = Z80Registers . getBankFromAddress ( addr ) ; if ( bank >= 0xFE ) return "ROM" ; const addr64k = addr & 0xFFFF ; if ( ( addr64k >= 0 && addr64k <= 0x07 ) || ( addr64k >= 0x66 && addr64k <= 0x73 ) ) return "addresses 0x0000-0x0007 and 0x0066-0x0073" ; } return undefined ; } /**
     * This command is not used anymore. Use the NMI button instead.
     */ protected async sendDzrpCmdPause ( ) { throw Error ( "To pause execution use the yellow NMI button of the ZX Next." ) ; } /**
     * Not supported.
     * The ZX Next can't read the sprite attributes.
     * Throws an exception.
      */ public async sendDzrpCmdGetSprites ( index , count ) { throw Error ( "The sprite attributes can't be read on a ZX Next unfortunately." ) ; } /**
    * Not supported.
    * The ZX Next can't read the sprite patterns.
    * Throws an exception.
    */ protected async sendDzrpCmdGetSpritePatterns ( index , count ) { throw Error ( "The sprite patterns can't be read on a ZX Next unfortunately." ) ; } /**
     * State saving is not supported with ZX Next.
     */ public async stateSave ( filePath ) { throw Error ( "Saving and restoring the state is not supported with the ZX Next." ) ; } public async stateRestore ( filePath ) { throw Error ( "Saving and restoring the state is not supported with the ZX Next." ) ; } }