    ;; A simple disc copier which uses the BDOS functions.
    ;;
    ;; This example shows the correct method to access the BDOS functions.
    ;; This copier will work on CPC & CPC+.
    ;;
    ;; Copier supports:
    ;; - Vendor and Data format discs ONLY
    ;; - 40 track disc drive ONLY
    ;; - single sided disc drive ONLY
    ;;
    ;; Copyprotected discs can't be copied using this program!
    ;;

    org &2000
    nolist
    write"copy.bin"

    ;;---------------------------------------------------------------------
    ;; operating system functions used

    .kl_find_command equ &bcd4
    .txt_output equ &bb5a
    .bdos_set_message equ 1
    .bdos_read_sector equ 4
    .bdos_write_sector equ 5
    .bdos_format equ 6
    .bdos_move_track equ 7
    .bdos_set_retry_count equ 9
    .bdos_get_status equ 8
    .km_read_char equ &bb09
    .km_wait_char equ &bb06
    .bdos_select_format equ 3

    ;;---------------------------------------------------------------------
    ;; offsets into our data buffer for our variables

    .SOURCE_DRIVE equ 0
    .DEST_DRIVE equ 1
    .SECTOR_ID equ 2
    .TRACK equ 3
    .CUR_TRACK equ 4
    .CUR_SECTOR equ 5
    .DATA_PTR equ 6
    .PREV_SET_MESSAGE equ 7
    .PREV_RETRY equ 8
    .NUM_TRACKS equ 9

    ;;=====================================================================
    ;; start of copy program

    .copy

    ;;------------------------------------------------------
    ;; search for commands required for copy
    ;;
    ;; this is the correct method and will work with CPC and CPC+

    ld hl,disc_command
    call kl_find_command
    ret nc

    ;; get address of commands
    ld hl,read_sector_command
    call kl_find_command
    ret nc
    ld (read_sector_cmd_data),hl
    ld a,c
    ld (read_sector_cmd_data+2),a

    ld hl,write_sector_command
    call kl_find_command
    ret nc
    ld (write_sector_cmd_data),hl
    ld a,c
    ld (write_sector_cmd_data+2),a

    ld hl,format_command
    call kl_find_command
    ret nc
    ld (format_cmd_data),hl
    ld a,c
    ld (format_cmd_data+2),a

    ld hl,select_format_command
    call kl_find_command
    ret nc
    ld (select_format_cmd_data),hl
    ld a,c
    ld (select_format_cmd_data+2),a

    ld hl,move_track_command
    call kl_find_command
    ret nc
    ld (move_track_cmd_data),hl
    ld a,c
    ld (move_track_cmd_data+2),a

    ld hl,set_retry_count_command
    call kl_find_command
    ret nc
    ld (set_retry_count_cmd_data),hl
    ld a,c
    ld (set_retry_count_cmd_data+2),a

    ld hl,set_message_command
    call kl_find_command
    ret nc
    ld (set_message_cmd_data),hl
    ld a,c
    ld (set_message_cmd_data+2),a

    ;;------------------------------------------------------

    ld ix,copy_data

    ;;------------------------------------------------------
    ;; get source drive

    ;; display source drive question
    ld hl,source_drive_txt
    call print
    ld hl,drive_txt
    call print

    ;; ask user to select drive
    call get_drive
    ld (ix+source_drive),c

    ;;------------------------------------------------------
    ;; get dest drive

    ld hl,dest_drive_txt
    call print
    ld hl,drive_txt
    call print

    ;; ask user to select drive
    call get_drive
    ld (ix+dest_drive),c

    ;;------------------------------------------------------
    ;; display insert message(s)

    ;; display message to insert disc into source drive
    call source_drive_msg
    call crlf

    ;; same drive used as source and destination?
    ld a,(ix+source_drive)
    cp (ix+dest_drive)
    jr z,copy2

    ;; different drives used as source and destination
    ;; display message to insert disc into destination drive
    call dest_drive_msg
    call crlf
    .copy2

    ;;------------------------------------------------------
    ;; display ready message

    ;; display ready text
    ld hl,ready_txt
    call print
    call flush_keyboard
    call km_wait_char

    call crlf

    ;;------------------------------------------------------
    ;; I have disabled messages and setup a high retry count
    ;; so that if there are any errors on the disc, they will
    ;; be retried without user intervention.

    ;; disable display of messages
    ld a,0
    call do_set_message
    ;; store old state
    ld (ix+prev_set_message),a

    ;; set retry count
    ld a,255
    call do_set_retry_count
    ;; store old state
    ld (ix+prev_retry),a

    ;;------------------------------------------------------
    ;; detect the format of the disc
    ;; will only detect vendor or data formats.
    call detect_format
    jr c,copy5

    ;;------------------------------------------------------
    ;; format could not be detected
    ld hl,not_standard_format_txt
    call print

    ;; quit copy
    ld hl,copy_failed_txt
    call print
    jp exit

    ;;------------------------------------------------------
    ;; format was detected
    .copy5
    ;; store first sector ID of the format detected
    ld (ix+sector_id),a

    ;;------------------------------------------------------
    ;; select format for source drive
    ;; -this will setup the XDPB for the destination drive
    ;; -XDPB parameters used by FORMAT and READ/WRITE commands

    ld a,(ix+sector_id)
    ld e,(ix+source_drive)
    call do_select_format

    ;; source and destination different?
    ld a,(ix+source_drive)
    cp (ix+dest_drive)
    jr z,copy6

    ;; source and destination are different

    ;;------------------------------------------------------
    ;; select format for destination drive
    ;; -this will setup the XDPB for the destination drive
    ;; -XDPB parameters used by FORMAT and READ/WRITE commands

    ld a,(ix+sector_id)
    ld e,(ix+dest_drive)
    call do_select_format

    .copy6

    ;;------------------------------------------------------

    xor a       ;; initial track
    ld (ix+track),a

    .copy_disc

    ;;------------------------------------------------------
    ;; calculate number of tracks to read/write/format in one go
    ;; 7 is the maximum number of tracks to read at one time
    ;; because of the limited buffer space

    ld a,40
    sub (ix+track)
    cp 7
    jr c,cd3
    ld a,7       ;; maximum number of tracks at one time
    .cd3
    ld (ix+num_tracks),a

    ;;------------------------------------------------------
    ;; read up to 7 tracks of data into the buffer

    call read_tracks

    ;;------------------------------------------------------
    ;; display message to insert disc if source drive
    ;; is the same as the dest drive

    ;; source same as dest?
    ld a,(ix+source_drive)
    cp (ix+dest_drive)
    jr nz,cd2

    call clear
    ld hl,insert_dest_txt
    call print
    ld hl,press_key_txt
    call print
    call flush_keyboard
    call km_wait_char

    .cd2
    ;;------------------------------------------------------
    ;; format and then write the data of the tracks we read
    call write_tracks

    ;;------------------------------------------------------
    ;; - store index of last track written (ready for next read)
    ;; - test if the last track written is the last track to write

    ld a,(ix+cur_track)
    ld (ix+track),a
    cp 40
    jr z,cd5

    ;;------------------------------------------------------
    ;; display message to insert disc if source drive
    ;; is the same as the dest drive

    ;; source same as dest?
    ld a,(ix+source_drive)
    cp (ix+dest_drive)
    jr nz,copy_disc						;; if source is not the same as dest, continue to copy

    ;; display message
    call clear
    ld hl,insert_source_txt
    call print
    ld hl,press_key_txt
    call print
    call flush_keyboard
    call km_wait_char

    ;; continue copying
    jr copy_disc

    .cd5
    ld hl,copy_complete_txt
    call print

    ;;------------------------------------
    ;; restore parameters that were changed

    .exit
    ;; restore message display state
    ld a,(ix+prev_set_message)
    call do_set_message

    ;; restore retry count
    ld a,(ix+prev_retry)
    call do_set_retry_count
    ret

    ;;------------------------------------
    ;; remove all characters from keyboard input buffer

    .flush_keyboard
    call km_read_char
    jr nc,flush_keyboard
    ret

    ;;------------------------------------
    ;; Exit: C = drive index

    .get_drive
    ;; remove keys from keyboard buffer
    call flush_keyboard
    ;; wait for next character from keyboard
    call km_wait_char
    ;; convert to upper case
    and &df
    cp "A"
    ld c,0
    jr z,gd2
    cp "B"
    ld c,1
    jr z,gd2
    ;; error (beep)
    ld a,7
    call txt_output
    jr get_drive
    .gd2
    call txt_output
    call crlf
    ret

    ;;-------------------------
    ;; clear the current line

    .clear
    ld a,13
    call txt_output
    ld a,18
    call txt_output
    ld a,13
    call txt_output
    ret

    ;;-------------------------
    ;; display CR,LF control codes (go to next line)
    .crlf
    ld a,13
    call txt_output
    ld a,10
    call txt_output
    ret

    ;;-------------------------
    ;; display message to "Insert SOURCE disc into drive x"
    .source_drive_msg
    ld hl,insert_source_txt
    ld a,(ix+source_drive)
    jr insert2

    ;;-------------------------
    ;; display message to "Insert DEST disc into drive x"
    .dest_drive_msg
    ld hl,insert_dest_txt
    ld a,(ix+dest_drive)
    .insert2
    push af
    call print
    pop af
    add a,"A"
    call txt_output
    ret

    ;;-------------------------------------
    ;; display a message starting at memory address pointed to by HL.
    ;; (message is terminated with 0 character)

    .print
    ld a,(hl)
    inc hl
    or a
    ret z
    call txt_output
    jr print

    ;;-------------------------------------
    ;; these functions handle the BDOS functions
    ;; using the address of the command that was found at initialisation
    ;; time

    .do_set_retry_count
    push ix
    rst 3
    defw set_retry_count_cmd_data
    pop ix
    ret

    .do_move_track
    push ix
    rst 3
    defw move_track_cmd_data
    pop ix
    ret

    .do_format
    push ix
    rst 3
    defw format_cmd_data
    pop ix
    ret

    .do_select_format
    push ix
    rst 3
    defw select_format_cmd_data
    pop ix
    ret

    .do_read
    push ix
    rst 3
    defw read_sector_cmd_data
    pop ix
    ret

    .do_write
    push ix
    rst 3
    defw write_sector_cmd_data
    pop ix
    ret

    .do_set_message
    push ix
    rst 3
    defw set_message_cmd_data
    pop ix
    ret

    ;;----------------------------------------------
    ;; detect the format of the disc from track 0
    ;; of source disc

    .detect_format
    ld e,(ix+source_drive)
    ld d,(ix+cur_track)
    call do_move_track

    ;; attempt to read the first sector of a data formatted
    ;; disc
    ld hl,data_buffer
    ld e,(ix+source_drive)
    ld d,(ix+cur_track)
    ld c,&c1
    call do_read
    ld a,&c1
    ret c

    ;; attempt to read the first sector of a system formatted
    ;; disc
    ld hl,data_buffer
    ld e,(ix+source_drive)
    ld d,(ix+cur_track)
    ld c,&41
    call do_read
    ld a,&41
    ret c
    or a
    ret

    ;;----------------------------------------------
    ;; format current track

    .format_track

    ;; initialise format data
    ld hl,format_data
    push hl
    ld b,9
    ld a,(ix+sector_id)
    ld c,(ix+cur_track)
    .ft2
    ld (hl),c
    inc hl
    ld (hl),0
    inc hl
    ld (hl),a
    inc hl
    ld (hl),2
    inc hl
    inc a
    djnz ft2
    pop hl

    ;; do the format
    ld e,(ix+dest_drive)
    ld d,(ix+cur_track)
    call do_format
    ret

    ;;----------------------------------------------
    ;; write data to all sectors on current track

    .write_track
    ld b,9
    ld a,(ix+sector_id)
    .wt1 push bc
    push af

    ;; write data
    ld l,(ix+data_ptr)
    ld h,(ix+data_ptr+1)
    ld e,(ix+dest_drive)
    ld d,(ix+cur_track)
    ld c,a
    call do_write

    ;; update data pointer
    ld l,(ix+data_ptr)
    ld h,(ix+data_ptr+1)
    ld bc,512
    add hl,bc
    ld (ix+data_ptr),l
    ld (ix+data_ptr+1),h

    pop af
    pop bc
    inc a
    djnz wt1
    ret

    ;;----------------------------------------------
    ;; format and write multiple tracks

    .write_tracks
    ld a,(ix+track)
    ld (ix+cur_track),a

    ld hl,data_buffer
    ld (ix+data_ptr),l
    ld (ix+data_ptr+1),h

    ld b,(ix+num_tracks)
    .wts1
    push bc

    ;; move to track
    ld e,(ix+dest_drive)
    ld d,(ix+cur_track)
    call do_move_track

    ;; format it
    ld hl,format_track_txt
    call print
    call disp_track

    call format_track

    ;; write it
    ld hl,write_track_txt
    call print
    call disp_track

    call write_track

    ;; increment track number
    inc (ix+cur_track)

    pop bc
    djnz wts1
    ret

    ;;----------------------------------------------
    ;; read data from all sectors on current track

    .read_track
    ld b,9
    ld a,(ix+sector_id)
    .rt1 push bc
    push af

    ld l,(ix+data_ptr)
    ld h,(ix+data_ptr+1)
    ld e,(ix+source_drive)
    ld d,(ix+cur_track)
    ld c,a
    call do_read

    ;; update pointer for size of data
    ld l,(ix+data_ptr)
    ld h,(ix+data_ptr+1)
    ld bc,512
    add hl,bc
    ld (ix+data_ptr),l
    ld (ix+data_ptr+1),h

    pop af
    pop bc
    inc a
    djnz rt1
    ret

    ;;----------------------------------------------
    ;; read multiple tracks

    .read_tracks
    ld a,(ix+track)
    ld (ix+cur_track),a

    ld hl,data_buffer
    ld (ix+data_ptr),l
    ld (ix+data_ptr+1),h

    ld b,(ix+num_tracks)
    .rts1
    push bc

    ld hl,read_track_txt
    call print
    call disp_track

    ld e,(ix+source_drive)
    ld d,(ix+cur_track)
    call do_move_track

    call read_track

    ;; increment track number
    inc (ix+cur_track)

    pop bc
    djnz rts1
    ret

    ;;----------------------------------------------
    ;; output the number of the current track to the screen

    .disp_track
    ld a,(ix+cur_track)
    call print_decimal
    ret

    ;;----------------------------------------------
    ;; display a decimal number to the screen

    .print_decimal
    ld e,1
    ld b,100
    call print_decimal_digit
    ld b,10
    call print_decimal_digit
    dec e
    ld b,1

    .print_decimal_digit
    ld c,0
    .dd
    sub b
    jr c,dd2
    inc c
    jr dd
    .dd2
    add a,b
    push af
    ld a,e
    or a
    ld a,c
    jr z,dd4
    or a
    jr z,dd5
    dec e

    .dd4
    add a,"0"
    call txt_output
    .dd5
    pop af
    ret

    ;;----------------------------------------------

    .disc_command
    defb "DIS","C"+&80

    .read_sector_command
    defb bdos_read_sector+&80

    .write_sector_command
    defb bdos_write_sector+&80

    .format_command
    defb bdos_format+&80

    .select_format_command
    defb bdos_select_format+&80

    .move_track_command
    defb bdos_move_track+&80

    .set_retry_count_command
    defb bdos_set_retry_count+&80

    .set_message_command
    defb bdos_set_message+&80

    ;;-------------------------------------

    .format_cmd_data
    defw 0
    defb 0

    .read_sector_cmd_data
    defw 0
    defb 0

    .write_sector_cmd_data
    defw 0
    defb 0

    .select_format_cmd_data
    defw 0
    defb 0

    .move_track_cmd_data
    defw 0
    defb 0

    .set_retry_count_cmd_data
    defw 0
    defb 0

    .set_message_cmd_data
    defw 0
    defb 0

    ;;---------------------------------------------------------------------

    .source_drive_txt
    defb "Source drive ",0
    .dest_drive_txt
    defb "Destination drive ",0
    .drive_txt
    defb "(A or B):",0

    .not_standard_format_txt
    defb "The format of the source disc is not standard.",0

    .copy_failed_txt
    defb 13,10,"Copy failed",13,10,0
    .copy_complete_txt
    defb 13,10,"Copy complete",13,10,0

    .insert_source_txt
    defb "Insert SOURCE disc into drive ",0
    .insert_dest_txt
    defb "Insert DEST disc into drive ",0

    .ready_txt
    defb "Press any key to start copying",0

    .press_key_txt
    defb " and press any key",0

    .read_track_txt
    defb 13,18,13,"Reading   track ",0
    .write_track_txt
    defb 13,18,13,"Writing   track ",0
    .format_track_txt
    defb 13,18,13,"Formating track ",0

    ;;---------------------------------------------------------------------

    ;; buffer for our variables
    .copy_data
    defs 16

    ;; temporary buffer used to store formatting data

    .format_data
    defs 9*4

    ;; the sector data is stored from this point onwards
    ;;
    ;; We read a maximum of 7 tracks, with a maximum of 512 bytes per sector and
    ;; a 9 sectors per track. This equates to 512*9*7 = 32256 bytes. There must
    ;; be enough space after the program to store this amount of data without
    ;; overwriting any system variables or the system jumpblock.
    .data_buffer equ $+1