$NOLIST

	NAME	UtilsR


PUBLIC WeNeedEmulator
PUBLIC DriveIsValid
PUBLIC GetNumMsDosDevices
PUBLIC GetNumCharacterDevs
PUBLIC GetNumHardDisks
PUBLIC GetNumFloppies
PUBLIC ReadPageZero
PUBLIC GetMsDosProgramSegment
PUBLIC GetTime
PUBLIC SetTime
PUBLIC ClearScreenAlpha
PUBLIC ClearMessage
PUBLIC DisplayMessage
PUBLIC AppendCurDir


OS_CGROUP GROUP OS_CODE
OS_DGROUP GROUP OS_DATA


FcbType STRUC
  fDrive    DB  1 DUP (?)
  fName     DB  8 DUP (?)
  fExt      DB  3 DUP (?)
  fWhocares DB 13 DUP (?)
  pDevEntry DD  ?
  fExtra    DB  3 DUP (?)
FcbType ENDS


Os_DATA SEGMENT PUBLIC 'DATA'

EXTRN fcb: FcbType

Os_DATA ENDS


Os_CODE	SEGMENT	BYTE PUBLIC 'CODE'
 	ASSUME	CS:OS_CGROUP

; general

FALSE             EQU 0
TRUE              EQU 1
$EJECT
;
; WeNeedEmulator: PROCEDURE BOOLEAN CLEAN;
;  END;
;
; This routine will determine if the 8087 is present.
; It does this by performing an FNINIT and determining if
; the control word is set correctly by doing this.
; Note the DB's in the code... This is done this way so that
; we will go straight to the chip w/o an FWAIT (which would 
; occur if FNINIT was placed in there (Fake8087 does an FWAIT).

control EQU [BP-2]

WeNeedEmulator PROC NEAR
  PUSH BP
  MOV  BP, SP

  XOR  AX, AX
  PUSH AX
  DB 90h, 0DBh, 0E3h         ; FNINIT
  DB 90h, 0D9h,  7Eh, 0FEh   ; FNSTCW control
  MOV  AX, control           ; IF control <> 3FFh THEN
  CMP  AH, 3                 
  JE   WeHaveChip
  MOV  AX, TRUE              ;   RETURN (TRUE);
  JMP  SHORT WeHaveExit
WeHaveChip:                  ; ELSE
  MOV  AX, FALSE             ;   RETURN (FALSE);

WeHaveExit:
  MOV  SP, BP
  POP  BP
  RET
WeNeedEmulator ENDP
PURGE control
$EJECT

; 
; DriveIsValid: PROCEDURE (drive) BOOLEAN CLEAN;
;   DCL drive     BYTE;
;
; This routine will return if the passed drive is a valid 
; MS-DOS drive.
;

stak STRUC
  oldBP      DW ?
  oldIP      DW ?
  drive      DB ?
stak ENDS

params EQU [BP]

DriveIsValid PROC NEAR
  PUSH BP
  MOV  BP, SP

  XOR  CL, CL                 ; drive is assumed invalid
  MOV  AH, 19H
  INT  21H
  MOV  BL, AL                 ; store boot device in BL

  MOV  AH, 0Eh                ; Select disk function
  MOV  DL, params.drive       ; get the passed drive
  INT  21h

  MOV  AH, 19H
  INT  21H
  CMP  DL, AL                 ; see if the drive is set
  JNE  DriveExit
  MOV  CL, 1                  ; drive is valid

DriveExit:
  MOV  AH, 0Eh
  MOV  DL, BL
  INT  21h                    ; restore the boot device

  MOV  AL, CL                 ; function return in AL
  POP  BP
  RET  2
DriveIsValid ENDP

PURGE stak
PURGE oldBP
PURGE oldIP
PURGE drive
PURGE params
$EJECT

; GetNumMsDosDevices: PROCEDURE WORD CLEAN;
;
; This routine will return the number of MsDos devices.
; 

GetNumMsDosDevices PROC NEAR
  MOV  AH, 19h
  INT  21h         ; get the current drive

  MOV  AH, 0Eh
  MOV  DL, AL
  INT  21h         ; set the current drive
  XOR  AH, AH      ; AX = num drives
  RET
GetNumMsDosDevices ENDP


;      GetNumHardDisks: PROCEDURE WORD CLEAN;
;
;  This routine will return the number of hard disks in the system
;

GetNumHardDisks PROC NEAR
  MOV  AH, 8
  MOV  DL, 80h
  INT  13h          ; call the BIOS
  JNC  NoError
  XOR  DL, DL

NoError:
  XOR  AH, AH
  MOV  AL, DL
  RET
GetNumHardDisks ENDP
$EJECT

;      GetNumFloppies: PROCEDURE WORD CLEAN;
;
;  This routine will return the number of floppies in the system
;

GetNumFloppies PROC NEAR
  INT  11h          ; equipment check
  TEST AX, 1        ; are there any floppies?
  JNZ  GotFloppies
  XOR  AX, AX
  RET

GotFloppies:
  AND  AX, 0C0h
  MOV  CX, 6        ; shift the floppy bits over to LSB
  SHR  AX, CL
  INC  AX           ; make it 1 relative
  RET
GetNumFloppies ENDP


;    ReadPageZero: PROCEDURE (drive, pBuffer) WORD CLEAN;
;      DCL drive    BYTE;
;      DCL pBuffer  PTR;
;
;  This routine will read physical page zero on the drive specified.
;  It will do this via ROM BIOS calls.
;
stak STRUC
  oldBP      DW ?
  oldIP      DW ?
  pBuf       DD ?
  drive      DB ?
stak ENDS

params EQU [BP]

ReadPageZero PROC NEAR
  PUSH BP
  MOV  BP, SP
  LES  BX, params.pBuf
  MOV  AX, 201h
  MOV  CX, 1
  MOV  DH, 0
  MOV  DL, params.drive
  INT  13h
  JC   ErrorRead

  XOR  AX, AX
  JMP  SHORT ReadPageZeroEnd

ErrorRead:
  MOV  AX, 1

ReadPageZeroEnd:
  POP  BP
  RET  6
ReadPageZero ENDP

PURGE stak
PURGE oldBP
PURGE oldIP
PURGE drive
PURGE params
PURGE pBuf
$EJECT

;
;  GetNumCharacterDevs: PROCEDURE (pNumCharDevs, pNumMsDosDevs) PTR CLEAN;
;    DCL pNumCharDevs     PTR;  /* returns # character devices  */
;    DCL pNumMsDosDevs    PTR;  /* returns # MsDos mass devices */
;
; This routine returns a pointer to the device chain */
;
stak STRUC
  oldDS      DW ?
  oldBP      DW ?
  oldIP      DW ?
  pMsDosDevs DD ?
  pCharDevs  DD ?
stak ENDS

DeviceHeaderType STRUC
  pDevice     DD ?
  mode        DW ?
  dummy       DB 4 DUP (?)
  numDevices  DB ?
DeviceHeaderType ENDS

device EQU ES:[BX]
params EQU [BP]

; Entry
;  DS must point to the Data segment for the variable fcb

GetNumCharacterDevs PROC NEAR
  PUSH BP
  PUSH DS
  MOV  BP, SP

  MOV  AH, 30h            ; Get DOS version
  INT  21h
  CMP  AL, 3              ; If earlier than version 3.0
  JB   UseFcbMethod       ; then use the Fcb Open (NUL) method
  CMP  AH, 20             ; If earlier than version 3.2
  JB   UseFcbMethod       ; then use the Fcb Open (NUL) method

  MOV  AH, 52H            ; Look at MsDos internal variables
  INT  21H
  LES  BX, DWORD PTR ES:[BX+22H]
  JMP  SHORT CountCharDevs

UseFcbMethod:
  XCHG BX, AX             ; Save version in BX

  MOV  AH, 0Fh            ; FCB Open call
  MOV  DX, OFFSET OS_DGROUP:fcb
  INT  21h                ; FCB Open to MsDos

  CMP  BL, 3
  JB   OlderThanV30

  LES  BX, DS:fcb.pDevEntry+1
  JMP  SHORT GotPDevEntry

OlderThanV30:
  LES  BX, DS:fcb.pDevEntry  ; pointer to nul device

GotPDevEntry:
  LES  BX, device.pDevice ; pointer to next device
  MOV  AH, 10h
  INT  21h                ; close the file. (Close doesn't change ES:BX)

; count the number of char devices and mass devices

CountCharDevs:
  PUSH ES                 ; save for return
  PUSH BX

  XOR  AX, AX
  MOV  SI, AX

StartCount:
  MOV  DX, ES
  CMP  DX, 0FFFFh         ; at end of chain?
  JZ   EndOfChain
  CMP  BX, 0FFFFh         ; at end of chain?
  JZ   EndOfChain
  MOV  CX, device.mode
  AND  CX, 8000h          ; is this a character dev?
  JNZ  GotCharDevice
  MOV  CL, device.numDevices
  XOR  CH, CH
  ADD  SI, CX
  JMP  SHORT GoToNext

GotCharDevice:
  INC  AX

GoToNext:
  LES  BX, device.pDevice
  JMP  SHORT StartCount
  
EndOfChain:
  LES  BX, params.pMsDosDevs
  MOV  WORD PTR ES:[BX], SI
  LES  BX, params.pCharDevs
  MOV  WORD PTR ES:[BX], AX

; return the start address

  POP  BX
  POP  ES

  POP  DS
  POP  BP
  RET  8
GetNumCharacterDevs ENDP
PURGE params
PURGE oldDS
PURGE oldBP
PURGE oldIP
PURGE pMsDosDevs
PURGE pCharDevs
PURGE DeviceHeaderType
PURGE pDevice
PURGE device
PURGE mode
PURGE dummy
PURGE numDevices
PURGE stak
$EJECT

;   GetMsDosProgramSegment: PROCEDURE SELECTOR;
;
;  This routine will return the selector to the 
;  MsDos program segment.

GetMsDosProgramSegment PROC NEAR
  MOV AH, 51h      ; get PDB
  INT 21h
  MOV AX, BX       ; return parameter
  RET
GetMsDosProgramSegment ENDP
$EJECT

; GetTime: PROCEDURE (pTime) CLEAN;
;   DCL pTime    PTR;
;   DCL time     BASED pTime  TimeType;
;
TimeType STRUC
  year          DW ?
  month         DB ?
  day           DB ?
  hour          DB ?
  minute        DB ?
  second        DB ?
  tenthOfSecond DB ?
  dayOfWeek     DB ?
  dayOfYear     DW ?
TimeType ENDS

stak STRUC
  oldBP DW ?
  oldIP DW ?
  pTime DD ?
stak ENDS

params EQU [BP]
time   EQU ES:[BX]

GetTime PROC NEAR
  PUSH BP
  MOV  BP, SP
  LES  BX, params.pTime

  MOV  AH, 2ah       ; get date function
  INT  21h           ; call MsDos

  MOV  time.year, CX
  MOV  time.month, DH
  MOV  time.day, DL
  MOV  time.dayOfWeek, AL

  MOV  AH, 2Ch       ; get time function
  INT  21h

  MOV  time.hour, CH
  MOV  time.minute, CL
  MOV  time.second, DH
  MOV  AL, DL
  XOR  AH, AH
  MOV  DL, 10
  DIV  DL
  MOV  time.tenthOfSecond, AL

  POP  BP
  RET  4
GetTime ENDP
PURGE TimeType
PURGE year
PURGE month
PURGE day
PURGE hour
PURGE minute
PURGE second
PURGE tenthOfSecond
PURGE dayOfWeek
PURGE dayOfYear
PURGE stak
PURGE oldBP
PURGE oldIP
PURGE pTime
PURGE params
PURGE time
$EJECT

; SetTime: PROCEDURE (pTime) CLEAN;
;   DCL pTime    PTR;
;   DCL time     BASED pTime  TimeType;
;
TimeType STRUC
  year          DW ?
  month         DB ?
  day           DB ?
  hour          DB ?
  minute        DB ?
  second        DB ?
  tenthOfSecond DB ?
  dayOfWeek     DB ?
  dayOfYear     DW ?
TimeType ENDS

stak STRUC
  oldBP DW ?
  oldIP DW ?
  pTime DD ?
stak ENDS

params EQU [BP]
time   EQU ES:[BX]

SetTime PROC NEAR
  PUSH BP
  MOV  BP, SP
  LES  BX, params.pTime

  MOV  CX, time.year
  MOV  DH, time.month
  MOV  DL, time.day

  MOV  AH, 2bh       ; set date function
  INT  21h           ; call MsDos

  MOV  CH, time.hour
  MOV  CL, time.minute
  MOV  DH, time.second
  MOV  AL, time.tenthOfSecond
  MOV  DL, 10
  MUL  DL
  MOV  DL, AL

  MOV  AH, 2Dh       ; set time function
  INT  21h

  POP  BP
  RET  4
SetTime ENDP
PURGE TimeType
PURGE year
PURGE month
PURGE day
PURGE hour
PURGE minute
PURGE second
PURGE tenthOfSecond
PURGE dayOfWeek
PURGE dayOfYear
PURGE stak
PURGE oldBP
PURGE oldIP
PURGE pTime
PURGE params
PURGE time
$EJECT

; Clear Screen Alpha
;  This routine will ensure that the display is in alpha mode and that
;  the display is cleared

ClearScreenAlpha PROC NEAR
    PUSH BP                          ; Save BP (Changed by Int 10H)
    MOV  AH, 15                      ; get current video state
    INT  10H
    PUSH CX                          ; save cursor mode
    CMP  AL, 2                       ; if already in 80x25 mode
    JE   AlreadyInAlphaMode          ; then clear the display

    MOV  AX, 2                       ; set display to alpha mode
    INT  10H                         ; which also clears the display
    JMP  SHORT ClearScreenRet

AlreadyInAlphaMode:
    MOV  AX, 600H                    ; scroll page up (AL=0 means clear scr)
    XOR  CX, CX                      ; upper left is 0,0
    MOV  DX, 184FH                   ; lower right is 24,79
    MOV  BH, 0
    INT  10H

ClearScreenRet:
    POP  CX                          ; Old cursor mode
    OR   CH, 20H                     ; Turn off the cursor
    MOV  AH, 1                       ; Set the cursor mode
    INT  10H
    POP  BP                          ; Restore BP (Changed by Int 10H)
    RET
ClearScreenAlpha ENDP



; Clear Message
;  This routine blanks out the message (bottom) line

ClearMessage PROC NEAR
    XOR  BX, BX

; Clear Message Subroutine
;  This routine will clear the last line of the screen using the attribute
;  passed in register BL (And assumes that BH is 0)

;  Regs AX,CX,DX changed
 
ClearMsgSub PROC NEAR
    PUSH BP                          ; Save BP (Changed by Int 10H)

    MOV  AH, 2                       ; prepare to set cursor position
    MOV  DX, 1800H                   ; set for last line
    INT  10H                         ; set the cursor position

    MOV  AL, 20H                     ; set to output blanks
    MOV  CX, 80                      ; 80 chars to be blanked out
    MOV  AH, 9                       ; set to output characters
    INT  10H                         ; blank the line

    POP  BP                          ; Restore BP (Changed by Int 10H)
    RET
ClearMsgSub ENDP
ClearMessage ENDP
$EJECT

; Display "Reading volume name on remote drive X" message
;  This routine will display a message centered on the bottom of the screen
;  It assumes that the display is in 80x25 BW alpha mode (mode 2)
;  The message format is a length byte followed by length characters
;  NOTE: The msg passed must not be in the InteGRiD ROM for this to work

pMsg EQU DWORD PTR [BP+4]

DisplayMessage PROC NEAR
    PUSH BP
    MOV  BP, SP

    MOV  BX, 70H                     ; white attribute for alpha mode
    CALL ClearMsgSub                 ; current display page is 0 (in BH)

    LES  DI, pMsg                    ; get pointer to the message
    MOV  AL, ES:[DI]                 ; get length of the message
    CBW                              ; make a word wide value
    MOV  CX, AX                      ; save original length in CX
    JCXZ DisplayMsgRet               ; done for zero length messages

    SHR  AL, 1                       ; divide by two and subtract from
    MOV  DL, 40                      ; subtract from center of screen
    SUB  DL, AL                      ; so the message will be centered
    MOV  DH, 18H                     ; set for last line
    INC  DI                          ; point to next char in the message

OutputMsg:
    MOV  AH, 2                       ; prepare to set cursor position
    INT  10H                         ; set the cursor position

    PUSH CX                          ; save characters remaining
    MOV  AL, ES:[DI]                 ; get the character
    MOV  CX, 1                       ; set to output 1 character
    MOV  AH, 9                       ; set to output character
    INT  10H                         ; output the character
    POP  CX                          ; restore characters remaining

    INC  DI                          ; point to next character
    INC  DX                          ; point to next char position
    LOOP OutputMsg                   ; output the next character

DisplayMsgRet:
    POP  BP                          ; Int 10 may destroy BP !!!
    RET  4
DisplayMessage   ENDP

PURGE pMsg
$EJECT

; This will append the current directory from device to the shortstring path
; All \s will be converted to `s and path.length will be adjusted.

device EQU  BYTE PTR [BP+10]
pPath  EQU DWORD PTR [BP+6]

AppendCurDir PROC NEAR
    PUSH DS
    PUSH BP
    MOV  BP, SP
    CLD                              ; Increment SI
    LDS  SI, pPath                   ; Get pointer to path
    MOV  DI, SI                      ; Save address to path.len
    LODSB                            ; Get length & increment SI
    XOR  AH, AH
    ADD  SI, AX                      ; @path (path.length)
    XCHG CX, AX                      ; Save length in CL
    MOV  DL, device
    MOV  AH, 47H                     ; Get current dir for device in DL
    INT  21H                         ; Call MsDos
    JC   AppendCurDirRet             ; Return if error

    CLD                              ; Increment SI (Redo CLD after Int 21)

AppendCurDirLoop:
    LODSB                            ; Get next character of current directory
    OR   AL, AL                      ; If zero then
    JZ   AppendCurDirExit            ; done

    CMP  AL, '\'                     ; If not backslash
    JNE  AppendCurDirNext            ; then no change

    MOV  BYTE PTR DS:[SI-1], '`'     ; else change backslash to backquote

AppendCurDirNext:
    INC  CX                          ; increment total character count
    JMP  SHORT AppendCurDirLoop      ; get next character

AppendCurDirExit:
    MOV  DS:[DI], CL                 ; Update length

AppendCurDirRet:
    POP  BP
    POP  DS
    RET  6
AppendCurDir ENDP

PURGE device, pPath


OS_CODE ENDS

	END
