Title Irvine32 Link Library Source Code (Irvine32.asm) Comment @ Recent Updates: 04/06/2003 (Irvine): ReadKey corrected ReadKeyFlush added ReadKeyTranslate added 04/16/2003 (Cahill): Modifications to ReadKey and ReadKeyTranslate 08/18/2003 (Irvine) ReadChar replaced by Kip Irvine 10/12/2003 (Cahill) ReadInt acoorected by Cahill This library was created exlusively for use with the book, "Assembly Language for Intel-Based Computers", 4th Edition, by Kip R. Irvine, 2002-2003. Copyright 2002-2003, Prentice-Hall Incorporated. No part of this file may be reproduced, in any form or by any other means, without permission in writing from the author or publisher. Acknowledgements: ------------------------------ Most of the code in this library was written by Kip Irvine. Special thanks to Gerald Cahill for his many insights, suggestions, and bug fixes. Thanks to Richard Stam for his development of Readkey-related procedures. Alphabetical Listing of Procedures ---------------------------------- (Unless otherwise marked, all procedures are documented in Chapter 5.) Clrscr Crlf Delay DumpMem DumpRegs GetCommandtail GetDateTime Chapter 11 GetMaxXY Added 10/20/2002 GetMseconds Gotoxy IsDigit Random32 Randomize RandomRange ReadChar ReadDec Added 11/11/2002 ReadHex ReadInt ReadKey Added 4/5/2003 ReadKeyFlush Added 4/5/2003 ReadKeyTranslate Added 4/5/2003 (private) ReadString SetTextColor Str_compare Chapter 9 Str_copy Chapter 9 Str_length Chapter 9 Str_trim Chapter 9 Str_ucase Chapter 9 WaitMsg WriteBin WriteChar WriteDec WriteHex WriteHexB Added 11/18/2002 WriteInt WriteString Implementation Notes: -------------------- 1. The Windows Sleep function modifies the contents of ECX. 2. Remember to save and restore all 32-bit general purpose registers (except EAX) before calling MS-Windows API functions. ---------------------------------------------------------------------@ ;OPTION CASEMAP:NONE ; optional: force case-sensitivity INCLUDE Irvine32.inc ; function prototypes for this library INCLUDE Macros.inc ; macro definitions ;--------------------------------------------------------------------- ShowFlag MACRO flagName,shiftCount LOCAL flagStr, flagVal, L1 ; ; Helper macro. ; Display a single CPU flag value ; Directly accesses the eflags variable in Irvine16.asm/Irvine32.asm ; (This macro cannot be placed in Macros.inc) ;--------------------------------------------------------------------- .data flagStr DB " &flagName=" flagVal DB ?,0 .code push eax push edx mov eax,eflags ; retrieve the flags mov flagVal,'1' shr eax,shiftCount ; shift into carry flag jc L1 mov flagVal,'0' L1: mov edx,OFFSET flagStr ; display flag name and value call WriteString pop edx pop eax ENDM ;------------------------------------------------------------- CheckInit MACRO ; ; Helper macro ; Check to see if the console handles have been initialized ; If not, initialize them now. ;------------------------------------------------------------- LOCAL exit cmp InitFlag,0 jne exit call Initialize exit: ENDM ;********************* SHARED DATA AREA ********************** MAX_DIGITS = 80 .data ; initialized data InitFlag DB 0 ; initialization flag xtable BYTE "0123456789ABCDEF" .data? ; uninitialized data consoleInHandle DWORD ? ; handle to standard input device consoleOutHandle DWORD ? ; handle to standard output device bytesWritten DWORD ? ; number of bytes written eflags DWORD ? digitBuffer BYTE MAX_DIGITS DUP(?),? buffer DB 512 DUP(?) bufferMax = ($ - buffer) bytesRead DD ? sysTime SYSTEMTIME <> ; system time structure .code ;------------------------------------------------------------- Clrscr PROC LOCAL bufInfo:CONSOLE_SCREEN_BUFFER_INFO ; ; Clear the screen by writing blanks to all positions ; Receives: nothing ; Returns: nothing ; Last update: 10/15/02 ; ; The original version of this procedure incorrectly assumed that the ; console window dimensions were 80 X 25 (the default MS-DOS screen). ; This new version writes both blanks and attribute values to each buffer position. ; Restriction: Only the first 512 columns of each line are cleared. ; The name capitalization was changed to "Clrscr". ;------------------------------------------------------------- MAX_COLS = 512 .data blanks BYTE MAX_COLS DUP(' ') ; one screen line attribs WORD MAX_COLS DUP(0) lineLength DWORD 0 cursorLoc COORD <0,0> count DWORD ? .code pushad CheckInit ; Get the console buffer size and attributes INVOKE GetConsoleScreenBufferInfo, consoleOutHandle, ADDR bufInfo mov ax,bufInfo.dwSize.X; mov WORD PTR lineLength,ax .IF lineLength > MAX_COLS mov lineLength,MAX_COLS .ENDIF ; Fill the attribs array mov ax,bufInfo.wAttributes mov ecx,lineLength mov edi,OFFSET attribs rep stosw movzx ecx,bufInfo.dwSize.Y ; loop counter: number of lines L1: push ecx ; Write a blank line to the screen buffer INVOKE WriteConsoleOutputCharacter, consoleOutHandle, ADDR blanks, ; pointer to buffer lineLength, ; number of blanks to write cursorLoc, ; first cell coordinates ADDR count ; output count ; Fill all buffer positions with the current attribute INVOKE WriteConsoleOutputAttribute, consoleOutHandle, ADDR attribs, ; point to attribute array lineLength, ; number of attributes to write cursorLoc, ; first cell coordinates ADDR count ; output count add cursorLoc.Y, 1 ; point to the next buffer line pop ecx Loop L1 ; Move cursor to 0,0 mov cursorLoc.Y,0 INVOKE SetConsoleCursorPosition, consoleOutHandle, cursorLoc popad ret Clrscr ENDP ;----------------------------------------------------- Crlf PROC ; ; Writes a carriage return / linefeed ; sequence (0Dh,0Ah) to standard output. ;----------------------------------------------------- CheckInit mNewLine ; invoke a macro ret Crlf ENDP ;------------------------------------------------------ Delay PROC ; ; THIS FUNCTION IS NOT IN THE IRVINE16 LIBRARY ; Delay (pause) the current process for a given number ; of milliseconds. ; Receives: EAX = number of milliseconds ; Returns: nothing ; Last update: 7/11/01 ;------------------------------------------------------ pushad INVOKE Sleep,eax popad ret Delay ENDP ;--------------------------------------------------- DumpMem PROC LOCAL unitsize:dword, byteCount:word ; ; Writes a range of memory to standard output ; in hexadecimal. ; Receives: ESI = starting offset, ECX = number of units, ; EBX = unit size (1=byte, 2=word, or 4=doubleword) ; Returns: nothing ; Last update: 7/11/01 ;--------------------------------------------------- .data oneSpace DB ' ',0 dumpPrompt DB 13,10,"Dump of offset ",0 dashLine DB "-------------------------------",13,10,0 .code pushad mov edx,OFFSET dumpPrompt call WriteString mov eax,esi ; get memory offset to dump call WriteHex mNewLine mov edx,OFFSET dashLine call WriteString mov byteCount,0 mov unitsize,ebx cmp ebx,4 ; select output size je L1 cmp ebx,2 je L2 jmp L3 ; 32-bit doubleword output L1: mov eax,[esi] call WriteHex mWriteSpace 2 add esi,ebx Loop L1 jmp L4 ; 16-bit word output L2: mov ax,[esi] ; get a word from memory ror ax,8 ; display high byte call HexByte ror ax,8 ; display low byte call HexByte mWriteSpace 1 ; display 1 space add esi,unitsize ; point to next word Loop L2 jmp L4 ; 8-bit byte output, 16 bytes per line L3: mov al,[esi] call HexByte inc byteCount mWriteSpace 1 inc esi ; if( byteCount mod 16 == 0 ) call Crlf mov dx,0 mov ax,byteCount mov bx,16 div bx cmp dx,0 jne L3B mNewLine L3B: Loop L3 jmp L4 L4: mNewLine popad ret DumpMem ENDP ;--------------------------------------------------- DumpRegs PROC ; ; Displays EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP in ; hexadecimal. Also displays the Zero, Sign, Carry, and ; Overflow flags. ; Receives: nothing. ; Returns: nothing. ; Last update: 8/23/01 ; ; Warning: do not create any local variables or stack ; parameters, because they will alter the EBP register. ;--------------------------------------------------- .data saveIP DWORD ? saveESP DWORD ? .code pop saveIP ; get current EIP mov saveESP,esp ; save ESP's value at entry push saveIP ; replace it on stack push eax ; save EAX (restore on exit) pushfd ; push extended flags pushfd ; push flags again, and pop eflags ; save them in a variable mNewLine mShowRegister EAX,EAX mShowRegister EBX,EBX mShowRegister ECX,ECX mShowRegister EDX,EDX mNewLine mShowRegister ESI,ESI mShowRegister EDI,EDI mShowRegister EBP,EBP mov eax,saveESP mShowRegister ESP,EAX mNewLine mov eax,saveIP mShowRegister EIP,EAX mov eax,eflags mShowRegister EFL,EAX ; Show the flags (using the eflags variable) ShowFlag CF,1 ShowFlag SF,8 ShowFlag ZF,7 ShowFlag OF,12 mNewLine mNewLine popfd pop eax ret DumpRegs ENDP ;------------------------------------------------------------ GetCommandtail PROC ; ; Copies the tail of the program command line into a buffer ; (after stripping off the first argument - the program's name) ; Receives: EDX points to a 129-byte buffer that will receive ; the data. ; Returns: Carry Flag = 1 if no command tail, otherwise CF=0 ; Contributed by Gerald Cahill, 9/26/2002 ;------------------------------------------------------------- pushad INVOKE GetCommandLine ; Win32 API function ; returns pointer in EAX ; Copy the command-line string to the array ; pointed to by EDX (after stripping off filename.exe). mov esi,eax L0: mov al,[esi] ; strip off first argument inc esi cmp al,' ' ; look for blank jne L0 mov edi,edx ; save buffer addr L1: mov al,[esi] mov [edx],al inc esi inc edx cmp al,0 ; null byte found? jne L1 ; no, loop clc ; assume tail is not null cmp BYTE PTR [edi],0 ; buffer start with null? jne L2 stc ; yes, set carry flag L2: popad ret GetCommandtail ENDP ;-------------------------------------------------- GetDateTime PROC, pDateTime:PTR QWORD LOCAL flTime:FILETIME ; ; Gets the current local date and time, storing it as a ; 64-bit integer. ; Receives: pointer to a QWORD ; Returns: nothing ; Updated 10/20/2002 ;-------------------------------------------------- pushad ; Get the system local time INVOKE GetLocalTime, ADDR sysTime ; Convert the SYSTEMTIME to FILETIME INVOKE SystemTimeToFileTime, ADDR sysTime, ADDR flTime ; Copy the FILETIME to a Quadword mov esi,pDateTime mov eax,flTime.loDateTime mov DWORD PTR [esi],eax mov eax,flTime.hiDateTime mov DWORD PTR [esi+4],eax popad ret GetDateTime ENDP ;---------------------------------------------------------------- GetMaxXY PROC LOCAL bufInfo:CONSOLE_SCREEN_BUFFER_INFO ; ; Returns the current columns (X) and rows (Y) of the console ; window buffer. These values can change while a program is running ; if the user modifies the properties of the application window. ; Receives: nothing ; Returns: DH = rows (Y); DL = columns (X) ; (range of each is 1-255) ; ; Added to the library on 10/20/2002, on the suggestion of Ben Schwartz. ;---------------------------------------------------------------- .data .code push eax CheckInit ; Get the console buffer size and attributes pushad INVOKE GetConsoleScreenBufferInfo, consoleOutHandle, ADDR bufInfo popad mov dx,bufInfo.dwSize.X mov ax,bufInfo.dwSize.Y mov dh,al pop eax ret GetMaxXY ENDP ;---------------------------------------------------------------- GetMseconds PROC USES ebx edx LOCAL hours:DWORD, min:DWORD, sec:DWORD ; Comment ! Returns the number of milliseconds that have elapsed past midnight. Receives: nothing; Returns: milliseconds Implementation Notes: Calculation: ((hours * 3600) + (minutes * 60) + seconds)) * 1000 + milliseconds Under Win NT/ 2000/ XT, the resolution is 10ms. Under Win 98/ ME/ or any DOS-based version, the resolution is 55ms (average). Last update: 1/30/03 -----------------------------------------------------------------! pushad INVOKE GetLocalTime,OFFSET sysTime ; convert hours to seconds popad movzx eax,sysTime.wHour mov ebx,3600 mul ebx mov hours,eax ; convert minutes to seconds movzx eax,sysTime.wMinute mov ebx,60 mul ebx mov min,eax ; add seconds to total seconds movzx eax,sysTime.wSecond mov sec,eax ; multiply seconds by 1000 mov eax,hours add eax,min add eax,sec mov ebx,1000 mul ebx ; add milliseconds to total movzx ebx,sysTime.wMilliseconds add eax,ebx ret GetMseconds ENDP ;-------------------------------------------------- Gotoxy PROC ; ; Locate the cursor ; Receives: DH = screen row, DL = screen column ; Last update: 7/11/01 ;-------------------------------------------------------- .data _cursorPosition COORD <> .code pushad CheckInit ; was console initialized? movzx ax,dl mov _cursorPosition.X, ax movzx ax,dh mov _cursorPosition.Y, ax INVOKE SetConsoleCursorPosition, consoleOutHandle, _cursorPosition popad ret Gotoxy ENDP ;---------------------------------------------------- Initialize PROC private ; ; Get the standard console handles for input and output, ; and set a flag indicating that it has been done. ; Updated 03/17/2003 ;---------------------------------------------------- pushad INVOKE GetStdHandle, STD_INPUT_HANDLE mov [consoleInHandle],eax INVOKE GetStdHandle, STD_OUTPUT_HANDLE mov [consoleOutHandle],eax mov InitFlag,1 popad ret Initialize ENDP ;----------------------------------------------- IsDigit PROC ; ; Determines whether the character in AL is a ; valid decimal digit. ; Receives: AL = character ; Returns: ZF=1 if AL contains a valid decimal ; digit; otherwise, ZF=0. ;----------------------------------------------- cmp al,'0' jb ID1 cmp al,'9' ja ID1 test ax,0 ; set ZF = 1 ID1: ret IsDigit ENDP ;-------------------------------------------------------------- RandomRange PROC ; ; Returns an unsigned pseudo-random 32-bit integer ; in EAX, between 0 and n-1. Input parameter: ; EAX = n. ; Last update: 09/06/2002 ;-------------------------------------------------------------- push ebx push edx mov ebx,eax ; maximum value call Random32 ; eax = random number mov edx,0 div ebx ; divide by max value mov eax,edx ; return the remainder pop edx pop ebx ret RandomRange ENDP ;-------------------------------------------------------------- Random32 PROC ; ; Generates an unsigned pseudo-random 32-bit integer ; in the range 0 - FFFFFFFFh. ; Receives: nothing ; Returns: EAX = random integer ; Last update: 7/11/01 ;-------------------------------------------------------------- .data seed DWORD 1 .code push edx mov eax, 343FDh imul seed add eax, 269EC3h mov seed, eax ; save the seed for the next call ror eax,8 ; rotate out the lowest digit (10/22/00) pop edx ret Random32 ENDP ;-------------------------------------------------------- Randomize PROC ; ; Re-seeds the random number generator with the current time ; in seconds. ; Receives: nothing ; Returns: nothing ; Last update: 09/06/2002 ;-------------------------------------------------------- pushad INVOKE GetSystemTime,OFFSET sysTime movzx eax,sysTime.wMilliseconds mov seed,eax popad ret Randomize ENDP ;------------------------------------------------------------ ReadChar PROC ; ; Reads one character from standard input. The character is ; not echoed on the screen. Waits for the character if none is ; currently in the input buffer. ; Returns: AL = ASCII code ; Added 8/18/2003, by Kip Irvine ;---------------------------------------------------------- push ebx push eax L1: mov eax,10 ; give Windows 10ms to process messages call Delay call ReadKey ; look for key in buffer jz L1 ; no key in buffer if ZF=1 ; Special epilogue code used here to return AL, yet ; preserve the high 24 bits of EAX. mov bl,al ; save ASCII code pop eax mov al,bl pop ebx ret ReadChar ENDP COMMENT @ *** This version was replaced 8/18/2003 by Kip Irvine *** Because it does work consistently under Windows 98. ;--------------------------------------------------------- ReadChar PROC LOCAL saveFlags:DWORD ; ; Reads one character from standard input. The character is ; not echoed on the screen. Waits for the character if none is ; currently in the input buffer. ; Receives: nothing ; Returns: AL = ASCII code ; Last update: 11/20/02 ;---------------------------------------------------------- pushad CheckInit ; Get & save the current console input mode flags: INVOKE GetConsoleMode,consoleInHandle,ADDR saveFlags ; Clear all flags INVOKE SetConsoleMode,consoleInHandle,0 ; Read a single character from input buffer: INVOKE ReadConsole, consoleInHandle, ; console input handle ADDR buffer, ; pointer to buffer 1, ; max characters to read ADDR bytesRead,0 ; Restore the previous flags state: INVOKE SetConsoleMode,consoleInHandle,saveFlags popad mov al,buffer ; return the input character ret ReadChar ENDP ************************************************** @ ;-------------------------------------------------------- ReadDec PROC USES ebx ecx edx esi LOCAL saveDigit:DWORD ; ; Reads a 32-bit unsigned decimal integer from standard ; input, stopping when the Enter key is pressed. ; All valid digits occurring before a non-numeric character ; are converted to the integer value. Leading spaces are ; ignored. ; Receives: nothing ; Returns: ; If the integer is blank, EAX=0 and CF=1 ; If the integer contains only spaces, EAX=0 and CF=1 ; If the integer is larger than 2^32-1, EAX=0 and CF=1 ; Otherwise, EAX=converted integer, and CF=0 ; ; Last update: 11/11/02 ;-------------------------------------------------------- ; Input a string of digits using ReadString. mov edx,OFFSET digitBuffer mov esi,edx ; save offset in ESI mov ecx,MAX_DIGITS call ReadString mov ecx,eax ; save length in CX cmp ecx,0 ; greater than zero? jne L1 ; yes: continue mov eax,0 ; no: set return value jmp L5 ; and exit with CF=1 ; Skip over any leading spaces. L1: mov al,[esi] ; get a character from buffer cmp al,' ' ; space character found? jne L2 ; no: goto next step inc esi ; yes: point to next char loop L1 ; all spaces? jmp L5 ; yes: exit with CF=1 ; Start to convert the number. L2: mov eax,0 ; clear accumulator mov ebx,10 ; EBX is the divisor ; Repeat loop for each digit. L3: mov dl,[esi] ; get character from buffer cmp dl,'0' ; character < '0'? jb L4 cmp dl,'9' ; character > '9'? ja L4 and edx,0Fh ; no: convert to binary mov saveDigit,edx mul ebx ; EDX:EAX = EAX * EBX jc L5 ; quit if Carry (EDX > 0) mov edx,saveDigit add eax,edx ; add new digit to sum jc L5 ; quit if Carry generated inc esi ; point to next digit jmp L3 ; get next digit L4: clc ; succesful completion (CF=0) jmp L6 L5: mov eax,0 ; clear result to zero stc ; signal an error (CF=1) L6: ret ReadDec ENDP ;-------------------------------------------------------- ReadHex PROC USES ebx ecx edx esi ; ; Reads a 32-bit hexadecimal integer from standard input, ; stopping when the Enter key is pressed. ; Receives: nothing ; Returns: EAX = binary integer value ; Returns: ; If the integer is blank, EAX=0 and CF=1 ; If the integer contains only spaces, EAX=0 and CF=1 ; Otherwise, EAX=converted integer, and CF=0 ; Remarks: No error checking performed for bad digits ; or excess digits. ; Last update: 11/11/02 ;-------------------------------------------------------- .data xbtable BYTE 0,1,2,3,4,5,6,7,8,9,7 DUP(0FFh),10,11,12,13,14,15 numVal DWORD ? charVal BYTE ? .code mov edx,OFFSET digitBuffer mov esi,edx ; save in ESI also mov ecx,MAX_DIGITS call ReadString ; input the string mov ecx,eax ; save length in ECX cmp ecx,0 ; greater than zero? jne B1 ; yes: continue jmp B8 ; no: exit with CF=1 ; Skip over any leading spaces. B1: mov al,[esi] ; get a character from buffer cmp al,' ' ; space character found? jne B4 ; no: goto next step inc esi ; yes: point to next char loop B1 ; all spaces? jmp B8 ; yes: exit with CF=1 ; Start to convert the number. B4: mov numVal,0 ; clear accumulator mov ebx,OFFSET xbtable ; translate table ; Repeat loop for each digit. B5: mov al,[esi] ; get character from buffer cmp al,'F' ; lowercase letter? jbe B6 ; no and al,11011111b ; yes: convert to uppercase B6: sub al,30h ; adjust for table xlat ; translate to binary mov charVal,al mov eax,16 ; numVal *= 16 mul numVal mov numVal,eax movzx eax,charVal ; numVal += charVal add numVal,eax inc esi ; point to next digit loop B5 ; repeat, decrement counter B7: mov eax,numVal ; return valid value clc ; CF=0 jmp B9 B8: mov eax,0 ; error: return 0 stc ; CF=1 B9: ret ReadHex ENDP ;-------------------------------------------------------- ReadInt PROC USES ebx ecx edx esi LOCAL Lsign:SDWORD, saveDigit:DWORD ; ; Reads a 32-bit signed decimal integer from standard ; input, stopping when the Enter key is pressed. ; All valid digits occurring before a non-numeric character ; are converted to the integer value. Leading spaces are ; ignored, and an optional leading + or - sign is permitted. ; All spaces return a valid integer, value zero. ; Receives: nothing ; Returns: If CF=0, the integer is valid, and EAX = binary value. ; If CF=1, the integer is invalid and EAX = 0. ; ; Contains corrections by Gerald Cahill ; Updated: 10/10/2003 ;-------------------------------------------------------- .data overflow_msgL BYTE " <32-bit integer overflow>",0 invalid_msgL BYTE " ",0 ;allspace_msgL BYTE " ",0 .code ; Input a string of digits using ReadString. mov Lsign,1 ; assume number is positive mov edx,OFFSET digitBuffer mov esi,edx ; save offset in SI mov ecx,MAX_DIGITS call ReadString mov ecx,eax ; save length in ECX cmp ecx,0 ; length greater than zero? jne L1 ; yes: continue mov eax,0 ; no: set return value jmp L10 ; and exit ; Skip over any leading spaces. L1: mov al,[esi] ; get a character from buffer cmp al,' ' ; space character found? jne L2 ; no: check for a sign inc esi ; yes: point to next char loop L1 mov eax,0 ; all spaces? jmp L10 ; return zero as valid value ; mov edx,OFFSET allspace_msgL (line removed) ; jcxz L8 (line removed) ; Check for a leading sign. L2: cmp al,'-' ; minus sign found? jne L3 ; no: look for plus sign mov Lsign,-1 ; yes: sign is negative dec ecx ; subtract from counter inc esi ; point to next char jmp L3A L3: cmp al,'+' ; plus sign found? jne L3A ; no: skip inc esi ; yes: move past the sign dec ecx ; subtract from digit counter ; Test the first digit, and exit if nonnumeric. L3A:mov al,[esi] ; get first character call IsDigit ; is it a digit? jnz L7A ; no: show error message ; Start to convert the number. L4: mov eax,0 ; clear accumulator mov ebx,10 ; EBX is the divisor ; Repeat loop for each digit. L5: mov dl,[esi] ; get character from buffer cmp dl,'0' ; character < '0'? jb L9 cmp dl,'9' ; character > '9'? ja L9 and edx,0Fh ; no: convert to binary mov saveDigit,edx imul ebx ; EDX:EAX = EAX * EBX mov edx,saveDigit jo L6 ; quit if overflow add eax,edx ; add new digit to AX jo L6 ; quit if overflow inc esi ; point to next digit jmp L5 ; get next digit ; Overflow has occured, unlesss EAX = 80000000h ; and the sign is negative: L6: cmp eax,80000000h jne L7 cmp Lsign,-1 jne L7 ; overflow occurred jmp L9 ; the integer is valid ; Choose "integer overflow" messsage. L7: mov edx,OFFSET overflow_msgL jmp L8 ; Choose "invalid integer" message. L7A: mov edx,OFFSET invalid_msgL ; Display the error message pointed to by EDX, and set the Overflow flag. L8: call WriteString call Crlf mov al,127 add al,1 ; set Overflow flag mov eax,0 ; set return value to zero jmp L10 ; and exit ; IMUL leaves the Sign flag in an undeterminate state, so the OR instruction ; determines the sign of the iteger in EAX. L9: imul Lsign ; EAX = EAX * sign or eax,eax ; determine the number's Sign L10:ret ReadInt ENDP COMMENT @ ****************************************************************** This version of ReadInt was replaced on 10/10/2003, as a result of corrections by Gerald Cahill. ****************************************************************** ;-------------------------------------------------------- ReadInt PROC USES ebx ecx edx esi LOCAL Lsign:SDWORD, saveDigit:DWORD ; ; Reads a 32-bit signed decimal integer from standard ; input, stopping when the Enter key is pressed. ; All valid digits occurring before a non-numeric character ; are converted to the integer value. Leading spaces are ; ignored, and an optional leading + or - sign is permitted. ; Receives: nothing ; Returns: If CF=0, the integer is valid, and EAX = binary value. ; If CF=1, the integer is invalid and EAX = 0. ; ; Credits: Thanks to Courtney Amor, a student at the UCLA Mathematics ; department, for pointing out improvements that have been ; implemented in this version. ; Last update: 1/27/02 ;-------------------------------------------------------- .data overflow_msgL BYTE " <32-bit integer overflow>",0 invalid_msgL BYTE " ",0 .code ; Input a string of digits using ReadString. mov edx,OFFSET digitBuffer mov esi,edx ; save offset in SI mov ecx,MAX_DIGITS call ReadString mov ecx,eax ; save length in CX mov Lsign,1 ; assume number is positive cmp ecx,0 ; greater than zero? jne L1 ; yes: continue mov eax,0 ; no: set return value jmp L10 ; and exit ; Skip over any leading spaces. L1: mov al,[esi] ; get a character from buffer cmp al,' ' ; space character found? jne L2 ; no: check for a sign inc esi ; yes: point to next char loop L1 jcxz L8 ; quit if all spaces ; Check for a leading sign. L2: cmp al,'-' ; minus sign found? jne L3 ; no: look for plus sign mov Lsign,-1 ; yes: sign is negative dec ecx ; subtract from counter inc esi ; point to next char jmp L4 L3: cmp al,'+' ; plus sign found? jne L4 ; no: must be a digit inc esi ; yes: skip over the sign dec ecx ; subtract from counter ; Test the first digit, and exit if it is nonnumeric. L3A:mov al,[esi] ; get first character call IsDigit ; is it a digit? jnz L7A ; no: show error message ; Start to convert the number. L4: mov eax,0 ; clear accumulator mov ebx,10 ; EBX is the divisor ; Repeat loop for each digit. L5: mov dl,[esi] ; get character from buffer cmp dl,'0' ; character < '0'? jb L9 cmp dl,'9' ; character > '9'? ja L9 and edx,0Fh ; no: convert to binary mov saveDigit,edx imul ebx ; EDX:EAX = EAX * EBX mov edx,saveDigit jo L6 ; quit if overflow add eax,edx ; add new digit to AX jo L6 ; quit if overflow inc esi ; point to next digit jmp L5 ; get next digit ; Overflow has occured, unlesss EAX = 80000000h ; and the sign is negative: L6: cmp eax,80000000h jne L7 cmp Lsign,-1 jne L7 ; overflow occurred jmp L9 ; the integer is valid ; Choose "integer overflow" messsage. L7: mov edx,OFFSET overflow_msgL jmp L8 ; Choose "invalid integer" message. L7A: mov edx,OFFSET invalid_msgL ; Display the error message pointed to by EDX, and set the Overflow flag. L8: call WriteString call Crlf mov al,127 add al,1 ; set Overflow flag mov eax,0 ; set return value to zero jmp L10 ; and exit ; IMUL leaves the Sign flag in an undeterminate state, so the OR instruction ; determines the sign of the iteger in EAX. L9: imul Lsign ; EAX = EAX * sign or eax,eax ; determine the number's Sign L10:ret ReadInt ENDP ********************** END COMMENT @ ;------------------------------------------------------------------------------ ReadKey PROC USES ecx LOCAL evEvents:WORD, saveFlags:DWORD ; ; Performs a no-wait keyboard check and single character read if available. ; If Ascii is zero, special keys can be processed by checking scans and VKeys ; Receives: nothing ; Returns: ZF is set if no keys are available, clear if we have read the key ; al = key Ascii code (is set to zero for special extended codes) ; ah = Virtual scan code (or translated if Translate is called) ; dx = Virtual key code ; ebx = Keyboard flags (Alt,Ctrl,Caps,etc.) ; Upper halves of EAX and EDX are overwritten ; ; ** Note: calling ReadKey prevents Ctrl-C from being used to terminate a program. ; ; Written by Richard Stam, used by permission. ; Modified 4/6/03 by Irvine; modified 4/16/03 by Jerry Cahill ;------------------------------------------------------------------------------ .data evBuffer INPUT_RECORD <> ; Buffers our key "INPUT_RECORD" evRepeat WORD 0 ; Controls key repeat counting .code CheckInit ; call Inititialize, if not already called ; Save console flags INVOKE GetConsoleMode,consoleInHandle,ADDR saveFlags ; Clear console flags, making it possible to detect Ctrl-C and Ctrl-S. INVOKE SetConsoleMode,consoleInHandle,0 cmp evRepeat,0 ; key already processed by previous call to this function? ja HaveKey ; if so, process the key Peek: ; Peek to see if we have a pending event. If so, read it. INVOKE PeekConsoleInput, consoleInHandle, ADDR evBuffer, 1, ADDR evEvents test evEvents,0FFFFh jz NoKey ; No pending events, so done. INVOKE ReadConsoleInput, consoleInHandle, ADDR evBuffer, 1, ADDR evEvents test evEvents,0FFFFh jz NoKey ; No pending events, so done. cmp evBuffer.eventType,KEY_EVENT ; Is it a key event? jne Peek ; No -> Peek for next event TEST evBuffer.Event.bKeyDown, KBDOWN_FLAG ; is it a key down event? jz Peek ; No -> Peek for next event mov ax,evBuffer.Event.wRepeatCount ; Set our internal repeat counter mov evRepeat,ax HaveKey: mov al,evBuffer.Event.uChar.AsciiChar ; copy Ascii char to al mov ah,BYTE PTR evBuffer.Event.wVirtualScanCode ; copy Scan code to ah mov dx,evBuffer.Event.wVirtualKeyCode ; copy Virtual key code to dx mov ebx,evBuffer.Event.dwControlKeyState ; copy keyboard flags to ebx ; Ignore the key press events for Shift, Ctrl, Alt, etc. ; Don't process them unless used in combination with another key .IF dx == VK_SHIFT || dx == VK_CONTROL || dx == VK_MENU || \ dx == VK_CAPITAL || dx == VK_NUMLOCK || dx == VK_SCROLL jmp Peek ; Don't process -> Peek for next event .ENDIF call ReadKeyTranslate ; Translate scan code compatability dec evRepeat ; Decrement our repeat counter or dx,dx ; Have key: clear the Zero flag jmp Done NoKey: mov evRepeat,0 ; Reset our repeat counter test eax,0 ; No key: set ZF=1 and quit Done: pushfd ; save Zero flag pushad ; Restore Console mode INVOKE SetConsoleMode,consoleInHandle,saveFlags ;Unless we call ReadKeyFlush in Windows 98, the key we just read ;reappears the next time ReadString is called! We don't know why. call ReadKeyFlush popad popfd ; restore Zero flag ret ReadKey ENDP ;------------------------------------------------------------------------------ ReadKeyFlush PROC ; Flushes the console input buffer and clears our internal repeat counter. ; Can be used to get faster keyboard reponse in arcade-style games, where ; we don't want to processes accumulated keyboard data that would slow down ; the program's response time. ; Receives: nothing ; Returns: nothing ; By Richard Stam, used by permission. ; Modified 4/5/03 by Irvine ;------------------------------------------------------------------------------ INVOKE FlushConsoleInputBuffer, consoleInHandle ; Flush the buffer mov evRepeat,0 ; Reset our repeat counter ret ReadKeyFlush ENDP ;------------------------------------------------------------------------------ ReadKeyTranslate PROC PRIVATE USES ebx ecx edx esi ; Translates special scan codes to be compatible with DOS/BIOS return values. ; Called directly by ReadKey. ; Receives: ; al = key Ascii code ; ah = Virtual scan code ; dx = Virtual key code ; ebx = Keyboard flags (Alt,Ctrl,Caps,etc.) ; Returns: ; ah = Updated scan code (for Alt/Ctrl/Shift & special cases) ; al = Updated key Ascii code (set to 0 for special keys) ; Written by Richard Stam, used by permission. ; Modified 4/5/03 by Irvine ;------------------------------------------------------------------------------ .data ; Special key scan code translation table ; order: VirtualKey,NormalScan,CtrlScan,AltScan SpecialCases \ BYTE VK_LEFT, 4Bh, 73h, 4Bh CaseSize = ($ - SpecialCases) ; Special case table element size BYTE VK_RIGHT, 4Dh, 74h, 4Dh BYTE VK_UP, 48h, 8Dh, 48h BYTE VK_DOWN, 50h, 91h, 50h BYTE VK_PRIOR, 49h, 84h, 49h ; PgUp BYTE VK_NEXT, 51h, 76h, 51h ; PgDn BYTE VK_HOME, 47h, 77h, 47h BYTE VK_END, 4Fh, 75h, 4Fh BYTE VK_INSERT,52h, 92h, 52h BYTE VK_DELETE,53h, 93h, 53h BYTE VK_ADD, 4Eh, 90h, 4Eh BYTE VK_SUBTRACT,4Ah,8Eh, 4Ah BYTE VK_F11, 85h, 85h, 85h BYTE VK_F12, 86h, 86h, 86h BYTE VK_11, 0Ch, 0Ch, 82h ; see above BYTE VK_12, 0Dh, 0Dh, 83h ; see above BYTE 0 ; End of Table .code pushfd ; Push flags to save ZF of ReadKey mov esi,0 ; Search through the special cases table Search: cmp SpecialCases[esi],0 ; Check for end of search table je NotFound cmp dl,SpecialCases[esi] ; Check if special case is found je Found add esi,CaseSize ; Increment our table index jmp Search ; Continue searching Found: .IF ebx & CTRL_MASK mov ah,SpecialCases[esi+2] ; Specify the Ctrl scan code mov al,0 ; Updated char for special keys .ELSEIF ebx & ALT_MASK mov ah,SpecialCases[esi+3] ; Specify the Alt scan code mov al,0 ; Updated char for special keys .ELSE mov ah,SpecialCases[esi+1] ; Specify the normal scan code .ENDIF jmp Done NotFound: .IF ! (ebx & KEY_MASKS) ; Done if not shift/ctrl/alt combo jmp Done .ENDIF .IF dx >= VK_F1 && dx <= VK_F10 ; Check for F1 to F10 keys .IF ebx & CTRL_MASK add ah,23h ; 23h = Hex diff for Ctrl/Fn keys .ELSEIF ebx & ALT_MASK add ah,2Dh ; 2Dh = Hex diff for Alt/Fn keys .ELSEIF ebx & SHIFT_MASK add ah,19h ; 19h = Hex diff for Shift/Fn keys .ENDIF .ELSEIF al >= '0' && al <= '9' ; Check for Alt/1 to Alt/9 .IF ebx & ALT_MASK add ah,76h ; 76h = Hex diff for Alt/n keys mov al,0 .ENDIF .ELSEIF dx == VK_TAB ; Check for Shift/Tab (backtab) .IF ebx & SHIFT_MASK mov al,0 ; ah already has 0Fh, al=0 for special .ENDIF .ENDIF Done: popfd ; Pop flags to restore ZF of ReadKey ret ReadKeyTranslate ENDP ;-------------------------------------------------------- ReadString PROC LOCAL bufSize:DWORD, saveFlags:DWORD, junk:DWORD ; ; Reads a string from the console and places the characters ; in a buffer. ; Receives: EDX offset of the input buffer ; ECX = maximum characters to input (including terminal null) ; Returns: EAX = size of the input string. ; Comments: Stops when Enter key (0Dh,0Ah) is pressed. If the user ; types more characters than (ECX-1), the excess characters ; are ignored. ; Written by Kip Irvine and Gerald Cahill ; ; Last update: 11/19/92, 03/20/2003 ;-------------------------------------------------------- .data _$$temp DWORD ? ; added 03/20/03 .code pushad CheckInit mov edi,edx ; set EDI to buffer offset mov bufSize,ecx ; save buffer size push edx INVOKE ReadConsole, consoleInHandle, ; console input handle edx, ; buffer offset ecx, ; max count OFFSET bytesRead, 0 pop edx cmp bytesRead,0 jz L5 ; skip move if zero chars input dec bytesRead ; make first adjustment to bytesRead cld ; search forward mov ecx,bufSize ; repetition count for SCASB mov al,0Ah ; scan for 0Ah (Line Feed) terminal character repne scasb jne L1 ; if not found, jump to L1 ;if we reach this line, length of input string <= (bufsize - 2) dec bytesRead ; second adjustment to bytesRead sub edi,2 ; 0Ah found: back up two positions cmp edi,edx ; don't back up to before the user's buffer jae L2 mov edi,edx ; 0Ah must be the only byte in the buffer jmp L2 ; and jump to L2 L1: mov edi,edx ; point to last byte in buffer add edi,bufSize dec edi mov BYTE PTR [edi],0 ; insert null byte ; Save the current console mode INVOKE GetConsoleMode,consoleInHandle,ADDR saveFlags ; Switch to single character mode INVOKE SetConsoleMode,consoleInHandle,0 ; Clear excess characters from the buffer, 1 byte at a time L6: INVOKE ReadConsole,consoleInHandle,ADDR junk,1,ADDR _$$temp,0 mov al,BYTE PTR junk cmp al,0Ah ; the terminal line feed character jne L6 ; keep looking, it must be there somewhere INVOKE SetConsoleMode,consoleInHandle,saveFlags ; restore console mode. jmp L5 L2: mov BYTE PTR [edi],0 ; insert null byte L5: popad mov eax,bytesRead ret ReadString ENDP ;------------------------------------------------------------ SetTextColor PROC ; ; Change the color of all subsequent text output. ; Receives: EAX = attribute. Bits 0-3 are the foreground ; color, and bits 4-7 are the background color. ; Returns: nothing ; Last update: 1/18/02 ;------------------------------------------------------------ .data scrAttrib DWORD ? .code pushad mov scrAttrib,eax ; lowest byte contains the attribute ; Get the console standard output handle: INVOKE GetStdHandle, STD_OUTPUT_HANDLE mov [consoleOutHandle],eax ; Set the text color (both background and foreground) ; to white on blue INVOKE SetConsoleTextAttribute, consoleOutHandle, scrAttrib popad ret SetTextColor ENDP ;---------------------------------------------------------- Str_compare PROC USES eax edx esi edi, string1:PTR BYTE, string2:PTR BYTE ; ; Compare two strings. ; Returns nothing, but the Zero and Carry flags are affected ; exactly as they would be by the CMP instruction. ; Last update: 1/18/02 ;----------------------------------------------------- mov esi,string1 mov edi,string2 L1: mov al,[esi] mov dl,[edi] cmp al,0 ; end of string1? jne L2 ; no cmp dl,0 ; yes: end of string2? jne L2 ; no jmp L3 ; yes, exit with ZF = 1 L2: inc esi ; point to next inc edi cmp al,dl ; chars equal? je L1 ; yes: continue loop ; no: exit with flags set L3: ret Str_compare ENDP ;--------------------------------------------------------- Str_copy PROC USES eax ecx esi edi, source:PTR BYTE, ; source string target:PTR BYTE ; target string ; ; Copy a string from source to target. ; Requires: the target string must contain enough ; space to hold a copy of the source string. ; Last update: 1/18/02 ;---------------------------------------------------------- INVOKE Str_length,source ; EAX = length source mov ecx,eax ; REP count inc ecx ; add 1 for null byte mov esi,source mov edi,target cld ; direction = up rep movsb ; copy the string ret Str_copy ENDP ;--------------------------------------------------------- Str_length PROC USES edi, pString:PTR BYTE ; pointer to string ; ; Return the length of a null-terminated string. ; Receives: pString - pointer to a string ; Returns: EAX = string length ; Last update: 1/18/02 ;--------------------------------------------------------- mov edi,pString mov eax,0 ; character count L1: cmp BYTE PTR [edi],0 ; end of string? je L2 ; yes: quit inc edi ; no: point to next inc eax ; add 1 to count jmp L1 L2: ret Str_length ENDP ;----------------------------------------------------------- Str_trim PROC USES eax ecx edi, pString:PTR BYTE, ; points to string char:BYTE ; char to remove ; ; Remove all occurences of a given character from ; the end of a string. ; Returns: nothing ; Last update: 1/18/02 ;----------------------------------------------------------- mov edi,pString INVOKE Str_length,edi ; returns length in EAX cmp eax,0 ; zero-length string? je L2 ; yes: exit mov ecx,eax ; no: counter = string length dec eax add edi,eax ; EDI points to last char mov al,char ; char to trim std ; direction = reverse repe scasb ; skip past trim character jne L1 ; removed first character? dec edi ; adjust EDI: ZF=1 && ECX=0 L1: mov BYTE PTR [edi+2],0 ; insert null byte L2: ret Str_trim ENDP ;--------------------------------------------------- Str_ucase PROC USES eax esi, pString:PTR BYTE ; Convert a null-terminated string to upper case. ; Receives: pString - a pointer to the string ; Returns: nothing ; Last update: 1/18/02 ;--------------------------------------------------- mov esi,pString L1: mov al,[esi] ; get char cmp al,0 ; end of string? je L3 ; yes: quit cmp al,'a' ; below "a"? jb L2 cmp al,'z' ; above "z"? ja L2 and BYTE PTR [esi],11011111b ; convert the char L2: inc esi ; next char jmp L1 L3: ret Str_ucase ENDP ;------------------------------------------------------ WaitMsg PROC ; ; Displays a prompt and waits for the user to press Enter. ; Receives: nothing ; Returns: nothing ;------------------------------------------------------ WAITMSG_BUFSIZE = 5 .data waitmsgstr DB "Press [Enter] to continue...",0 localBuf BYTE WAITMSG_BUFSIZE DUP(?) .code pushad CheckInit mov edx,OFFSET waitmsgstr call WriteString mNewLine w1: INVOKE FlushConsoleInputBuffer,consoleInHandle INVOKE ReadConsole, consoleInHandle, ; console input handle OFFSET localBuf, ; pointer to local buffer WAITMSG_BUFSIZE, ; max count OFFSET bytesRead, 0 cmp bytesRead,2 jnz w1 ;loop until ReadConsole returns 2 bytes read popad ret WaitMsg ENDP ;------------------------------------------------------ WriteBin PROC ; ; Writes a 32-bit integer to standard output in ; binary format. Converted to a shell that calls the ; WriteBinB procedure, to be compatible with the ; library documentation in Chapter 5. ; Receives: EAX = the integer to write ; Returns: nothing ; ; Last update: 11/18/02 ;------------------------------------------------------ push ebx mov ebx,4 ; select doubleword format call WriteBinB pop ebx ret WriteBin ENDP ;------------------------------------------------------ WriteBinB PROC ; ; Writes a 32-bit integer to standard output in ; binary format. ; Receives: EAX = the integer to write ; EBX = display size (1,2,4) ; Returns: nothing ; ; Last update: 11/18/02 (added) ;------------------------------------------------------ pushad cmp ebx,1 ; ensure EBX is 1, 2, or 4 jz WB0 cmp ebx,2 jz WB0 mov ebx,4 ; set to 4 (default) even if it was 4 WB0: mov ecx,ebx shl ecx,1 ; number of 4-bit groups in low end of EAX cmp ebx,4 jz WB0A ror eax,8 ; assume TYPE==1 and ROR byte cmp ebx,1 jz WB0A ; good assumption ror eax,8 ; TYPE==2 so ROR another byte WB0A: mov esi,OFFSET buffer WB1: push ecx ; save loop count mov ecx,4 ; 4 bits in each group WB1A: shl eax,1 ; shift EAX left into Carry flag mov BYTE PTR [esi],'0' ; choose '0' as default digit jnc WB2 ; if no carry, then jump to L2 mov BYTE PTR [esi],'1' ; else move '1' to DL WB2: inc esi Loop WB1A ; go to next bit within group mov BYTE PTR [esi],' ' ; insert a blank space inc esi ; between groups pop ecx ; restore outer loop count loop WB1 ; begin next 4-bit group dec esi ; eliminate the trailing space mov BYTE PTR [esi],0 ; insert null byte at end mov edx,OFFSET buffer ; display the buffer call WriteString popad ret WriteBinB ENDP ;------------------------------------------------------ WriteChar PROC ; ; Write a character to standard output ; Recevies: AL = character ; Last update: 10/30/02 ; Note: WriteConole will not work unless direction flag is clear. ;------------------------------------------------------ pushad pushfd ; save flags CheckInit mov buffer,al cld ; clear direction flag INVOKE WriteConsole, consoleOutHandle, ; console output handle OFFSET buffer, ; points to string 1, ; string length OFFSET bytesWritten, ; returns number of bytes written 0 popfd ; restore flags popad ret WriteChar ENDP ;----------------------------------------------------- WriteDec PROC ; ; Writes an unsigned 32-bit decimal number to ; standard output. Input parameters: EAX = the ; number to write. ; Last update: 7/11/01 ;------------------------------------------------------ .data ; There will be as many as 10 digits. BUFFER_SIZE = 12 bufferL BYTE BUFFER_SIZE DUP(?),0 .code pushad CheckInit mov ecx,0 ; digit counter mov edi,OFFSET bufferL add edi,(BUFFER_SIZE - 1) mov ebx,10 ; decimal number base WI1: mov edx,0 ; clear dividend to zero div ebx ; divide EAX by the radix xchg eax,edx ; swap quotient, remainder call AsciiDigit ; convert AL to ASCII mov [edi],al ; save the digit dec edi ; back up in buffer xchg eax,edx ; swap quotient, remainder inc ecx ; increment digit count or eax,eax ; quotient = 0? jnz WI1 ; no, divide again ; Display the digits (CX = count) WI3: inc edi mov edx,edi call WriteString WI4: popad ; restore 32-bit registers ret WriteDec ENDP ;------------------------------------------------------ WriteHex PROC ; ; Writes an unsigned 32-bit hexadecimal number to ; standard output. ; Input parameters: EAX = the number to write. ; Shell interface for WriteHexB, to retain compatibility ; with the documentation in Chapter 5. ; ; Last update: 11/18/02 ;------------------------------------------------------ push ebx mov ebx,4 call WriteHexB pop ebx ret WriteHex ENDP ;------------------------------------------------------ WriteHexB PROC LOCAL displaySize:DWORD ; ; Writes an unsigned 32-bit hexadecimal number to ; standard output. ; Receives: EAX = the number to write. EBX = display size (1,2,4) ; Returns: nothing ; ; Last update: 11/18/02 ;------------------------------------------------------ DOUBLEWORD_BUFSIZE = 8 .data bufferLHB BYTE DOUBLEWORD_BUFSIZE DUP(?),0 .code pushad ; save all 32-bit data registers mov displaySize,ebx ; save component size ; Clear unused bits from EAX to avoid a divide overflow. ; Also, verify that EBX contains either 1, 2, or 4. If any ; other value is found, default to 4. .IF EBX == 1 ; check specified display size and eax,0FFh ; byte == 1 .ELSE .IF EBX == 2 and eax,0FFFFh ; word == 2 .ELSE mov displaySize,4 ; default (doubleword) == 4 .ENDIF .ENDIF CheckInit mov edi,displaySize ; let EDI point to the end of the buffer: shl edi,1 ; multiply by 2 (2 digits per byte) mov bufferLHB[edi],0 ; store null string terminator dec edi ; back up one position mov ecx,0 ; digit counter mov ebx,16 ; hexadecimal base (divisor) L1: mov edx,0 ; clear upper dividend div ebx ; divide EAX by the base xchg eax,edx ; swap quotient, remainder call AsciiDigit ; convert AL to ASCII mov bufferLHB[edi],al ; save the digit dec edi ; back up in buffer xchg eax,edx ; swap quotient, remainder inc ecx ; increment digit count or eax,eax ; quotient = 0? jnz L1 ; no, divide again ; Insert leading zeros mov eax,displaySize ; set EAX to the shl eax,1 ; number of digits to print sub eax,ecx ; subtract the actual digit count jz L3 ; display now if no leading zeros required mov ecx,eax ; CX = number of leading zeros to insert L2: mov bufferLHB[edi],'0' ; insert a zero dec edi ; back up loop L2 ; continue the loop ; Display the digits. ECX contains the number of ; digits to display, and EDX points to the first digit. L3: mov ecx,displaySize ; output format size shl ecx,1 ; multiply by 2 inc edi mov edx,OFFSET bufferLHB add edx,edi call WriteString popad ; restore 32-bit registers ret WriteHexB ENDP ;----------------------------------------------------- WriteInt PROC ; ; Writes a 32-bit signed binary integer to standard output ; in ASCII decimal. ; Receives: EAX = the integer ; Returns: nothing ; Comments: Displays a leading sign, no leading zeros. ; Last update: 7/11/01 ;----------------------------------------------------- WI_Bufsize = 12 true = 1 false = 0 .data buffer_B BYTE WI_Bufsize DUP(0),0 ; buffer to hold digits neg_flag BYTE ? .code pushad CheckInit mov neg_flag,false ; assume neg_flag is false or eax,eax ; is AX positive? jns WIS1 ; yes: jump to B1 neg eax ; no: make it positive mov neg_flag,true ; set neg_flag to true WIS1: mov ecx,0 ; digit count = 0 mov edi,OFFSET buffer_B add edi,(WI_Bufsize-1) mov ebx,10 ; will divide by 10 WIS2: mov edx,0 ; set dividend to 0 div ebx ; divide AX by 10 or dl,30h ; convert remainder to ASCII dec edi ; reverse through the buffer mov [edi],dl ; store ASCII digit inc ecx ; increment digit count or eax,eax ; quotient > 0? jnz WIS2 ; yes: divide again ; Insert the sign. dec edi ; back up in the buffer inc ecx ; increment counter mov BYTE PTR [edi],'+' ; insert plus sign cmp neg_flag,false ; was the number positive? jz WIS3 ; yes mov BYTE PTR [edi],'-' ; no: insert negative sign WIS3: ; Display the number mov edx,edi call WriteString popad ret WriteInt ENDP ;-------------------------------------------------------- WriteString PROC ; ; Writes a null-terminated string to standard ; output. Input parameter: EDX points to the ; string. ; Last update: 9/7/01 ;-------------------------------------------------------- pushad CheckInit INVOKE Str_length,edx ; return length of string in EAX cld ; must do this before WriteConsole INVOKE WriteConsole, consoleOutHandle, ; console output handle edx, ; points to string eax, ; string length OFFSET bytesWritten, ; returns number of bytes written 0 popad ret WriteString ENDP ;----------- PRIVATE PROCEDURES -------------------------- ; Convert AL to an ASCII digit. Used by WriteHex & WriteDec AsciiDigit PROC PRIVATE push ebx mov ebx,OFFSET xtable xlat pop ebx ret AsciiDigit ENDP HexByte PROC PRIVATE ; Display the byte in AL in hexadecimal pushad mov dl,al rol dl,4 mov al,dl and al,0Fh mov ebx,OFFSET xtable xlat mov buffer,al ; save first char rol dl,4 mov al,dl and al,0Fh xlat mov [buffer+1],al ; save second char mov [buffer+2],0 ; null byte mov edx,OFFSET buffer ; display the buffer call WriteString popad ret HexByte ENDP end