; XMODEM file transfer .include "../inc/common.i" .ifndef KPR_API_VERSION_NUMBER .define EQU = .include "../inc/kipper_constants.i" .endif XMODEM_BLOCK_SIZE=$80 ;how many bytes (excluding header & checksum) in each block? XMODEM_TIMEOUT_SECONDS=5 XMODEM_MAX_ERRORS=10 SOH = $01 EOT = $04 ACK = $06 NAK = $15 CAN = $18 PAD = $1A ;padding added to end of file .export xmodem_receive .export xmodem_send .export xmodem_iac_escape ;are IAC bytes ($FF) escaped? .import ip65_process .import ip65_error .import tcp_callback .import copymem .importzp copy_src .importzp copy_dest .import tcp_send .import tcp_send_data_len .import tcp_inbound_data_ptr .import tcp_inbound_data_length .import check_for_abort_key .import print_a .import print_cr .import print_ascii_as_native .import print_hex .import timer_seconds .segment "SELF_MODIFIED_CODE" got_byte: jmp $ffff get_byte: jmp $ffff next_char: lda buffer_length bne @not_eof lda buffer_length+1 bne @not_eof sec rts @not_eof: next_char_ptr=*+1 lda $ffff pha inc next_char_ptr bne :+ inc next_char_ptr+1 : sec lda buffer_length sbc #1 sta buffer_length lda buffer_length+1 sbc #0 sta buffer_length+1 pla clc rts emit_a: ;put a byte to the output buffer ;if the byte is $FF, and xmodem_iac_escape is not zero, it is doubled jsr @real_emit_a cmp #$ff bne exit_emit_a ldx xmodem_iac_escape beq exit_emit_a @real_emit_a: emit_a_ptr=*+1 sta $ffff inc emit_a_ptr bne :+ inc emit_a_ptr+1 : inc xmodem_block_buffer_length bne :+ inc xmodem_block_buffer_length+1 : exit_emit_a: rts .bss original_tcp_callback: .res 2 getc_timeout_end: .res 1 getc_timeout_seconds: .res 1 buffer_length: .res 2 .code ;send a file via XMODEM (checksum mode only, not CRC) ;assumes that a tcp connection has already been set up, and that the other end is waiting to start receiving ;inputs: AX points to routine to call once for each byte in file to send (e.g. save to disk, print to screen, whatever) - byte will be in A, carry flag set means EOF ; xmodem_iac_escape should be set to non-zero if the remote end escapes $FF bytes (i.e. if it is a real telnet server) ;outputs: none xmodem_send: stax get_byte+1 jsr xmodem_transfer_setup lda #0 sta at_eof @send_block: ldax #sending jsr print_ascii_as_native ldax #block_number_msg jsr print_ascii_as_native lda expected_block_number jsr print_hex jsr print_cr @wait_for_ack_or_nak: lda #XMODEM_TIMEOUT_SECONDS jsr getc bcs @synch_error cmp #ACK beq @got_ack cmp #NAK beq @got_nak @synch_error: pha lda user_abort beq @no_user_abort pla jmp xmodem_transfer_exit @no_user_abort: ;flush the input buffer lda #0 sta buffer_length lda buffer_length+1 lda #'(' jsr print_a pla jsr print_hex lda #')' jsr print_a inc error_number ldax #sync_error_msg jsr print_ascii_as_native ldax #error_count_msg jsr print_ascii_as_native lda error_number jsr print_hex jsr print_cr lda error_number cmp #XMODEM_MAX_ERRORS bcc @wait_for_ack_or_nak lda #KPR_ERROR_TOO_MANY_ERRORS sta ip65_error jmp xmodem_transfer_exit @got_ack: inc expected_block_number lda at_eof bne @send_eot @got_nak: lda #0 sta checksum sta xmodem_block_buffer_length sta xmodem_block_buffer_length+1 ldax #xmodem_block_buffer stax emit_a_ptr lda #SOH jsr emit_a lda expected_block_number jsr emit_a eor #$ff jsr emit_a lda #$80 sta block_ptr @copy_one_byte: lda at_eof bne @add_pad_byte jsr get_byte bcc @got_byte ;sec indicates EOF lda block_ptr cmp #$80 ;have we sent any data at all? bne @add_pad_byte @send_eot: ;if we get here, we should send an EOT, then read an ACK ldax #1 stax tcp_send_data_len ldax #eot_packet jsr tcp_send lda #XMODEM_TIMEOUT_SECONDS jsr getc ;should be an ACK coming back, doesn't really matter if we don't see it though jmp xmodem_transfer_exit @add_pad_byte: lda #PAD @got_byte: pha clc adc checksum sta checksum pla jsr emit_a dec block_ptr bne @copy_one_byte lda checksum jsr emit_a ldax xmodem_block_buffer_length stax tcp_send_data_len ldax #xmodem_block_buffer jsr tcp_send bcc @send_ok ldax #send_error jsr print_ascii_as_native lda ip65_error jsr print_hex jsr print_cr @send_ok: jmp @send_block rts xmodem_transfer_setup: lda #0 sta buffer_length sta buffer_length+1 sta error_number sta user_abort lda #1 sta expected_block_number ldax tcp_callback stax original_tcp_callback ldax #xmodem_tcp_callback stax tcp_callback rts ;recieve a file via XMODEM (checksum mode only, not CRC) ;assumes that a tcp connection has already been set up, and that the other end is waiting to start sending ;inputs: AX points to routine to call once for each byte in downloaded file (e.g. save to disk, print to screen, whatever) - byte will be in A ; xmodem_iac_escape should be set to non-zero if the remote end escapes $FF bytes (i.e. if it is a real telnet server) ;outputs: none xmodem_receive: stax got_byte+1 jsr xmodem_transfer_setup jsr send_nak @next_block: lda #0 sta block_ptr sta checksum ldax #expecting jsr print_ascii_as_native ldax #block_number_msg jsr print_ascii_as_native lda expected_block_number jsr print_hex jsr print_cr @wait_for_block_start: lda #XMODEM_TIMEOUT_SECONDS jsr getc bcc @got_block_start lda user_abort beq @no_user_abort sec jmp xmodem_transfer_exit @no_user_abort: jsr send_nak inc error_number ldax #timeout_msg jsr print_ascii_as_native ldax #error_count_msg jsr print_ascii_as_native lda error_number jsr print_hex jsr print_cr lda error_number cmp #XMODEM_MAX_ERRORS bcc @wait_for_block_start lda #KPR_ERROR_TOO_MANY_ERRORS sta ip65_error jmp xmodem_transfer_exit @got_block_start: cmp #EOT bne :+ ldax #got_eot jsr print_ascii_as_native jsr send_ack clc jmp xmodem_transfer_exit : cmp #$81 ;jamming signal BBS seems to use $81 not $01 as SOH beq @got_soh cmp #SOH beq @got_soh lda #'!' ;we got an unexpected character jsr print_a jsr print_hex ;we need to clear the input buffer @clear_input_buffer: lda #'!' ;we got an unexpected character jsr print_a lda #1 jsr getc bcc @clear_input_buffer jmp @wait_for_block_start @got_soh: ;now get block number lda #XMODEM_TIMEOUT_SECONDS jsr getc bcc :+ jsr send_nak lda #'.' jmp print_a jmp @wait_for_block_start : sta actual_block_number ;now get block number check lda #XMODEM_TIMEOUT_SECONDS jsr getc bcc :+ lda #'.' jmp print_a jsr send_nak jmp @wait_for_block_start : adc actual_block_number cmp #$ff beq :+ lda #'?' jsr print_a jmp @wait_for_block_start : ldax #receiving jsr print_ascii_as_native ldax #block_number_msg jsr print_ascii_as_native lda actual_block_number jsr print_hex jsr print_cr @next_byte: lda #XMODEM_TIMEOUT_SECONDS jsr getc bcc :+ jmp xmodem_transfer_exit : ldx block_ptr sta xmodem_block_buffer,x adc checksum sta checksum inc block_ptr lda block_ptr bpl @next_byte ldax #checksum_msg jsr print_ascii_as_native lda checksum jsr print_hex lda #'/' jsr print_a lda #XMODEM_TIMEOUT_SECONDS jsr getc bcs xmodem_transfer_exit sta received_checksum jsr print_hex jsr print_cr lda received_checksum cmp checksum beq @checksum_ok ;checksum error :-( inc error_number ldax #checksum_error_msg jsr print_ascii_as_native ldax #error_count_msg jsr print_ascii_as_native lda error_number jsr print_hex jsr print_cr lda error_number cmp #XMODEM_MAX_ERRORS bcs :+ jmp @wait_for_block_start : lda #KPR_ERROR_TOO_MANY_ERRORS sta ip65_error jmp xmodem_transfer_exit jsr send_nak jmp @next_block @checksum_ok: lda expected_block_number cmp actual_block_number bne @skip_block_output lda #0 sta block_ptr @output_byte: ldx block_ptr lda xmodem_block_buffer,x jsr got_byte inc block_ptr lda block_ptr bpl @output_byte inc expected_block_number @skip_block_output: jsr send_ack jmp @next_block clc xmodem_transfer_exit: ldax original_tcp_callback stax tcp_callback rts xmodem_tcp_callback: lda tcp_inbound_data_length+1 cmp #$ff bne @not_eof rts @not_eof: ldax tcp_inbound_data_ptr stax copy_src ldax #xmodem_stream_buffer stax copy_dest stax next_char_ptr ldax tcp_inbound_data_length stax buffer_length jsr copymem rts send_nak: ldax #1 stax tcp_send_data_len ldax #nak_packet jmp tcp_send send_ack: ldax #1 stax tcp_send_data_len ldax #ack_packet jmp tcp_send getc: jsr @real_getc bcc :+ ;of we got an error, then bail rts : cmp #$ff beq @got_ff clc rts @got_ff: lda xmodem_iac_escape bne @real_getc ;need to skip over the $FF and go read another byte lda #$ff clc rts @real_getc: sta getc_timeout_seconds clc jsr timer_seconds ;time of day clock: seconds (in BCD) sed adc getc_timeout_seconds cmp #$60 bcc @timeout_set sec sbc #$60 @timeout_set: cld sta getc_timeout_end @poll_loop: jsr next_char bcs @no_char rts ;done! @no_char: jsr check_for_abort_key bcc @no_abort lda #KPR_ERROR_ABORTED_BY_USER sta ip65_error inc user_abort rts @no_abort: jsr ip65_process jsr timer_seconds ;time of day clock: seconds cmp getc_timeout_end bne @poll_loop lda #00 sec rts .rodata ack_packet: .byte ACK nak_packet: .byte NAK eot_packet: .byte EOT block_number_msg: .byte " block $",0 expecting: .byte "expecting",0 receiving: .byte "receiving",0 sending: .byte "sending",0 got_eot: .byte "end of transmission",10,0 bad_block_number: .byte "bad block number",0 checksum_msg: .byte "checksum $",0 checksum_error_msg : .byte "checksum",0 timeout_msg: .byte "timeout error",0 sync_error_msg: .byte "sync",0 error_count_msg: .byte " error - error count $",0 send_error: .byte " send error - $",0 .segment "APP_SCRATCH" xmodem_stream_buffer: .res 1600 xmodem_block_buffer: .res 300 xmodem_block_buffer_length: .res 2 expected_block_number: .res 1 actual_block_number: .res 1 checksum: .res 1 received_checksum: .res 1 block_ptr: .res 1 error_number: .res 1 user_abort: .res 1 xmodem_iac_escape: .res 1 at_eof: .res 1 ;-- LICENSE FOR xmodem.s -- ; The contents of this file are subject to the Mozilla Public License ; Version 1.1 (the "License"); you may not use this file except in ; compliance with the License. You may obtain a copy of the License at ; http://www.mozilla.org/MPL/ ; ; Software distributed under the License is distributed on an "AS IS" ; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the ; License for the specific language governing rights and limitations ; under the License. ; ; The Original Code is ip65. ; ; The Initial Developer of the Original Code is Jonno Downes, ; jonno@jamtronix.com. ; Portions created by the Initial Developer are Copyright (C) 2009 ; Jonno Downes. All Rights Reserved. ; -- LICENSE END --