;telnet routines ; to use: ; 1) include this file ; 2) include these other files: ; .include "../inc/common.i" ; .include "../inc/commonprint.i" ; .include "../inc/net.i" ; .include "../inc/char_conv.i" ; .include "../inc/c64keycodes.i" ; 3) define a routine called 'exit_telnet' ; 4) define a buffer called 'scratch_buffer' ; 5) define a zero page var called buffer_ptr .code telnet_main_entry: ;prompt for a hostname, then resolve to an IP address ldax #remote_host jsr print nb65call #NB65_INPUT_HOSTNAME bcc @host_entered ;if no host entered, then bail. jmp exit_telnet @host_entered: stax nb65_param_buffer jsr print_cr ldax #resolving jsr print ldax nb65_param_buffer nb65call #NB65_PRINT_ASCIIZ jsr print_cr ldax #nb65_param_buffer nb65call #NB65_DNS_RESOLVE bcc @resolved_ok print_failed jsr print_cr jsr print_errorcode jmp telnet_main_entry @resolved_ok: ldx #3 @copy_telnet_ip_loop: lda nb65_param_buffer,x sta telnet_ip,x dex bpl @copy_telnet_ip_loop @get_port: ldax #remote_port jsr print nb65call #NB65_INPUT_PORT_NUMBER bcc @port_entered ;if no port entered, then assume port 23 ldax #23 @port_entered: stax telnet_port jsr print_cr ldax #char_mode_prompt jsr print @char_mode_input: jsr get_key cmp #'A' beq @ascii_mode cmp #'a' beq @ascii_mode cmp #'P' beq @petscii_mode cmp #'p' beq @petscii_mode cmp #'l' beq @line_mode cmp #'L' beq @line_mode jmp @char_mode_input @ascii_mode: lda #0 sta character_set sta line_mode lda #1 sta local_echo lda #telnet_state_normal sta telnet_state jmp @after_mode_set @petscii_mode: lda #1 sta character_set lda #0 sta local_echo sta line_mode jmp @after_mode_set @line_mode: lda #0 sta character_set lda #1 sta local_echo sta line_mode @after_mode_set: lda #147 ; 'CLR/HOME' jsr print_a ldax #connecting_in jsr print lda character_set beq @a_mode ldax #petscii jmp @c_mode @a_mode: lda line_mode bne @l_mode ldax #ascii jmp @c_mode @l_mode: ldax #line @c_mode: jsr print ldax #mode jsr print telnet_connect: ldax #telnet_callback stax nb65_param_buffer+NB65_TCP_CALLBACK ldx #3 @copy_dest_ip: lda telnet_ip,x sta nb65_param_buffer+NB65_TCP_REMOTE_IP,x dex bpl @copy_dest_ip ldax telnet_port stax nb65_param_buffer+NB65_TCP_PORT ldax #nb65_param_buffer nb65call #NB65_TCP_CONNECT bcc @connect_ok jsr print_cr print_failed jsr print_errorcode jmp telnet_main_entry @connect_ok: print_ok jsr print_cr lda #0 sta connection_closed @main_polling_loop: jsr NB65_PERIODIC_PROCESSING_VECTOR lda connection_closed beq @not_disconnected ldax #disconnected jsr print jmp telnet_main_entry @not_disconnected: lda line_mode beq @not_line_mode nb65call #NB65_INPUT_STRING stax buffer_ptr ldy #0 @copy_one_char: lda (buffer_ptr),y tax lda petscii_to_ascii_table,x beq @end_of_input_string sta scratch_buffer,y iny bne @copy_one_char @end_of_input_string: lda #$0d sta scratch_buffer,y iny lda #$0a sta scratch_buffer,y iny sty nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH lda #0 sta nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH+1 jsr print_cr jmp @no_more_input @not_line_mode: ;is there anything in the input buffer? lda $c6 ;NDX - chars in keyboard buffer beq @main_polling_loop lda #0 sta nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH sta nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH+1 @get_next_char: jsr $ffe4 ;getkey - 0 means no input ; pha ; jsr print_hex ; pla tax beq @no_more_input cmp #$03 ;RUN/STOP bne @not_runstop lda #0 sta $cb ;overwrite "current key pressed" else it's seen by the tcp stack and the close aborts ldax #closing_connection jsr print nb65call #NB65_TCP_CLOSE_CONNECTION bcs @error_on_disconnect ldax #disconnected jsr print jmp telnet_main_entry @error_on_disconnect: jsr print_errorcode jsr print_cr jmp telnet_main_entry @not_runstop: lda character_set bne @no_conversion_required txa cmp #$0d bne @not_cr ;if we get a CR in ascii mode, send CR/LF ldy nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH sta scratch_buffer,y inc nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH ldx #$0a jmp @no_conversion_required @not_cr: lda petscii_to_ascii_table,x tax @no_conversion_required: txa ldy nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH sta scratch_buffer,y inc nb65_param_buffer+NB65_TCP_PAYLOAD_LENGTH jmp @get_next_char @no_more_input: ldax #scratch_buffer stax nb65_param_buffer+NB65_TCP_PAYLOAD_POINTER ldax #nb65_param_buffer nb65call #NB65_SEND_TCP_PACKET bcs @error_on_send jmp @main_polling_loop @error_on_send: ldax #transmission_error jsr print jsr print_errorcode jmp telnet_main_entry ;tcp callback - will be executed whenever data arrives on the TCP connection telnet_callback: ldax #nb65_param_buffer nb65call #NB65_GET_INPUT_PACKET_INFO lda nb65_param_buffer+NB65_PAYLOAD_LENGTH+1 cmp #$ff bne @not_eof lda #1 sta connection_closed rts @not_eof: ldax nb65_param_buffer+NB65_PAYLOAD_POINTER stax buffer_ptr lda nb65_param_buffer+NB65_PAYLOAD_LENGTH sta buffer_length lda nb65_param_buffer+NB65_PAYLOAD_LENGTH+1 sta buffer_length+1 ;since we don't check the buffer length till the end of the loop, set 'buffer length' to be 1 less than the actual number of bytes dec buffer_length bpl :+ dec buffer_length+1 : ldy #0 sty iac_response_buffer_length @next_byte: lda (buffer_ptr),y tax lda character_set beq :+ jmp @no_conversion_req : lda line_mode beq :+ jmp@convert_to_petscii : ;if we get here, we are in ASCII 'char at a time' mode, so look for (and process) Telnet style IAC bytes lda telnet_state cmp #telnet_state_got_command beq @waiting_for_option cmp #telnet_state_got_iac beq @waiting_for_command ; we must be in 'normal' mode txa cmp #255 beq :+ jmp @not_iac : lda #telnet_state_got_iac sta telnet_state jmp @byte_processed @waiting_for_command: txa sta telnet_command cmp #$fb ;WILL beq @option cmp #$fc ;WONT beq @option cmp #$fd ; DO beq @option cmp #$fe ;DONT beq @option ;we got a command we don't understand - just ignore it lda #telnet_state_normal sta telnet_state jmp @byte_processed @option: lda #telnet_state_got_command sta telnet_state jmp @byte_processed @waiting_for_option: ;we have now got IAC, ,