www.pudn.com > drdossrc.zip > DOSIF.ASM


;    File              : $Workfile: DOSIF.ASM$ 
; 
;    Description       : 
; 
;    Original Author   :  
; 
;    Last Edited By    : $Author: RGROSS$ 
; 
;    Copyright         : (C) 1992 Digital Research (UK), Ltd. 
;                                 Charnham Park 
;                                 Hungerford, Berks. 
;                                 U.K. 
; 
;    *** Current Edit History *** 
;    *** End of Current Edit History *** 
; 
;    $Log: $ 
;    DOSIF.ASM 1.1 94/06/28 16:01:14 RGROSS 
;    Initial PUT 
;    DOSIF.ASM 1.16 94/06/28 16:01:28 IJACK 
;    ms_x_expand returns error codes (for benefit of TRUENAME) 
;    DOSIF.ASM 1.15 93/11/29 19:57:24 IJACK 
;     
;    -------- 
;     
;    DOSIF.ASM 1.13 93/09/09 10:24:50 RFREEBOR 
;    call_novell() now returns all allowed error codes. 
;     
;    DOSIF.ASM 1.12 93/02/24 17:42:49 EHILL 
;    _get_scr_width() function added. 
;     
;    DOSIF.ASM 1.11 93/01/21 16:19:31 EHILL 
;     
;    DOSIF.ASM 1.10 93/01/21 14:32:32 EHILL 
;     
;    DOSIF.ASM 1.8 92/09/11 10:46:28 EHILL 
;     
;    DOSIF.ASM 1.7 92/08/06 09:56:07 EHILL 
;    Added DOS 5 calls to get and set memory allocation strategy and 
;    upper memory link. 
;     
;    DOSIF.ASM 1.6 92/07/10 17:47:13 EHILL 
;    No comment 
; 
;    ENDLOG 
; 
;	This file provides all the assembler level interfaces to the 
;	underlying operating system that are required by COMMAND.COM. 
;	The type of functions calls that will be made is controlled 
;	by a Assemble time flag. 
; 
;	Currently the Operating System Interfaces that are supported 
;	are MS-DOS and Concurrent DOS 6.0. 
; 
; 
;	Command Line Flags 
;	================== 
; 
;	MSDOS		If defined then USE MSDOS function Calls 
;	CDOS		If defined then Use Concurrent DOS Calls 
; 
; 
;  2/Sep/87 jc	Convert the Concurrent IOCTL function to use the FDOS 
;		command. 
; 19/Oct/87 jc	Handle Fail on Get current directory correctly 
; 23/Feb/88 jc	Use Text substitution function to get the path assigned 
;		to floating drives (ms_x_subst). 
;  3/Mar/88 jc	Support Server Password Error 
;  9/Mar/88 jc	Return a NULL terminated string when an error occurs on the 
;		ms_x_subst function. 
; 15/Mar/88 jc	Correct ms_x_subst register corruption bug 
; 13/Apr/88 jc	Support the FAR_READ and FAR_WRITE routines as well as external 
;		copy buffer allocation via MEM_ALLOC and MEM_FREE 
; 20/May/88 jc	Return the current country code to the calling application 
; 25/May/88 jc	Missing dataOFFSET causing garbage offset to be passed 
;		ms_x_subst. 
; 18/Jul/88 jc	Modify LOGICAL_DRV test to detect substituted physical drives 
; 17/Aug/88 jc	Return the current Break Status using DL not AL 
; 22/Sep/88 jc	Replace MS_X_SUBST with more general MS_X_EXPAND routine 
; 25/Jan/89 ij	If new DRDOS internal data layout get DPHTBL the new way 
; 07/Feb/89 jc	Add the Get and Set Global Codepage MS_X_GETCP/MS_X_SETCP 
; 25/Jan/89 ij	Get DPHTBL using DRDOS_DPHTBL_OFFSET equate 
; 14/Apr/89 jjs	Add ms_x_setdev 
; 31/May/89 ij	Get SYSDAT for DPHTBL using new f4458 function  
; 19/May/89 jc	Remove "Alternative" techniques of getting SYSDAT:DPHTABLE 
; 20/Jun/89 js	ms_f_parse, ms_f_delete, for DEL cmd 
; 30/Aug/89 js  ms_idle_ptr 
;  6/Sep/89 ij	network_drvs really does something on DRDOS 
; 16/Oct/89 ach Added double byte character set support routines: dbcs_init, 
;		dbcs_expected and dbcs_lead. 
; 18/Jan/90 ij	HILOAD interfaces added 
;  4/Apr/90 ij	dbcs_init moved to cstart, use system table, then we can throw 
;		away the init code 
; 24-May-90 ij	ms_x_expand sets up ES.... 
; 17 Sep 90 ij	TMP Control Break kludge echo's ^C to console 
;  4 Oct 90 ij	Use P_CAT, not P_HDS 
; 15 Mar 91 jc	DRDOS_DPHTBL is now called SYSDAT_DPHTBL cos thats where it lives 
; 28 May 91 ejh No longer use SYSDAT to determine if drives are physical, 
;		logical or networked. See _physical_drive, _logical_drive and 
;		_network_drive. 
; 23 Jun 91 ejh SUBST and ASSIGN are now external commands, so the following 
;		are no longer required: 
;			_physical_drvs, _logical_drvs, _network_drvs 
;			_physical_drive,_logical_drive,_network_drive 
;  3 jul 91 ij	except for NETDRIVE in MDOS 
; 
; 18 Jun 92 ejh Added get_lines_page() function. 
; 24 Jun 92 ejh Added novell_copy() function. 
 
CGROUP	group	_TEXT 
DGROUP	group	_DATA 
 
codeOFFSET	equ	offset CGROUP: 
dataOFFSET	equ	offset DGROUP: 
 
EXT_SUBST	equ	1		; External Subst and Assign commands 
 
CRET	MACRO	num 
	ret 
	ENDM 
 
ifndef	??Version			;; Turbo Assembler always knows RETF 
ifndef	retf				;; some versions of MASM do as well 
retf	macro				;; define far return macro for others 
	db	0cbh 
	endm 
endif 
endif 
 
 
ifndef	CDOSTMP 
include	msdos.equ 
endif 
 
ifndef DOSPLUS 
include system.def 
include pd.def 
include	ccpm.equ 
;include udaa.def 
include	net.def 
include	mserror.equ 
else 
include	f52data.def 
endif 
; 
ifdef	CDOSTMP 
OK_RIF		equ	00111000b	; All Responsese are Valid 
OK_RI		equ	00110000b	; Retry and Ignore are Valid 
OK_RF		equ	00011000b	; Retry and Fail are Valid 
; 
;		      Structure of DOS DPB 
;		      -------------------- 
;	The  layout  of  this  structure  is  a  guess  based  on 
;	examples.  It is returned by PC MODE on functions 1Fh and 
;	32h  and is required  by various  disk-related  utilities 
;	like disk editors and CHKDSK. 
 
DDSC_UNIT	equ	es:byte ptr 0		; absolute drive number 
DDSC_RUNIT	equ	es:byte ptr 1		; relative unit number 
DDSC_SECSIZE	equ	es:word ptr 2		; sector size in bytes 
DDSC_CLMSK	equ	es:byte ptr 4		; sectors/cluster - 1 
DDSC_CLSHF	equ	es:byte ptr 5		; log2 (sectors/cluster) 
DDSC_FATADDR	equ	es:word ptr 6		; sector address of FAT 
DDSC_NFATS	equ	es:byte ptr 8		; # of FAT copies 
DDSC_DIRENT	equ	es:word ptr 9		; size of root directory 
DDSC_DATADDR	equ	es:word ptr 11		; sector address of cluster #2 
DDSC_NCLSTRS	equ	es:word ptr 13		; # of clusters on disk 
DDSC_NFATRECS	equ	es:byte ptr 15		; # of sectors per FAT 
DDSC_DIRADDR	equ	es:word ptr 16		; sector address of root dir 
DDSC_DEVHEAD	equ	es:dword ptr 18		; device driver header 
DDSC_MEDIA	equ	es:byte ptr 22		; current media byte 
DDSC_FIRST	equ	es:byte ptr 23		; "drive never accessed" flag 
DDSC_LINK	equ	es:dword ptr 24		; next drive's DDSC 
DDSC_BLOCK	equ	es:word ptr 28		; next block to allocate 
DDSC_FREE	equ	es:word ptr 30		; total free clusters on drive 
DDSC_MAP	equ	es:word ptr 32		; free blocks/FAT sector 
endif 
 
BDOS_INT	equ	224		; ##jc## 
 
 
_DATA	SEGMENT	byte public 'DATA' 
	extrn	__psp:word 
ifndef DOSPLUS 
	extrn	_pd:dword		; Process Descriptor Pointer 
endif 
	extrn	_country:WORD 
 
ifdef DOSPLUS 
	extrn	dbcs_table_ptr:dword	; points to system DBCS table 
endif 
 
ifdef CDOSTMP 
; 
;	The following buffer is used by the P_PATH function. 
;	FINDFILE uses the first three fields to get the full path and 
;	filename of the command.  
; 
exec_block	label	byte 
exec_pathoff	dw	?		; Offset of ASCIIZ Load file 
exec_pathseg	dw	?		; Segment of ASCIIZ Load File 
exec_filetype	db	?		; File Type Index 
 
fdos_data	dw	7 dup(0)	; FDOS parameter Block 
 
mpb_start	dw	?		; Memory parameter Block 
mpb_min		dw	? 
mpb_max		dw	? 
mpb_pdadr	dw	? 
mpb_flags	dw	? 
 
mfpb_start	dw	?		; Memory Free Parameter Block 
mfpb_res	dw	? 
 
country_data	label	word		; GET country data 
cd_country	dw	?		; Requested Country Code 
cd_codepage	dw	?		; Requested Code Page 
cd_table	dw	?		; Table Number 
cd_offset	dw	?		; Buffer Offset 
cd_segment	dw	?		; Buffer Segment 
 
valid		dw	0		; Valid Error Responses 
retry_ip	dw	0		; Critical Error Retry IP 
retry_sp	dw	0		; Critical Error Retry SP 
crit_flg	db	FALSE		; Critical Section of Error Handler 
 
include fdos.def 
endif 
 
ifdef NETWARE 
ipx		label	dword 
ipx_offset	dw	0 
ipx_segment	dw	0 
; 
;	Socket Allocation by Novell 
; 
;	Socket Nos 4000 and 4001 appear to be used by the IPX internally 
;	and these are NOT closed by the NET_WARE routine. All other USER 
;	sockets are closed. 
; 
;	List of sockets to be closed on shutdown 
;			Start	Count 
socket		label	word 
;;		dw	0001h,	0BB8h 
		dw	4002h,	3FFFh - 2	; User Socket Numbers 
		dw	0, 0 
 
aes		label 	byte		; Event Control Block 
aes_link	label	dword		; Link Field 
aes_linkoff	dw	0 
aes_linkseg	dw	0 
aes_esr		label	dword		; Service Routine Address 
aes_esroff	dw	codeOFFSET aes_retf 
aes_esrseg	dw	0000 
aes_inuse	db	0		; Flag Field 
aes_workspc	db	5 dup(?)	; AES WorkSpace 
endif 
_DATA	ENDS 
 
_TEXT	SEGMENT	byte public 'CODE' 
	assume	cs:CGROUP, ds:DGROUP, es:DGROUP 
 
extrn	_int_break:near		; Control-C Break Handler 
 
ifdef	CDOSTMP 
ifdef MWC 
extrn	_critical_error:near	; Default Critical Error Handler 
CRITICAL_ERR	equ	_critical_error 
else 
extrn	CRITICAL_ERROR:near	; Default Critical Error Handler 
CRITICAL_ERR	equ	CRITICAL_ERROR 
endif 
endif 
 
; 
;	UWORD	psp_poke(WORD handle, BYTE ifn); 
; 
	Public	_psp_poke 
_psp_poke: 
	push	bp 
	mov	bp,sp 
	push	es 
 
ifdef CDOSTMP 
	mov	es,__psp		; ES:0 -> our PSP 
else 
	mov	ah,MS_P_GETPSP 
	int	DOS_INT			; for software carousel 
	mov	es,bx 
endif 
	les	bx,es:[0034h]		; ES:BX -> external file table 
	add	bx,4[bp]		; ES:BX -> XFT entry for our handle 
	mov	al,6[bp]		; get new value to use 
	xchg	al,es:[bx]		; get old value, set new value 
	xor	ah,ah 
 
	pop	es 
	pop	bp 
	ret 
 
ifndef	CDOSTMP 
 
 
	Public	_ms_drv_set 
;----------- 
_ms_drv_set: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	dl,04[bp] 
	mov	ah,MS_DRV_SET		; Select the Specified Disk Drive 
	int	DOS_INT			; Nothing Returned to caller 
	pop	bp 
	ret 
 
	Public	_ms_drv_get 
;----------- 
_ms_drv_get: 
;----------- 
	mov	ah,MS_DRV_GET		; Return the Currently selected 
	int	DOS_INT			; disk drive 
	cbw 
	ret 
 
	Public	_ms_drv_space 
;------------ 
_ms_drv_space: 
;------------ 
; 
;	ret = _ms_drv_space (drive, &free, &secsiz, &nclust); 
;	where:	drive	= 0, 1-16 is drive to use 
;		free    = free cluster count 
;		secsiz  = bytes/sector 
;		nclust	= clusters/disk 
;		ret	= sectors/cluster -or- (0xFFFFh) 
 
	push	bp 
	mov	bp,sp 
	mov	dx,4[bp] 
	mov	ah,MS_DRV_SPACE 
	int	DOS_INT 
	push	bx 
	mov	bx,6[bp]		; get free cluster count 
	pop	word ptr [bx] 
	mov	bx,8[bp] 
	mov	[bx],cx			; bytes/sector 
	mov	bx,10[bp] 
	mov	[bx],dx			; clusters/disk 
	cbw 
	pop	bp 
	ret 
 
	Public	_ms_s_country 
;------------ 
_ms_s_country: 
;------------ 
	push	bp 
	mov	bp,sp 
	mov	ax,MS_S_COUNTRY shl 8	; Get the curremt country information 
	mov	dx,4[bp]		; and return the current country code 
	int	DOS_INT			; to the calling application. 
	mov	ax,bx 
	pop	bp 
	ret 
 
 
	Public _ms_x_mkdir 
;---------- 
_ms_x_mkdir: 
;---------- 
	mov	ah,MS_X_MKDIR 
	jmp	ms_dx_call 
 
 
	Public	_ms_x_rmdir 
;---------- 
_ms_x_rmdir: 
;---------- 
	mov	ah,MS_X_RMDIR 
	jmp	ms_dx_call 
 
 
	Public	_ms_x_chdir 
;---------- 
_ms_x_chdir: 
;---------- 
	mov	ah,MS_X_CHDIR 
	jmp	ms_dx_call 
 
	Public	_ms_x_creat 
;---------- 
_ms_x_creat: 
;---------- 
	mov	ah,MS_X_CREAT 
	jmp	ms_open_creat 
 
 
	Public	_ms_x_open 
;--------- 
_ms_x_open: 
;--------- 
	mov	ah,MS_X_OPEN 
ms_open_creat: 
	push	bp 
	mov	bp,sp 
	mov	dx,4[bp] 
	mov	cx,6[bp]		; get mode for new file (CREAT) 
	mov	al,cl			;          or open mode (OPEN) 
	int	DOS_INT 
	jnc	ms_open_ret		; AX = handle if no error 
	neg	ax			; else mark as error code 
ms_open_ret: 
	pop	bp 
	ret 
 
	Public _ms_x_close 
;---------- 
_ms_x_close: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	bx,4[bp]		; get the open handle 
	mov	ah,MS_X_CLOSE		; get the function 
	jmp	ms_call_dos		; call DOS, handle errors 
 
	Public	_ms_x_unique 
;---------- 
_ms_x_unique: 
;---------- 
	mov	ah,MS_X_MKTEMP 
	jmp	ms_open_creat 
 
	Public	_ms_x_fdup 
;---------- 
_ms_x_fdup: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	cx,4[bp]		; get the destination handle 
	mov	bx,6[bp]		; Get the current handle	 
	mov	ah,MS_X_DUP2		; get the function 
	jmp	ms_call_dos		; call DOS, handle errors 
 
 
	Public	_far_read 
;--------- 
_far_read: 
;--------- 
	mov	ah,MS_X_READ 
	jmp	far_read_write 
 
	Public	_far_write 
;---------- 
_far_write: 
;---------- 
	mov	ah,MS_X_WRITE 
far_read_write: 
	push	bp 
	mov	bp,sp 
	push	ds 
	mov	bx,4[bp]		; get file handle 
	lds	dx,dword ptr 6[bp]	; get buffer address 
	mov	cx,10[bp]		; get byte count 
	int	DOS_INT			; call the DOS 
	jnc	far_rw_ok		; skip if no error 
	neg	ax			; else make it negative error code 
far_rw_ok: 
	pop	ds 
	pop	bp 
	ret 
 
 
	Public	_ms_x_read 
;--------- 
_ms_x_read: 
;--------- 
	mov	ah,MS_X_READ 
	jmp	ms_read_write 
 
	Public	_ms_x_write 
;---------- 
_ms_x_write: 
;---------- 
	mov	ah,MS_X_WRITE 
ms_read_write: 
	push	bp 
	mov	bp,sp 
	mov	bx,4[bp]		; get file handle 
	mov	dx,6[bp]		; get buffer address 
	mov	cx,8[bp]		; get byte count 
	int	DOS_INT			; call the DOS 
	jnc	ms_rw_ok		; skip if no error 
	neg	ax			; else make it negative error code 
ms_rw_ok: 
	pop	bp 
	ret 
 
	Public	_ms_x_unlink 
;----------- 
_ms_x_unlink: 
;----------- 
	mov	ah,MS_X_UNLINK 
	jmp	ms_dx_call 
 
 
	Public	_ms_x_lseek 
;---------- 
_ms_x_lseek: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	ah,MS_X_LSEEK		; get the function 
	mov	bx,4[bp]		; get the file handle 
	mov	dx,6[bp]		; get the offset 
	mov	cx,8[bp] 
	mov	al,10[bp]		; get the seek mode 
	int	DOS_INT 
	jnc	ms_lseek_ok		; skip if no errors 
	neg	ax			; make error code negative 
	cwd				; sign extend to long 
ms_lseek_ok: 
	mov	bx,dx			; AX:BX = DRC long return 
	pop	bp 
	ret 
 
	Public	_ms_x_ioctl 
;---------- 
_ms_x_ioctl: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	bx,4[bp]		; get our handle 
	mov	ah,MS_X_IOCTL		; get IO Control function 
	mov	al,0			; get file/device status 
	int	DOS_INT			; do INT 21h 
	jnc	ms_x_i10 
	neg	ax 
ms_x_i10:	 
	pop	bp			;  
	ret 
 
	Public	_ms_x_setdev 
;------------ 
_ms_x_setdev: 
;------------ 
	push	bp 
	mov	bp, sp 
	mov	bx, 4[bp]		; handle 
	mov	dx, 6[bp]		; byte value to set 
	sub	dh, dh 
	mov	ah, MS_X_IOCTL 
	mov	al, 1 
	int	DOS_INT 
	jnc	ms_x_sd10 
	neg	ax 
ms_x_sd10: 
	pop	bp 
	ret 
 
	Public	_ms_x_chmod 
;---------- 
_ms_x_chmod: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	ah,MS_X_CHMOD 
	mov	dx,4[bp] 
	mov	cx,6[bp] 
	mov	al,8[bp] 
	int	DOS_INT 
	jnc	ms_chmod_ok 
	neg	ax			; make error code negative 
	jmp	ms_chmod_ret 
ms_chmod_ok: 
	sub	ax,ax			; assume no error 
	cmp	byte ptr 8[bp],0	; getting attributes 
	jne	ms_chmod_ret		; return ax = 0 if setting & no error 
	xchg	ax,cx			; return ax = attrib otherwise 
ms_chmod_ret: 
	pop	bp 
	ret 
 
 
	Public	_ms_x_curdir 
;----------- 
_ms_x_curdir: 
;----------- 
	push	bp 
	mov	bp,sp 
	push	si 
	mov	si,6[bp]		; Get the buffer address and  
	mov	byte ptr [si],0		; put a zero in the first byte in 
	mov	ah,MS_X_CURDIR		; the command is FAILED 
	push	word ptr 4[bp] 
	call	ms_dx_call 
	pop	dx 
	pop	si 
	pop	bp 
	ret 
 
	Public _ms_x_exit 
;--------- 
_ms_x_exit: 
;--------- 
	push	bp 
	mov	bp,sp 
 
ifdef NETWARE 
	push	es			; If this is Novell Netware and 
	mov	ax,__psp		; the command processor is terminating 
	mov	es,ax			; ie PSP_PARENT == PSP then do the 
	cmp	ax,es:word ptr 16h	; special Novell Close down sequence 
	pop	es 
	jnz	ms_x_exit10 
 
	mov	ax,7A00h		; Check for IPX being present using 
	int	2Fh			; the Multi-Plex Interrupt. 
	cmp	al,0FFh 
	jz	net_ware 
 
ms_x_exit10: 
endif 
	mov	al,04[bp]		; Get the Return Code 
	mov	ah,MS_X_EXIT		; terminate process function 
	int	DOS_INT			; call the DOS 
	pop	bp 
	ret 
 
ifdef NETWARE 
; 
;	The following routine attempts to clean-up after a Novell  
;	session. It does so in the following manner:- 
; 
;	1)	Close all file handles (May be Networked !!) 
;	2)	Close all User Sockets 
;	3)	Remove all User Events from Internal lists 
;	4)	Use CDOS terminate function 
; 
net_ware: 
	mov	ipx_offset,di 
	mov	ipx_segment,es 
 
	mov	cx,20			; Close all the possible handles  
	mov	bx,0			; used by the command processor 
net_w05:				; in case any have been redirected 
	mov	ah,MS_X_CLOSE		; accross the Network 
	int	DOS_INT 
	inc	bx 
	loop	net_w05 
 
	mov	si,dataOFFSET socket 
net_w10: 
	mov	cx,word ptr 02[si]	; Get the number of sockets to close 
	mov	dx,word ptr 00[si]	; starting at Socket No. 
	jcxz	net_w30			; Terminate on a 0 Count 
	push	si 
net_w20: 
	push	cx 
	push	dx			; Save Count and Socket No. 
	xchg	dl,dh			; Swap socket no to High/Low 
	mov	bx,1			; Close Socket Function 
	call	ipx			; Close Socket. 
	pop	dx 
	pop	cx 
	inc	dx			; Increment Socket No 
	loop	net_w20			; and Loop 
	pop	si	 
	add	si,4			; Point to next entry in the array 
	jmp	net_w10			; and repeat till count is 0 
 
net_w30:				; All sockets have been closed 
	mov	aes_esrseg,cs 
	mov	ax,0FFFFh		; Create Special event with the  
	mov	bx,7			; maximum time delay 
	push	ds 
	pop 	es			; Pass the address of the Special 
	mov	si,dataOFFSET aes	; Event control block call the IPX 
	call	ipx 
 
net_w40: 
	les	si,aes_link		; Remove all entries from the Link 
					; Which are not owned by the IPX 
net_w50: 
	mov	bx,es			; get the AES segment 
	cmp	bx,ipx_segment		; and check for a match 
	jnz	net_w60			; Remove this entry 
	les	si,es:dword ptr [si]	; get the next entry and try again 
	jmp short net_w50 
 
net_w60: 
	or	bx,si			; End of List 
	jz	net_w70			; Yes terminate our entry 
	mov	bx,0006h		; Cancel this event 
	call	ipx 
	jmp short net_w40 
 
net_w70: 
	mov	bx,0006h		; Cancel our event 
	push	ds 
	pop 	es 
	mov	si,dataOFFSET aes 
	call	ipx 
	 
net_exit: 
	mov	dh,0			; Standard Exit 
	mov	dl,04[bp]		; With the supplied ExitCode 
	mov	cx,P_EXITCODE		; Set the ExitCode for the Parent 
	int	BDOS_INT 
	mov	cx,P_TERMCPM		; Use a Concurrent Terminate Call 
	int	BDOS_INT		; because Novell has taken over 4Ch 
 
aes_retf:				; Dummy AES routine 
	retf 
endif 
 
; 
;	ms_x_expand(dstbuf, srcbuf) returns the full path of SRCBUF 
; 
	Public	_ms_x_expand 
;----------- 
_ms_x_expand: 
;----------- 
	push	bp 
	mov	bp,sp 
	push	si 
	push	di 
	mov	si,06[bp]		; Get the source String Address 
	mov	di,04[bp]		; Get the destination string 
	mov	byte ptr [di],0		; address and force it to be a NULL 
	push	ds 
	pop	es			; ES:DI -> destination 
	mov	ah,60h			; terminated string in case of errors 
	int	DOS_INT 
	jc	ms_exp_ret		; skip if error 
	xor	ax,ax			; signal no errors 
ms_exp_ret: 
	neg	ax			; make error negative, 0 = 0 
	pop	di 
	pop	si 
	pop	bp 
	CRET	4 
 
	Public	_ms_x_wait 
;--------- 
_ms_x_wait:		; retrieve child return code 
;--------- 
	mov	ah,MS_X_WAIT		; Top byte is abort code ie ^C 
	int	DOS_INT			; Bottom byte is return code 
	ret 
 
	Public	_ms_x_first 
;---------- 
_ms_x_first: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	dx,8[bp]		; get DMA buffer address 
	mov	ah,MS_F_DMAOFF 
	int	DOS_INT 
	mov	dx,4[bp]		; get ASCII string 
	mov	cx,6[bp]		; get attribute 
	mov	ah,MS_X_FIRST		; get search function 
	jmp	ms_call_dos		; call DOS, check for errors 
 
	Public	_ms_x_next 
;--------- 
_ms_x_next: 
;--------- 
	push	bp 
	mov	bp,sp 
	mov	dx,4[bp]		; get DMA buffer address 
	mov	ah,MS_F_DMAOFF 
	int	DOS_INT 
	mov	ah,MS_X_NEXT		; get the function 
	jmp	ms_call_dos		; get DX, call DOS, handle errors 
 
ms_dx_call:				; call DOS with parameter in DX 
	push	bp 
	mov	bp,sp 
	mov	dx,4[bp] 
ms_call_dos: 
	int	DOS_INT 
	jnc	ms_dos_ok		; no carry = no error 
	neg	ax			; else make it negative 
	jmp	ms_dos_ret		; and return with error 
ms_dos_ok: 
	sub	ax,ax			; return 0 if no error 
ms_dos_ret: 
	pop	bp			; return 0 or negative error code 
	ret 
 
 
	Public _ms_x_rename 
;----------- 
_ms_x_rename: 
;----------- 
	push	bp 
	mov	bp,sp 
	push	di 
	push	ds 
	pop	es 
	mov	ah,MS_X_RENAME 
	mov	di,6[bp]		; ES:DI = new name 
	push	word ptr 4[bp]		; make it look like DRC call 
	call	ms_dx_call		; DX = 4[bp], call DOS, handle errors 
	pop	di			; remove parameter 
	pop	di 
	pop	bp 
	ret 
 
	Public	_ms_x_datetime 
;	ret = _ms_x_datetime (gsflag, h, &time, &date); 
;------------- 
_ms_x_datetime: 
;------------- 
	push	bp 
	mov	bp,sp 
	mov	ah,MS_X_DATETIME	; set/get time stamp function 
	mov	al,4[bp]		; get/set subfunction (0/1) 
	mov	bx,8[bp]		; get address of time 
	mov	cx,[bx]			; get time 
	mov	bx,10[bp]		; get address of date 
	mov	dx,[bx]			; get date 
	mov	bx,6[bp]		; get handle 
	int	DOS_INT			; call the DOS 
	jc	ms_dt_ret		; skip if error 
	sub	ax,ax			; signal no errors 
	cmp	byte ptr 4[bp],0	; geting time/date? 
	jne	ms_dt_ret		; skip if setting 
	mov	bx,8[bp]		; get time address 
	mov	[bx],cx			; update time 
	mov	bx,10[bp]		; get date address 
	mov	[bx],dx			; update date 
ms_dt_ret: 
	neg	ax			; make error negative, 0 = 0 
	pop	bp 
	ret 
 
 
; 
;	The following routines allow COMMAND.COM to manipulate 
;	the system time and date. Four functions are provided and 
;	these are MS_GETDATE, MS_SETDATE, MS_GETTIME and MS_SETTIME 
; 
;	Date information is passed and return in a structure which  
;	has the following format. 
; 
;	WORD		Year (1980 - 2099) 
;	BYTE		Month 
;	BYTE		Day 
;	BYTE		Day of the Week (Ignored on SET DATE) 
 
	Public	_ms_getdate 
_ms_getdate: 
	push	bp 
	mov	bp,sp 
	mov	ah,MS_T_GETDATE		; get the current date from DOS 
	int	DOS_INT 
	mov	bx,4[bp]		; and get the structure address 
	mov	[bx],cx			; save the year 
	xchg	dh,dl			; swap month and day 
	mov	2[bx],dx		; and save 
	mov	4[bx],al		; and finally save the day number 
	pop	bp			; and exit 
	ret 
 
	Public	_ms_setdate 
_ms_setdate: 
	push	bp 
	mov	bp,sp 
	mov	bx,4[bp]		; and get the structure address 
	mov	cx,0[bx]		; det the year 
	mov	dx,2[bx]		; get the month and day 
	xchg	dh,dl			; swap month and day 
	mov	ah,MS_T_SETDATE		; set the current date 
	int	DOS_INT 
	cbw				; 0000 = Ok and FFFF = Bad 
	pop	bp			; and exit 
	ret 
 
 
;	Time information is passed and return in a structure which  
;	has the following format. 
; 
;	BYTE		Hours (0 - 23) 
;	BYTE		Minutes (0 - 59) 
;	BYTE		Seconds (0 - 59) 
;	BYTE		Hundredths of a second (0 - 99) 
 
	Public	_ms_gettime 
_ms_gettime: 
	push	bp 
	mov	bp,sp 
	mov	ah,MS_T_GETTIME		; get the current date from DOS 
	int	DOS_INT 
	mov	bx,4[bp]		; and get the structure address 
	xchg	cl,ch 
	mov	[bx],cx			; save the hours and minutes 
	xchg	dh,dl 
	mov	2[bx],dx		; save seconds and hundredths 
	pop	bp			; and exit 
	ret 
 
	Public _ms_settime 
_ms_settime: 
	push	bp 
	mov	bp,sp 
	mov	bx,4[bp]		; and get the structure address 
	mov	cx,[bx]			; get the hours and minutes 
	xchg	cl,ch 
	mov	dx,2[bx]		; get seconds and hundredths 
	xchg	dh,dl 
	mov	ah,MS_T_SETTIME		; get the current date from DOS 
	int	DOS_INT 
	cbw				; 0000 = Ok and FFFF = Bad 
	pop	bp			; and exit 
	ret 
 
	Public _ms_idle_ptr 
;------------ 
_ms_idle_ptr: 
;------------ 
	push	es 
	push	si 
	push	di 
	mov	ax, 4458h 
	int	DOS_INT			; ptr in ES:AX 
	mov	dx, es 
	pop	di 
	pop	si 
	pop	es 
	ret 
 
	Public _ms_switchar 
;----------- 
_ms_switchar: 
;----------- 
	mov	ax,3700h 
	int	DOS_INT 
	sub	ah,ah 
	mov	al,dl 
	ret 
if 0 
	Public	_ms_p_getpsp 
;----------- 
_ms_p_getpsp: 
;----------- 
	mov	ah,51h			; Note: SeCRET DOS 2.x entry 
	int	DOS_INT 
	xchg	ax,bx 
	ret 
endif 
	Public	_ms_f_verify 
;----------- 
_ms_f_verify: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	ah,MS_F_VERIFY 
	mov	al,4[bp]		;get 0/1 al parameter  
	int	DOS_INT 
	pop	bp 
	ret 
 
	Public	_ms_f_getverify 
;-------------- 
_ms_f_getverify: 
;-------------- 
	mov	ah,MS_F_GETVERIFY 
	int	DOS_INT 
	cbw 
	ret 
 
ifndef	CDOSTMP 
	Public	_ms_f_parse 
;----------- 
_ms_f_parse: 
;----------- 
	push	bp 
	mov	bp, sp 
	push	es 
	push	si 
	push	di 
	 
	push	ds 
	pop	es 
	mov	di, 4[bp]		; fcb 
	mov	si, 6[bp]		; filename 
	mov	al, 8[bp]		; flags 
	mov	ah, MS_F_PARSE 
	int	DOS_INT 
	 
	cbw				; return code in ax 
	pop	di 
	pop	si 
	pop	es 
	pop	bp 
	ret 
 
	Public	_ms_f_delete 
;------------ 
_ms_f_delete: 
;------------ 
	push	bp 
	mov	bp, sp 
	mov	dx, 4[bp]			; fcb 
	mov	ah, MS_F_DELETE 
	int	DOS_INT 
	 
	cbw					; return code 
	pop	bp 
	ret 
endif 
; 
;	The SET BREAK function returns the previous Break Flag Status 
;	 
	Public	_ms_set_break 
;------------ 
_ms_set_break: 
;------------ 
	push	bp 
	mov	bp,sp 
	mov	dl,04[bp] 
	mov	ax,(MS_S_BREAK SHL 8) + 2 
	int	DOS_INT 
	pop	bp 
	mov	al,dl 
	cbw 
	ret 
 
if 0 
	Public _ms_get_break 
;------------ 
_ms_get_break: 
;------------ 
	mov	ax,MS_S_BREAK SHL 8 
	int	DOS_INT 
	mov	al,dl 
	cbw 
	ret 
endif 
 
; 
;	mem_alloc(BYTE FAR * NEAR * bufaddr, UWORD * bufsize, UWORD min, UWORD max); 
; 
;	max		10[bp] 
;	min		08[bp] 
;	bufsize		06[bp] 
;	buffadr 	04[bp] 
; 
	Public _mem_alloc 
;--------- 
_mem_alloc: 
;--------- 
	push	bp 
	mov	bp,sp 
	mov	bx,10[bp]		; Start with request maximum size 
mem_all10: 
	mov	ah,MS_M_ALLOC		; Attempt to allocate the maximum 
	int	DOS_INT			; memory requested by the user.  
	jnc	mem_all20		; Allocation OK 
	cmp	bx,08[bp]		; Is this less than the requested 
	jae	mem_all10		; No then allocate this amount 
	xor	ax,ax			; Force the Buffer address and Buffer 
	mov	bx,ax			; Size to Zero 
 
mem_all20: 
	mov	cx,bx			; Save the Buffer Size 
	mov	bx,04[bp]		; Update the Buffer Address 
	mov	word ptr 00[bx],0	; Offset 0 
	mov	word ptr 02[bx],ax	; Segment AX 
	mov	bx,06[bp]		; Now Update the Buffer Size 
	mov	word ptr 00[bx],cx	; and return to the caller 
	pop	bp 
	ret 
; 
;	mem_free(BYTE FAR * NEAR * bufaddr); 
; 
;	buffadr 	04[bp] 
; 
	Public _mem_free 
;--------- 
_mem_free: 
;--------- 
	push	bp 
	mov	bp,sp 
	xor	ax,ax 
	mov	bx,04[bp]		; Get the Buffer Pointer address 
	xchg	ax,word ptr 02[bx]	; and from this the segment of the 
	cmp	ax,0			; allocated memory. If the memory 
	jz	mem_free10		; has already been freed the quit 
	push	es			; Otherwise Free the Memory 
	mov	es,ax 
	mov	ah,MS_M_FREE 
	int	DOS_INT 
	pop	es 
mem_free10: 
	pop	bp 
	ret 
 
	Public _msdos 
;------- 
_msdos: 
;------- 
	push	bp 
	mov	bp,sp 
	push	si 
	push	di 
 
	mov	ah,4[bp] 
	mov	dx,6[bp] 
	int	DOS_INT 
 
	pop	di 
	pop	si 
	pop	bp 
	ret 
 
	Public	_ioctl_ver 
;--------- 
_ioctl_ver:	 
;--------- 
ifdef DOSPLUS 
 	mov	ax,4452h		; Get DOS Plus BDOS version Number 
else 
 	mov	ax,4451h		; Get Concurrent BDOS Version 
endif 
	int	DOS_INT			; Real DOS returns with Carry Set 
	jc	cdos_v10 
	and	ax,not 0200h		; Reset the Networking Bit 
	ret 
cdos_v10:	 
	xor	ax,ax 
	ret 
 
ifdef DOSPLUS 
; 
;	Get CodePage information form the system. Return both the currently 
;	active CodePage and the System CodePage. 
; 
;	ms_x_getcp(&globalcp, &systemcp); 
; 
	Public	_ms_x_getcp 
;----------- 
_ms_x_getcp:	 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	ax,MS_X_GETCP			; Get the CodePage Information 
	int	DOS_INT				; and return an error if not 
	jc	ms_x_getcp10			; supported. 
	mov	ax,bx				; Now update the callers 
	mov	bx,04[bp]			; Global and System Codepage 
	mov	word ptr [bx],ax		; variables  
	mov	bx,06[bp] 
	mov	word ptr [bx],dx 
	xor	ax,ax 
 
ms_x_getcp10: 
	neg	ax				; Negate the error code has  
	pop	bp				; no effect on 0 
	ret 
; 
;	Change the current CodePage 
; 
;	ms_x_setcp(globalcp); 
; 
	Public	_ms_x_setcp 
;----------- 
_ms_x_setcp:	 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	bx,04[bp]			; Get the requested CodePage 
	mov	ax,MS_X_SETCP			; and make this the default 
	int	DOS_INT 
	jc	ms_x_getcp10 
	xor	ax,ax 
	pop	bp 
	ret 
endif 
endif 
 
ifdef CDOSTMP 
 
 
	Public	_ms_drv_set 
;----------- 
_ms_drv_set: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	dl,04[bp]		; Get the Specified drive 
	or	dl,80h			; Prevent any Select Errors 
	mov	cl,DRV_SET		; and go select the bugger 
	int	BDOS_INT 
	pop	bp 
	ret 
 
	Public	_ms_drv_get 
;----------- 
_ms_drv_get: 
;----------- 
	mov	cl,DRV_GET		; Return the Currently selected 
	int	BDOS_INT		; disk drive 
	cbw 
	ret 
 
	Public	_ms_drv_space 
;------------ 
_ms_drv_space: 
;------------ 
; 
;	ret = _ms_drv_space (drive, &free, &secsiz, &nclust); 
;	where:	drive	= 0, 1-16 is drive to use 
;		free    = free cluster count 
;		secsiz  = bytes/sector 
;		nclust	= clusters/disk 
;		ret	= sectors/cluster -or- (0xFFFFh) 
 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF			; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC, FD_DISKINFO 
	mov	ax,04[bp] 
	mov	FD_DRIVE,ax 
	call	fdos_entry 
	or	ax,ax				; Check for Errors 
	mov	ax,0FFFFh 
	jnz	ms_drv_exit			; Error Exit 
 
	push	es				; Save ES 
	push	di 
	les	di,FD_DPB			; Get the DPB Address 
 
	mov	ax,es:DDSC_FREE[di]		; Get the number of free 
	mov	bx,06[bp]			; clusters on the drive 
	mov	[bx],ax 
 
	mov	ax,es:DDSC_SECSIZE[di]		; Get the Physical Sector Size 
	mov	bx,08[bp]			; in bytes 
	mov	[bx],ax 
	 
	mov	ax,es:DDSC_NCLSTRS[di]		; Get the disk size in 
	mov	bx,10[bp]			; clusters and save in DX 
	mov	[bx],ax 
 
	mov	al,es:DDSC_CLMSK[di]		; Get the sectors per Cluster -1 
	cbw					; and save in AX 
	inc	ax 
	pop	di 
	pop	es 
 
ms_drv_exit: 
	pop	bp 
	CRET	8 
 
	Public	_ms_s_country 
;------------ 
_ms_s_country: 
;------------ 
	push	bp 
	mov	bp,sp 
	mov	ax,04[bp]		; Get the data Block Offset 
 
	mov	cd_country,0		; Get the Current Country	 
	mov	cd_codepage,0		; Current CodePage 
	mov	cd_table,0		; Country Information 
	mov	cd_offset,ax		; Save the Buffer Offset 
	mov	cd_segment,ds		; and the Buffer Segment 
 
	mov	dx,dataOFFSET country_data 
	mov	cl,S_GETCOUNTRY		; Get the country information 
	int	BDOS_INT		; and return the current country 
	pop	bp			; code to the caller 
	CRET	2 
 
	Public _ms_x_mkdir 
;---------- 
_ms_x_mkdir: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_MKDIR	; Make Directory 
 
mkdir_10: 
	mov	ax,04[bp] 
	mov	FD_NAMEOFF,ax 
	mov	FD_NAMESEG,ds 
	call	fdos_entry 
	pop	bp 
	CRET	2 
	 
	Public	_ms_x_rmdir 
;---------- 
_ms_x_rmdir: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_RMDIR 
	jmp	mkdir_10 
 
	Public	_ms_x_chdir 
;---------- 
_ms_x_chdir: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF			; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_CHDIR 
	jmp	mkdir_10 
 
	Public	_ms_x_creat 
;---------- 
_ms_x_creat: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF			; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_CREAT 
	jmp	ms_open_creat 
 
 
	Public	_ms_x_open 
;--------- 
_ms_x_open: 
;--------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF			; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_OPEN 
ms_open_creat: 
	mov	ax,4[bp] 
	mov	FD_NAMEOFF,ax 
	mov	FD_NAMESEG,ds 
	mov	ax,6[bp]		; get mode for new file (CREAT) 
	mov	FD_MODE,ax		; or the OPEN mode 
	call	fdos_entry		; Call the FDOS and return either 
	pop	bp			; a handle or error code 
	CRET	4 
 
	Public _ms_x_close 
;---------- 
_ms_x_close: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	ax,4[bp]		; get the open handle 
	mov	FD_FUNC,FD_CLOSE 
	mov	FD_HANDLE,ax 
	call	fdos_entry 
	pop	bp 
	CRET	2 
 
	Public	_ms_x_unique 
;---------- 
_ms_x_unique: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF			; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_MKTEMP 
	jmp	ms_open_creat 
 
	Public	_ms_x_fdup 
;---------- 
_ms_x_fdup: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_FDUP 
	mov	ax,4[bp]		; get the destination handle 
	mov	FD_NEWHND,ax 
	mov	ax,6[bp]		; Get the current handle	 
	mov	FD_HANDLE,ax 
	call	fdos_entry 
	pop	bp 
	CRET	4 
 
	Public	_far_read 
;--------- 
_far_read: 
;--------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF			; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_READ 
	jmp	far_read_write 
 
	Public	_far_write 
;---------- 
_far_write: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_WRITE 
 
far_read_write: 
	mov	ax,4[bp]		; get file handle 
	mov	FD_HANDLE,ax 
	mov	ax,6[bp]		; get buffer offset address 
	mov	FD_BUFOFF,ax 
	mov	ax,8[bp]		; get buffer Segment address 
	mov	FD_BUFSEG,ax 
	mov	ax,10[bp]		; get byte count 
	mov	FD_COUNT,ax 
	call	fdos_entry 
	or	ax,ax 
	jnz	far_rw_fail	 
	mov	ax,FD_COUNT		; Get the Byte Count 
far_rw_fail: 
	pop	bp 
	CRET	6 
 
	Public	_ms_x_read 
;--------- 
_ms_x_read: 
;--------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF			; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_READ 
	jmp	ms_read_write 
 
	Public	_ms_x_write 
;---------- 
_ms_x_write: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_WRITE 
 
ms_read_write: 
	mov	ax,4[bp]		; get file handle 
	mov	FD_HANDLE,ax 
	mov	ax,6[bp]		; get buffer address 
	mov	FD_BUFOFF,ax 
	mov	FD_BUFSEG,ds 
	mov	ax,8[bp]		; get byte count 
	mov	FD_COUNT,ax 
	call	fdos_entry 
	or	ax,ax 
	jnz	ms_rw_fail	 
	mov	ax,FD_COUNT		; Get the Byte Count 
ms_rw_fail: 
	pop	bp 
	CRET	6 
 
	Public	_ms_x_unlink 
;----------- 
_ms_x_unlink: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_UNLINK 
	jmp	mkdir_10 
 
 
	Public	_ms_x_lseek 
;---------- 
_ms_x_lseek: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF			; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_LSEEK	; get the function 
	mov	ax,4[bp]		; get the file handle 
	mov	FD_HANDLE,ax 
	mov	ax,6[bp]		; get the offset 
	mov	word ptr FD_OFFSET+0,ax 
	mov	ax,8[bp] 
	mov	word ptr FD_OFFSET+2,ax 
	mov	ax,10[bp]		; get the seek mode 
	mov	FD_METHOD,ax 
	call	fdos_entry 
	cwd 
	or	ax,ax 
	jnz	ms_lseek_fail		; skip if errors 
	mov	ax,word ptr FD_OFFSET+0 ; Return the New Location 
	mov	dx,word ptr FD_OFFSET+2 
ms_lseek_fail: 
	mov	bx,dx			; AX:BX = DRC long return 
	pop	bp 
	CRET	8 
 
 
	Public	_ms_x_ioctl 
;---------- 
_ms_x_ioctl: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF		; Retry or Fail 
	call	fdos_retry 
	mov	ax,4[bp]		; get Enquiry Handle 
	mov	FD_FUNC,FD_IOCTL	; Use the IOCTL function 
	mov	FD_HANDLE,ax		; For Handle AX 
	mov	FD_CTLFUNC,0000		; Get the Handle Status 
	mov	FD_CTLSTAT,0		; Invalidate CTLSTAT 
	call	fdos_entry		; Call the FDOS 
	mov	ax,FD_CTLSTAT		; and return the STATUS 
	pop	bp 
	CRET	2 
 
	Public	_ms_x_setdev 
;------------ 
_ms_x_setdev: 
;------------ 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF		; Retry or Fail 
	call	fdos_retry 
	mov	ax,4[bp]		; get Handle 
	mov	FD_HANDLE,ax 
	mov	FD_FUNC,FD_IOCTL	; Use the IOCTL function 
	mov	FD_CTLFUNC,1		; Set device info 
	mov	ax, 6[bp]		; status to set 
	sub	ah, ah 
	mov	FD_CTLSTAT,ax 
	call	fdos_entry		; Call the FDOS 
	mov	ax,FD_CTLSTAT		; and return the STATUS 
	pop	bp 
	CRET	2 
 
	Public	_ms_x_chmod 
;---------- 
_ms_x_chmod:			;	ms_x_chmod(path, attrib, get/set) 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF		; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_CHMOD 
	mov	ax,4[bp]		; Get the FileName 
	mov	FD_NAMEOFF,ax 
	mov	FD_NAMESEG,ds 
	mov	ax,6[bp]		; Get the Required Attributes 
	mov	FD_ATTRIB,ax 
	mov	ax,8[bp]		; Finally Get the GET/SET flag 
	mov	FD_FLAG,ax 
	call	fdos_entry		; Returns with AX equal to the   
	or	ax,ax			; error code or with the file 
	js	chmod10			; attributes. 
	mov	ax,FD_ATTRIB 
chmod10: 
	pop	bp 
	CRET	6 
 
 
	Public	_ms_x_curdir 
;----------- 
_ms_x_curdir: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF		; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_GETDIR 
	mov	ax,04[bp]		; Get the drive 
	mov	FD_DRIVE,ax 
	mov	bx,06[bp]		; and then the path 
	mov	byte ptr [bx],0		; Put a Zero byte in the buffer in 
	mov	FD_PATHOFF,bx		; case the command fails and the 
	mov	FD_PATHSEG,ds		; user selects the FAIL Option 
	call	fdos_entry 
	pop	bp 
	CRET	4 
 
; 
;	ms_x_expand(dstbuf, srcbuf) returns the full path of SRCBUF 
; 
	Public	_ms_x_expand 
;----------- 
_ms_x_expand: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF		; Retry or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_EXPAND 
	mov	ax,06[bp]		; Get Source Buffer Offset 
	mov	FD_ONAMEOFF,ax 
	mov	FD_ONAMESEG,ds 
	mov	bx,04[bp]		; Get the destination string 
	mov	byte ptr [bx],0		; address and force it to be a NULL 
	mov	FD_NNAMEOFF,bx		; terminated string in case of errors 
	mov	FD_NNAMESEG,ds 
	call	fdos_entry 
	pop	bp 
	CRET	4 
 
	Public	_ms_x_wait 
;--------- 
_ms_x_wait:		; retrieve child return code 
;--------- 
	mov	cl,P_EXITCODE		; Return the Exit Code 
	mov	dx,0FFFFh		; Get the Exit Code 
	int	BDOS_INT 
	CRET	0 
 
	Public	_ms_x_first 
;---------- 
_ms_x_first: 
;---------- 
	push	bp 
	mov	bp,sp 
 
	mov	dx,8[bp]		; get DMA buffer address 
	mov	cl,F_DMAOFF 
	call	bdos_entry 
 
	mov	dx,ds			;##jc## 
	mov	cl,F_DMASEG		;##jc## 
	call	bdos_entry		;##jc## 
 
	mov	al,OK_RF		; Retry or Fail 
	call	fdos_retry 
 
	mov	FD_FUNC,FD_FFIRST	; Search First 
	mov	ax,04[bp]		; Get the FileName 
	mov	FD_NAMEOFF,ax 
	mov	FD_NAMESEG,ds 
	mov	ax,06[bp]		; Get the Attributes 
	mov	FD_ATTRIB,ax 
	mov	FD_COUNT, 0		; Search for a File at a time 
	call	fdos_entry 
	cmp	ax,1			; Did we match 1 entry 
	jnz	ms_x_f10		; No so return Error Code 
	mov	ax,0			; Return Zero on sucess 
ms_x_f10: 
	pop	bp 
	CRET	6 
 
	Public	_ms_x_next 
;--------- 
_ms_x_next: 
;--------- 
	push	bp 
	mov	bp,sp 
 
	mov	dx,4[bp]		; get DMA buffer address 
	mov	cl,F_DMAOFF 
	call	bdos_entry 
 
	mov	dx,ds			;##jc## 
	mov	cl,F_DMASEG		;##jc## 
	call	bdos_entry		;##jc## 
 
	mov	al,OK_RF		; Retry or Fail 
	call	fdos_retry 
 
	mov	FD_FUNC,FD_FNEXT	; Search Next 
	mov	FD_NEXTCNT, 0		; Search for a File at a time 
	call	fdos_entry 
	cmp	ax,1			; Did we match 1 entry 
	jnz	ms_x_n0		; No so return Error Code 
	mov	ax,0			; Return Zero on sucess 
ms_x_n0: 
	pop	bp 
	CRET	2 
 
 
	Public _ms_x_rename 
;----------- 
_ms_x_rename: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF		; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_RENAME 
	mov	ax,04[bp]		; Get the Old Name 
	mov	FD_ONAMEOFF,ax 
	mov	FD_ONAMESEG,ds 
	mov	ax,06[bp]		; Get the New Name 
	mov	FD_NNAMEOFF,ax 
	mov	FD_NNAMESEG,ds 
	call	fdos_entry 
	pop	bp 
	CRET	4 
 
	Public	_ms_x_datetime 
;------------- 
_ms_x_datetime:			; ms_x_datetime (gsflag, h, &time, &date); 
;------------- 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RIF		; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	FD_FUNC,FD_DATETIME	; set/get Time Stamp 
	mov	ax,4[bp]		; get/set subfunction (0/1) 
	mov	FD_SFLAG,ax 
	mov	ax,6[bp]		; get handle 
	mov	FD_HANDLE,ax 
	mov	bx,8[bp]		; get address of time 
	mov	ax,[bx]			; get time 
	mov	FD_TIME,ax		; and Save 
	mov	bx,10[bp]		; get address of date 
	mov	ax,[bx]			; get date 
	mov	FD_DATE,ax 
	call	fdos_entry 
	or	ax,ax			; Skip if Failed 
	jnz	ms_dt_ret 
	mov	ax,FD_TIME 
	mov	bx,8[bp]		; get time address 
	mov	[bx],ax			; update time 
	mov	ax,FD_DATE 
	mov	bx,10[bp]		; get date address 
	mov	[bx],ax			; update date 
	xor	ax,ax 
ms_dt_ret: 
	pop	bp 
	CRET	8 
 
 
; 
;	The following routines allow COMMAND.COM to manipulate 
;	the system time and date. Four functions are provided and 
;	these are GETDATE, SETDATE, GETTIME and SETTIME 
; 
;	Date information is passed and return in a structure which  
;	has the following format. 
; 
;	WORD		Year (1980 - 2099) 
;	BYTE		Month 
;	BYTE		Day 
;	BYTE		Day of the Week (Ignored on SET DATE) 
 
	Public	_ms_getdate 
;----------- 
_ms_getdate: 
;----------- 
	push	bp 
	mov	bp,sp 
	mov	dx,04[bp]		; Get the structure address 
	mov	cl,T_GETDATE		; and call the BDOS 
	call	bdos_entry 
	pop	bp 
	CRET	2 
 
	Public	_ms_setdate 
;---------- 
_ms_setdate: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	dx,4[bp]		; and get the structure address 
	mov	cl,T_SETDATE		; and call the BDOS to do the work 
	call	bdos_entry		; Return 0 Good and FFFF Bad 
	pop	bp 
	CRET	2 
 
 
;	Time information is passed and return in a structure which  
;	has the following format. 
; 
;	BYTE		Hours (0 - 23) 
;	BYTE		Minutes (0 - 59) 
;	BYTE		Seconds (0 - 59) 
;	BYTE		Hundredths of a second (0 - 99) 
 
	Public	_ms_gettime 
;---------- 
_ms_gettime: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	dx,04[bp]		; Get the Time Structure address 
	mov	cl,T_GETTIME		; and call the OS 
	call	bdos_entry 
	pop	bp 
	CRET	2 
 
	Public _ms_settime 
;---------- 
_ms_settime: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	dx,4[bp]		; and get the structure address 
	mov	cl,T_SETTIME		; and call the BDOS SET Time Function 
	call	bdos_entry		; Return 0 Good and FFFF Bad 
	pop	bp 
	CRET	2 
 
	Public	_ms_f_verify 
;----------- 
_ms_f_verify: 
;----------- 
	push	bp 
	mov	bp,sp 
	push	es 
	mov	ax,04[bp]			; Get the required state 
	les	bx,_pd				; Update the Verify flag in 
	and	es:P_SFLAG[bx],not PSF_VERIFY	; current PD 
	or	ax,ax				; Set the Flag 
	jz	ms_fv10				; No 
	or	es:P_SFLAG[bx],PSF_VERIFY	; Flag set in PD 
ms_fv10: 
	pop	es 
	pop	bp 
	ret 
 
	Public	_ms_f_getverify 
;-------------- 
_ms_f_getverify: 
;-------------- 
	push	es 
	xor	ax,ax				; Assume the flag is RESET 
	les	bx,_pd				; now test the state of the 
	test	es:P_SFLAG[bx],PSF_VERIFY	; flag in the current PD 
	jz	ms_fgv10			; Verify = OFF 
	inc	ax				; Verify = ON 
ms_fgv10: 
	pop	es 
	ret 
 
; 
;	mem_alloc(BYTE FAR * NEAR * bufaddr, UWORD * bufsize, UWORD min, UWORD max); 
; 
;	max		10[bp] 
;	min		08[bp] 
;	bufsize		06[bp] 
;	buffadr 	04[bp] 
; 
	Public _mem_alloc 
;--------- 
_mem_alloc: 
;--------- 
	push	bp 
	mov	bp,sp 
 
	mov	mpb_start,0 
	mov	ax,08[bp]		; Get the Minimum and Maximum values 
	mov	mpb_min,ax		; and fill in the parameter block 
	mov	ax,10[bp] 
	mov	mpb_max,ax 
	mov	mpb_pdadr,0 
	mov	mpb_flags,0 
	 
	mov	cx,M_ALLOC		; Call the Concurrent Allocate function 
	mov	dx,dataOFFSET mpb_start	;  
	call	bdos_entry 
	xor	cx,cx			; Assume that the function fails 
	mov	dx,cx			; and zero the start and size fields 
	cmp	ax,0 
	jnz	mem_all10 
	mov	cx,mpb_min		; Get the Allocation Size 
	mov	dx,mpb_start		; and the starting segment 
 
mem_all10: 
	mov	bx,04[bp]		; Update the Buffer Address 
	mov	word ptr 00[bx],0	; Offset 0 
	mov	word ptr 02[bx],dx	; Segment DX 
	mov	bx,06[bp]		; Now Update the Buffer Size 
	mov	word ptr 00[bx],cx	; and return to the caller 
	pop	bp 
	ret 
; 
;	mem_free(BYTE FAR * NEAR * bufaddr); 
; 
;	buffadr 	04[bp] 
; 
	Public _mem_free 
;--------- 
_mem_free: 
;--------- 
	push	bp 
	mov	bp,sp 
	xor	ax,ax 
	mov	bx,04[bp]		; Get the Buffer Pointer address 
	xchg	ax,word ptr 02[bx]	; and from this the segment of the 
	cmp	ax,0			; allocated memory. If the memory 
	jz	mem_free10		; has already been freed the quit 
	mov	mfpb_start,ax		; Otherwise Free the Memory 
	mov	mfpb_res,0 
	mov	cx,M_FREE 
	mov	dx,dataOFFSET mfpb_start 
	call	bdos_entry 
 
mem_free10: 
	pop	bp 
	ret 
; 
;	findfile(BYTE *loadpath, UWORD *loadtype) 
;  
	Public	_findfile 
_findfile: 
	push	bp 
	mov	bp,sp 
	mov	al,OK_RF		; Retry, Ignore or Fail 
	call	fdos_retry 
	mov	ax,word ptr 04[bp] 
	mov	exec_pathoff,ax 
	mov	exec_pathseg,ds 
	mov	cx,P_PATH 
	mov	dx,dataOFFSET exec_block 
	call	ppath_entry 
	or	ax,ax 
	jnz	ff_error 
	mov	al,exec_filetype 
	cbw 
	mov	bx,word ptr 06[bp] 
	mov	word ptr [bx],ax 
	xor	ax,ax 
ff_error:	 
	pop	bp 
	ret 
 
; 
fdos_retry: 
	xor	ah,ah 
	mov	valid,ax		; Save the Valid Error responses 
	pop	retry_ip		; Get the return Address 
	mov	retry_sp,sp		; and Stack Pointer 
	jmp	retry_ip 
; 
;	FDOS_ENTRY is an internal function entry point which makes the 
;	F_DOS function call. As the F_DOS data area used by COMMAND.COM  
;	is always FDOS_DATA. 
; 
;	WORD PASCAL critical_error(error, valid, drive, mode, server); 
; 
;	critical_error will return an WORD response which (R,I,A,F) 
;	after displaying the appropriate error message and get the 
;	correct response from the user. 
; 
fdos_entry: 
	mov	cl,F_DOS 
	mov	dx,dataOFFSET fdos_data 
ppath_entry: 
	call	bdos_entry 
	cmp	ax,ED_LASTERROR		; Did an Error Occur 
	jb	fdos_exit		; No So Exit OK 
	cmp	crit_flg,TRUE		; Already in handler 
	jz	fdos_exit		; Yes Skip Critical Error 
	cmp	ax,ED_PROTECT		; Is this a Physical Error 
	jg	fdos_exit		; if so then simulate a  
	cmp	ax,ED_GENFAIL		; Critical Error by calling 
	jge	fdos_e05		; the COMMAND routine Critical 
					; error 
	cmp	ax,ED_NETPWD		; Now check for DR-NET errors 
	jg	fdos_exit		; if so then simulate a  
	cmp	ax,ED_NETLOG		; Critical Error by calling 
	jge	fdos_e05		; the COMMAND routine Critical 
					; error 
fdos_exit: 
	ret 
 
fdos_e05: 
	push	retry_ip		; Save Retry IP and SP and valid 
	push	retry_sp		; responses 
	push	valid 
	mov	crit_flg,TRUE		; Start Critical Section 
	mov	cx,es			; Save the Segment Regsiter 
 	push	ax 			; Save the Error Code 
	push	valid			; Save the Valid Responses (R,I,F) 
	les	bx,_pd			; Get the PD address 
	mov	es,es:P_UDA[bx]		; and then the UDA address 
	xor	ah,ah			; Zero the top byte of AX and 
	mov	al,es:byte ptr 15h ;;U_ERR_DRV		; Get the Failing Drive 
	push	ax			; Save on the Stack 
	mov	al,es:byte ptr 14h ;;U_ERR_RW		; Get the Error Mode 
	push	ax			; and Save 
 
	mov	ax,00FFH		; Default Server NO is (INVALID) 
	les	bx,_pd			; Get the PD address Again 
	mov	bx,es:P_NDA[bx]		; and then the NDA address 
	cmp	bx,0			; Are we attached to DR-NET 
	jz	fdos_e08		; NO 
	mov	es,bx			; ES -> DR-Net NDA 
	mov	al,es:byte ptr 0Ch 	;; NDA_CXRTN 
 
fdos_e08:	 
	push	ax			; Pass the DR-NET Server No. 
	mov	es,cx			; Restore ES 
	call	CRITICAL_ERR		; Handle the Critical Error. Parameters 
					; are removed by the CALLEE 
	mov	crit_flg,FALSE		; Critical Section complete 
	pop	valid			; Restore our original RETRY IP 
	pop	retry_sp		; and SP values which have been 
	pop	retry_ip		; corrupted by the "CRITICAL_ERROR" 
					; routine during message printing 
	cmp	ax,0			; Ignore the Error 
	jz	fdos_exit		; Then Exit with no Error 
 
	cmp	ax,1			; Retry the Operation 
	jnz	fdos_e10		; using information saved by FDOS_RETRY 
	mov	sp,retry_sp		; Reset the Stack Pointer 
	jmp	retry_ip		; and retry the Operation 
 
fdos_e10: 
	cmp	ax,3			; FAIL this function 
	mov	ax,ED_FAIL		; Fail the function 
	jz	fdos_exit		; Yes otherwise ABORT 
 
	call	_int_break		; Simulate a Control C to terminate 
					; We are never coming back 
; 
;	BDOS_ENTRY is the usual method of calling the Operating System. 
;	In order to provide a DOS compatible environment this function. 
;	does Control-C checking on function exit. 
;  
bdos_entry: 
	int	BDOS_INT 
	push	es 
	les	bx,_pd			; Get our process descriptor address 
	test	es:P_SFLAG[bx],PSF_CTLC	; Check if a Control-C has been typed 
	jnz	bdos_e10		; Jump to Abort Handler 
	mov	bx,ax			; Restore BX and Return. 
	pop	es 
	ret 
 
bdos_e10: 
	and	es:P_SFLAG[bx],not PSF_CTLC 
	pop	es 
bdos_e20: 
	mov	cl,C_RAWIO		; Flush the Character buffer until 
	mov	dl,0FFh			; Return the character or 00 if queue 
	int	BDOS_INT		; is empty. Repeat till the Keyboard 
	or 	al,al			; Buffer has been flushed 
	jnz	bdos_e20 
	push	ds 
	push	cs 
	pop	ds 
	mov	cl,C_WRITESTR 
	mov	dx,offset break_str	; echo ^C to screen 
	int	BDOS_INT 
	pop	ds 
	call	_int_break		; Place the Control Break Code on the  
					; Stack and the call the error handler 
					; ** We will never return ** 
 
break_str	db	'^C$' 
 
endif 
 
ifndef DOSPLUS 
	Public	__BDOS 
;------- 
__BDOS: 
;------- 
	push	bp 
	mov	bp,sp 
	push	si 
	push	di 
	mov	cl,4[bp] 
	mov	dx,6[bp] 
	int	BDOS_INT 
	pop	di 
	pop	si 
	pop	bp 
	ret 
; 
endif 
 
ifdef DOSPLUS 
ifndef EXT_SUBST 
	Public	_physical_drvs		; Physical Drives returns a LONG 
_physical_drvs:				; Vector with bits set for every drive 
	mov	ax,0			; start with drive A: 
	mov	cx,16			; check the first 16 drives 
	mov	bx,0 
p_d10: 
	push	ax			; pass drive no. to _physical_drive 
	call	_physical_drive		; call it 
	cmp	ax,0			; check return value  
	pop	ax			; restore ax 
	jz	p_d20			; if zero skip setting the bit in  
	or	bx,1			; the bitmap 
p_d20: 
	ror	bx,1			; shift bitmap right 
	inc	ax			; next drive 
	loop	p_d10			; Loop 16 Times 
	mov	cx,10			; Finally check the last 10 drives 
	mov	dx,0 
p_d30: 
	push	ax			; pass drive no. to _physical_drive 
	call	_physical_drive		; call it 
	cmp	ax,0			; check return val 
	pop	ax			; restore ax 
	jz	p_d40			; id zero skip setting the bit in  
	or	dx,1			; the bitmap 
p_d40: 
	ror	dx,1			; shift bitmap right 
	inc	ax			; next drive 
	loop	p_d30			; Loop 10 Times 
	 
	mov	cl,6			; Now rotate the contents of  
	ror	dx,cl			; DX 6 more times for correct 
					; alignment of the Physical Drive Vector 
	mov	ax,bx 
	mov	bx,dx			; Return the long value in both 
					; AX:BX and AX:DX 
	ret 
 
	Public	_logical_drvs		; Logical Drives returns a LONG 
_logical_drvs:				; vector with bits set for every 
 
	mov	cx,16			; check the first 16 drives 
	mov	ax,0			; start with drive A: 
	mov	bx,ax	 
 
l_d10: 
	push	ax			; pass the drive to _logical_drive 
	call	_logical_drive		; call it 
	cmp	ax,0			; check return value 
	pop	ax			; restore ax 
	jz	l_d20			; skip if zero return 
	or	bx,1			; set bit in bitmap 
l_d20: 
	ror	bx,1			; shift bitmap right 
	inc	ax			; next drive 
	loop	l_d10			; Loop 16 Times 
 
	mov	cx,10			; Finally check the last 10 drives 
	mov	dx,0 
l_d30: 
	push	ax			; pass the drive to _logical_drive 
	call	_logical_drive		; call it 
	cmp	ax,0			; check return value 
	pop	ax			; restore ax 
	jz	l_d40			; skip if zero return 
	or	dx,1			; set bit in bitmap 
l_d40: 
	ror	dx,1			; shift bitmap right 
	inc	ax			; next drive 
	loop	l_d30			; Loop 10 Times 
 
	mov	cl,6			; Now rotate the contents of  
	ror	dx,cl			; DX 6 more times for correct 
					; alignment of bits 
	mov	ax,bx 
	mov	bx,dx			; Return the long value in both 
	ret				; AX:BX and AX:DX 
 
	Public	_network_drvs		; Network Drives returns a LONG 
_network_drvs:				; vector with bits set for every drive 
	xor	ax,ax			; Start with BX:AX as 
	mov	bx,ax			; zeros. 
	mov	cx,'Z'-'A'		; We look at drives A-Z 
n_d10: 
	add	ax,ax			; we move the dword vector 
	adc	bx,bx			;  one place left 
	push	ax 
	push	bx			; save the vector 
	mov	ah,MS_X_IOCTL 
	mov	al,9			; is device local ? 
	mov	bl,cl			; drive number in BL 
	int	DOS_INT 
	pop	bx 
	pop	ax			; recover the vector 
	 jc	n_d20			; if an error skip network bit 
	test	dx,1000h		; is device local ? 
	 jz	n_d20			; if not then 
	or	ax,1			;  set bit for this drive 
n_d20: 
	loop	n_d10 
	mov	dx,bx			; long value in both AX:BX and AX:DX 
	ret 
	public	_physical_drive 
_physical_drive	PROC NEAR 
 
;	BOOLEAN	physical_drive(WORD); 
;	returns true if given drive (0-25) is physical. 
; 
	push	bp 
	mov	bp,sp 
	push	ds 
	push	es 
	push	si 
	push	di 
	push	dx 
	push	cx 
	push	bx 
	 
	mov	bx,4[bp]	; get the drive number 
	inc	bx		; A=1, B=2, etc 
	mov	ax,4409h	; IOCTL Network/Local 
	int	21h		; do it 
	jc	not_phys	; carry means invalid drive 
	and	dx,1000h	; 
	cmp	dx,0 
	jne	not_phys	; its a network drive 
 
	mov	ax,cs 
	mov	ds,ax 
	mov	es,ax 
	mov	si,offset func60_in 
	mov	di,offset func60_out 
	mov	ax,4[bp]	; insert drive letter in input string 
	add	al,'A' 
	mov	[si],al		; 
	mov	ah,60h		; Expand Path string 
	int	21h		; do it 
	jc	not_phys	; carry set means invalid drive 
	 
	mov	ax,4[bp]	; if drive letter changes then drive is 
	add	al,'A'		; substed 
	cmp	al,cs:[func60_out] 
	jne	not_phys	 
 
	mov	ax,-1 
	jmp 	phys_exit 
not_phys: 
	mov	ax,0 
phys_exit: 
	pop	bx 
	pop	cx 
	pop	dx 
	pop	di 
	pop	si 
	pop	es 
	pop	ds 
	pop	bp 
	ret  
 
func60_in	db "d:con",0 
func60_out	db 0,0,0,0,0,0,0,0,0,0 
 
_physical_drive	ENDP 
 
; 
;	This function translates a logical to physical drive. 
; 
	Public	_pdrive 
;------ 
_pdrive: 
;------ 
	push	bp 
	mov	bp,sp 
	push	ds 
	push	es 
	push	si 
	push	di 
 
	mov	ax,cs 
	mov	ds,ax 
	mov	es,ax 
	mov	si,offset func60_in 
	mov	di,offset func60_out 
	mov	ax,4[bp]		; insert drive letter in input string 
	add	al,'A' 
	mov	[si],al 
	mov	ah,60h			; Expand Path string 
	int	21h			; do it 
	mov	ax,4[bp]		; assume invalid, hence no change 
	 jc	pdrive_exit		; carry set means invalid drive 
	mov	al,cs:[func60_out] 
	sub	al,'A' 
pdrive_exit: 
	pop	di 
	pop	si 
	pop	es 
	pop	ds 
	pop	bp 
	CRET	2 
 
	public	_logical_drive 
_logical_drive	PROC NEAR 
 
;	BOOLEAN	logical_drive(WORD); 
;	returns TRUE if given drive (0-25) is logical 
; 
	push	bp 
	mov	bp,sp 
	push	ds 
	push	es 
	push	si 
	push	di 
	push	dx 
	push	cx 
	push	bx 
	 
	mov	bx,4[bp]	; get the drive number 
	inc	bx		; A=1, B=2, etc 
	mov	ax,4409h	; IOCTL Network/Local 
	int	21h		; do it 
	jc	not_logical	; carry means invalid drive 
	and	dx,1000h	; 
	cmp	dx,0 
	jne	not_logical	; its a network drive 
 
	mov	ax,cs 
	mov	ds,ax 
	mov	es,ax 
	mov	si,offset func60_in 
	mov	di,offset func60_out 
	mov	ax,4[bp]	; insert drive letter in input string 
	add	al,'A' 
	mov	[si],al		; 
	mov	ah,60h		; Expand Path string 
	int	21h		; do it 
	jc	not_logical	; carry set means invalid drive 
	 
	mov	ax,4[bp]	; if drive letter changes then drive is 
	add	al,'A'		; substed 
	cmp	al,cs:[func60_out] 
	je	not_logical 
 
	mov	ax,-1 
	jmp 	logical_exit 
not_logical: 
	mov	ax,0 
logical_exit: 
	pop	bx 
	pop	cx 
	pop	dx 
	pop	di 
	pop	si 
	pop	es 
	pop	ds 
	pop	bp 
	ret  
 
_logical_drive	ENDP 
 
	public	_network_drive 
_network_drive	PROC NEAR 
 
;	BOOLEAN	network_drive(WORD); 
;	returns TRUE if given drive (0-25) is networked 
; 
	push	bp 
	mov	bp,sp 
	push	dx 
	push	cx 
	push	bx 
	 
	mov	bx,4[bp]	; get the drive number 
	inc	bx		; A=1, B=2, etc 
	mov	ax,4409h	; IOCTL Network/Local 
	int	21h		; do it 
	jc	not_networked	; carry means invalid drive 
	and	dx,1000h	; 
	cmp	dx,0 
	jne	not_networked	; its a network drive 
 
	mov	ax,-1 
	jmp 	network_exit 
not_networked: 
	mov	ax,0 
network_exit: 
	pop	bx 
	pop	cx 
	pop	dx 
	pop	bp 
	ret  
 
_network_drive	ENDP 
 
endif	;EXT_SUBST 
 
else	;!DOSPLUS 
	 
	Public	_physical_drvs		; Physical Drives returns a LONG 
_physical_drvs:				; Vector with bits set for every 
	mov	cx,DRV_LOGINVEC		; Physical or Networked Drive  
	int	BDOS_INT		; attached to the system 
	xor	dx,dx			; Return the LONG value in both 
	mov	bx,dx			; AX:DX and AX:BX for maximum 
	ret				; compatibility 
 
	Public	_network_drvs		; Network Drives returns a LONG 
_network_drvs:				; vector with bits set for every 
	push	es			; physical drive which has been 
	mov	ax,0			; mapped to a remote DRNET server 
	mov	dx,ax 
	les	bx,_pd			; Get Our Process Descriptor 
	mov	cx,es:P_NDA[bx]		; and then the NDA Segment 
	 jcxz	n_d20			; Skip Drive Test if no NDA 
	mov	es,cx			; Get the RCT Address 
	les	bx,es:dword ptr 04h	;; NDA_RCT ; From the NDA 
	mov	cx,16 
n_d10: 
	test	es:RCT_DSK[bx],080h	; Is this a Remote drive 
	 jz	n_d15			; No 
	or	ax,1			; Set Drive Bit 
n_d15: 
	ror	ax,1			; Rotate Drive Bit Vector 
	add	bx,2			; Update the Drive Pointer 
	loop	n_d10			; Loop Till Done 
n_d20: 
	mov	bx,dx			; Return the long value in both 
	pop	es			; AX:BX and AX:DX 
	ret 
 
ifndef	EXT_SUBST 
 
	Public	_logical_drvs		; Logical Drives returns a LONG 
_logical_drvs:				; vector with bits set for every 
	push	es 
	push	si 
	push	di 
 
	les	si,_pd			; Get Our Process Descriptor 
	mov	si,es:P_CAT[si]		; and then the address of the  
	mov	cx,16			; first HDS and check the first 
	mov	ax,0			; 16 Drives 
	mov	bx,ax	 
 
l_d10: 
	mov	di,es:word ptr [si]	; Has this drive got an HDS 
	test	di,di			; then skip setting the bit in  
	 jz	l_d20			; the vector register 
	cmp	es:byte ptr [di],bl	; Is the HDS pointing to same drive 
	 jz	l_d20			; Yes then this is a Physical Drive 
	or	ax,1 
l_d20: 
	ror	ax,1 
	add	si,2 
	inc	bx 
	loop	l_d10			; Loop 16 Tines 
 
	mov	cx,10			; Finally check the last 10 HDS 
	mov	dx,0 
l_d30: 
	mov	di,es:word ptr [si]	; Has this drive got an HDS 
	test	di,di			; then skip setting the bit in  
	 jz	l_d40			; the vector register 
	cmp	es:byte ptr [di],bl	; Is the HDS pointing to same drive 
	 jz	l_d40			; Yes then this is a Physical Drive 
	or	dx,1 
l_d40: 
	ror	dx,1 
	add	si,2 
	inc	bx 
	loop	l_d30			; Loop 10 Tines 
 
	mov	cl,6			; Now rotate the contents of  
	ror	dx,cl			; DX 6 more times for correct 
					; alignment of bits 
	pop	di 
	pop	si 
	pop	es 
	mov	bx,dx			; Return the long value in both 
	ret				; AX:BX and AX:DX 
 
 
; 
;	This function use the CONCURRENT 5.xx CP/M function to translate 
;	a logical to physical drive. 
; 
	Public	_pdrive 
;------ 
_pdrive: 
;------ 
	push	bp 
	mov	bp,sp 
	mov	dl,4[bp]		; get the logical drive 
	mov	cl,175			; Logical to Physical Xlat 
	int	BDOS_INT 
	cbw 
	pop	bp 
	CRET	2 
 
endif	;!EXT_SUBST 
endif	;!DOSPLUS 
 
 
	Public	_toupper 
 
UCASE	equ	18			; offset of dword ptr to uppercase func 
 
;------- 
_toupper	proc	near 
;------- 
; Return the uppercase equivilant of the given character. 
; The uppercase function defined in the international info block is  
; called for characters above 80h. 
; 
; char	ch;				char to be converted 
; char	result;				uppercase equivilant of ch 
; 
; result = toupper(ch); 
 
	push	bp 
	mov	bp, sp 
 
	mov	ax, 4[bp] 
	mov	ah, 0			; al = character to be converted 
	cmp	al, 'a'			; al < 'a'? 
	jb	exit_toupper		;  yes - done (char unchanged) 
	cmp	al, 'z'			; al <= 'z'? 
	jbe	a_z			;  yes - do ASCII conversion 
	cmp	al, 80h			; international char? 
	jb	exit_toupper		;  no - done (char unchanged) 
 
; ch >= 80h  -- call international routine 
	call	dword ptr [_country+UCASE] 
	jmp	exit_toupper 
 
a_z: 
; 'a' <= ch <= 'z'  -- convert to uppercase ASCII equivilant 
	and	al, 0DFh 
 
exit_toupper: 
	pop	bp 
	ret 
 
_toupper	endp 
 
 
 
ifdef DOSPLUS 
if 0 
	Public	_hiload_status 
;---------- 
_hiload_status: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	dx,100h			; get hiload state 
	mov	ax,(MS_X_IOCTL*256)+57h	; IO Control function 
	int	DOS_INT			; do INT 21h 
	pop	bp			;  
	ret 
 
	Public	_hiload_set 
;---------- 
_hiload_set: 
;---------- 
	push	bp 
	mov	bp,sp 
	mov	dx,4[bp]		; get state 
	mov	dh,2			; set hiload state 
	mov	ax,(MS_X_IOCTL*256)+57h	; IO Control function 
	int	DOS_INT			; do INT 21h 
	pop	bp			;  
	ret 
endif 
 
	Public	_get_upper_memory_link 
_get_upper_memory_link: 
 
	mov	ax,5802h 
	int	21h 
	cbw 
	ret 
 
	Public	_set_upper_memory_link 
_set_upper_memory_link: 
 
	push	bp 
	mov	bp,sp 
	mov	bx,4[bp] 
	mov	ax,5803h 
	int	21h 
	pop	bp 
	ret 
 
	Public	_get_alloc_strategy 
_get_alloc_strategy: 
	 
	mov	ax,5800h 
	int	21h 
	ret 
 
	Public	_set_alloc_strategy 
_set_alloc_strategy: 
 
	push	bp 
	mov	bp,sp 
	mov	bx,4[bp] 
	mov	ax,5801h 
	int	21h 
	pop	bp 
	ret 
 
	Public	_alloc_region 
 
_alloc_region: 
	push	es 
	xor	ax,ax 
	mov	es,ax			; assume no block allocated 
	mov	ah,MS_M_ALLOC 
	mov	bx,1 
	int	21h			; allocate a small block 
	 jc	_alloc_region10 
	mov	es,ax 
	mov	ah,MS_M_SETBLOCK 
	mov	bx,0FFFFh 
	int	21h			; find out how big the block is 
	mov	ah,MS_M_SETBLOCK 
	int	21h			; now grow to take up the block 
_alloc_region10: 
	mov	ax,es			; return address of block 
	pop	es 
	ret 
 
	Public	_free_region 
 
_free_region: 
	push	bp 
	mov	bp,sp 
	push	es 
	mov	es,4[bp] 
	mov	ah,MS_M_FREE 
	int	21h			; free the block 
	pop	es 
	pop	bp 
	ret 
 
endif 
 
; The Double Byte Character Set lead byte table. 
; Each entry in the table except the last specifies a valid lead byte range. 
; 
;   0	+---------------+---------------+ 
;   	|    start of	|    end of 	|	DBCS table entry 0 
;	|    range 0	|    range 0	| 
;   2	+---------------+---------------+ 
;    	|    start of	|    end of 	|	DBCS table entry 1 
;	|    range 1	|    range 1	| 
;	+---------------+---------------+ 
;			: 
;   n	+---------------+---------------+ 
;	|       0	|       0 	|	end of DBCS table 
;	|    		|    		| 
;	+---------------+---------------+ 
 
 
	Public	_dbcs_expected 
 
_dbcs_expected	proc	near 
;------------- 
; Returns true if double byte characters are to be expected. 
; A call to dbcs_init() MUST have been made. 
; Entry 
;	none 
; Exit 
;	ax	= 1 - double byte characters are currently possible 
;		  0 - double byte characters are not currently possible 
 
ifdef DOSPLUS 
	push	ds 
	push	si 
	lds	si, dbcs_table_ptr	; DS:SI -> system DBCS table 
	lodsw				; ax = first entry in DBCS table 
	test 	ax, ax			; empty table? 
	 jz	de_exit			;  yes - return 0 (not expected) 
	mov	ax, 1			; return 1 (yes you can expect DBCS) 
de_exit: 
	pop	si 
	pop	ds 
else 
	xor	ax,ax			; CDOS doesn't support them 
endif 
	ret 
_dbcs_expected	endp 
 
 
	Public	_dbcs_lead 
 
_dbcs_lead	proc	near 
;--------- 
; Returns true if given byte is a valid lead byte of a 16 bit character. 
; A call to init_dbcs() MUST have been made. 
; Entry 
;	2[bp]	= possible lead byte 
; Exit 
;	ax	= 1 - is a valid lead byte 
;		  0 - is not a valid lead byte 
 
ifdef DOSPLUS 
	push	bp 
	mov	bp, sp 
	push	ds 
	push	si 
 
	mov	bx, 4[bp]		; bl = byte to be tested 
	lds	si,dbcs_table_ptr	; ds:si -> system DBCS table 
	lodsw				; any entries ? 
	test	ax,ax 
	 jz	dl_not_valid		; no DBC entries 
 
dl_loop: 
	lodsw				; al/ah = start/end of range 
	test 	ax, ax			; end of table? 
	 jz	dl_not_valid		;  yes - exit (not in table) 
	cmp	al, bl			; start <= bl? 
	 ja	dl_loop			;  no - try next range 
	cmp	ah, bl			; bl <= end? 
	 jb	dl_loop			;  no - try next range 
 
	mov	ax, 1			; return 1 - valid lead byte 
 
dl_not_valid: 
	pop	si 
	pop	ds 
	pop	bp 
else 
	xor	ax,ax			; CDOS doesn't support them 
endif 
	ret 
_dbcs_lead	endp 
 
 
	PUBLIC	_extended_error 
_extended_error PROC NEAR 
 
	mov	ah,59h 
	mov	bx,0 
	int	21h 
	neg	ax 
	ret 
 
_extended_error	ENDP 
 
	PUBLIC	_get_lines_page 
_get_lines_page PROC NEAR 
 
	push	bp 
	push	es 
 
	mov	ax,1130h 
	mov	bx,0 
	mov	dx,24	; preset dx to 24 in case function not supported  
	int	10h	  
	 
	mov	ax,dx	; returns (no. rows)-1 in dx 
	inc	ax 
 
	pop	es 
	pop	bp 
	ret 
	 
_get_lines_page ENDP 
 
	PUBLIC	_get_scr_width 
_get_scr_width PROC NEAR 
 
	push	bp 
	mov	ah,0fh 
	int	10h 
	xor	al,al 
	xchg	ah,al 
	pop	bp 
	ret 
	 
_get_scr_width ENDP 
 
	PUBLIC	_novell_copy 
_novell_copy PROC NEAR 
 
	push	bp 
	mov	bp,sp 
	push	si 
	push	di 
	 
	mov	ax,11f0h 
	mov	si,4[bp]	; si = source handle 
	mov	di,6[bp]	; di = destination handle 
	mov	dx,8[bp]	; lo word of source length 
	mov	cx,10[bp]	; hi word of source length 
	clc			; start with carry cleared 
	 
	int	2fh		; do it 
 
	jc	novcop_failure	; carry set means novell couldn't handle it 
 
	cmp	ax,11f0h	 
	je	novcop_failure  ; ax hasn't changed, so novell isn't there 
	 
	mov	ax,1		; success ! 
	jmp	novcop_exit 
	 
novcop_failure: 
	mov	ax,0 
novcop_exit:	 
	pop	di 
	pop	si 
	pop	bp 
	ret 
	 
_novell_copy ENDP 
 
	PUBLIC	_call_novell 
_call_novell	PROC NEAR 
	 
	push	bp 
	mov	bp,sp 
	push	es	 
	push	si 
	push	di 
 
	mov	ah,8[bp] 
	mov	al,0ffh 
	push	ds 
	pop	es 
	mov	si,4[bp] 
	mov	di,6[bp] 
	int	21h 
 
	cmp	al,0 
	jne	call_nov_err 
	jc	call_nov_err 
 
	mov	ax,0	 
	jmp	call_nov_exit 
 
call_nov_err: 
	mov	ah,0 ;; clear ah, BUT allow all ret' values in al  
call_nov_exit:	 
	pop	di 
	pop	si 
	pop	es 
	pop	bp 
	ret 
 
_call_novell	ENDP 
 
	PUBLIC	_nov_station 
_nov_station	PROC	NEAR 
 
	push	bp 
	mov	bp,sp 
	push	si 
	 
	mov	ax,0eeffh 
	int	21h	 
	cmp	ax,0ee00h 
	je	ns_err 
	 
	mov	si,4[bp] 
	mov	[si],cx 
	mov	2[si],bx 
	mov	4[si],ax 
	mov	ax,0 
	jmp	ns_exit 
 
ns_err: 
	mov	ax,-1 
 
ns_exit: 
	pop	si 
	pop	bp 
	ret	 
 
_nov_station	ENDP 
 
	public	_nov_connection 
_nov_connection	PROC NEAR 
 
	push	es 
	push	si 
 
if 0 
	mov	ax,0 
	mov	es,ax 
	mov	si,0 
	mov	ax,0ef03h 
	int	21h 
	 
	mov	ax,es 
	cmp	ax,0 
	jne	nc_ok 
	cmp	si,0 
	jne	nc_ok 
	mov	ax,-1 
	jmp	nc_exit	 
 
nc_ok: 
	mov	al,es:23[si] 
	mov	ah,0 
endif 
 
	mov	ax,0dc00h 
	int	21h 
	jc	nc_err 
	sub	ah,ah 
	jmp	nc_exit 
 
nc_err: 
	mov	al,-1;	 
 
nc_exit: 
	pop	si 
	pop	es 
	ret 
 
_nov_connection	ENDP 
 
_TEXT	ENDS 
	END