; 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