Programming:A simple disc copier using BDOS functions
From CPCWiki - THE Amstrad CPC encyclopedia!
;; 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