.MODEL	SMALL

;-----------------------------------------------------------------------;
; This file contains the procedures to handle disk I/O, such as reading	;
; and writing a disk sector.  It also contains procedures that handle	;
; reading and writing of sectors within a file.				;
;									;
; One word of caution.  As with the version in the book, Dskpatch	;
; assumes that sectors are 512 bytes.  If your machine uses longer	;
; sectors, you may want to modify Dskpatch so that it handles longer	;
; sectors.  In any case, the procedures that read and write file	;
; "sectors" are not dependent on the actual sector size.		;
;									;
; Here is a list of procedure in this file:				;
;									;
; INIT_DISK			Initializes variables, etc.		;
; TRAP_DISK_ERRORS		Handles disk errors for file functions	;
; WRITE_IO_ERROR		Writes or clears the error message	;
; WRITE_ERROR_MESSAGE		Writes an error message on screen	;
; CLEAR_ERROR_MESSAGE		Clears the last error message		;
; READ_SECTOR			Read logical or file sector		;
; READ_LOGICAL_SECTOR		Read a logical disk sector		;
; WRITE_SECTOR			Write logical or file sector		;
; WRITE_LOGICAL_SECTOR		Writes a logical sector to the disk	;
; PREVIOUS_SECTOR		Read the previous sector from disk/file	;
; NEXT_SECTOR			Read the next sector from disk/file	;
; CHANGE_DISK_DRIVE		Change to a different disk drive	;
; READ_SECTOR_NUMBER		Change to a sector, by number		;
; OPEN_NEW_FILE			Open a file for reading and writing	;
; READ_FILE_NAME		Read the name of the file to open	;
; OPEN_FILE			Actually open the file			;
; QUIT_FILE_MODE		Closes the open file			;
; READ_OFFSET_NUMBER		Ask for the sector number within a file	;
; READ_FILE_SECTOR		Actually read a sector from the file	;
; SET_FILE_POINTER		Move to the start of a sector in file	;
; WRITE_FILE_SECTOR		Writes a file's sector back to the disk	;
;-----------------------------------------------------------------------;

.DATA

	EXTRN	SECTOR:BYTE
	EXTRN	DISK_DRIVE_NO:BYTE
	EXTRN	CURRENT_SECTOR_NO:WORD
	EXTRN	ERROR_MESSAGE_LINE_NO:BYTE

ERROR_CODE_TABLE	LABEL	WORD
	DW	OFFSET ERROR_0
	DW	OFFSET ERROR_1
	DW	OFFSET ERROR_2
	DW	OFFSET ERROR_3
	DW	OFFSET ERROR_4
	DW	OFFSET ERROR_5
	DW	OFFSET ERROR_6
	DW	OFFSET ERROR_7
	DW	OFFSET ERROR_8
	DW	OFFSET ERROR_9
	DW	OFFSET ERROR_A
	DW	OFFSET ERROR_B
	DW	OFFSET ERROR_C
	DW	OFFSET ERROR_D		;Used by File I/O procedure
	DW	OFFSET ERROR_E		;Used by File I/O procedure
	DW	OFFSET ERROR_F		;Used by READ_FILE_SECTOR
	DW	OFFSET ERROR_10		;Used by FILE_WRITE_SECTOR

ERROR_0		DB	'Write attempt on write-protected diskette',0
ERROR_1		DB	'Unknown unit',0
ERROR_2		DB	'Drive not ready',0
ERROR_3		DB	'Unknown command',0
ERROR_4		DB	'Data error (CRC)',0
ERROR_5		DB	'Bad request structure length',0
ERROR_6		DB	'Seek error',0
ERROR_7		DB	'Unknown media type',0
ERROR_8		DB	'Sector not found',0
ERROR_9		DB	'Printer out of paper',0
ERROR_A		DB	'Write fault',0
ERROR_B		DB	'Read fault',0
ERROR_C		DB	'General failure',0
ERROR_D		DB	'Error reading file',0	;Used by File I/O procedures
ERROR_E		DB	'File not found',0	;Used by File I/O procedures
ERROR_F		DB	'Read past end of file',0	;Used by READ_FILE_S...
ERROR_10	DB	'No room on disk',0	;Used by WRITE_FILE_SECTOR

READING_MESSAGE	DB	' Reading...',0
WRITING_MESSAGE	DB	' Writing...',0
PRESS_KEY	DB	'Press any key to continue... ',0

BYTES_READ	DW	?		;Bytes read by READ_FILE_SECTOR


.CODE

	PUBLIC	DISK_ERROR_FLAG, DISK_ERROR_CODE
DISK_ERROR_FLAG		DB	0	;Used to keep track of errors
DISK_ERROR_CODE		DB	0	;Error code from disk operation


	PUBLIC	INIT_DISK
;-----------------------------------------------------------------------;
; This procedure initializes several things that Disk Patch needs to	;
; function properly:							;
;									;
;	1.  It sets DISK_DRIVE_NO to the number of the current drive,	;
;	    rather than simply drive A:.  This is useful if you start	;
;	    Dskpatch from a hard disk with no floppy disk in the drive.	;
;									;
;	2.  It tells DOS to call TRAP_DISK_ERRORS whenever there is a	;
;	    disk error and we're reading a file.  We use this so that	;
;	    we can report disk errors.					;
;									;
; Writes:	DISK_DRIVE_NO						;
;-----------------------------------------------------------------------;
INIT_DISK	PROC
	MOV	AH,19h			;Ask DOS for the disk drive number
	INT	21h
	MOV	DISK_DRIVE_NO,AL	;Save this drive number

	MOV	AL,24h			;Set INT 24H (ERROR) vector
	MOV	AH,25h			;Set vector function
	LEA	DX,TRAP_DISK_ERRORS
	INT	21h

	RET
INIT_DISK	ENDP


	PUBLIC	TRAP_DISK_ERRORS
;-----------------------------------------------------------------------;
; DOS calls this procedure in case of INT 21h errors using an INT 24h	;
; call.									;
;									;
; This procedure saves the error code in DISK_ERROR_CODE, then returns	;
; to DOS with AL set to 0.  This tells DOS to "ignore" the error.	;
; Dskpatch checks the error flag for errors after making INT 21h calls.	;
;									;
; Writes:	DISK_ERROR_FLAG, DISK_ERROR_CODE			;
;-----------------------------------------------------------------------;
TRAP_DISK_ERRORS	PROC
	PUSH	AX
	MOV	AX,DI			;Get error code into AL
	MOV	CS:DISK_ERROR_FLAG,1	;Set the error flag
	MOV	CS:DISK_ERROR_CODE,AL	;Save this disk error code
	POP	AX
	MOV	AL,0			;Tell DOS to ignore this error
	IRET				;Let DOS return to Dskpatch
TRAP_DISK_ERRORS	ENDP



	PUBLIC	WRITE_IO_ERROR
;-----------------------------------------------------------------------;
; This procedure writes an error message to the error message line.	;
;									;
; Uses:		WRITE_ERROR_MESSAGE, CLEAR_ERROR_MESSAGE		;
; Reads:	DISK_ERROR_FLAG, DISK_ERROR_CODE, ERROR_CODE_TABLE	;
;-----------------------------------------------------------------------;
WRITE_IO_ERROR	PROC
	PUSH	BX
	PUSH	DX

	CMP	DISK_ERROR_FLAG,1	;Was there an error?
	JE	WRITE_MESSAGE		;Yes, then write the message
	CALL	CLEAR_ERROR_MESSAGE	;No, then clear the message
	JMP	DONE_WRITE_IO_ERROR	;We're all done here
WRITE_MESSAGE:
	MOV	BL,DISK_ERROR_CODE	;Get the error code
	XOR	BH,BH			;Convert to a word
	SHL	BX,1			;Multiply by 2 for word look-up
	MOV	DX,ERROR_CODE_TABLE[BX]	;Get address of error message
	CALL	WRITE_ERROR_MESSAGE	;Display the error message

DONE_WRITE_IO_ERROR:
	POP	DX
	POP	BX
	RET
WRITE_IO_ERROR	ENDP


	PUBLIC	WRITE_ERROR_MESSAGE
	EXTRN	GOTO_XY:PROC
	EXTRN	WRITE_STRING:PROC
	EXTRN	CLEAR_TO_END_OF_LINE:PROC
	EXTRN	WRITE_ATTRIBUTE_N_TIMES:PROC
;-----------------------------------------------------------------------;
; This procedure writes an error message in the message area near the	;
; bottom of the screen.							;
;									;
;	DS:DX		Address of the error message			;
;									;
; Uses:		GOTO_XY, WRITE_STRING, CLEAR_TO_END_OF_LINE		;
;		WRITE_ATTRIBUTE_N_TIMES					;
; Reads:	ERROR_MESSAGE_LINE_NO					;
;-----------------------------------------------------------------------;
WRITE_ERROR_MESSAGE	PROC
	PUSH	CX
	PUSH	DX
	MOV	CX,DX			;Put the string address into CX
	MOV	DH,ERROR_MESSAGE_LINE_NO	;Get the line number
	MOV	DL,30			;Indent message 30 spaces
	CALL	GOTO_XY			;Move cursor to start of the message
	XCHG	CX,DX			;Put the string address back into DX
	CALL	WRITE_STRING		;Write the string here
	CALL	CLEAR_TO_END_OF_LINE	;Then clear the rest of the line
	XCHG	CX,DX			;Put the coordinates back into DX
	CALL	GOTO_XY			;Move to the start of the message
	MOV	DL,0Fh			;High-intensity attribute
	MOV	CX,49			;Set the entire line to bright
	CALL	WRITE_ATTRIBUTE_N_TIMES
	POP	DX
	POP	CX
	RET
WRITE_ERROR_MESSAGE	ENDP


	PUBLIC	CLEAR_ERROR_MESSAGE
	EXTRN	GOTO_XY:PROC
	EXTRN	CLEAR_TO_END_OF_LINE:PROC
;-----------------------------------------------------------------------;
; This procedure clears the error message which appears after a disk	;
; error.								;
;									;
; Uses:		GOTO_XY, CLEAR_TO_END_OF_LINE				;
; Reads:	ERROR_MESSAGE_LINE_NO					;
;-----------------------------------------------------------------------;
CLEAR_ERROR_MESSAGE	PROC
	PUSH	DX
	MOV	DH,ERROR_MESSAGE_LINE_NO ;Get the line number for the error
	XOR	DL,DL			;Start of the message line
	CALL	GOTO_XY			;Move to start of error message line
	CALL	CLEAR_TO_END_OF_LINE	;Clear the entire line
	POP	DX
	RET
CLEAR_ERROR_MESSAGE	ENDP


	PUBLIC	READ_SECTOR
.DATA
	EXTRN	DISK_DRIVE_NO:BYTE
	EXTRN	CURRENT_SECTOR_NO:WORD
	EXTRN	FILE_FLAG:BYTE
.CODE
;-----------------------------------------------------------------------;
;  This procedure reads one sector (512 bytes) into SECTOR.		;
;									;
; Uses:		CLEAR_SECTOR, READ_LOGICAL_SECTOR, READ_FILE_SECTOR	;
;		WRITE_IO_ERROR						;
; Reads:	DISK_ERROR_FLAG, FILE_FLAG				;
; Writes:	DISK_ERROR_FLAG						;
;-----------------------------------------------------------------------;
READ_SECTOR	PROC
	MOV	DISK_ERROR_FLAG,0	;Clear the disk error flag before read
	CALL	CLEAR_SECTOR		;Clear the sector before we start

	CMP	FILE_FLAG,1		;Are we in file mode?
	JE	FILE_READ		;Yes, then read sector from file
	CALL	READ_LOGICAL_SECTOR	;No, read a logical disk sector
	JMP	JUST_READ_SECTOR	;Now check for errors
FILE_READ:
	CALL	READ_FILE_SECTOR	;Read a sector from the file

JUST_READ_SECTOR:
	CMP	DISK_ERROR_FLAG,1	;Was there a disk error?
	JNE	DONE_READ_SECTOR	;No, then we're all done
	CALL	CLEAR_SECTOR		;Yes, then clear SECTOR
DONE_READ_SECTOR:
	CALL	WRITE_IO_ERROR		;Write or clear error message
	RET
READ_SECTOR	ENDP


	PUBLIC	CLEAR_SECTOR
.DATA
	EXTRN	SECTOR:BYTE
.CODE
;-----------------------------------------------------------------------;
; This procedure sets SECTOR to all 0s.  Call this procedure before	;
; you try a read a sector so that the sector will be all 0s if there	;
; is an error.								;
;									;
; Writes:	SECTOR							;
;-----------------------------------------------------------------------;
CLEAR_SECTOR	PROC
	PUSH	AX			;Clear SECTOR to all 0s
	PUSH	CX
	PUSH	DI
	LEA	DI,SECTOR		;Point to the sector buffer
	MOV	CX,512			;Number of bytes in one sector
	CLD				;Set direction for increment
	MOV	AL,0			;Store zeros in SECTOR
REP	STOSB				;Clear SECTOR
	POP	DI
	POP	CX
	POP	AX
	RET
CLEAR_SECTOR	ENDP


	PUBLIC	READ_LOGICAL_SECTOR
.DATA
	EXTRN	DISK_DRIVE_NO:BYTE
	EXTRN	CURRENT_SECTOR_NO:WORD
	EXTRN	SECTOR:BYTE

LONG_SECTOR_NO		DD	0	;A 4-byte sector number
SECTORS_TO_READ		DW	1	;Number of sectors to read
BUFFER_OFFSET		DW	0	;Where to save the segment
BUFFER_SEGMENT		DW	0

.CODE
;-----------------------------------------------------------------------;
; This procedure reads a logical sector from the disk, using the DOS	;
; function 25h.								;
;									;
; Reads:	DISK_DRIVE_NO, CURRENT_SECTOR_NO			;
; Writes:	SECTOR, DISK_ERROR_FLAG, DISK_ERROR_CODE		;
;-----------------------------------------------------------------------;
READ_LOGICAL_SECTOR	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	MOV	AL,DISK_DRIVE_NO	;Drive number
	MOV	CX,1			;Read only 1 sector
	MOV	DX,CURRENT_SECTOR_NO	;Logical sector number
	LEA	BX,SECTOR		;Where to store this sector
	INT	25h			;Read the sector
	JNC	READ_LOGICAL_NO_ERROR	;There wasn't an error, continue

	;---------------------------------------------------------------;
	; If we're using DOS 4.0 (or COMPAQ DOS 3.31) with a hard disk	;
	; partition larger than 32MB, we'll get the error 0207 in AX.	;
	; In this case we need to set up the register differently.	;
	;---------------------------------------------------------------;
	CMP	AX,207h			;Could this be an extended partition?
	JNE	READ_LOGICAL_ERROR	;No, report the error

	POPF				;Pop old flags off the stack
	MOV	AL,DISK_DRIVE_NO	;Set up for DOS 4.0 extended read
	MOV	CX,-1			;Tell DOS we're making special read
	MOV	BX,CURRENT_SECTOR_NO	;Which sector to read
	MOV	Word Ptr LONG_SECTOR_NO,BX
	LEA	BX,SECTOR		;Where to save the sector
	MOV	BUFFER_OFFSET,BX
	MOV	BUFFER_SEGMENT,DS	;Which segment buffer is in
	LEA	BX,LONG_SECTOR_NO	;DS:BX points to extended info
	INT	25h
	JNC	READ_LOGICAL_NO_ERROR	;There wasn't an error

READ_LOGICAL_ERROR:
	MOV	DISK_ERROR_FLAG,1	;Set the error flag
	MOV	DISK_ERROR_CODE,AL	;And save the error code
READ_LOGICAL_NO_ERROR:
	POPF				;Discard flags put on stack by INT 25h
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
READ_LOGICAL_SECTOR	ENDP


	PUBLIC	WRITE_SECTOR
	EXTRN	WRITE_STRING:PROC, WRITE_PROMPT_LINE:PROC
.DATA
	EXTRN	FILE_FLAG:BYTE
.CODE
;-----------------------------------------------------------------------;
; This procedure writes a sector back to the disk.  Note, if the last	;
; read resulted in an error (in which case the error flag will still	;
; be set) this procedure does nothing.					;
;									;
; Uses:		WRITE_IO_ERROR, WRITE_STRING, WRITE_EDITOR_PROMPT	;
;		WRITE_LOGICAL_SECTOR, WRITE_FILE_SECTOR			;
; Reads:	FILE_FLAG, WRITING_MESSAGE, DISK_ERROR_FLAG		;
;-----------------------------------------------------------------------;
WRITE_SECTOR	PROC
	PUSH	AX
	CMP	DISK_ERROR_FLAG,0	;Was there a disk error?
	JNE	DONT_WRITE_SECTOR	;Yes, then don't write a sector

	LEA	DX,WRITING_MESSAGE	;Provide feedback while we write
	CALL	WRITE_STRING

	CMP	FILE_FLAG,1		;Are we in file mode?
	JE	FILE_WRITE		;Yes, then write sector to file
	CALL	WRITE_LOGICAL_SECTOR	;No, write a logical disk sector
	JMP	JUST_WROTE_SECTOR	;Now check for errors
FILE_WRITE:
	CALL	WRITE_FILE_SECTOR	;Write a sector to the file

JUST_WROTE_SECTOR:
	CMP	DISK_ERROR_FLAG,1	;Was there a disk error?
	JE	WRITE_SECTOR_ERROR	;Yes, then report it

DONE_WRITE_SECTOR:
	CALL	WRITE_IO_ERROR		;Write or clear error message
	CALL	WRITE_EDITOR_PROMPT
DONT_WRITE_SECTOR:
	POP	AX
	RET

WRITE_SECTOR_ERROR:
	CALL	WRITE_IO_ERROR		;Display the error message
	LEA	DX,PRESS_KEY		;Display "Press any key..." message
	CALL	WRITE_PROMPT_LINE	;Display this prompt
	CALL	READ_KEY		;And read a character
	MOV	DISK_ERROR_FLAG,0	;Clear the error flag
	JMP	DONE_WRITE_SECTOR	;We're all done
WRITE_SECTOR	ENDP


	PUBLIC	WRITE_LOGICAL_SECTOR
	EXTRN	WRITE_EDITOR_PROMPT:PROC
.DATA
	EXTRN	SECTOR:BYTE
	EXTRN	DISK_DRIVE_NO:BYTE
	EXTRN	CURRENT_SECTOR_NO:WORD
.CODE
;-----------------------------------------------------------------------;
; This procedure writes a logical sector to the disk drive.  Not, if	;
; the last read resulted in an error, this procedure won't write a	;
; sector to the disk.							;
;									;
; Reads:	DISK_DRIVE_NO, CURRENT_SECTOR_NO			;
; Writes:	SECTOR, DISK_ERROR_FLAG, DISK_ERROR_CODE		;
;-----------------------------------------------------------------------;
WRITE_LOGICAL_SECTOR	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	MOV	AL,DISK_DRIVE_NO	;Drive number
	MOV	CX,1			;Write 1 sector
	MOV	DX,CURRENT_SECTOR_NO	;Logical sector
	LEA	BX,SECTOR		;Get address of our sector
	INT	26h			;Write the sector to disk
	JNC	WRITE_LOGICAL_NO_ERROR	;There wasn't an error, continue

	;---------------------------------------------------------------;
	; If we're using DOS 4.0 (or COMPAQ DOS 3.31) with a hard disk	;
	; partition larger than 32MB, we'll get the error 0207 in AX.	;
	; In this case we need to set up the registers differently.	;
	;---------------------------------------------------------------;
	CMP	AX,207h			;Could this be an extended partition?
	JNE	WRITE_LOGICAL_ERROR	;No, report the error

	POPF				;Pop old flags off the stack
	MOV	AL,DISK_DRIVE_NO	;Set up for DOS 4.0 extended read
	MOV	CX,-1			;Tell DOS we're making special read
	MOV	BX,CURRENT_SECTOR_NO	;Which sector to read
	MOV	Word Ptr LONG_SECTOR_NO,BX
	LEA	BX,SECTOR		;Where to save the sector
	MOV	BUFFER_OFFSET,BX
	MOV	BUFFER_SEGMENT,DS	;Which segment buffer is in
	LEA	BX,LONG_SECTOR_NO	;DS:BX points to extended info
	INT	26h
	JNC	WRITE_LOGICAL_NO_ERROR	;There wasn't an error

WRITE_LOGICAL_ERROR:
	MOV	DISK_ERROR_FLAG,1	;Set the error flag
	MOV	DISK_ERROR_CODE,AL	;Save the error code
WRITE_LOGICAL_NO_ERROR:
	POPF				;Discard the flag information
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
WRITE_LOGICAL_SECTOR	ENDP


	PUBLIC	PREVIOUS_SECTOR
	EXTRN	INIT_SEC_DISP:PROC
	EXTRN	WRITE_STRING:PROC
	EXTRN	WRITE_EDITOR_PROMPT:PROC
.DATA
	EXTRN	CURRENT_SECTOR_NO:WORD
.CODE
;-----------------------------------------------------------------------;
; This procedure reads the previous sector, if possible			;
;									;
; Uses:		WRITE_STRING, WRITE_EDITOR_PROMPT, INIT_SEC_DISP	;
; Reads:	CURRENT_SECTOR_NO, READING_MESSAGE			;
; Writes:	CURRENT_SECTOR_NO					;
;-----------------------------------------------------------------------;
PREVIOUS_SECTOR		PROC
	PUSH	AX
	PUSH	DX
	MOV	AX,CURRENT_SECTOR_NO	;Get current sector number
	OR	AX,AX			;Is sector number zero?
	JZ	DONT_DECREMENT_SECTOR	;Yes, then we're all done
	DEC	AX			;No, then subtract one
	MOV	CURRENT_SECTOR_NO,AX	;Save new sector number
	LEA	DX,READING_MESSAGE	;Provide feedback while we're reading
	CALL	WRITE_STRING
	CALL	INIT_SEC_DISP		;Display the new screen
	CALL	WRITE_EDITOR_PROMPT	;Display the editor prompt
DONT_DECREMENT_SECTOR:
	POP	DX
	POP	AX
	RET
PREVIOUS_SECTOR		ENDP


	PUBLIC	NEXT_SECTOR
	EXTRN	INIT_SEC_DISP:PROC
	EXTRN	WRITE_STRING:PROC
	EXTRN	WRITE_EDITOR_PROMPT:PROC
.DATA
	EXTRN	CURRENT_SECTOR_NO:WORD
.CODE
;-----------------------------------------------------------------------;
; Read the next sector.							;
;									;
; Uses:		WRITE_EDITOR_PROMPT, INIT_SEC_DISP, WRITE_STRING	;
; Reads:	CURRENT_SECTOR_NO, READING_MESSAGE			;
; Writes:	CURRENT_SECTOR_NO					;
;-----------------------------------------------------------------------;
NEXT_SECTOR	PROC
	PUSH	AX
	PUSH	DX
	MOV	AX,CURRENT_SECTOR_NO	;Get the current sector number
	INC	AX			;Move to next sector
	MOV	CURRENT_SECTOR_NO,AX	;Save this new number
	LEA	DX,READING_MESSAGE	;Provide feedback while we read
	CALL	WRITE_STRING
	CALL	INIT_SEC_DISP		;Display the new screen
	CALL	WRITE_EDITOR_PROMPT
	POP	DX
	POP	AX
	RET
NEXT_SECTOR	ENDP


	PUBLIC	CHANGE_DISK_DRIVE
	EXTRN	READ_BYTE:PROC
	EXTRN	WRITE_STRING:PROC
	EXTRN	CHAR_TO_UPPER:PROC
	EXTRN	WRITE_PROMPT_LINE:PROC
	EXTRN	WRITE_EDITOR_PROMPT:PROC
	EXTRN	INIT_SEC_DISP:PROC
.DATA
	EXTRN	DISK_DRIVE_NO:BYTE, DISK_PROMPT:BYTE
	EXTRN	FILE_FLAG:BYTE, CURRENT_SECTOR_NO:WORD
.CODE
;-----------------------------------------------------------------------;
; This procedure prompts for a new disk drive letter, like A, B, ....	;
;									;
; Uses:		WRITE_PROMPT_LINE, WRITE_EDITOR_PROMPT, INIT_SEC_DISP	;
;		WRITE_STRING, CHAR_TO_UPPER, READ_BYTE, QUIT_FILE_MODE	;
; Reads:	DISK_PROMPT, FILE_FLAG, READING_MESSAGE			;
; Writes:	DISK_DRIVE_NO, CURRENT_SECTOR_NO			;
;-----------------------------------------------------------------------;
CHANGE_DISK_DRIVE	PROC
	PUSH	AX
	PUSH	DX

READ_DRIVE_LOOP:
	LEA	DX,DISK_PROMPT		;Display prompt for drive letter
	CALL	WRITE_PROMPT_LINE
	CALL	READ_BYTE		;Get new disk-drive number
	CMP	AH,0			;Did it read one character?
	JNZ	DONT_CHANGE_DRIVE	;No, don't change the drive number
	CMP	AL,25			;Yes, was it hex number <=25 decimal
	JBE	CHANGE_DRIVE		;Yes, change drive number

	CMP	AL,'0'			;No, is it a single digit number
	JB	READ_DRIVE_LOOP		;Yes, convert to drive number
	CMP	AL,'9'			;Is it a digit?
	JBE	IS_SINGLE_DIGIT		;Yes, convert to drive number

	CALL	CHAR_TO_UPPER		;Convert letter to upper-case
	CMP	AL,'A'			;Is this a legal drive letter?
	JB	READ_DRIVE_LOOP		;No, try again
	CMP	AL,'Z'			;Is this a legal drive letter?
	JA	READ_DRIVE_LOOP		;No, then try again

IS_DRIVE_LETTER:			;Yes, then convert to drive number
	SUB	AL,'A'			;Convert Upper letter to drive number
	JMP	CHANGE_DRIVE		;Change the drive number

IS_SINGLE_DIGIT:
	SUB	AL,'0'			;Convert to drive number

CHANGE_DRIVE:
	CALL	QUIT_FILE_MODE		;Turn off file mode, if on
	MOV	CURRENT_SECTOR_NO,0	;And view sector 0 of new drive
CHANGE_DRIVE_NOT_FILE:
	MOV	DISK_DRIVE_NO,AL	;Save the new drive number
	LEA	DX,READING_MESSAGE	;Provide some feedback while reading
	CALL	WRITE_STRING		;Display this string
	CALL	INIT_SEC_DISP		;And display the new sector
DONT_CHANGE_DRIVE:
	CALL	WRITE_EDITOR_PROMPT	;Display the Edit prompt
	POP	DX
	POP	AX
	RET
CHANGE_DISK_DRIVE	ENDP


	PUBLIC	READ_SECTOR_NUMBER
	EXTRN	WRITE_PROMPT_LINE:PROC, READ_DECIMAL:PROC
	EXTRN	WRITE_STRING:PROC, INIT_SEC_DISP:PROC
	EXTRN	WRITE_EDITOR_PROMPT:PROC
.DATA
	EXTRN	SECTOR_PROMPT:BYTE
	EXTRN	CURRENT_SECTOR_NO:WORD, FILE_FLAG:BYTE
.CODE
;-----------------------------------------------------------------------;
; This procedure prompts for the number of a new sector to read.	;
;									;
; Uses:		WRITE_PROMPT_LINE, READ_DECIMAL, WRITE_STRING		;
;		INIT_SEC_DISP, QUIT_FILE_MODE, WRITE_EDITOR_PROMPT	;
; Reads:	SECTOR_PROMPT, READING_MESSAGE				;
; Writes:	CURRENT_SECTOR_NO					;
;-----------------------------------------------------------------------;
READ_SECTOR_NUMBER	PROC
	PUSH	AX
	PUSH	DX
	LEA	DX,SECTOR_PROMPT	;Prompt for the sector number
	CALL	WRITE_PROMPT_LINE
	CALL	READ_DECIMAL		;Read the new sector number into AX
	JC	DONT_CHANGE_SECTOR	;Error, so exit without changing
	CALL	QUIT_FILE_MODE		;Turn off file mode, if on
	MOV	CURRENT_SECTOR_NO,AX	;Save the new sector number
	LEA	DX,READING_MESSAGE	;Provide feedback while we read
	CALL	WRITE_STRING
	CALL	INIT_SEC_DISP		;Display the new sector
DONT_CHANGE_SECTOR:
	CALL	WRITE_EDITOR_PROMPT	;Display the Edit prompt
	POP	DX
	POP	AX
	RET
READ_SECTOR_NUMBER	ENDP


	PUBLIC	OPEN_NEW_FILE
	EXTRN	WRITE_STRING:PROC, INIT_SEC_DISP:PROC
	EXTRN	WRITE_PROMPT_LINE:PROC, READ_KEY:PROC
	EXTRN	WRITE_EDITOR_PROMPT:PROC
.DATA
	EXTRN	CURRENT_SECTOR_NO:WORD, FILE_FLAG:BYTE
.CODE
;-----------------------------------------------------------------------;
; This procedure reads a file name, opens that file, and displays the	;
; first sector.								;
;									;
; If the open failed, puts you back to editing whatever you were	;
; editing before.							;
;									;
; NOTE: This procedure needs a lot of work.  If you were in file mode	;
;	when you ask to open a new file, this procedure closes the old	;
;	file, then tries to open the new file.  If there was an error	;
;	reading the new file, or you just hit <Enter>, this procedure	;
;	doesn't return to the file you were previously editing.		;
;	instead, it returns to sector mode.  See if you can change	;
;	this procedure so that it returns to editing the last file you	;
;	were on if you were in file mode.				;
;									;
; Uses:		READ_FILE_NAME, OPEN_FILE, WRITE_STRING			;
;		INIT_SEC_DISP, WRITE_IO_ERROR, WRITE_PROMPT_LINE	;
;		READ_KEY, WRITE_EDITOR_PROMPT, QUIT_FILE_MODE		;
; Reads:	READING_MESSAGE, DISK_ERROR_FLAG, PRESS_KEY		;
; Writes:	CURRENT_SECTOR_NO, FILE_FLAG, DISK_ERROR_FLAG		;
;-----------------------------------------------------------------------;
OPEN_NEW_FILE	PROC
	PUSH	AX
	PUSH	DX
	CALL	QUIT_FILE_MODE		;Turn off old file mode, if on

	CALL	READ_FILE_NAME		;First, read in the file name
	OR	AX,AX			;Did we read any characters?
	JLE	DONE_OPEN_NEW_FILE	;No, then return without change

	LEA	DX,READING_MESSAGE	;Yes, Provide feedback while we read
	CALL	WRITE_STRING
	CALL	OPEN_FILE		;Try to open this file
	CMP	DISK_ERROR_FLAG,1	;Was there an I/O error?
	JE	OPEN_NEW_FILE_ERROR	;Yes, then report it.

SET_FILE_MODE:
	MOV	FILE_FLAG,1		;No, signal now in file mode
	MOV	CURRENT_SECTOR_NO,0	;Start at beginning of file

DONE_OPEN_NEW_FILE:
	CALL	INIT_SEC_DISP		;Display this new sector
	CALL	WRITE_IO_ERROR		;Display or clear error message
	CALL	WRITE_EDITOR_PROMPT	;Display the Edit prompt
	POP	DX
	POP	AX
	RET

;-----------------------------------------------;
; Here we report the error, then pause until	;
; you press a key before returning to the	;
; previous mode and sector that you were	;
; viewing.					;
;-----------------------------------------------;
OPEN_NEW_FILE_ERROR:
	CALL	WRITE_IO_ERROR		;Display the error message
	LEA	DX,PRESS_KEY		;Display prompt message
	CALL	WRITE_PROMPT_LINE	;Display this prompt
	CALL	READ_KEY		;Read one character
	JMP	DONE_OPEN_NEW_FILE	;We're all done now
OPEN_NEW_FILE	ENDP


	PUBLIC	READ_FILE_NAME
	EXTRN	WRITE_PROMPT_LINE:PROC, READ_STRING:PROC
.DATA
	EXTRN	FILE_NAME_PROMPT:BYTE
	EXTRN	FILE_NAME_STRING:BYTE
	EXTRN	LENGTH_READ:BYTE
	EXTRN	FILE_NAME:BYTE
.CODE
;-----------------------------------------------------------------------;
; This procedure prompts for a file name, then reads in the file name.	;
; We've written this as a separate procedure only to make the code more	;
; readable.								;
;									;
; Returns:	AX	Number of characters read.			;
;			-1 if you pressed a special key			;
;									;
; Uses:		WRITE_PROMPT_LINE, READ_STRING				;
; Reads:	FILE_NAME_PROMPT, FILE_NAME_STRING, LENGTH_READ		;
; Writes:	FILE_NAME						;
;-----------------------------------------------------------------------;
READ_FILE_NAME	PROC
	PUSH	BX
	PUSH	DX
	LEA	DX,FILE_NAME_PROMPT	;Display the "file name:"
	CALL	WRITE_PROMPT_LINE	; prompt
	LEA	DX,FILE_NAME_STRING	;Read in the file name
READ_NAME_AGAIN:
	CALL	READ_STRING
	MOV	BL,LENGTH_READ		;Get number of 
	OR	BL,BL			;Was this a function key?
	JL	DONE_READ_FILE_NAME	;Yes, then ignore it

	XOR	BH,BH			;Clear the upper byte
	MOV	FILE_NAME[BX],0		;Convert string into ASCIIZ
DONE_READ_FILE_NAME:
	MOV	AL,BL			;Return the length of the string
	CBW				;Extend sign into the upper byte
	POP	DX
	POP	BX
	RET
READ_FILE_NAME	ENDP


	PUBLIC	OPEN_FILE
.DATA
	EXTRN	FILE_HANDLE:WORD
.CODE
;-----------------------------------------------------------------------;
; This procedure is used by OPEN_NEW_FILE to open a file for reading	;
; and writing.								;
;									;
; Reads:	FILE_NAME						;
; Writes:	DISK_ERROR_FLAG, DISK_ERROR_CODE, FILE_HANDLE		;
;-----------------------------------------------------------------------;
OPEN_FILE	PROC
	PUSH	AX
	PUSH	DX
	MOV	DISK_ERROR_FLAG,0	;Clear the error condition
	MOV	AX,3D02h		;Open file for read/write
	LEA	DX,FILE_NAME		;Get address of the file name
	INT	21h
	JC	FILE_OPEN_ERROR		;There was an error, report it
	MOV	FILE_HANDLE,AX		;Save this file handle
DONE_OPEN_FILE:
	POP	DX
	POP	AX
	RET

FILE_OPEN_ERROR:
	CMP	AX,2			;Is this a file-not-found message?
	JNE	CHECK_ACCESS
	MOV	AX,0Eh			;Error code for file-not-found
	JMP	DONE_FILE_ERROR
CHECK_ACCESS:
	MOV	AX,0Dh			;General Error reading file message
DONE_FILE_ERROR:
	MOV	DISK_ERROR_FLAG,1	;Set the error flag
	MOV	DISK_ERROR_CODE,AL	;Save the new error code
	JMP	DONE_OPEN_FILE		;We're all done, return.
OPEN_FILE	ENDP


	PUBLIC	QUIT_FILE_MODE
.DATA
	EXTRN	FILE_HANDLE:WORD
	EXTRN	CURRENT_SECTOR_NO:WORD
.CODE
;-----------------------------------------------------------------------;
; This procedure checks to see if we're in file mode, and if so, it	;
; closes the file, then turns off file mode.				;
;									;
; Reads:	FILE_HANDLE						;
; Writes:	FILE_FLAG, CURRENT_SECTOR_NO, DISK_ERROR_FLAG		;
;-----------------------------------------------------------------------;
QUIT_FILE_MODE	PROC
	PUSH	AX
	PUSH	BX
	CMP	FILE_FLAG,1		;Are we in file mode?
	JNE	DONE_QUIT_FILE		;No, then we're all done
	MOV	AH,3Eh			;Ask to close this file
	MOV	BX,FILE_HANDLE		;Get the file handle
	INT	21h			;Close this file
	MOV	FILE_FLAG,0		;Turn off file mode
	MOV	CURRENT_SECTOR_NO,0	;Move to start of disk
	MOV	DISK_ERROR_FLAG,0	;Ignore any error conditions
DONE_QUIT_FILE:
	POP	BX
	POP	AX
	RET
QUIT_FILE_MODE	ENDP



	PUBLIC	READ_OFFSET_NUMBER
.DATA
	EXTRN	OFFSET_PROMPT:BYTE
	EXTRN	FILE_FLAG:BYTE, CURRENT_SECTOR_NO:WORD
.CODE
	EXTRN	WRITE_PROMPT_LINE:PROC, READ_DECIMAL:PROC
	EXTRN	WRITE_STRING:PROC, INIT_SEC_DISP:PROC
	EXTRN	WRITE_EDITOR_PROMPT:PROC
;-----------------------------------------------------------------------;
; This procedure reads in the sector offset within a file, then		;
; displays the new sector.						;
;									;
; Uses:		WRITE_PROMPT_LINE, READ_DECIMAL, WRITE_STRING		;
;		INIT_SEC_DISP, WRITE_EDITOR_PROMPT			;
; Reads:	FILE_FLAG, OFFSET_PROMPT, READING_MESSAGE		;
; Writes:	CURRENT_SECTOR_NO					;
;-----------------------------------------------------------------------;
READ_OFFSET_NUMBER	PROC
	PUSH	AX
	PUSH	DX
	CMP	FILE_FLAG,1		;In file mode?
	JNE	READ_OFFSET_NOT_FILE_MODE	;No, then just ignore request
	LEA	DX,OFFSET_PROMPT	;Yes, display the offset prompt
	CALL	WRITE_PROMPT_LINE
	CALL	READ_DECIMAL		;Read in the decimal number
	JC	DONT_CHANGE_OFFSET	;Error, so exit without changing
	LEA	DX,READING_MESSAGE	;Provide feedback while we read
	CALL	WRITE_STRING
	MOV	CURRENT_SECTOR_NO,AX	;Save the new number
	CALL	INIT_SEC_DISP		;Draw the new display
DONT_CHANGE_OFFSET:
READ_OFFSET_NOT_FILE_MODE:
	CALL	WRITE_EDITOR_PROMPT	;Display the Edit prompt
	POP	DX
	POP	AX
	RET
READ_OFFSET_NUMBER	ENDP


	PUBLIC	READ_FILE_SECTOR
.DATA
	EXTRN	FILE_HANDLE:WORD
	EXTRN	SECTOR:BYTE
.CODE
;-----------------------------------------------------------------------;
; This procedure reads a random sector from a file.  It also saves a	;
; count of the number of bytes actually read so that the write function	;
; won't increase the size of a file.					;
;									;
; Uses:		SET_FILE_POINTER					;
; Reads:	FILE_HANDLE, DISK_ERROR_FLAG				;
; Writes:	SECTOR, DISK_ERROR_FLAG, DISK_ERROR_CODE, BYTES_READ	;
;-----------------------------------------------------------------------;
READ_FILE_SECTOR	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	CALL	SET_FILE_POINTER	;Set to start of current sector
	CMP	DISK_ERROR_FLAG,1	;Was there an error?
	JE	DONE_READ_FILE_SECTOR	;Yes, then don't try to read sector

	MOV	AH,3Fh			;Call for a read on this file
	MOV	BX,FILE_HANDLE		;Get the file handle
	MOV	CX,512			;Read one sector of 512 bytes
	LEA	DX,SECTOR		;Address of our sector buffer
	INT	21h			;Let DOS read this sector
	MOV	BYTES_READ,AX		;Save number of bytes we read

	OR	AX,AX			;Did we read anything from file?
	JZ	READ_PAST_END		;No, we read past end of file, signal

DONE_READ_FILE_SECTOR:
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET

READ_PAST_END:
	MOV	DISK_ERROR_FLAG,1	;Signal that there was an error
	MOV	DISK_ERROR_CODE,0Fh	;Read-past-end error code
	JMP	DONE_READ_FILE_SECTOR
READ_FILE_SECTOR	ENDP


	PUBLIC	SET_FILE_POINTER
.DATA
	EXTRN	CURRENT_SECTOR_NO:WORD
	EXTRN	FILE_HANDLE:WORD
.CODE
;-----------------------------------------------------------------------;
; This procedure sets the file pointer to the sector number.		;
;									;
; Reads:	CURRENT_SECTOR_NO, FILE_HANDLE				;
;-----------------------------------------------------------------------;
SET_FILE_POINTER	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	MOV	AX,CURRENT_SECTOR_NO	;Get sector number for sector to read
	MOV	BX,512			;Size of the sector
	MUL	BX			;DX:AX = Offset from start of file
	MOV	CX,AX			;DX:CX = Offset in bytes
	XCHG	CX,DX			;CX:DX now contains the offset

	MOV	AX,4200h		;Move to this position in the file
	MOV	BX,FILE_HANDLE		;Get the file handle
	INT	21h			;Move the file pointer

	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
SET_FILE_POINTER	ENDP


	PUBLIC	WRITE_FILE_SECTOR
.DATA
	EXTRN	FILE_HANDLE:WORD
.CODE
;-----------------------------------------------------------------------;
; This procedure writes a single sector back to the file that you were	;
; viewing.  Note, this can change the length of the file if you write	;
; the last sector, and the file was not a multiple of 512 bytes.  In	;
; such cases, this procedure will increase the file length to a		;
; multiple of 512 bytes.						;
;									;
; Uses:		SET_FILE_POINTER
; Reads:	DISK_ERROR_FLAG, FILE_HANDLE, BYTES_READ, SECTOR
;-----------------------------------------------------------------------;
WRITE_FILE_SECTOR	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX

	CALL	SET_FILE_POINTER	;Set to start of current sector
	CMP	DISK_ERROR_FLAG,1	;Was there an error?
	JE	DONE_WRITE_FILE_SECTOR	;Yes, then don't try to write sector

	MOV	AH,40h			;Call for a write to this file
	MOV	BX,FILE_HANDLE		;Get the file handle
	MOV	CX,BYTES_READ		;Don't write more than we read
	LEA	DX,SECTOR		;Address of our sector buffer
	INT	21h			;Let DOS write this sector
	CMP	DISK_ERROR_FLAG,1	;Was there a disk error
	JE	DONE_WRITE_FILE_SECTOR	;Yes, then return

	MOV	AH,45h			;Duplicate the file handle
	INT	21h			;AX == the new file handle
	MOV	BX,AX			;Put new file handle into AX
	MOV	AH,3Eh			;Close duplicate handle to update
	INT	21h			;The changes we just made

DONE_WRITE_FILE_SECTOR:
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
WRITE_FILE_SECTOR	ENDP


	END
