TITLE   'FINDBAD Ver 1.0 - Universal Version for CP/M-86'
;
;                   FINDBAD.A86 Ver. 1.0
;
;    Modified for CP/M-86 from FINDBAD52.ASM of 02/16/82
;
;FINDBAD will find all bad blocks on a disk and build a file
;named [UNUSED].BAD to allocate them, thus "locking out" the
;bad blocks so CP/M-86 will not use them.
;
;Originally written by Gene Cotton,  published in "Interface
;Age", September 1980 issue, page 80.
;
;               SYSTST and BADUSR options:
;
;  Many double-density disk systems have single-density system
;tracks.  If this is true with your system, you can change the
;program to skip the system tracks,  without re-assembling it.
;To do this, set the byte at 103H to a 0 if you don't want the
;system  tracks tested,  otherwise leave it 0.   This is  also
;necessary if you have a "blocked" disk system;  that is, when
;the same physical disk is seperated into logical disks by use
;of the SYSTRK word in the disk parameter block.
;
;   If  you  are a CP/M-86 1.x user,  you may assign the  user
;number  where  [UNUSED.BAD] will be created by  changing  the
;byte  at 104H  to  the  desired user number.   If you want it
;in  the default user,  then leave it 0FFH.
;
;
;-------------------------------------------------------------
;NOTE: If you want to  update this program, make sure you have
;the latest version first.   After adding your changes, please
;modem  a  copy of the new file to CP/M-NET  in  Simi  Valley,
;California - phone (805) 527-9321 (50,  110,  300, 450 or 600
;baud). Use the filename FNDBDA86.NEW. (K. Smith)
;
;Modifications/updates:
;
;02/03/82  Changed to CP/M-86 compatibility with the help  of
;          DR's XLT86 utility, made changes for goofs made by
;           XLT86,  removed ANYTHING to do with CP/M-80  Ver.
;          1.4. (K. Smith, CP/M-Net 'SYSOP')
;
;         Note: You must assemble with ASM86.CMD as follows:
;
;               ASM86 FINDBAD<cr>
;
;               Then create a '.CMD' as,
;
;               GENCMD FINDBAD 8080<cr>
;
;04/10/81 Changed extent DB from -1 to 0FFH so program can be
;         assembled by ASM.  Added BADUSR info to instructions
;         for altering with DDT.  (KBP)
;
;04/09/81 Changed sign-on message, added control-c abort test,
;         added '*' to console once each track  (RGF)
;
;04/07/81 Re-wrote to add the following features:
;               1) "universal" operation
;               2) DDT-changeable "SYSTRK" boolean (see above)
;               3) Report to console when bad blocks are detected
;               4) Changed the method of printing the number of
;                  bad blocks found (at end of run)...the old
;                  method used too much code, and was too cum-
;                  bersome.
;               5) Made several cosmetic changes
;
;                       Ron Fowler
;                       Westland, Mich
;
;03/23/81 Set equates to standard drive and not double-sided. (KBP)
;
;03/01/81 Corrected error for a Horizon with double sided drive.
;         This uses 32k extents, which code did not take into account.
;         (Bob Clyne)
;
;02/05/81 Merged 2/2/81 and 1/24/81 changes, which were done
;         independently by Clyne and Mack.  (KBP)
;
;02/02/81 Added equates for North Star Horizon - 5.25" drives,
;         double density, single and double sided. (Bob Clyne)
;
;01/24/81 Added equates for Jade DD disk controller
;         (Pete H. Mack)
;
;01/19/81 Added equates for Icom Microfloppy 5.25" drives.
;         (Eddie Currie)
;
;01/05/81 Added equates for Heath H-17 5.25" drives.
;         (Ben Goldfarb)
;
;12/08/80 Added equates for National Multiplex D3S/D4S
;         double-density board in various formats.
;         (David Fiedler)
;
;09/22/80 Added equates for Morrow Disk Jockey 2D/SS, 256,
;         512 and 1024-byte sector options.  Fix 'S2' update
;         flag for larger max number of extents. Cleaned up
;         file. (Ben Bronson and KBP)
;
;09/14/80 Corrected DGROUP equate for MMDBL. Added new routine
;         to correct for IMDOS group allocation.  Corrected
;         error in instructions for using TEST routine.
;         (CHS) (AJ) (KBP) - (a group effort)
;
;09/08/80 Fixed several errors in Al Jewer's mods.  Changed
;         return to CP/M to warm boot so bitmap in memory will
;         be properly updated. Added conditional assembly for
;         testing program. (KBP)
;
;09/02/80 Added IMDOS double-density equates & modified for
;         more then 256 blocks per disk. (Al Jewer)
;
;09/01/80 Changed equates so that parameters are automatically
;         set for each disk system conditional assembly (KBP)
;
;08/31/80 Add conditional assembly for Digital Microsystems FDC3
;         controller board in double-density format and fix to
;         do 256 blocks in one register. (Thomas V. Churbuck)
;
;08/31/80 Correct MAXB equate - MAXB must include the directory
;         blocks as well as the data blocks.  Fix to make sure
;         any [UNUSED].BAD file is erased before data area is
;         checked. (KBP)
;
;08/30/80 Added conditional assembly for Micromation
;         double-density format. (Charles H. Strom)
;
;08/27/80 Fix missing conditional assembly in FINDB routine.
;         Put version number in sign-on message. (KBP)
;
;08/26/80 Modified by Keith Petersen, W8SDZ, to:
;         (1) Add conditional assembly for 1k/2k groups
;         (2) Add conditional assembly for standard drives
;             and Micropolis MOD II
;         (3) Make compatible with CP/M-2.x
;         (4) Remove unneeded code to check for drive name
;             (CP/M does it for you and returns it in the FCB)
;         (5) Changed to open additional extents as needed for
;             overflow, instead of additional files
;         (6) Add conditional assembly for system tracks check
;             (some double-density disks have single-density
;             system tracks which cannot be read by this program)
;         (7) Increased stack area (some systems use more than
;             others).
;
;08/06/80 Added comments and crunched some code.
;         KELLY SMITH.  805-527-9321 (Modem, 300 Baud)
;                       805-527-0518 (Verbal)
;
;
;                       Using the Program
;
; Before  using this program to "reclaim" a diskette,  it  is
;recommended that the diskette be reformatted. If this is not
;possible,  at least assure yourself that any existing  files
;on the diskette  do not contain unreadable  sectors.  If you
;have changed disks since the last warm-boot, you  must warm-
;boot again before running this program.
;
; To  use the program,  insert  both the disk containing  the
;program  FINDBAD.CMD and the diskette to be checked into the
;disk drives. It is possible that the diskette containing the
;program is the one to be checked. Assume that the program is
;on drive "A" and the suspected bad disk is on drive "B".  In
;response to the CP/M prompt "A>",  type in FINDBAD B:.  This
;will  load the file FINDBAD.CMD from drive "A" and test  the
;diskette  on  drive "B" for  unreadable  sectors.  The  only
;allowable  parameter  after  the  program name  is  a  drive
;specification  (of the form " N:") for up to four (A  to  D)
;disk drives.  If no drive is specified, the currently logged
;in drive is assumed to contain the diskette to check.
;
; The  program first checks the CP/M System tracks (0 and 1),
;and  any  errors here prohibit the disk from being  used  on
;drive  "A",  since all "warm  boots" occur using the  system
;tracks from the "A" drive.
;
; The  program next checks the first two data blocks  (groups
;to some of us) containing the directory of the diskette.  If
;errors  occur  here,  the  program  terminates  and  control
;returns  to  CP/M  (no other data blocks are  checked  since
;errors in the directory render the disk useless).
;
; Finally,  all  the remaining data blocks are  checked.  Any
;sectors  which  are  unreadable cause the data  block  which
;contains them to be stored temporarily as a "bad block".  At
;the end of this phase,  the message "XX bad blocks found" is
;displayed (where XX is replaced by the number of bad blocks,
;or "No" if no read errors occur).  If bad blocks occur,  the
;filname [UNUSED].BAD is created, the list of "bad blocks" is
;placed  in  the allocation map of the  directory  entry  for
;[UNUSED].BAD,  and the file is closed.  Note,  that when the
;number of "bad blocks" exceeds 16,  the  program  will  open
;additional  extents  as  required  to  hold the overflow.  I
;suggest that if the diskette has more than  32 "bad blocks",
;perhaps it should be sent to the "big disk drive in the sky"
;for the rest it deserves.
;
; The  nifty part of all this is that if any "bad blocks"  do
;occur, they are allocated to [UNUSED].BAD and no longer will
;be available to CP/M-86 for future allocation... bad sectors
;are logically locked out on the diskette
;
;
;              Using the TEST conditional assembly
;
;A  conditional  assembly has been added to allow  testing  this
;program  to  make sure it is reading all sectors on  your  disk
;that  are accessible to CP/M.  The program reads the disk on  a
;block by block basis, so it is necessary to first determine the
;number of blocks present.  To start, we must know the number of
;sectors/block (8 sectors/block for standard IBM single  density
;format).  If  this  value  is  not  known,  it  can  easily  be
;determined  by saving one page in a test file and interrogating
;using the STAT command:
;
;For standard single-density STAT will report this file as being
;1k.  The file size reported (in bytes) is the size of a  block.
;This  value  divided  by 128 bytes/sector  (the  standard  CP/M
;sector  size)  will  give sectors/block.  For  our  IBM  single
;density example, we have:
;
;  (1024 bytes/block) / (128 bytes/sector) = 8 sectors/block.
;
;We  can now calculate blocks/track (assuming we know the number
;sectors/track). In our example:
;
;  (26 sectors/track) / (8 sectors/block) = 3.25 blocks/track
;
;Now  armed with the total number of data tracks (75 in our  IBM
;single density example), we get total blocks accessible:
;
;  75 (tracks/disk) x (3.25 blocks/track) = 243.75 blocks/disk
;
;CP/M cannot access a fractional block, so we round down (to 243
;blocks  in  our  example).  Now  multiplying  total  blocks  by
;sectors/block  results in total sectors as should  be  reported
;when TEST is set TRUE and a good disk is read. For our example,
;this value is 1944 sectors.
;
;Finally,  note that if SYSTEM is set TRUE,  the sectors present
;on  the  first  two tracks must be added in  as  well.  In  the
;previous  example,  this  results in  1944 + 52 = 1996  sectors
;reported by the TEST conditional.
;
;Run the program on a KNOWN-GOOD disk.  It should report that it
;has read  the  correct number of sectors.  The test conditional
;assembly should then be set FALSE and the program re-assembled.
;The test routines  cannot be left in  because this program does
;not read all the sectors in a block that is found to be bad and
;thus will report an inaccurate number of sectors read.
;
;
;Define TRUE and FALSE
;
FALSE   EQU     0
TRUE    EQU     NOT FALSE
;
;******************************************************************
;
;Conditional assembly switch for testing this program
;(for initial testing phase only - see remarks above)
;
TEST    EQU     FALSE                   ;TRUE FOR TESTING ONLY
;
;******************************************************************
;
;System equates
;
BASE    EQU     0                       ;STANDARD CP/M BASE ADDRESS
FCB     EQU     BASE+5CH                ;CP/M DEFAULT FCB LOCATION
;
;Define ASCII characters used
;
CR      EQU     0DH                     ;CARRIAGE RETURN CHARACTER
LF      EQU     0AH                     ;LINE FEED CHARACTER
TAB     EQU     09H                     ;TAB CHARACTER
BEL     EQU     07H                     ;BELL CHARACTER
;
M       EQU     Byte Ptr        0[BX]
;
       cseg
       ORG     BASE+100H
;
       JMPS    START                   ;JMP AROUND OPTION BYTES
;
;If you want the system tracks tested, then put a 1 here, otherwise 0.
;
SYSTST  DB      1                       ;0 IF NO SYS TRACKS, OTHERWISE 1
;
;   Change this byte to the user number you want [UNUSED].BAD
;to reside in.  If you want it in the default user, then leave
;it 0FFH.
;
BADUSR  DB      0FFH                    ;USER # WHERE [UNUSED.BAD] GOES
;0FFH = DEFAULT USER
;
START:  CALL    START2                  ;GO PRINT SIGNON
;
       DB      CR,LF,'FINDBAD - Ver 1.0, Bad Sector Lockout Utility'
       DB      CR,LF
       DB      '------- Universal Version for CP/M-86 -------'
       DB      CR,LF,CR,LF,'Type CTL-C to abort',CR,LF,'$'
;
START2: POP     DX                      ;GET MSG ADRS
       MOV     CL,9                    ;bdos PRINT BUFFER function
       INT     224                     ;bdos PRINT SIGN-ON MSG
       CALL    SETUP                   ;SET BIOS ENTRY, AND CHECK DRIVE
       CALL    FINDB                   ;ESTABLISH ALL BAD BLOCKS
       JZ      NOBAD                   ;SAY NO BAD BLOCKS, IF SO
       CALL    SETDM                   ;FIX DM BYTES IN FCB
;
NOBAD:  CALL    CRLF
       MOV     AL,TAB
       CALL    DISPLAY
       MOV     DX,(Offset NOMSG)       ;POINT FIRST TO 'NO'
       MOV     BX,Word Ptr BADBKS      ;PICK UP # BAD BLOCKS
       MOV     AL,BH                   ;CHECK FOR ZERO
       OR      AL,BL
       JZ      PMSG1                   ;JUMP IF NONE
       CALL    DECOUT                  ;OOPS..HAD SOME BAD ONES, REPORT
       JMPS    PMSG2
;
PMSG1:  MOV     CL,9                    ;bdos PRINT BUFFER function
       INT     224
;
PMSG2:  MOV     DX,(Offset ENDMSG)      ;REST OF EXIT MESSAGE
;
PMSG:   MOV     CL,9
       INT     224
;
       MOV     CL,0                    ;EXIT TO CP/M WARM BOOT
       MOV     DL,0
       INT     224
;
;Get actual address of BIOS routines
;
SETUP   EQU     $       ;Check for drive specification
;
GDRIV:  MOV     AL,Byte Ptr .FCB        ;GET DRIVE NAME
       MOV     CL,AL
       OR      AL,AL                   ;ZERO?
       JNZ     GD2                     ;IF NOT,THEN GO SPECIFY DRIVE
       MOV     CL,25                   ;GET LOGGED-IN DRIVE
       INT     224
       INC     AL                      ;MAKE 1-RELATIVE
       MOV     CL,AL
;
GD2:    CMP     AL,15+1                 ;CHECK FOR HIGHEST DRIVE NUMBER
       JNAE    GD3
       JMP     SELERR                  ;SELECT ERROR
;
GD3:    DEC     CL                      ;BACK OFF FOR CP/M
       PUSH    CX                      ;SAVE DISK SELECTION
       MOV     DL,CL                   ;ALIGN FOR BDOS
       MOV     CL,14                   ;SELECT DISK function
       INT     224
       POP     CX                      ;GET BACK DISK NUMBER
;
SETDSK: MOV     Byte Ptr FUNC,9         ;bios select disk function
       MOV     Word Ptr BIOS_DESC,CX   ;pass disk number to bios descriptor
       MOV     Word Ptr BIOS_DESC+2,0  ;fill remaining descriptor (DX) zero
       MOV     DX,(Offset FUNC)        ;point to function parameter block
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
;
       MOV     DL,ES: M                ;GET SECTOR TABLE PNTR
       LAHF
       INC     BX
       SAHF
       MOV     DH,ES: M
       LAHF
       INC     BX
       SAHF
       XCHG    BX,DX
       MOV     Word Ptr SECTBL,BX      ;STORE IT AWAY
       MOV     BX,8                    ;OFFSET TO DPB POINTER
       LAHF
       ADD     BX,DX
       RCR     SI,1
       SAHF
       RCL     SI,1
       MOV     AL,ES: M                        ;PICK UP DPB POINTER
       LAHF                            ;  TO USE
       INC     BX
       SAHF
       MOV     BH,ES: M                        ;  AS PARAMETER
       MOV     BL,AL                   ;  TO LOGIT
;
DOLOG:  CALL    LOGIT                   ;LOG IN DRIVE, GET DISK PARMS
       CALL    GETDIR                  ;CALCULATE DIRECTORY INFORMATION
;
;
HOMDSK: MOV     Byte Ptr FUNC,8         ;bios HOME DISK function
       MOV     Word Ptr BIOS_DESC,0    ;pass 'nothing' number to bios descriptor
       MOV     Word Ptr BIOS_DESC+2,0  ;fill remaining descriptor (DX) zero
       MOV     DX,(Offset FUNC)        ;point to function parameter block
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
;
;Now set the required user number
;
       MOV     AL,Byte Ptr BADUSR      ;GET THE USER NUMBER
       CMP     AL,0FFH                 ;IF IT IS 0FFH, THEN RETURN
       JNZ     L_6
       RET
L_6:
       MOV     CL,32                   ;GET/SET USER CODE
       INT     224
       RET
;
;Look for bad blocks
;
FINDB:  MOV     AL,Byte Ptr SYSTST
       OR      AL,AL
       JZ      DODIR                   ;JUMP IF NO SYS TRACKS TO BE TESTED
       CALL    CHKSYS                  ;CHECK FOR BAD BLOCKS ON TRACK 0 AND 1
;
DODIR:  CALL    CHKDIR                  ;CHECK FOR BAD BLOCKS IN DIRECTORY
       CALL    TELL1
;
       DB      CR,LF,'Testing data area...',CR,LF,'$'
;
TELL1:  POP     DX
       MOV     CL,9                    ;bdos PRINT STRING function
       INT     224
       CALL    ERAB                    ;ERASE ANY [UNUSED].BAD FILE
       MOV     BX,Word Ptr DIRBKS      ;START AT FIRST DATA BLOCK
       MOV     CX,BX                   ;PUT INTO [CX]
;
FINDBA: CALL    READB                   ;READ THE BLOCK
       JZ      L_7
       CALL    SETBD                   ;IF BAD, ADD BLOCK TO LIST
L_7:
       LAHF                            ;BUMP TO NEXT BLOCK
       INC     CX
       SAHF
       MOV     BX,Word Ptr DSM
       MOV     DX,CX                   ;SET UP FOR (MAXGRP - CURGRP)
       SBB     BX,DX                   ;DO SUBTRACT: (MAXGRP - CURGRP)
       JNB     FINDBA                  ;UNTIL CURGRP>MAXGRP
       CALL    CRLF
       MOV     BX,Word Ptr DMCNT       ;GET NUMBER OF BAD SECTORS
       MOV     AL,BH
       OR      AL,BL                   ;SET ZERO FLAG, IF NO BAD BLOCKS
       RET                             ;RETURN FROM "FINDB"
;
;Check system tracks, notify user if bad, but continue
;
CHKSYS: CALL    CHSY1                   ;bdos PRINT MESSAGE
;
       DB      CR,LF,'Testing system tracks...',CR,LF,'$'
;
CHSY1:  POP     DX
       MOV     CL,9                    ;bdos PRINT STRING function
       INT     224
       MOV     BX,0                    ;SET TRACK 0, SECTOR 1
       MOV     Word Ptr TRACK,BX
       LAHF
       INC     BX
       SAHF
       MOV     Word Ptr SECTOR,BX
;
CHKSY1: CALL    READS                   ;READ A SECTOR
       JNZ     SYSERR                  ;NOTIFY, IF BAD BLOCKS HERE
       MOV     BX,Word Ptr SYSTRK      ;SET UP (TRACK-SYSTRK)
       XCHG    BX,DX
       MOV     BX,Word Ptr TRACK
       SBB     BX,DX                   ;DO THE SUBTRACT
       JB      CHKSY1                  ;LOOP WHILE TRACK < SYSTRK
       RET                             ;RETURN FROM "CHKSYS"
;
SYSERR: MOV     DX,(Offset ERMSG5)      ;SAY NO GO, AND BAIL OUT
       MOV     CL,9                    ;bdos PRINT BUFFER function
       INT     224
       RET                             ;RETURN FROM "SYSERR"
;
;Check for bad blocks in directory area
;
CHKDIR: CALL    CHKD1
;
       DB      CR,LF,'Testing directory area...',CR,LF,'$'
;
CHKD1:  POP     DX
       MOV     CL,9                    ;bdos PRINT STRING function
       INT     224
       MOV     CX,0                    ;START AT BLOCK 0
;
CHKDI1: CALL    READB                   ;READ A BLOCK
       JZ      L_8
       JMP     ERROR6                  ;IF BAD, INDICATE ERROR IN DIRECTORY AREA
L_8:
       LAHF                            ;BUMP FOR NEXT BLOCK
       INC
       CX
       SAHF
       MOV     BX,Word Ptr DIRBKS      ;SET UP (CURGRP - DIRBKS)
       LAHF                            ;MAKE 0-RELATIVE
       DEC     BX
       SAHF
       MOV     DX,CX
       SBB     BX,DX                   ;DO THE SUBTRACT
       JNB     CHKDI1                  ;LOOP UNTIL CURGRP > DIRGRP
       RET                             ;RETURN FROM "CHKDIR"
;
;Read all sectors in block, and return zero flag set if none bad
;
READB:  CALL    CNVRTB                  ;CONVERT TO TRACK/SECTOR IN H&L REGS.
       MOV     AL,Byte Ptr BLM
       INC     AL                      ;NUMBER OF SECTORS/BLOCK
       MOV     DH,AL                   ;  IN D REG
;
READBA: PUSH    DX
       CALL    READS                   ;READ SKEWED SECTOR
       POP     DX
       JZ      L_9
       RET                             ;ERROR IF NOT ZERO...
L_9:
       DEC     DH                      ;DEBUMP SECTOR/BLOCK
       JNZ     READBA                  ;DO NEXT, IF NOT FINISHED
       RET                             ;RETURN FROM "READBA"
;
;Convert block number to track and skewed sector number
;
CNVRTB: PUSH    CX                      ;SAVE CURRENT GROUP
       MOV     BX,CX                   ;NEED IT IN [BX], FOR EASY SHIFTING
       MOV     AL,Byte Ptr BSH         ;DPB VALUE THAT TELLS HOW TO
;
SHIFT:  SHL     BX,1                    ;  SHIFT GROUP NUMBER TO GET
       DEC     AL                      ;  DISK-DATA-AREA RELATIVE
       JNZ     SHIFT                   ;  SECTOR NUMBER
       XCHG    BX,DX                   ;REL SECTOR # INTO DE
       MOV     BX,Word Ptr SPT         ;SECTORS PER TRACK FROM DPB
       NOT     BX                      ;1ST 1'S COMPLEMENT...
       INC     BX                      ;...THEN 2'S COMPLEMENT
       XCHG    BX,DX
       MOV     CX,0                    ;INITIALIZE QUOTIENT
;
;Divide by number of sectors
;       quotient = track
;            mod = sector
;
DIVLP:  LAHF                            ;DIRTY DIVISION
       INC     CX
       SAHF
       LAHF
       ADD     BX,DX
       RCR     SI,1
       SAHF
       RCL     SI,1
       JB      DIVLP
       LAHF                            ;FIXUP LAST
       DEC     CX
       SAHF
       XCHG    BX,DX
       MOV     BX,Word Ptr SPT
       LAHF
       ADD     BX,DX
       SAHF
       LAHF
       INC     BX
       SAHF
       MOV     Word Ptr SECTOR,BX      ;NOW HAVE LOGICAL SECTOR
       MOV     BX,Word Ptr SYSTRK      ;BUT BEFORE WE HAVE TRACK #,
       LAHF                            ;  WE HAVE TO ADD SYS TRACK OFFSET
       ADD     BX,CX
       RCR     SI,1
       SAHF
       RCL     SI,1
       MOV     Word Ptr TRACK,BX
       POP     CX                      ;THIS WAS OUR GROUP NUMBER
       RET
;
;READS reads a logical sector (if it can)
;and returns zero flag set if no error.
;
READS:  PUSH    CX                      ;SAVE THE GROUP NUMBER
;
       CALL    LTOP                    ;CONVERT LOGICAL TO PHYSICAL
       MOV     BX,Word Ptr PHYSEC      ;GET PHYSICAL SECTOR
       MOV     CX,BX                   ;INTO [CX]
;
SETSEC: MOV     BYTE PTR FUNC,11        ;bios SET SECTOR function
       MOV     WORD PTR BIOS_DESC,CX   ;pass sector number to bios descriptor
       MOV     WORD PTR BIOS_DESC+2,0  ;fill remaining descriptor (DX) zero
       MOV     DX,(Offset FUNC)        ;point to function parameter block
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
;
       MOV     BX,Word Ptr TRACK       ;NOW SET THE TRACK
       MOV     CX,BX                   ;CP/M WANTS IT IN [CX]
;
SETTRK: MOV     BYTE PTR FUNC,10        ;bios SELECT TRACK function
       MOV     WORD PTR BIOS_DESC,CX   ;pass track number to bios descriptor
       MOV     WORD PTR BIOS_DESC+2,0  ;fill remaining descriptor (DX) zero
       MOV     DX,(Offset FUNC)        ;point to function parameter block
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
;
;Now do the sector read
;
DREAD:  MOV     BYTE PTR FUNC,13        ;bios READ SECTOR function
       MOV     WORD PTR BIOS_DESC,0    ;pass 'nothing' number to bios descriptor
       MOV     WORD PTR BIOS_DESC+2,0  ;fill remaining descriptor (DX) zero
       MOV     DX,(Offset FUNC)        ;point to function parameter block
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
;
       OR      AL,AL                   ;SET FLAGS
       LAHF                            ;SAVE ERROR FLAG
       XCHG    AL,AH
       PUSH    AX
       MOV     BX,Word Ptr SECTOR      ;GET LOGICAL SECTOR #
       LAHF                            ;WE WANT TO INCREMENT TO NEXT
       INC     BX
       SAHF
       XCHG    BX,DX                   ;BUT FIRST...CHECK OVERFLOW
       MOV     BX,Word Ptr SPT         ;  BY DOING (SECPERTRK-SECTOR)
       SBB     BX,DX                   ;DO THE SUBTRACTION
       XCHG    BX,DX
       JNB     NOOVF                   ;JUMP IF NOT SECTOR>SECPERTRK
;
;Sector overflow...bump track number, reset sector
;
       MOV     BX,Word Ptr TRACK
       LAHF
       INC     BX
       SAHF
       MOV     Word Ptr TRACK,BX
       MOV     AL,'*'                  ;TELL CONSOLE ANOTHER TRACK DONE
       CALL    DISPLAY
       CALL    STOP                    ;SEE IF CONSOLE WANTS TO QUIT
       MOV     BX,1                    ;NEW SECTOR NUMBER ON NEXT TRACK
;
NOOVF:  MOV     Word Ptr SECTOR,BX      ;PUT SECTOR AWAY
       POP     AX                      ;GET BACK ERROR FLAGS
       XCHG    AL,AH
       SAHF
       POP     CX                      ;RESTORE GROUP NUMBER
       RET
;
;Convert logical sector # to physical
;
LTOP:   MOV     BX,Word Ptr SECTBL      ;SET UP PARAMETERS
       XCHG    BX,DX                   ;  FOR CALL TO SECTRAN
       MOV     BX,Word Ptr SECTOR
       MOV     CX,BX
       DEC     CX                      ;ALWAYS CALL SECTRAN W/ZERO-REL SEC #
;
SECT1:  CALL    SECTRN                  ;DO THE SECTOR TRANSLATION
       MOV     AL,Byte Ptr SPT+1       ;CHECK IF BIG TRACKS
       OR      AL,AL                   ;SET FLAGS (TRACKS > 256 SECTORS)
       JNZ     LTOP1                   ;NO SO SKIP
       MOV     BH,AL                   ;ZERO OUT UPPER 8 BITS
;
LTOP1:  MOV     Word Ptr PHYSEC,BX      ;PUT AWAY PHYSICAL SECTOR
       RET
;
;Sector translation vector
;
SECTRN: MOV     BYTE PTR FUNC,16        ;bios SECTOR TRANSLATE function
       MOV     WORD PTR BIOS_DESC,CX   ;pass sector to bios descriptor
       MOV     WORD PTR BIOS_DESC+2,DX ;translate table offset
       MOV     DX,(Offset FUNC)        ;point to function parameter block
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
       ret                             ;return from 'sectrn'
;
;Put bad block in bad block list
;
SETBD:  PUSH    CX
       CALL    SETBD1
       DB      CR,LF,BEL,'Bad block: $'
;
SETBD1: POP     DX                      ;RETRIEVE ARG
       MOV     CL,9                    ;bdos PRINT STRING
       INT     224
       POP     CX                      ;GET BACK BLOCK NUMBER
       MOV     AL,CH
       CALL    HEXO                    ;bdos PRINT IN HEX
       MOV     AL,CL
       CALL    HEXO
       CALL    CRLF
       MOV     BX,Word Ptr DMCNT       ;GET NUMBER OF SECTORS
       MOV     AL,Byte Ptr BLM         ;GET BLOCK SHIFT VALUE
       INC     AL                      ;MAKES SECTOR/GROUP VALUE
       MOV     DL,AL                   ;WE WANT 16 BITS
       MOV     DH,0
       ADD     BX,DX                   ;BUMP BY NUMBER IN THIS BLOCK
       MOV     Word Ptr DMCNT,BX       ;UPDATE NUMBER OF SECTORS
       MOV     BX,Word Ptr BADBKS      ;INCREMENT NUMBER OF BAD BLOCKS
       INC     BX
       MOV     Word Ptr BADBKS,BX
       MOV     BX,Word Ptr DMPTR       ;GET POINTER INTO DM
       MOV     M,CL                    ;...AND PUT BAD BLOCK NUMBER
       INC     BX                      ;BUMP TO NEXT AVAILABLE EXTENT
       MOV     AL,Byte Ptr DSM+1       ;CHECK IF 8 OR 16 BIT BLOCK SIZE
       OR      AL,AL
       JZ      SMGRP                   ;JUMP IF 8 BIT BLOCKS
       MOV     M,CH                    ;ELSE STORE HI BYTE OF BLOCK #
       LAHF                            ;AND BUMP POINTER
       INC     BX
       SAHF
;
SMGRP:  MOV     Word Ptr DMPTR,BX       ;SAVE DM POINTER, FOR NEXT TIME THROUGH HERE
       RET                             ;RETURN FROM "SETBD"
;
;Eliminate any previous [UNUSED].BAD entries
;
ERAB:   MOV     DX,(Offset BFCB)        ;POINT TO BAD FCB
       MOV     CL,19                   ;bdos DELETE FILE function
       INT     224
       RET
;
;Create [UNUSED].BAD file entry
;
OPENB:  MOV     DX,(Offset BFCB)        ;POINT TO BAD FCB
       MOV     CL,22                   ;bdos MAKE FILE function
       INT     224
       CMP     AL,0FFH                 ;CHECK FOR OPEN ERROR
       JZ      L_10
       RET                             ;RETURN FROM "OPENB", IF NO ERROR
L_10:
       JMP     ERROR7                  ;BAIL OUT...CAN'T CREATE [UNUSED].BAD
;
CLOSEB: XOR     AL,AL
       MOV     AL,Byte Ptr BFCB+14     ;GET CP/M 'S2' BYTE
       AND     AL,1FH                  ;ZERO UPDATE FLAGS
       MOV     Byte Ptr BFCB+14,AL     ;RESTORE IT TO OUR FCB
       MOV     DX,(Offset BFCB)        ;FCB FOR [UNUSED].BAD
       MOV     CL,16                   ;bdos CLOSE FILE function
       INT     224
       RET                             ;RETURN FROM "CLOSEB"
;
;Move bad area DM to BFCB
;
SETDM:  MOV     BX,(Offset DM)          ;GET DM
       MOV     Word Ptr DMPTR,BX       ;SAVE AS NEW POINTER
       MOV     AL,Byte Ptr EXM         ;GET THE EXTENT SHIFT FACTOR
       MOV     CL,0                    ;INIT BIT COUNT
       CALL    COLECT                  ;GET SHIFT VALUE
       MOV     BX,128                  ;STARTING EXTENT SIZE
       MOV     AL,CL                   ;FIRST SEE IF ANY SHIFTS TO DO
       OR      AL,AL
       JZ      NOSHFT                  ;JUMP IF NONE
;
ESHFT:  SHL     BX,1                    ;SHIFT
       DEC     AL                      ;BUMP
       JNZ     ESHFT                   ;LOOP
;
NOSHFT: PUSH    BX                      ;SAVE THIS, IT IS RECORDS PER EXTENT
       MOV     AL,Byte Ptr BSH         ;GET BLOCK SHIFT
       MOV     CH,AL
;
BSHFT:  CLC                             ;CLEAR CARRY FLAG
       RCR     BX,1                    ;SHIFT RIGHT
       DEC     CH
       JNZ     BSHFT                   ;TO GET BLOCKS PER EXTENT
       MOV     AL,BL                   ;IT'S IN [BL] (CAN'T BE >16)
       MOV     Byte Ptr BLKEXT,AL      ;SET BLOCK EXTENT, WILL NEED THIS LATER
       POP     BX                      ;GET BACK REC/EXT
;
SET1:   XCHG    BX,DX                   ;NOW HAVE REC/EXTENT IN [DX]
       MOV     BX,Word Ptr DMCNT       ;COUNT OF BAD SECTORS
;
SETDMO: PUSH    BX                      ;SET FLAGS ON (DMCNT-BADCNT)
       SBB     BX,DX                   ;HAVE TO SUBTRACT FIRST
       MOV     CX,BX                   ;SAVE RESULT IN [CX]
       POP     BX                      ;THIS POP MAKES IT COMPARE ONLY
       JB      SETDME                  ;JUMP IF LESS THAN 1 EXTENT WORTH
       MOV     AL,CH
       OR      AL,CL                   ;TEST IF SUBTRACT WAS 0
       JZ      EVENEX                  ;EXTENT IS EXACTLY FILLED (SPL CASE)
       MOV     BX,CX                   ;RESTORE RESULT TO [BX]
       PUSH    BX                      ;SAVE TOTAL
       PUSH    DX                      ;AND SECTORS/EXTENT
       XCHG    BX,DX
       CALL    SETDME                  ;PUT AWAY ONE EXTENT
       XCHG    BX,DX
       MOV     Word Ptr DMPTR,BX       ;PUT BACK NEW DM POINTER
       POP     DX                      ;GET BACK SECTORS/EXTENT
       POP     BX                      ;AND COUNT OF BAD SECTORS
       JMPS    SETDMO                  ;AND LOOP
;
;Handle the special case of a file that ends on an extent
;boundary.  CP/M requires that such a file have a succeeding
;empty extent in order for the BDOS to properly access the file.
;
EVENEX: XCHG    BX,DX                   ;FIRST SET EXTENT W/BAD BLOCKS
       CALL    SETDME
       XCHG    BX,DX
       MOV     Word Ptr DMPTR,BX
       MOV     BX,0                    ;NOW SET ONE WITH NO DATA BLOCKS
;
;Fill in an extent's worth of bad sectors/block numbers.
;Also fill in the extent number in the FCB.
;
SETDME: PUSH    BX                      ;SAVE RECORD COUNT
       MOV     AL,Byte Ptr EXTNUM      ;UPDATE EXTENT BYTE
       INC     AL
       MOV     Byte Ptr EXTNUM,AL      ;SAVE FOR LATER
       MOV     Byte Ptr BFCB+12,AL     ; AND PUT IN FCB
       CALL    OPENB                   ;OPEN THIS EXTENT
       POP     BX                      ;RETRIEVE REC COUNT
;
;Divide record count by 128 to get the number
;of logical extents to put in the EX field
;
       MOV     CH,0                    ;INIT QUOTIENT
       MOV     DX,-128                 ;-DIVISOR
       MOV     AL,BH                   ;TEST FOR SPL CASE
       OR      AL,BL                   ;  OF NO RECORDS
       JZ      SKIP
;
DIVLOP: ADD     BX,DX                   ;SUBTRACT
       INC     CH                      ;BUMP QUOTIENT
       JB      DIVLOP
       MOV     DX,128                  ;FIX UP OVERSHOOT
       ADD     BX,DX
       DEC     CH
       MOV     AL,BH                   ;TEST FOR WRAPAROUND
       OR      AL,BL
       JNZ     SKIP
       MOV     BL,80H                  ;RECORD LENGTH
       DEC     CH
;
SKIP:   MOV     AL,Byte Ptr EXTNUM      ;NOW FIX UP EXTENT NUM
       ADD     AL,CH
       MOV     Byte Ptr EXTNUM,AL
       MOV     Byte Ptr BFCB+12,AL
       MOV     AL,BL                   ;MOD IS RECORD COUNT
       MOV     Byte Ptr BFCB+15,AL     ;THAT GOES IN RC BYTE
;
MOVDM:  MOV     AL,Byte Ptr BLKEXT      ;GET BLOCKS PER EXTENT
       MOV     CH,AL                   ;INTO B
;
SETD1:  MOV     BX,Word Ptr DMPTR       ;POINT TO BAD ALLOCATION MAP
       XCHG    BX,DX
       MOV     BX,(Offset BFCB)+16     ;DISK ALLOC MAP IN FCB
;
SETDML: MOV     SI,DX
       MOV     AL,[SI]
       MOV     M,AL
       INC     BX
       INC     DX
;
;Now see if 16 bit groups...if so,
;we have to move another byte
;
       MOV     AL,Byte Ptr DSM+1       ;THIS TELLS US
       OR      AL,AL
       JZ      BUMP1                   ;IF ZERO, THEN NOT
       MOV     SI,DX                   ;IS 16 BITS, SO DO ANOTHER
       MOV     AL,[SI]
       MOV     M,AL
       LAHF
       INC     BX
       SAHF
       LAHF
       INC     DX
       SAHF
;
BUMP1:  DEC     CH                      ;COUNT DOWN
       JNZ     SETDML
       PUSH    DX
       CALL    CLOSEB                  ;CLOSE THIS EXTENT
       POP     DX
       RET
;
;Error messages
;
SELERR: MOV     DX,(Offset SELEMS)      ;SAY NO GO, AND BAIL OUT
       JMP     PMSG
;
SELEMS  DB      CR,LF,'Drive specifier out of range$'
;
ERMSG5  DB      CR,LF,BEL,'+++ Warning...System tracks'
       DB      ' bad +++',CR,LF,CR,LF,'$'
;
ERROR6: MOV     DX,(Offset ERMSG6)      ;OOPS...CLOBBERED DIRECTORY
       JMP     PMSG
;
ERMSG6  DB      CR,LF,BEL,'Bad directory area, try reformatting$'
;
ERROR7: MOV     DX,(Offset ERMSG7)      ;SAY NO GO, AND BAIL OUT
       JMP     PMSG
;
ERMSG7  DB      CR,LF,'Can''t create [UNUSED].BAD$'
;
;
;==== SUBROUTINES ====
;
;Decimal output routine
;
DECOUT: PUSH    CX
       PUSH    DX
       PUSH    BX
       MOV     CX,-10
       MOV     DX,-1
;
DECOU2: ADD     BX,CX
       INC     DX
       JB      DECOU2
       MOV     CX,10
       ADD     BX,CX
       XCHG    BX,DX
       MOV     AL,BH
       OR      AL,BL
       JZ      L_17
       CALL    DECOUT
L_17:
       MOV     AL,DL
       ADD     AL,'0'
       CALL    DISPLAY
       POP     BX
       POP     DX
       POP     CX
       RET
;
;Carriage-return/line-feed to console
;
CRLF:   MOV     AL,CR
       CALL    DISPLAY
       MOV     AL,LF                   ;FALL INTO 'DISPLAY'
;
DISPLAY:PUSH    CX
       PUSH    DX
       PUSH    BX
       MOV     DL,AL                   ;CHARACTER TO [DX] FOR CP/M
       MOV     CL,2                    ;bdos PRINT CONSOLE function
       INT     224                     ;bdos PRINT CHARACTER
       POP     BX
       POP     DX
       POP     CX
       RET
;
;Subroutine to test console for control-c abort
;
STOP:   MOV     BYTE PTR FUNC,2         ;BIOS CONSOLE STATUS FUNCTION
       MOV     WORD PTR BIOS_DESC,0    ;PASS 'NOTHING' NUMBER TO BIOS DESCRIPTOR
       MOV     WORD PTR BIOS_DESC+2,0  ;FILL REMAINING DESCRIPTOR (DX) ZERO
       MOV     DX,(OFFSET FUNC)        ;POINT TO FUNCTION PARAMETER BLOCK
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
;
       OR      AL,AL                   ;TEST FLAGS ON ZERO
       JNZ     L_18
       RET                             ;RETURN IF NO CHAR
L_18:   MOV     BYTE PTR FUNC,3         ;BIOS READ CONSOLE CHARACTER FUNCTION
       MOV     WORD PTR BIOS_DESC,0    ;PASS 'NOTHING' NUMBER TO BIOS DESCRIPTOR
       MOV     WORD PTR BIOS_DESC+2,0  ;FILL REMAINING DESCRIPTOR (DX) ZERO
       MOV     DX,(OFFSET FUNC)        ;POINT TO FUNCTION PARAMETER BLOCK
       MOV     CL,50                   ;direct bios call
       INT     224                     ;do it, to it...
;
       CMP     AL,'C'-40H              ;IS IT CONTROL-C?
       JZ      L_19
       RET                             ;RETURN IF NOT
L_19:
       MOV     DX,(Offset ABORTM)      ;EXIT WITH MESSAGE
       JMP     PMSG
;
ABORTM  DB      CR,LF
       DB      'Test aborted by control-C'
       DB      CR,LF,'$'
;
;Move from [BX] to [DX], Count in [CX]
;
MOVE:   MOV     AL,ES: M
       MOV     SI,DX
       MOV     [SI],AL
       LAHF
       INC     BX
       SAHF
       LAHF
       INC     DX
       SAHF
       DEC     CH
       JNZ     MOVE
       RET
;
;Print byte in accumulator in hex
;
HEXO:   LAHF                            ;SAVE FOR SECOND HALF
       XCHG    AL,AH
       PUSH    AX
       XCHG    AL,AH
       ROR     AL,1                    ;MOVE INTO POSITION
       ROR     AL,1
       ROR     AL,1
       ROR     AL,1
       CALL    NYBBLE                  ;bdos PRINT MS NYBBLE
       POP     AX
       XCHG    AL,AH
;
NYBBLE: AND     AL,0FH                  ;LO NYBBLE ONLY
       ADD     AL,90H
       DAA
       ADC     AL,40H
       DAA
       JMP     DISPLAY                 ;bdos PRINT IN HEX
;
;Subroutine to determine the number
;of groups reserved for the directory
;
GETDIR: MOV     CL,0                    ;INIT BIT COUNT
       MOV     AL,Byte Ptr AL0         ;READ DIR GRP BITS
       CALL    COLECT                  ;COLLECT COUNT OF DIR GRPS..
       MOV     AL,Byte Ptr AL1         ;..IN REGISTER [CL]
       CALL    COLECT
       MOV     BL,CL
       MOV     BH,0                    ;[CX] NOW HAS A DEFAULT START GRP #
       MOV     Word Ptr DIRBKS,BX      ;SAVE FOR LATER
       RET
;
;Collect the number of '1' bits in A as a count in [CL]
;
COLECT: MOV     CH,8
;
COLOP:  RCL     AL,1
       JNB     COSKIP
       INC     CL
;
COSKIP: DEC     CH
       JNZ     COLOP
       RET
;
;Routine to fill in disk parameters
;
LOGIT:  MOV     DX,(Offset DPB)         ;   THEN MOVE TO LOCAL
       MOV     CH,(Offset DPBLEN)      ;  WORKSPACE
       CALL    MOVE
       RET
;
L_22    EQU     $
       dseg    $
       ORG     Offset L_22
;
;--------------------------------------------------
;The disk parameter block is moved here from CP/M-86
;
DPB     EQU     (Offset $)              ;DISK PARAMETER BLOCK (COPY)
;
SPT     RS      2                       ;SECTORS PER TRACK
BSH     RS      1                       ;BLOCK SHIFT
BLM     RS      1                       ;BLOCK MASK
EXM     RS      1                       ;EXTENT MASK
DSM     RS      2                       ;MAXIMUM BLOCK NUMBER
DRM     RS      2                       ;MAXIMUM DIRECTORY BLOCK NUMBER
AL0     RS      1                       ;DIRECTORY ALLOCATION VECTOR
AL1     RS      1                       ;DIRECTORY ALLOCATION VECTOR
CKS     RS      2                       ;CHECKED DIRECTORY ENTRIES
SYSTRK  RS      2                       ;SYSTEM TRACKS
;
;End of disk parameter block
;
DPBLEN  EQU     (Offset $)-(Offset DPB) ;LENGTH OF DISK PARM BLOCK
;
;--------------------------------------------------
BLKEXT  DB      0                       ;BLOCKS PER EXTENT
DIRBKS  DW      0                       ;CALCULATED # OF DIR BLOCKS
;
BFCB    DB      0,'[UNUSED]BAD',0,0,0,0
FCBDM   RS      17
;
NOMSG   DB      'No$'
ENDMSG  DB      ' bad blocks found',CR,LF,'$'
;
BADBKS  DW      0                       ;COUNT OF BAD BLOCKS
SECTOR  DW      0                       ;CURRENT SECTOR NUMBER
TRACK   DW      0                       ;CURRENT TRACK NUMBER
PHYSEC  DW      0                       ;CURRENT PHYSICAL SECTOR NUMBER
SECTBL  DW      0                       ;SECTOR SKEW TABLE POINTER
;
EXTNUM  DB      0FFH                    ;USED FOR UPDATING EXTENT NUMBER
DMCNT   DW      0                       ;NUMBER OF BAD SECTORS
DMPTR   DW      (Offset DM)             ;POINTER TO NEXT BLOCK ID
;
;storage for 5 byte BIOS function descriptor
;
FUNC            RS      1               ;bios function code goes here
BIOS_DESC       RS      2               ;[CX] data goes here
               RS      2               ;[DX] data goes here
;
DM      EQU     (Offset $)              ;BAD BLOCK ALLOCATION MAP
;
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
       DW      0                       ;RESERVE 2 BYTES
;
       END