;       NUMBERS: Part of SIO2PC

; this file has the routines that INPUT, CONVERT, PRINT
; and COMPARE numbers to and from DECIMAL, HEX, ASCII, etc.

; Contains routines:

;       BYTE2HEX        DECTOINT        ASCI_SIZE
;       INT2DEC         HEX_IT          GET_HEX
;       RANGE           RANGE10         PUT_SIZE
;       GET4H           GET_HEX         GET_CRC
;	INC_CRC 	DO_CRC		HEXDIGIT

; HEXDIGIT takes a single hex digit (ASCII) in AL and converts it
; to the equivalent number in AL.  If the digit isn't valid, it
; returns with carry set

HEXDIGIT:
	 CMP AL, '0'
	 JB > L1
	 CMP AL, '9'
	 JA > L2

; it's a digit, 0 to 9 ...

	 SUB AL, '0'
	 JMP > L3

L2:	 CALL TO_UPPER
	 CMP AL, 'A'
	 JB > L1
	 CMP AL, 'F'
	 JA > L1

; it's a digit A-F

	 SUB AL, 'A'-10
	 CLC
L3:	 RET	; good return
L1:	 STC
	 RET	; bad return


; RANGE checks AL for ASCII value 1 to 8 and carry clear means
; OK, otherwise prints "value out of range". If carry is clear,
; AL contains # 0 - 7 corresponding to ASCII '1' to '8'.

RANGE:  SUB AL,'1'
        CMP AL, 8
        JB >L1
        PSTATUS VOR, ATTR9
        TICKS 36
        CALL FLUSH
        CLC
L1:     CMC
        RET

; RANGE10 checks a string, pointed to by BX and terminated by 0
; for values in range '0' thru '9'.  Returns with carry clear if
; good.  Uses AL
; If string is terminated by 0Dh, it converts the 0Dh to 0

R10A:   INC BX
RANGE10:
        MOV AL, BYTE PTR [BX]
	CMP AL, 0 ; 3.17
	JE > L2
        CMP AL, 0Dh     ; end of string?
        JNE >L1 ; check some more
        MOV BYTE PTR [BX], 0    ; convert to ASCIIZ
L2:	CLC	; good return
        RET

L1:     CMP AL, '0'
        JB >L1   ; (jumps if carry is set)
        CMP AL, '9'
        JA >L1   ; CF = 0
        JMP R10A
L1:     STC     ; bad return
        RET

        

; BYTE2HEX - A subroutine to take the byte in AL and convert
; it to ASCII hex digits in BX, low in BH, high in BL. AL is
; not changed. BX and AH are changed.

BYTE2HEX:
        PUSH CX
        XOR BH,BH       ; BL is second pass flag
        MOV AH,AL       ; Make copy of byte
ABR:    AND AL,00001111xB; mask off high nybble
        MOV BL,'A'-0Ah  ; Assume nybble >= 'A'
        CMP AL,0Ah      ; Less than hex 'A'?
        JAE >L1
        MOV BL,'0'      ; Assumption was wrong
L1:     ADD BL,AL
        TEST BH,BH      ; 0 means first pass
        JNE >L1
        MOV BH,BL
        MOV AL,AH
        MOV CL,4
        SHR AL,CL
        JMP ABR
L1:     POP CX
        RET

;
; A subroutine to convert an ASCII decimal number string into a 16 bit
; integer.  BX should  point to the ASCIIZ string's first byte on
; entry. Answer is returned in AX.

; Notes: The number can't be bigger than 2^16 or about 65000
;        It's assumed that the string is valid numeric chars only
;        A revision 3.00 addition.

DECTOINT:
       PUSH CX, DX
       SUB AX, AX       ; AX will accumulate and return the integer
       MOV CX, 10       ; CX holds the multiplier
       SUB DX, DX
       CMP BYTE PTR [BX], 0 ; 0 means end of ASCIIZ string
       JE ACH
L1:     MOV DL, BYTE PTR [BX]
        SUB DL, '0'     ; Convert ASCII digit to number
        ADD AX, DX      ; Subtotal in AX
        INC BX          ; point to next digit
        CMP BYTE PTR [BX], 0
        JE ACH
        MUL CX          ; puts AX * CX into DX:AX
        JMP SHORT L1
ACH:    POP DX,CX
        RET

; This routine takes the disk size information from the table
; entry pointed to by BP and converts it to ASCII (using INT2DEC)
; and puts it into the ADSKSZ field pointed to by BP. It also adds
; "K:" after the number.

ASCI_SIZE:
        PUSH BX
        MOV DX, [HI_SIZE+BP]   ; size in paragraphs, high word
        MOV AX, [LO_SIZE+BP]      ; low word
        MOV BX, 64              ; size in paragraphs divided by
        DIV BX                  ; 64 gives size in KB
        MOV DI, OFFSET ADSKSZ   ; INT2DEC wants DI to point to the
        ADD DI, BP              ; ASCII string field (LSB)
        ADD DI, 4               ; point to LSB - rightmost digit
        MOV BYTE PTR [DI+1], 'K'
        MOV BYTE PTR [DI+2], ':'
        CALL INT2DEC
        POP BX
        RET

; This routine converts the # in AX to an ASCII decimal string
; pointed to by DI. Note that this must be at least a 5 digit
; space since a 16 bit number can be 65000 or so. The 10,000's
; digit goes at the left.  Leading 0's are blanked.  DI must
; point to the one's digit on entry.

INT2DEC:
        PUSH BX
        PUSH DX ; Hi part of dividend: must be 0
        PUSH CX
        MOV CX, 5
L1:     SUB DX, DX
        MOV BX, 10
        DIV BX  ; quotient in AX, remainder in DX
        ADD DX, '0'     ; convert to ASCII
        MOV [DI], DL    ; one's digit
        DEC DI          ; move one digit left
        LOOP L1

; now, blank the leading 0's

        INC DI          ; adjust to 5th (left) digit
        MOV CX, 4       ; don't blank 0 in one's place if only digit
L1:     CMP BYTE PTR [DI], '0'
        JNE ACW
        MOV BYTE PTR [DI], ' '
        INC DI
        LOOP L1
ACW:    POP CX
        POP DX
        POP BX
        RET

; PUT_SIZE is to automatically fill in the size for the VIRUS warning


PUT_SIZE:
        MOV AX, (XEND_PROG - START + 1); get size of program

; REV 3.08 ADDED + 1 (above)

        MOV DI, (SAY_SIZE + 4)
        CALL INT2DEC
        RET

;       GET4H - Gets 4 hex digits (max) and returns the number in BX
;       RETURN terminates the input. ESC aborts, returns with carry set


GET4H:
        MOV CX,4; CX is the counter for the 4 digits
        MOV BX,0; BX will accumulate the 4 digits

GHX:    CALL GET_HEX
        CMP AL,15
        JA IVLD
        PUSH CX
        MOV CL,04
        SHL BX, CL; Move lower 3 nybbles of BX up one
        AND AL,0Fh; Mask high nybble of AL
        OR BL,AL;   Put in new low nybble
        POP CX
        LOOP GHX
        CLC     ; Carry clear means normal completion of subroutine
        RET
IVLD:
        CMP AL,020h;    Invalid char other than ESC or RETURN
        JE GHX;         Get another
        CMP AL,27;      Set carry to indicate abort subroutine
        JNE >L1
        STC;    Return with carry set means aborted by ESC key
        RET

L1:     CLC;    Return was pressed. Return with good # < 4 digits
        RET



;       GET_HEX - a routine which gets HEX digits from the keyboard
;       puts them to the screen, and returns with the digit's value
;       in AL. If return is pressed, FFh will be returned. If ESC is
;       pressed, 1Eh (27d) will be returned. Otherwise, 020h will be
;       returned. Normal range returned is 0 to F hex.

GET_HEX:
        PUSH DX
        PUSH BX
        PUSH CX
        MOV AH,08h
        INT 021h
        POP CX
        POP BX
        CMP AL,'f'; If a lowercase 'a' thru 'f' was returned, convert
        JA INVLD;   to upper and continue.
        CMP AL,'a'
        JB >L1
        AND AL,11011111xB;       Convert to uppercase
L1:     CMP AL,'0'
        JB INVLD
        CMP AL,'F'
        JA INVLD
        CMP AL,'9'
        JA >L1
        MOV AH,02h
        MOV DL,AL
        INT 021h
        SUB AL,030h
        POP DX
        RET
L1:     CMP AL,'A'
        JB INVLD
        MOV AH,02h
        MOV DL,AL
        PUSH CX,BX
        INT 021h
        POP BX,CX
        SUB AL,037h
L0:     POP DX
        RET

INVLD:
        CMP AL, 013; RETURN?
        JNE >L1
        MOV AL,0FFh
        JMP L0
L1:     CMP AL,27; ESCAPE?
        JE >L1
NOTCR:  MOV AL,020h
L1:     POP DX
        RET

; A routine to force the string byte in AL to uppercase:
; In rev 3.09, I'm going to make sure the character is
; really in the range [a,z] first

TO_UPPER:
        CMP AL,'a'
        JB >L1
        CMP AL,'z'
        JA >L1
        AND AL, 11011111xB; clear bit 5 to force uppercase
L1:     RET


; DO_CRC computes a CRC for certain specific parts of SIO2PC.COM
; by calling GET_CRC for each part to adjust the current CRC:

DO_CRC:
	PUSH NEXTS
	POP ES
	MOV DI, 0100h   ; Place to start CRC
        MOV CX, CRCA
	SUB CX, 0100h; # of bytes to check
	MOV AX, 0       ; Initial value of CRC
	MOV BX, AX      ; Initialize testing register
	CALL GET_CRC

; Now, skip user changeable fields and do CRC on the remainder:

	MOV DI, OFFSET SBAD_CRC
	MOV CX, (OFFSET NEXT_SEG - OFFSET SBAD_CRC + 1)
	CALL GET_CRC    ; AX already holds current CRC
	MOV CAL_CRC, AX; save it
	CALL BYTE2HEX
	MOV WORD PTR[SCRC+2],BX
	MOV AX, CAL_CRC
	XCHG AL, AH
	CALL BYTE2HEX
	MOV WORD PTR SCRC, BX
	MOV AX, CAL_CRC
	CMP CRC, AX; moment of truth...
        JE >L1
        PSTATUS  SBAD_CRC, ATTR8B
        PRINTLA SBAD_CRCA, ATTR11

; Offer to fix the bad CRC: rev 3.08:
; In rev 3.11, user has to type 'OK', not just 'Y', because
; I want him to think about it...

FUD:
        MOV AH,0
        INT 016h; Returns char in AL
        CALL TO_UPPER
        CMP AL, 'O'

        JNE >L2
        MOV AH,0
        INT 016h
        CALL TO_UPPER
        CMP AL, 'K'
        JNE >L2
        CALL W_CRC
L2:     RET
L1:     PSTATUS SGOOD_CRC, ATTR9
	TICKS 18
	RET

; GET_CRC computes the CRC of the specified range, where ES:DI
; points to the first byte and CX contains the number of bytes
; The CRC is returned in AX, which must be 0 on entry unless you
; are extending a partially completed CRC.  This sub. requires
; sub. INC_CRC below

GET_CRC:
	MOV BL, ES:[DI]
;        XOR BH, BH
	PUSH CX
	CALL INC_CRC
	POP CX
	INC DI
	LOOP GET_CRC
	RET

; This routine adjusts the CRC calculation for the next byte
; The CRC is in AX and the next byte is in BX on entry. This routine
; is called by GET_CRC above.

INC_CRC:
	MOV CX, 8; counter
TOP_CRC:
	TEST AX, 08000h
        JNE >L1
	SHL AX, 1
	SHL BX, 1
	TEST BX, 0400h
	JE NEXT_CRC
	INC AX
NEXT_CRC:
	XOR AX, 01021h
	LOOP TOP_CRC
	RET

L1:     SHL AX, 1
	SHL BX, 1
	TEST BX, 0400h
        JE >L1
	INC AX
L1:     LOOP TOP_CRC
        RET

