home *** CD-ROM | disk | FTP | other *** search
- ;----------------------------------------------------------------------------;
- ; ATOMS.ASM: A simple installable character device driver
- ;----------------------------------------------------------------------------;
- ; based on skeleton in The MS-DOS Encyclopedia, (c) 1988 Microsoft Press
- ;----------------------------------------------------------------------------;
-
- include dos.inc ; macros to @ShowStr, etc...
-
- .model tiny, c
- .386
-
- ;----------------------------------------------------------------------------;
- ; Typedefs & Prototypes
- ;----------------------------------------------------------------------------;
-
- NPBYTE TYPEDEF NEAR PTR BYTE ; near pointer to a byte
- FPTR TYPEDEF FAR PTR ; far pointer
- NPTR TYPEDEF NEAR PTR ; near pointer
- SEGPTR TYPEDEF WORD ; segment selector
- DEVLINK TYPEDEF SDWORD ; link to other devices
- ATRIBUT TYPEDEF WORD ; device attribute word
-
- st_len PROTO NEAR, :NPBYTE, :SEGPTR ; gets the length of string into ax
- int2hex PROTO NEAR, :WORD, :NPBYTE ; converts integers to hexadecimal
- scan PROTO NEAR, :NPBYTE, :SEGPTR ; scans the command line
-
- ;----------------------------------------------------------------------------;
- ; Flags & Numeric Equates
- ;----------------------------------------------------------------------------;
-
- MaxCmd EQU 24t ; max command code for DOS 3.2+
- cr EQU 0Dh ; carriage return
- lf EQU 0Ah ; linefeed
- eom EQU '$' ; end-of-message signal
- space EQU 20h ; ascii space
- tab EQU 09h ; ascii tab
-
- ; definition of a Request Header structure so that we can access its elements.
- ; this structure is not exhaustive at all: block device use some addresses for
- ; different purposes, and have data beyond the cmd_seg word.
-
- REQ_HEADER STRUCT 1t
- lengt BYTE ? ; request header length
- unit BYTE ? ; unit number (for Block Devs. only)
- code BYTE ? ; command code
- stat WORD ? ; status word
- reserv QWORD ? ; reserved for DOS us
- media BYTE ? ; Media ID (for Block Devs. only)
- xfer NPTR ? ; offset of data buffer
- xseg SEGPTR ? ; segment of data buffer
- UNION
- xcount WORD ? ; count of bytes in request, or
- cmd_off NPTR ? ; offset of CONFIG.SYS line
- ENDS
- cmd_seg SEGPTR ? ; segment of CONFIG.SYS line
- REQ_HEADER ENDS
-
- ; values for the different 'magic numbers' used with Device Drivers
-
- fNEXTLINK EQU -1 ; word to tell DOS to substitute by address of
- ; next device
- fCHARDEVICE EQU 8000h ; bit to define device as a character device
- fOPENCLOSE EQU 0040h ; bit to indicate that device supports open/close
- fDONE EQU 0100h ; Status Done bit meaning device is done
- fERROR EQU 8000h ; Status Error bit meaning error on operation
- fWRITE_E EQU 000Ah ; Write Fault Error bit
- fUNKNOWN_E EQU 0003h ; Unknown Command Error bit
-
- ; values for the search flags used by Write Routine
-
- fDELETE EQU 2
- fINSERT EQU 4
- fSEARCH EQU 8
-
- ; value for buffer (atom memory) limits
-
- BUFMINIMUM EQU 00400h ; the minimum will be 1024 bytes
- BUFMAXIMUM EQU 07FFFh ; the maximum will be 32K. It can be less than that,
- ; but not greater, because of the signed index for
- ; scasb operations.
-
- ;----------------------------------------------------------------------------;
- ; Code Segment
- ;----------------------------------------------------------------------------;
- .code
-
- org 0 ; drivers should start at address 0000h
- ; this will cause a linker warning - ignore it.
-
- Header: ; device driver header
- DEVLINK fNEXTLINK ; link to next device driver
- ATRIBUT fCHARDEVICE+fOPENCLOSE ; device attribute word:
- ; char.device+open/close
- NPTR Strat ; 'Strat' entry point
- NPTR Intr ; 'Intr' entry point
- BYTE 'ATMS ' ; logical device name (needs to be 8 chars)
-
- ;----------------------------------------------------------------------------;
- ; data variables
- ;----------------------------------------------------------------------------;
-
- null BYTE 0 ; dummy to do a quick erase of pAtomValue
- pAtomVal NPBYTE null ; pointer to value of result of atom search
- MemEnd NPBYTE AtomList ; end of used memory: initially AtomList
- MaxMem NPBYTE AtomList ; end of available memory: initially AtomList
- lpHeader FPTR 0 ; far pointer to request header
-
- ;----------------------------------------------------------------------------;
- ; Dispatch table for the interrupt routine command codes
- ;----------------------------------------------------------------------------;
-
- Dispatch:
- NPTR Init ; 0 = init driver
- NPTR Error ; 1 = Media Check (block devices only)
- NPTR Error ; 2 = build BPB (block devices only)
- NPTR Error ; 3 = I/O control read (not supported)
- NPTR Read ; 4 = read (input) from device (int 21h, 3Fh)
- NPTR Error ; 5 = nondestructive read (not supported)
- NPTR Success ; 6 = ret input status (int 21h, 4406h)
- NPTR Error ; 7 = flush device input buffer (not supportd)
- NPTR Write ; 8 = write (output) to device (int 21h, 40h)
- NPTR Write ; 9 = write with verify (== write) (21h, 40h)
- NPTR OutStat ; 10 = ret output status (int 21h, 4407h)
- NPTR Error ; 11 = flush output buffer (not supported)
- NPTR Error ; 12 = I/O control write (not supported)
- NPTR Success ; 13 = device open (int 21h, 3Dh)
- NPTR Success ; 14 = device close (int 21h, 3Eh)
- NPTR Error ; 15 = removable media (block devices only)
- NPTR Error ; 16 = Output until Busy (mostly for spooler)
- NPTR Error ; 17 = not used
- NPTR Error ; 18 = not used
- NPTR Error ; 19 = generic IOCTL (not supported)
- NPTR Error ; 20 = not used
- NPTR Error ; 21 = not used
- NPTR Error ; 22 = not used
- NPTR Error ; 23 = get logical device (block devices only)
- NPTR Error ; 24 = set logical device (block devices only)
-
- ;----------------------------------------------------------------------------;
- ; Strategy Routine
- ;----------------------------------------------------------------------------;
- ; device driver Strategy routine, called by MS-DOS kernel with
- ; ES:BX = address of request header
- ;----------------------------------------------------------------------------;
-
- Strat PROC FAR
- mov word ptr cs:[lpHeader], bx ; save the address of the
- mov word ptr cs:[lpHeader+2], es ; request into 'lpHeader', and
- ret ; back to MS-DOS kernel
- Strat ENDP
-
- ;----------------------------------------------------------------------------;
- ; Intr
- ;----------------------------------------------------------------------------;
- ; Device driver interrupt routine, called by MS-DOS kernel after call to
- ; Strategy routine
- ; This routine basically calls the appropiate driver routine to handle the
- ; requested function.
- ; Routines called by Intr expect:
- ; ES:DI will have the address of the request header
- ; DS will be set to cs
- ; These routines should only affect ax, saving es,di,ds at least
- ;
- ; Input: NONE Output: NONE -- data is transferred through request header
- ;
- ;----------------------------------------------------------------------------;
-
- Intr PROC FAR
- pusha ; save registers
- pushf ; save flags
- cld ; direction flag: go from low to high address
-
- mov si, cs ; make local data addressable
- mov ds, si ; by setting ds = cs
-
- les di, [lpHeader] ; ES:DI = address of req.header
-
- ASSUME di:PTR REQ_HEADER ; to be able to use the REQ_HEADER offsets
-
- xor ebx, ebx ; erase ebx
- mov bl,es:[di].code ; get EBX = command code (from req.header)
-
- .IF (bx > MaxCmd) ; check to make sure we have a valid
- call Error ; command code
- .ELSE ; else, call command-code routine,
- call NPTR PTR Dispatch[2*ebx] ; indexed from Dispatch table
- .ENDIF ; (Ebx used to allow scaling factors)
-
- or ax, fDONE ; merge Done bit into status and
- mov es:[di].stat,ax ; store status into request header
-
- ASSUME di:NOTHING ; di will be something else now
-
- popf ; restore registers
- popa ; restore flags
- ret ; return to MS-DOS kernel
- Intr ENDP
-
- ; Command-code routines are called by the Interrupt routine via the dispatch
- ; table. Each routine should return AX = 0000H if function was completed
- ; successfully or AX = 8000h + error code if function failed.
-
- ;----------------------------------------------------------------------------;
- ; function 4 = read (input) from device
- ;----------------------------------------------------------------------------;
- ; what the function basically does is transfer from the string that pAtomVal
- ; points, to the buffer pointed to in the request header. the values have
- ; to be loaded into ES:DI and DS:SI for the movsb instruction, and other
- ; checks are made to account for differences in sizes ;
- ;----------------------------------------------------------------------------;
-
- Read PROC NEAR USES ds es di
-
- lds si, [lpHeader] ; put request header address in DS:SI
-
- ASSUME si:PTR REQ_HEADER ; to use the REQ_HEADER offsets
-
- mov cx, [si].xcount ; load cx with the size of buffer
- mov es, [si].xseg ; load es with segment of buffer
- mov di, [si].xfer ; load di with offset of buffer
-
- ASSUME si:NOTHING ; si will be something else now
-
- mov si, cs ; load ds with cs again
- mov ds, si
- mov si, pAtomVal ; load si with the string's address
-
- INVOKE st_len, si, ds ; load length of pAtomVal into ax
-
- .IF (cx > ax) ; if cx > ax, then the buffer has space
- mov bx, ax ; to hold all the string. bx (used to
- mov cx, ax ; return number of chars transferred)
- rep movsb ; will hold the string length. We move
- ; into cx the length of the string,
- ; and copy the result string into the
- ; buffer, with the 'rep movsb' instruct
- .ELSE
- mov bx, cx ; otherwise, bx will be the size of the
- dec cx ; the buffer. decrement cx so that we
- rep movsb ; leave the last char of the buffer
- xor ax,ax ; free to store a 0 to mark the end of
- stosb ; the string.
- .ENDIF
-
- les di, [lpHeader] ; re-load the request header
-
- ASSUME di:PTR REQ_HEADER ; to use the REQ_HEADER offsets
-
- mov es:[di].xcount, bx ; store the number of chars transferred
-
- ASSUME di:NOTHING ; di will be something else now
-
- mov pAtomVal, offset null ; set pAtomVal to null:won't read twice
- xor ax, ax ; set status to OK
- ret ; return
-
- Read ENDP
-
- ;----------------------------------------------------------------------------;
- ; output (write) status: if there's still memory, fine. Otherwise, error
- ;----------------------------------------------------------------------------;
-
- OutStat PROC NEAR
- mov ax, MemEnd ; mov MemEnd into ax
- inc ax ; increment ax (test space for 1 more char)
- .IF (ax < MaxMem) ; if MemEnd less than MaxMem, we have space,
- xor ax, ax ; set status to OK
- .ELSE ; otherwise, set the Write Fault error
- mov ax, fERROR + fWRITE_E
- .ENDIF
- ret
- OutStat ENDP
-
- ;----------------------------------------------------------------------------;
- ; function 8 = write (output) to device from program
- ;----------------------------------------------------------------------------;
- ; big chunk of the program.
- ; if string: 'variable', it's a search
- ; if string: 'variable=' it's a delete
- ; if string: 'variable=value' it's an insert
- ;----------------------------------------------------------------------------;
-
- Write PROC NEAR USES si es di cx ds
- LOCAL func:BYTE, count:WORD, pAtom:NPTR, buf_seg:SEGPTR, buf_off:NPTR
- ; func is used to see what function we're doing
- ; count is used to store the length of the current atom
- ; pAtom is used to point to the start of the current atom
- ; buf_seg and buf_off point to the buffer to read from
-
- ASSUME di:PTR REQ_HEADER ; to use the REQ_HEADER offsets
-
- mov ax, es:[di].xseg ; load buf_seg with the buffer segment
- mov buf_seg, ax
- mov cx, es:[di].xfer ; load buf_off with the buffer offset
- mov buf_off, cx
-
- ASSUME di:NOTHING ; di will be something else now
-
- mov es, ax ; load string into ES:DI
- mov di, cx
- INVOKE st_len, di, es ; load string length into ax, since
- ; we're not interested in xcount (for
- ; safety reasons)
- mov count, ax ; Store length in count
- mov cx, ax ; and copy to cx
-
-
- ; find if we're doing a search, delete or insert
-
- mov bx, cx ; store the count temporarily
- mov al, '=' ; we will look in the string for a '='
- repnz scasb ; do the scanning
- .IF (ZERO?) ; zero will be set if we found a '=',
- xor al, al ; in which case we have to see if it's a delete
- scasb ; or insert: in a delete, the char after the
- ; '=' has to be a 0
- .IF (ZERO?) ; if there was a 0 after the =,
- mov func,fDELETE ; set the function to delete
- .ELSE
- mov func,fINSERT ; else, the function is insert
- .ENDIF
- .ELSE
- mov func, fSEARCH ; else (no '=' in string) it's a search
- .ENDIF
- inc cx ; cx holds amount of bytes after the '=', so
- sub bx, cx ; by increasing it, cx holds # of bytes after
- ; the variable name. If we substract cx from
- ; the original length (in bx), bx will have
- ; the length of the variable only
-
- .IF (bx == 0) ; if the length of the variable is 0 (i.e.
- xor ax, ax ; string= "=....", then finish. set ax to OK
- jmp wri_e ; jump to the return place
- .ENDIF
-
- mov pAtomVal, offset null ; set pAtomVal to null. only a search
- ; will change this value, taken care
- ; of in the loops below
-
- ; now go into the memory space and search for a matching variable. we will stop
- ; if we find a match or a variable that starts with a 0 (meaning no matches)
-
- mov si, offset AtomList ; load SI the start of the driver memory
- ; (pointed to by AtomList, of course)
-
- .WHILE 1
- mov pAtom, si ; save the start of the current atom in memory
- INVOKE st_len, si, ds ; length of current atom will be in ax
-
- .IF (ax <= 1) ; if the length of the current atom is 1 (never
- mov di, si ; can be 0), that means that we reached the end
- mov si, ds ; of the driver memory. set ES:DI to DS:SI, the
- mov es, si ; end of memory,
- .BREAK ; and break from the loop
- .ENDIF
-
- mov di, buf_off ; restore di to the buffer offset
- mov cx, bx ; load into the count reg. the variable length
-
- .IF (cx >= ax) ; if the length of the var.name is greater than
- add si, ax ; the whole atom length, it's sure not to be a
- .CONTINUE ; match. increase si by the length of the
- .ENDIF ; current atom, and continue to next iteration
-
- repe cmpsb ; find the first non-matching character between
- ; the current atom and the string buffer, for
- ; a maximum count of the length of the name
-
- .IF (!ZERO? || (byte ptr [si]!='='))
- mov si,pAtom; if we didn't stop because of two chars not
- add si,ax ; matching in the repe, or if the next char
- .CONTINUE ; is not a '=' (i.e. comspec would match com),
- .ENDIF ; increase si by the length of the atom, and
- ; go to the next iteration
-
- ; at this point we have found a match for the requested variable
-
- .IF (func==fSEARCH) ; if we're doing a search,
- inc si ; increment si (to skip over the '=')
- mov pAtomVal,si ; set pAtomVal to address of the char
-
- .ELSE ; else, we're doing a delete or insert: we have
- ; to take the current atom out of the memory.
- ; we will scan for the chars beyond our current
- ; atom. then we shift those chars to a position
- ; 'ax' places back. ax still holds the length
- ; of the current atom
- mov si,pAtom; set si to the beginning of the current atom,
- mov di, ds ; set ES:DI to be the same as DS:SI, since
- mov es, di ; scan uses ES:DI
- mov di, si
-
- add di, ax ; set DI to point to the char just after the
- mov bx, ax ; current atom. save atom length in bx.
-
- xor al, al ; clear al (will be used in the scan)
- mov cx, -1 ; cx=-1 so that the reps only stop with a match
-
- .WHILE 1 ; a loop:
- dec cx ; decrease cx (because scasb doesn't)
- scasb ; and test if it's a 0 (to signal end)
- .BREAK .IF ZERO?; break if the character matches
- repne scasb ; else repeat scanning (decrements cx)
- .ENDW ; until the next 0. then continue loop.
-
- not cx ; if cx=-(length+1), not cx=length (see st_len)
-
- mov si,pAtom; get back to the beginning of the atom to
- mov di, si ; erase. the source for the movsb will be ahead
- add si, bx ; by bx chars (the atom length).
- rep movsb ; move as many chars as left in memory
-
- dec di ; for the insert following, we'll want di to
- ; point to the end of memory. the rep took us
- ; one beyond, so we decrement it.
-
- sub MemEnd,bx ; De-allocate the memory used by atom
-
- .ENDIF ; of (func == fSEARCH)
-
- .BREAK ; break from the loop: one match is all we need
-
- .ENDW ; actually, no one gets here. they either do a
- ; continue or a break.
-
- ; if we're doing an insert, we'll do it at the end of the memory space
-
- .IF (func == fINSERT)
- mov ds, buf_seg ; load DS:SI with the buffer to read from
- mov si, buf_off
- mov cx, count ; cx and bx will have the count
- mov bx, cx
-
- add bx, cs:MemEnd ; add to bx the end of memory
-
- ; if we have a carry, it means that if bx > MaxMem, we don't have space
- ; in our local AtomList. The bx>MaxMem test is not enough because if
- ; MemEnd is high and adding cx goes over the segment boundary (starting
- ; at 0000h), we wouldn't catch it with a bx >= MaxMem
-
- .IF (CARRY? || bx >= cs:MaxMem)
- mov ax, fERROR + fWRITE_E ; set write fault error
- jmp wri_e
- .ELSE
- mov cs:MemEnd , bx ; set new memory size
- .ENDIF
-
- rep movsb ; copy the buffer into our memory
-
- xor ax, ax ; add a null byte to the end of strings,
- stosb ; to signal the end of memory
- .ENDIF
-
- xor ax, ax ; set status to OK
-
- wri_e: ret ; return!
-
- Write ENDP
-
- ;----------------------------------------------------------------------------;
- ; Success: When the only thing the program needs to do is set status to OK
- ;----------------------------------------------------------------------------;
-
- Success PROC NEAR
- xor ax, ax ; set status to OK
- ret
- Success ENDP
-
- ;----------------------------------------------------------------------------;
- ; error: set the status word to error: unknown command
- ;----------------------------------------------------------------------------;
- Error PROC NEAR
- mov ax, fERROR + fUNKNOWN_E ; error bit + "Unknown command" code
- ret
- Error ENDP
-
- ;----------------------------------------------------------------------------;
- ; st_len
- ; finds the length of the string pointed to by st_off and st_seg and stores it
- ; in ax. no other registers are affected
- ;----------------------------------------------------------------------------;
-
- st_len PROC NEAR USES es di cx, st_off:NPBYTE, st_seg:SEGPTR
- mov di, st_off ; load string into ES:DI, since scas uses
- mov es, st_seg ; those registers
- xor al, al ; clear al
- mov cx, -1 ; by using cx=-1, repnz won't stop until
- repnz scasb ; it finds a match. cx will be -(length-1)
- not cx ; since we use two's complement, not cx=length!
- mov ax, cx ; mov length into ax, and
- ret ; return
- st_len ENDP
-
-
- ;----------------------------------------------------------------------------;
- ;****************************************************************************;
- ;----------------------------------------------------------------------------;
- ; ;
- ; BEGINNING OF SPACE TO BE USED AS DRIVER MEMORY ;
- ; ALL CODE AFTER ATOMLIST WILL BE ERASED BY THE DRIVER'S DATA ;
- ; OR BY OTHER LOADED DRIVERS ;
- ; ;
- ;----------------------------------------------------------------------------;
- ;****************************************************************************;
- ;----------------------------------------------------------------------------;
-
-
- ;----------------------------------------------------------------------------;
- ; Initialization Data and Code
- ; Only needed once, so after the driver is loaded and initialized it releases
- ; any memory that it won't use. The device allocates memory for its own use
- ; starting from 'AtomList'.
- ;----------------------------------------------------------------------------;
-
- AtomList BYTE 0
- Ident BYTE '* ATOMS Device Driver *',lf,cr,eom
- Border BYTE '*********************************************',lf,cr,eom
- scan_default BYTE '* Using default value of 1024 bytes *',lf,cr,eom
- scan_ignore BYTE '* Ignoring extra parameters in command line *',lf,cr,eom
- scan_minimum BYTE '* Minimum value of 1024 bytes will be used *',lf,cr,eom
- scan_overflow BYTE '* Maximum value of 32K bytes will be used *',lf,cr,eom
- HexDump BYTE '* Beginning atoms memory: '
- Hex1 BYTE '0000h : '
- Hex2 BYTE '0000h *',cr,lf
- BYTE cr,'* Ending atoms memory: '
- Hex3 BYTE '0000h *',cr,lf,eom
-
- ;----------------------------------------------------------------------------;
- ; Init
- ;----------------------------------------------------------------------------;
- ; routine called only once to initialize the driver. It shows the startup
- ; message, scans the config.sys command line, and tells MS-DOS how much memory
- ; it needs. Note that under DOS 5.0 the request header also tells you how
- ; much memory you could use.
- ;----------------------------------------------------------------------------;
-
- Init PROC NEAR USES es di
-
- @ShowStr Border ; use dos.inc macros to start the init
- @ShowStr Ident ; message
-
- INVOKE int2hex, cs, ADDR Hex1 ; get starting segment in Hex
- INVOKE int2hex,MaxMem,ADDR Hex2; get starting offset in Hex
-
- les di, [lpHeader] ; allow us to use the request values
-
- ASSUME di:PTR REQ_HEADER ; to use the REQ_HEADER OFFSETS
-
- ; scan the config.sys command line
- INVOKE scan, es:[di].cmd_off, es:[di].cmd_seg
-
- add ax, MemEnd ; set ax to End of Memory relative to
- ; previous end of memory.
- mov MaxMem, ax ; store the new value in MaxMem
- mov es:[di].xseg,cs ; tell MS-DOS the end of our used
- mov es:[di].xfer,ax ; memory (the break address)
-
- ASSUME di:NOTHING ; di will be something else now
-
- INVOKE int2hex, ax, ADDR Hex3 ; Get ending offset in Hex
- @ShowStr HexDump ; print the Hex Dump of Memory bounds
-
- @ShowStr Border ; show the end of our initial message
-
- xor ax, ax ; return status OK
- ret
- Init ENDP
-
-
- ;----------------------------------------------------------------------------;
- ; scan
- ;----------------------------------------------------------------------------;
- ; scans the string passed by ms-dos on initialization for the amount of
- ; memory to allocate to the driver. Since we get a pointer to the string
- ; after the '=', first scan over the filename and white space, then for the
- ; ASCII number and turn it into decimal.
- ; the string we'll get might have some white space (or not) before the device
- ; name, then the device name, then either the end-of-line (and we'd use the
- ; default values) or some white space and the end-of-line (same thing) or
- ; some white space and a number maybe followed by some white space or the end
- ; of line. If there's something after the number, we'll output the extra
- ; parameters warning.
- ;
- ; returns: AX = number of bytes to allocate
- ;----------------------------------------------------------------------------;
-
- scan PROC NEAR USES ds si, st_off:NPBYTE, st_seg:SEGPTR
- LOCAL result ; result will hold the amount to allocate
-
- mov result, 0 ; clear the result variable
-
- mov ds, st_seg ; lodsb uses DS:SI as the source string
- mov si, st_off
-
- .REPEAT ; load from the string into the accumulator
- lodsb ; while it's white space
- .UNTIL ((al!=tab) && (al!=space))
-
- ; scan until we find a white space or the end
- ; of the line (note that we don't test the 1st
- .REPEAT ; character after the spaces: it has to be a
- lodsb ; letter o.w. DOS wouldn't load the driver)
- .UNTIL ((al==tab) || (al==space) || (al==cr) || (al==lf))
-
- .IF ((al==cr) || (al==lf)) ; if we found the end of line, we'll
- jmp scan_d ; use the default values. jump there.
- .ENDIF
-
- .REPEAT ; scan for the next non-white space
- lodsb
- .UNTIL ((al!=tab) && (al!=space))
-
- .IF ((al==cr) || (al==lf)) ; if we found the end of line, we'll
- jmp scan_d ; use the default values. jump there.
- .ENDIF
-
- .REPEAT ; scan for the decimal numbers:
- .IF ((al<'0')||(al>'9')); if it's not an ascii decimal number
- jmp scan_i ; ignore the parameter, use defaults
- .ENDIF
- cbw ; set ax to 0 (by extending al to ax)
- sub ax, '0' ; turn ax into a number 0-9
- mov cx, ax ; save the number in cx for a while,
- mov ax, result ; because ax will be used to multiply
- mov bx, 10t ; 'result' by 10, to add a 'digit' to
- mul bx ; the previous 'result'
- jc scan_o ; if we had an overflow, i.e. result
- ; is more than ffffh, then use max.val
- ; by jumping to the overflow section.
- add ax, cx ; else, add the units to the multiplied
- mov result, ax ; result, and store it where it goes
- lodsb
- .UNTIL ((al==tab) || (al==space) || (al==cr) || (al==lf))
- ; when we find a space, tab, cr, or lf,
- ; it's not an error: we've finished
- ; the number scan.
-
- .IF ((al==cr) || (al==lf)) ; if we found the end of line, we'll
- jmp scan_d ; use the result value so far.
- .ENDIF
-
- .REPEAT ; skip over the white space, again
- lodsb
- .UNTIL ((al!=tab) && (al!=space))
-
- .IF ((al==cr) || (al==lf)) ; if we found the end of line, we're
- jmp scan_d ; fine, use the result value.
- .ENDIF
- ; otherwise, we scanned something not
- ; a space or end-of-line after the
- ; digits, so we drop into the ignore
- ; warning display.
-
- scan_i:
- ; we found something extra
- @ShowStr scan_ignore,cs ; show the warning string
- ; and drop into scan_d
-
- scan_d:
- push cs ; make local data addressable (since
- pop ds ; we rewrote ds in the scanner above)
- .IF (result == 0) ; if we didn't scan anything into
- @ShowStr scan_default ; result, show a warning that we will
- mov ax, BUFMINIMUM ; use the minimum. Set ax to minimum.
- .ELSEIF (result < BUFMINIMUM) ; if the result is less than the
- @ShowStr scan_minimum ; minimum, display a warning and set
- mov ax, BUFMINIMUM ; the buffer to the minimum
- .ELSEIF (result > BUFMAXIMUM) ; if result is more than the maximum
- @ShowStr scan_overflow ; display a message to inform the user
- mov ax, BUFMAXIMUM ; and set the buffer to the maximum
- .ELSE ; otherwise, result is acceptable
- mov ax, result ; set ax to result.
- .ENDIF
- ret ; return
-
- scan_o: ; overflows (request for >ffffh buffer)
- mov result, BUFMAXIMUM+1 ; set ax to a value that will cause
- jmp scan_d ; scan_d to print out an overflow
- ; message and set buffer to 32K
-
- scan ENDP
-
-
- ;----------------------------------------------------------------------------;
- ; int2hex
- ; Converts a WORD into its hexadecimal representation.
- ; Based on Chapter 4 of the MASM Programmer's guide
- ;----------------------------------------------------------------------------;
-
- ; Table of hexadecimal digits
- hex BYTE '0123456789ABCDEF'
-
- int2hex PROC NEAR USES ax bx si, number:WORD, string:NPBYTE
-
- mov bx, OFFSET hex ; load table address
- mov si, string
-
- mov ax, number ; load value to convert
- shr ax, 12 ; shift right to get into table index
- and ax, 0000Fh ; remove all but least-significant byte
- xlat ; translate
- mov [si], al ; store as last byte
-
- mov ax, number ; load value to convert
- shr ax, 8 ; shift right to get into table index
- and ax, 0000Fh ; remove all but least-significant byte
- xlat ; translate
- mov [si+1], al ; store as third to last byte
-
- mov ax, number ; load value to convert
- shr ax, 4 ; shift right to get into table index
- and ax, 0000Fh ; remove all but least-significant byte
- xlat ; translate
- mov [si+2], al ; store as second to last byte
-
- mov ax, number ; load value to convert
- and ax, 0000Fh ; remove all but least-significant byte
- xlat ; translate
- mov [si+3], al ; store as last byte in string
-
- ret
-
- int2hex ENDP
-
- END
-