;; 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
