Assembly Programming Journal: Issue 1

Assembly Programming Journals: Previous123456 — 7 — 89Next

::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.                                              Dec 99-Feb 00
:::\_____\::::::::::.                                             Issue 7
::::::::::::::::::::::.........................................................

            A S S E M B L Y   P R O G R A M M I N G   J O U R N A L
                      http://asmjournal.freeservers.com
                           asmjournal@mailcity.com




T A B L E   O F   C O N T E N T S
----------------------------------------------------------------------
Introduction...................................................mammon_

"Extending DOS Executables"..........................Digital.Alchemist

"Creating a User-Friendly Interface"......................S.Sirajudeen

"ASM Building Blocks"...................................Laura.Fairhead

"Converting Strings to Numbers"...........................Chris.Dragan

"List Scan Library Routine".............................Laura.Fairhead

"Using the RTC"..........................................Jan.Verhoeven

"Chaos Animation".......................................Laura.Fairhead

"Inline Assembler With Modula"...........................Jan.Verhoeven

"Assembly on the Alpha Platform"........................Rudolf.Seemann

Column: Win32 Assembly Programming
    "Direct Draw Samples"....................................X-Calibre

Column: The Unix World
    "Enter fbcon".................................Konstantin.Boldyshev

Column: Assembly Language Snippets
    "ToHex".....................................................Ronald
    "Hex2ASCII"................................................cpuburn
    "MMX ltostr".....................................Cecchinel.Stephan

Column: Issue Solution
    "ScreenDump"........................................Laura.Fairhead

----------------------------------------------------------------------
       +++++++++++++++++++Issue Challenge++++++++++++++++++
            Dump the contents of the current console to a file
----------------------------------------------------------------------










::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::..............................................INTRODUCTION
                                                                     by mammon_


What? Late again? Wasn't there going to be a December issue?

Well, yeah, there was; unfortunately once again real-world concerns interfered
with timely distribution. And, as usually happens with late issues, this one
is waaaaay oversized, almost 200K due to all the articles I crammed into it. I
didn't even get a chance to include my linux kernel modules article...

This issue seems to have a bit of a 'Hex-to-ASCII' bent to it, mostly from the
snippets but also from the conversion routines offered by Chris and Laura. In
addition, some 'fringe' asm has been supplied with Jan's Modula article, along
with an introduction to Alpha assembly language by Rudolph Seeman. Konstantin
Boldyshev, who helps maintain the linuxassembly.org site, continues the Unix
trend with an introduction to frame-buffer programming under linux.

The two leading articles are both quite large and offer a wealth of information
for the beginning and experienced asm programmer. Digital Alchemist has produced
a work on applying virus techniques to non-destructive applications, and S.
Sirajudeen has tackled the  huge problem of creating a decent UI in console-mode
programs.

In this issue I have tried to leave the code comments as untouched as possible;
the coding styles of the authors vary quite widely, and each clearly demonstrates
the planning behind the program itself -- showing how the algorithm was
conceived before implementation. Stripping any of these examples of all but
comments will soon reveal the worksheet used by the coders to develop their
programs.

Finally, I have taken to formatting these issues in Vim under linux; to check
margins and pagination I have begun proofing them in Netscape and WordPerfect
[10 pt Courier, natch]; they should view fine in any web browser and in most
word processors; to those stuck with Notepad or Edit.com ... my apologies.

_m



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                      Extending DOS Executables
                                                      by Digital Alchemist


The reason behind this essay is to show how techniques first developed by virus
writers can be used for benevolent purposes.  It is my opinion that all
knowledge is good and viral techniques are certainly no exception.  I will lead
you through the development of a program called DOSGUARD which benignly
modifies DOS executables, both COM and EXE.


DESCRIPTION OF DOSGUARD
-----------------------
DOSGUARD is a DOS COM program which I developed in order to restrict access to
certain programs on my computer.  DOSGUARD modifies all of the COM and EXE
files in the current directory, adding code to each one that requires the user
to correctly enter a password before running the original program.

DOSGUARD, while sufficient for this article, could use a little work in the
realm of user friendliness.  More user feedback and a better way to specify
which files to be modified are needed.  In addition, I have written a version
of DOSGUARD that uses simple xor encryption to improve security.

DOSGUARD was written using turbo assembler.


STRUCTURE OF COM FILES
----------------------
Unlike the EXE file format, the programmer has no input into the segment format
of COM files.  All COM files consist of 1 segment only, with no predefined
distinction between data and code.  After DOS finishes some preparatory work,
the COM file is loaded at offset 100h.  The first 256 bytes are known as the
Program Segment Prefix(PSP).  Located at offset 80h is an important data
structure called the DTA or Data Transfer Area.  The DTA is important, but most
of the rest of the PSP can be ignored by the programmer.  Before actually
starting execution of the COM program, DOS sets up the stack at the top of the
segment(the highest memory address).


OUTLINE OF COM MODIFICATION
---------------------------
1. Open the file and read 1st 5 bytes.
2. Make sure the file is not really an EXE file because after DOS 6.0 some
   files ending in ".com" were really EXEs.
3. Check to see if the file has already been modified by DOSGUARD by checking
   if the values of the 4th and 5th bytes match the DOSGUARD identification
   string of "CG".
4. Make sure the file is not so large that when DOSGUARD adds its code it
   doesn't exceed the 64k segment size.
5. If the file passes 2-4 then its ok to modify, so DOSGUARD opens it and
   writes the code to the end of the file.
6. Calculate the size of the jump to the code we added and write the jump
   instruction along with the identification string to the beginning of the
   file.

I'll go over each of these steps in a little more detail with code snippets
where necessary.  The complete source code for DOSGUARD can be found at the
end of the article and at my web page.  Hopefully, the comments will be enough
to explain any areas I don't discuss in detail.

Essentially, the way DOSGUARD modifies COM files is by inserting a jump at the
beginning of the file which goes straight to the password authentication code,
located at the end of the file.  If the correct password is entered by the
user, then it will restore the 5 bytes that were overwritten by the jump and
the identification string and execute the program just like DOSGUARD was never
there.


COM MODIFICATION - STEP 1
-------------------------
Once we've found a COM file, the first thing to do is open it.  Then, after
running some tests on the file, we can determine if it is suitable for
modification.  But first, we need to read the first 5 bytes because we'll
need them later.

        mov     ax, 3D02h               ;Open file R/W
        mov     dx, 9Eh                 ;Filename, stored in DTA
        int     21h
        mov     bx, ax                  ;Save file handle in bx
        mov     ax, 3F00h               ;Read first 5 bytes from file
        mov     cx, 5
        mov     dx, offset obytes
        int     21h


COM MODIFICATION - STEP 2
-------------------------
After DOS 6.0, some files with the COM extension are actually EXEs.
COMMAND.COM, for instance, is one of these.  If we try to modify an EXE file as
if it were a COM file, then we're going to really screw things up.  To prevent
this, we make sure that the string "MZ" doesn't appear in the first two bytes of
the file.  "MZ" is the string which tells DOS that a file is an EXE.

        ;Check to see if file is really an EXE
        cmp     word ptr[obytes], 'ZM'
        je      EXE


COM MODIFICATION - STEP 3
-------------------------
If the file had been previously altered by DOSGUARD, then the 4th and 5th bytes
will contain the identification string "CG".  We need to make sure we skip files
that have this identification string.

        ;Check to see if file is already infected
        ;if it is, then skip it
        cmp     word ptr [obytes + 3], 'GC'
        je      NO_INFECT


COM MODIFICATION - STEP 4
-------------------------
Another thing to watch out for is the file's size.  If the file will exceed
one segment in size when we add our code, then the file is too big to modify.

        ;Make sure file isn't too large
        mov     ax, ds:[009Ah]          ;Size of file from DTA
        add     ax, offset ENDGUARD - offset COMGUARD + 100h
        jc      NO_INFECT               ;If ax overflows then don't infect


COM MODIFICATION - STEP 5
-------------------------
If the file is a suitable candidate for modification, then we simply write our
code to the end of the file.  Also, we have to save the original first 5 bytes
from the file somewhere in your code.  In DOSGUARD's case, the 5 bytes are
already saved in the proper place because "obytes" is located within the code
which we are about to write.

        xor     cx, cx                  ;cx = 0
        xor     dx, dx                  ;dx = 0
        mov     ax, 4202h               ;Move file pointer to the end of file
        int     21h

        mov     ax, 4000h               ;Write the code to the end of file
        mov     dx, offset COMGUARD
        mov     cx, offset ENDGUARD - offset COMGUARD
        int     21h


COM MODIFICATION - STEP 6
-------------------------
The final step is to calculate the size of the jump to our code and write the
opcode for the jump and the identification string over the first 5 bytes of the
file.

        mov     ax, 4200h               ;Move file pointer to beginning of
        xor     cx, cx                  ; file to write jump
        xor     dx, dx
        int     21h

        ;Prepare the jump instruction to be written to beginning of file
        xor     ax, ax
        mov     byte ptr [bytes], 0E9h  ;opcode for jmp
        mov     ax, ds:[009Ah]          ;size of the file
        sub     ax, 3                   ;size of the jump instruction
        mov     word ptr [bytes + 1], ax;size of the jump

        ;Write the jump
        mov     cx, 5;                  ;size to be written
        mov     dx, offset bytes
        mov     ax, 4000h
        int     21h

        mov     ah, 3Eh                 ;Close file
        int     21h


RESPONSIBILITIES OF INSERTED CODE
--------------------------------
There are two problems which the inserted code has to deal with.  First, since
the code could be located at any arbitrary offset within the segment, it cannot
depend on the compiled absolute addresses of its data labels.  To solve this
problem we use a technique virus writers call the delta offset.  The delta
offset is the difference between the actual and compiled addresses of data.
Anytime our code accesses data in memory it adds the delta offset to the data's
compiled address.  The following piece of code finds the delta offset.

        call    GET_START
        GET_START:
        pop     bp
        sub     bp, offset GET_START

The "call" pushes the current ip onto the stack, which is the actual address of
the label "GET_START."  Subtract the compiled address from the actual one and
there's our delta offset.

The second problem is to make sure the first 5 bytes of the host are restored to
their original values before we return from our jump and execute the host.


STRUCTURE OF EXE FILES
----------------------
The EXE file format is much more complicated than the COM format.  The big
difference is that EXE files allow the program to specify how it wants its
segments to be laid out in memory, allowing programs to exceed one 64k segment
in size.  Most EXEs will have separate code, data, and stack segments.

All of this information is stored in the EXE Header.  Here's a brief rundown of
what the header looks like:

        Offset  Size    Field
        0       2       Signature.  Will always be 'MZ'
        2       2       Last Page Size.  Number of bytes on the last
                        page of memory.
        4       2       Page Count.  Number of 512 byte pages in the file.
        6       2       Relocation Table Entries.  Number of items in the
                        relocation pointer table.
        8       2       Header Size.  Size of header in paragraphs,
                        including the relocation pointer table.
        10      2       Minalloc
        12      2       Maxalloc
        14      2       Initial Stack Segment.
        16      2       Initial Stack Pointer.
        18      2       Checksum.  (Usually ignored)
        20      2       Initial Instruction Pointer
        22      2       Initial Code Segment
        24      2       Relocation Table Offset.  Offset to the start of
                        the relocation pointer table.
        26      2       Overlay Number.  Primary executables(the ones we
                        wish to modify) always have this set to zero.

Following the EXE header is the relocation pointer table, with a variable
amount of blank space between the header and the start of the table.  The
relocation table is a table of offsets.  These offsets are combined with
starting segment values calculated by DOS to point to a word in memory where
the final segment address is written.  Essentially, the relocation pointer
table is DOS's way to handle the dynamic placement of segments into physical
memory.  This isn't a problem with COM files because there is only one segment
and the program isn't aware of anything else.  Following the relocation pointer
table is another variable amount of reserved space and finally the program
body.

To successfully add code to an EXE file requires careful manipulation of the EXE
header and relocation pointer table.


OUTLINE OF EXE MODIFICATION
---------------------------
1.  Open the file and read the 1st 2 bytes(DOSGUARD actually reads 5).
2.  Check for EXE signature "MZ".
3.  Read the EXE header.
4.  Check the file for previous infection.
5.  Make sure that the Overlay Number is 0.
6.  Make sure the file is a DOS EXE.
7.  If the file passes 2-6 then it is ok to modify.  The first step is to check
    the relocation pointer table to see if there is room to add 2 pointers.  If
    there is room, then jump to step 9.
8.  If there isn't enough room in the relocation pointer table, then DOSGUARD
    has to make room.  It reads in the entire file after the relocation pointer
    table and writes it back out one paragraph higher in memory.
9.  Save the original ss, sp, cs, and ip.
10. Adjust the file length to paragraph boundary.
11. Write code to the end of the file.
12. Adjust the EXE header to reflect the new starting segments and file size.
13. Write out the header.
14. Modify the relocation pointer table.

The easiest way to think about EXE modification is to imagine that we are
adding a complete COM program to the end of the file.  Our code will occupy its
own segment located just after the host.  This one segment will serve as a code,
data, and stack segment just like in a COM program.  Instead of inserting a jump
to take us there, we will simply adjust the starting segment values in the EXE
header to point to our segment.


EXE MODIFICATION - STEP 1
-------------------------
The same as with COM files, except that the only bytes we actually need are the
first two.  With EXE files we will use different methods for determining
previous modification(I try to avoid using the viral term "infection") and for
transferring execution to our code.


EXE MODIFICATION - STEP 2
-------------------------
Check the first two bytes for the EXE signature "MZ".  If the file doesn't
start with "MZ," then it isn't a DOS EXE.

        cmp     word ptr[obytes], 'ZM'
        je      EXE


EXE MODIFICATION - STEP 3
-------------------------
Now, DOSGUARD simply reads the EXE header into a 28 byte buffer.  Later, we
will make the necessary changes to the header and write it back out.

        xor     cx, cx                  ;Move the file pointer back
        xor     dx, dx                  ;to the beginning of the file
        mov     ax, 4200h
        int     21h
        mov     cx, 1Ch                 ;read exe header (28 bytes)
        mov     dx, offset exehead      ;into buffer
        mov     ah, 3Fh
        int     21h


EXE MODIFICATION - STEP 4
-------------------------
We don't use a signature string to mark EXE files.  Instead, we compare the
code entry point with the size of the file.  If the file has been previously
modified by DOSGUARD, then we know that the distance of the code entry point
from the end of the file will be the length of the code that DOSGUARD adds.  To
put things in mathematical terms:

        (initial cs * 16) + (size of code DOSGUARD adds) + (size of header)

will equal the size of the file.  The initial cs times 16 is the code entry
point, of course.  You have to add the header size because it isn't loaded into
memory along with the rest of the code and data.

        ;Make sure it hasn't already been infected
        ;If (initial CS * 16) + (size of code) + (size of header) == filesize
        ;  then the file has already been infected
        mov     ax, word ptr [exehead+22]
        mov     dx, 16
        mul     dx
        add     ax, offset ENDGUARD2 - offset EXEGUARD
        adc     dx, 0
        mov     cx, word ptr [exehead+8]
        add     cx, cx
        add     cx, cx
        add     cx, cx
        add     cx, cx
        add     ax, cx
        adc     dx, 0
        cmp     ax, word ptr cs:[9Ah]
        jne     EXEOK
        cmp     dx, word ptr cs:[9Ch]
        je      NO_INFECT


EXE MODIFICATION - STEP 5
-------------------------
Another simple test that needs to be done is to make sure that the Overlay
Number stored in the EXE header is 0.  The code for this is simple.

        ;Make sure Overlay Number is 0
        cmp     word ptr [exehead+26], 0
        jnz     NO_INFECT


EXE MODIFICATION - STEP 6
-------------------------
This part is kind of tricky.  There are lots of files out there with the EXE
extension that aren't DOS executables.  Both Windows and OS/2 use this
extension as well, for instance.  To complicate matters, there isn't an easy
way to automatically distinguish DOS EXEs from the others.  The technique that
I use in DOSGUARD is to check the offset of the relocation pointer table and
make sure that it is less than 40h.  This should always detect Windows and OS/2
programs, but it sometimes raises false alarms on valid DOS files.

        ;Make sure it is a DOS EXE (as opposed to windows or OS/2)
        cmp     word ptr [exehead+24], 40h
        jae     NO_INFECT


EXE MODIFICATION - STEP 7
-------------------------
Now that we know we have a file that we can modify we just have to determine if
its going to be easy to modify or a real pain.  Here's the deal.  The
relocation pointer table is always an even multiple of 16 bytes in size.  Each
pointer in the table is 4 bytes.  For our purposes, we need to add 2 pointers to
the table.  That means the table must have at least 8 bytes free in order to
leave it at its current size.  If it doesn't have room for two more pointers,
then we will have to make room.  That means reading in the whole file after the
table and writing it back out with 16 bytes more space for the table.

To find out if there is enough room, all you have to do is subtract the offset
of the relocation pointer table and the number of entries in the table from the
size of the header.  The result is the amount of free space in the table.  All
of this information can be found in the handy dandy EXE header.  Of course, you
have to take into account the units that each of these values are stored in
(bytes, paragraphs, etc.)

        ;Check the relocation pointer table to see if there is
        ;room.  If there isn't then we'll have to make room.
        mov     ax, word ptr [exehead+8];size of header in paragraphs
        add     ax, ax                  ;
        add     ax, ax                  ;Convert to double words.
        sub     ax, word ptr [exehead+6];Subtract # of entries each of
        add     ax, ax                  ;which is a double word and then
        add     ax, ax                  ;convert the final total to bytes.
        sub     ax, word ptr [exehead+24];If there are 8 bytes left after
        cmp     ax, 8                    ;you subtract the offset to the
        jc      NOROOM                   ;reloc table then there is room.
        jmp     HAVEROOM


EXE MODIFICATION - STEP 8
-------------------------
The first thing to do is move the file pointer to the correct spot just after
the last entry in the relocation pointer table.

        xor     cx, cx                  ;Move the file pointer to the end of
        mov     dx, word ptr [exehead+24]  ;the relocation pointer table.
        mov     ax, word ptr [exehead+6];size of relocation table in doubles
        add     ax, ax                  ;* 4 to get bytes
        add     ax, ax
        add     dx, ax                  ;add that to start of table
        push    dx
        mov     ax, 4200h
        int     21h

Now, DOSGUARD calculates the amount which needs to be written.  This code is in
the function called CALC_SIZE.  When CALC_SIZE is finished, cx will hold the
number of pages and "lps" will hold the size of the last page since it probably
will not be a full 512 byte page.

        ;dx holds the position in the file where we want to start reading.
        ;So, the amount to read in and write back out is equal to the size
        ;of the file minus dx.
        mov     cx, word ptr [exehead+2]
        mov     word ptr [lps], cx      ;Copy Last Page Size into lps
        mov     cx, word ptr [exehead+4];Copy Num Pages into cx
        cmp     dx, word ptr [lps]      ;If bytes to subtract are less than
        jbe     FINDLPS                 ;lps then just subtract them and exit
        mov     ax, dx
        xor     dx, dx
        mov     cx, 512
        div     cx                      ;ax = pages to subtract
        mov     cx, word ptr [exehead+4];dx = remainder to subtract from lps
        sub     cx, ax
        cmp     dx, word ptr [lps]
        jbe     FINDLPS
        sub     cx, 1
        mov     ax, dx
        sub     ax, word ptr [lps]
        mov     dx, 512
        sub     dx, ax

        FINDLPS:
        sub     word ptr [lps], dx      ;Subtract start position and leave
                                        ;Num Pages the same

Once you know the amount of code you have to move, you have to come up with a
way to simultaneously read and write from the same file without overwriting
data that hasn't been read yet.  DOSGUARD's solution is to use a 16 byte
buffer.  DOSGUARD's move loop reads 528 bytes and writes out 512 bytes with each
iteration.  In other words, it reads 16 bytes ahead of where it is writing so
that it doesn't overwrite bytes before they're read.  DOSGUARD has a number of
functions for reading and writing pages, reading and writing paragraphs,  and
moving the file pointer around.  It also has one function for moving the 16
bytes at the end of the 528 byte buffer in memory to the front.  Well, I'll shut
up now and show you the code for the move loop.

        mov     dx, offset buffer
        call    READ_PAGE
        mov     dx, offset para
        call    READ_PARA
        call    DECFP_PAGE
        call    WRITE_PAGE
        call    MOVE_PARA
        dec     cx
        cmp     cx, 1
        je      LASTPAGE

        MOVELOOP:
        mov     dx, offset buffer + 16
        call    READ_PAGE
        call    DECFP_PAGE
        call    WRITE_PAGE
        call    MOVE_PARA
        dec     cx
        cmp     cx, 1
        jne     MOVELOOP

When DOSGUARD gets to the last page, it finishes things off by reading the last
fraction of a page and then writing out those bytes plus the 16 bytes that were
left buffered from the last iteration of the move loop.

        LASTPAGE:
        sub     word ptr [lps], 16
        mov     cx, word ptr [lps]
        mov     dx, offset buffer + 16
        mov     ah, 3Fh
        int     21h
        push    cx
        mov     dx, cx
        neg     dx
        mov     cx, -1
        mov     ax, 4201h
        int     21h
        pop     cx
        add     cx, 16
        mov     dx, offset buffer
        mov     ah, 40h
        int     21h

Last, but not least, there is a little maintanence to do.

        ;Got to adjust the file size since it will be used later
        add     word ptr cs:[9Ah], 16
        adc     word ptr cs:[9Ch], 0

        ;Increment the header size within the EXE header
        add     word ptr cs:[exehead+8], 1

        ;Change Page Count and Last Page Size in EXE header
        cmp     word ptr [exehead+2], 496
        jae     ADDPAGE
        add     word ptr [exehead+2], 16
        jmp     HAVEROOM

Oh yeah, there is one more condition that needs to be handled here.  If the last
page was almost full(496 or more bytes), then adding 16 bytes to the file size
will overflow that page so you have to add a whole new page.

        ADDPAGE:
        ;Adjust the header to add a page if the 16 additional bytes run
        ;over to a new page.
        inc     word ptr [exehead+4]
        mov     ax, 512
        sub     ax, word ptr [exehead+2]
        mov     dx, 16
        sub     dx, ax
        mov     word ptr [exehead+2], dx


EXE MODIFICATION - STEP 9
-------------------------
Whew!  Step 8 was a doozy, but now we're almost done.  All Step 9 requires of
us is to save the original segment values from our victim.  DOSGUARD saves
these values in the order that they are found within the EXE header.

        mov     ax, word ptr [exehead+14] ;save orig stack segment
        mov     [hosts], ax
        mov     ax, word ptr [exehead+16] ;save orig stack pointer
        mov     [hosts+2], ax
        mov     ax, word ptr [exehead+20] ;save orig ip
        mov     [hostc], ax
        mov     ax, word ptr [exehead+22] ;save orig cs
        mov     [hostc+2], ax


EXE MODIFICATION - STEP 10
--------------------------
It will make things a little easier later on if the end of the file we are
about to modify lies on a paragraph boundary.  This way the starting ip for the
new code that we're adding will always be zero.

        ;adjust file length to paragraph boundary
        mov     cx, word ptr cs:[9Ch]
        mov     dx, word ptr cs:[9Ah]
        or      dl, 0Fh
        add     dx, 1
        adc     cx, 0
        mov     cs:[9Ch], cx
        mov     cs:[9Ah], dx
        mov     ax, 4200h               ;move file pointer to end of file
        int     21h                     ;plus boundary


EXE MODIFICATION - STEP 11
--------------------------
Finally, we can write our code to the file.  Just like with the COM file, we
will write our code to the end of the file.  The difference is in how we get
there when its time to execute it.  With COM files we used a jump.  With EXE
files we adjust the starting cs:ip to point to our code.

        mov     cx, offset ENDGUARD2 - offset EXEGUARD  ;write code to end
        mov     dx, offset EXEGUARD                     ;of the exe file
        mov     ah, 40h
        int     21h


EXE MODIFICATION - STEP 12
--------------------------
With our code neatly tucked after the host program's code, its time to modify
the EXE header so that our code is the first to execute.  We also have to
adjust the size fields in the EXE header to take into account all the code we
just added.

The first thing to is figure out what the starting segment values need to be.
The starting cs will simply be the original file size divided by 16 minus the
header size.  The initial ip will be 0 because of Step 11.  In DOSGUARD's case
the ss will be the same as the cs and the sp will point to an address 256 bytes
after the end of our code.  256 bytes is plenty of room for DOSGUARD's stack.

        mov     ax, word ptr cs:[9Ah]   ;calculate module's CS
        mov     dx, word ptr cs:[9Ch]   ;ax:dx contains orig file size
        mov     cx, 16                  ;CS = file size / 16 - header size
        div     cx
        sub     ax, word ptr [exehead+8];header size in paragraphs
        mov     word ptr [exehead+22], ax ;ax is now initial cs
        mov     word ptr [exehead+14], ax ;ax is now initial ss
        mov     word ptr [exehead+20], 0  ;initial ip
        mov     word ptr [exehead+16], ENDGUARD2 - EXEGUARD + 100h ;initial sp

This next bit of code calculates the new file size, in pages of course.

        ;calculate new file size
        mov     dx, word ptr cs:[9Ch]
        mov     ax, word ptr cs:[9Ah]
        add     ax, offset ENDGUARD2 - offset EXEGUARD + 200h
        adc     dx, 0
        mov     cx, 200h
        div     cx
        mov     word ptr [exehead+4], ax
        mov     word ptr [exehead+2], dx
        add     word ptr [exehead+6], 2


EXE MODIFICATION - STEP 13
--------------------------
Now, we should be through with the header so we can write it back out to the
file.

        ;Write out the new header
        mov     cx, 1Ch
        mov     dx, offset exehead
        mov     ah, 40h
        int     21h


EXE MODIFICATION - STEP 14
--------------------------
Last, but not least, we have to modify the relocation pointer table.  First,
we need to move the file pointer to where we need to add the new entries.

        mov     ax, word ptr [exehead+6];Get the # of relocatables
        dec     ax                      ;Position to add relocatable equals
        dec     ax                      ;(# - 2)*4 + table offset
        mov     cx, 4
        mul     cx
        add     ax, word ptr [exehead+24]
        adc     dx, 0
        mov     cx, dx
        mov     dx, ax
        mov     ax, 4200h               ;move file pointer to position
        int     21h

Now, we have to add two pointers to the table.  The first points to "hosts,"
which is the stack segment of the original program.  The second points to
"hostc+2," which holds the original program's code segment.

        ;Use exehead as a buffer for relocatables.
        ;Put two pointers in this buffer, first points to ss in
        ;hosts and second points to cs in hostc.
        mov     word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
        mov     ax, word ptr [exehead+22]
        mov     word ptr [exehead+2], ax
        mov     word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
        mov     word ptr [exehead+6], ax
        mov     cx, 8
        mov     dx, offset exehead
        mov     ah, 40h                 ;Write the 8 bytes.
        int     21h
        mov     ah, 3Eh                 ;Close the file.
        int     21h


RESPONSIBILITIES OF INSERTED CODE
---------------------------------
There are several items which the code module we added must take into
consideration.  First of all, when it is finished, the state of registers, etc.
must be exactly what the original program would expect them to be.  For
instance, ax is set by DOS to indicate whether or not the Drive ID stored in
the FCBs is valid.  So,  the value of ax must be preserved by our code.  Also,
the original program may expect other registers to be set to initial values
of zero.  And of course, the segment registers need to be restored after our
code's execution.

In order to actually restore control to the host, our code must restore ss and
sp to their original values.  Then, it jumps to the original cs:ip.

Also, inserted code can't be dependent on absolute addresses for its data.
Therefore, DOSGUARD accesses all data by its offset from the end of the file.


CONCLUSION
----------
Hopefully, i've explained the techniques I used in developing DOSGUARD well
enough for you to develop your own binary modiying programs.  As I mentioned at
the beginning of this article, DOSGUARD has a lot a room for improvement.  If
you are interested then you should check out my web page and download the
source for ENCGUARD, a more secure version of DOSGUARD.  A nice way to extend
DOSGUARD would be to improve on the encryption techniques used in ENCGUARD.  If
I ever find the time I would like to write a Win32 version of DOSGUARD which
could safely modify the PE file format.  If I ever do embark on such a task,
I'll be sure to let the readers of Assembly Programming Journal know about it.


REFERENCES
----------
"The Giant Black Book of Computer Viruses, 2nd edition" by Mark Ludwig


CONTACT INFORMATION
-------------------
email:  jjsimpso@eos.ncsu.edu
web page: http://www4.ncsu.edu/~jjsimpso/index.html

Check out my web page for more information on my research into code
modification.  Also, feel free to email me with ideas, corrections,
improvements, etc.


---------------------------BEGIN DOSGUARD.ASM----------------------------------
.model tiny
.code

        ORG     100h

START:
        jmp     BEGINCODE               ;Jump the identification string
        DB      'CG'

BEGINCODE:

        mov     dx, offset filter1
        call    FIND_FILES
        mov     dx, offset filter2
        call    FIND_FILES

        mov     ax, 4C00h               ;DOS terminate
        int     21h

;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:

        mov     ah, 4Eh                 ;Search for files matching filter
        int     21h

SLOOP:
        jc      DONE
        mov     ax, 3D02h               ;Open file R/W
        mov     dx, 9Eh                 ;Filename, stored in DTA
        int     21h
        mov     bx, ax                  ;Save file handle in bx
        mov     ax, 3F00h               ;Read first 5 bytes from file
        mov     cx, 5
        mov     dx, offset obytes
        int     21h

        ;Check to see if file is really an EXE
        cmp     word ptr[obytes], 'ZM'
        je      EXE

COM:
        ;Check to see if file is already infected
        ;if it is, then skip it
        cmp     word ptr [obytes + 3], 'GC'
        je      NO_INFECT


        ;Make sure file isn't too large
        mov     ax, ds:[009Ah]          ;Size of file
        add     ax, offset ENDGUARD - offset COMGUARD + 100h
        jc      NO_INFECT               ;If ax overflows then don't infect

        ;If we made it this far then we know the file is safe to modify
        call    INFECT_COM
        jmp     NO_INFECT

EXE:
        ;Read the EXE Header
        call    READ_HEADER
        jc      NO_INFECT               ;error reading file so skip it

        ;Make sure it hasn't already been infected
        ;If (initial CS * 16) + (size of EXEGUARD) + (size of header) == size
        ;  then the file has already been infected
        mov     ax, word ptr [exehead+22]
        mov     dx, 16
        mul     dx
        add     ax, offset ENDGUARD2 - offset EXEGUARD
        adc     dx, 0
        mov     cx, word ptr [exehead+8]
        add     cx, cx
        add     cx, cx
        add     cx, cx
        add     cx, cx
        add     ax, cx
        adc     dx, 0
        cmp     ax, word ptr cs:[9Ah]
        jne     EXEOK
        cmp     dx, word ptr cs:[9Ch]
        je      NO_INFECT

EXEOK:
        ;Make sure Overlay Number is 0
        cmp     word ptr [exehead+26], 0
        jnz     NO_INFECT

        ;Make sure it is a DOS EXE (as opposed to windows or OS/2
        cmp     word ptr [exehead+24], 40h
        jae     NO_INFECT

        call    INFECT_EXE

NO_INFECT:
        mov     ax, 4F00h               ;Find next file
        int     21h
        jmp     SLOOP

DONE:

        ret


;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
        xor     cx, cx                  ;cx = 0
        xor     dx, dx                  ;dx = 0
        mov     ax, 4202h               ;Move file pointer to the end of file
        int     21h

        mov     ax, 4000h               ;Write the code to the end of file
        mov     dx, offset COMGUARD
        mov     cx, offset ENDGUARD - offset COMGUARD
        int     21h

        mov     ax, 4200h               ;Move file pointer to beginning of
        xor     cx, cx                  ; file to write jump
        xor     dx, dx
        int     21h

        ;Prepare the jump instruction to be written to beginning of file
        xor     ax, ax
        mov     byte ptr [bytes], 0E9h  ;opcode for jmp
        mov     ax, ds:[009Ah]          ;size of the file
        sub     ax, 3                   ;size of the jump instruction
        mov     word ptr [bytes + 1], ax;size of the jump

        ;Write the jump
        mov     cx, 5;                  ;size to be written
        mov     dx, offset bytes
        mov     ax, 4000h
        int     21h

        mov     ah, 3Eh                 ;Close file
        int     21h

        ret


;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:

        ;Check the relocation pointer table to see if there is
        ;room.  If there isn't then we'll have to make room.
        mov     ax, word ptr [exehead+8];size of header in paragraphs
        add     ax, ax                  ;
        add     ax, ax                  ;Convert to double words.
        sub     ax, word ptr [exehead+6];Subtract # of entries each of
        add     ax, ax                  ;which is a double word and then
        add     ax, ax                  ;convert the final total to bytes.
        sub     ax, word ptr [exehead+24];If there are 8 bytes left after
        cmp     ax, 8                    ;you subtract the offset to the
        jc      NOROOM                   ;reloc table then there is room.
        jmp     HAVEROOM

NOROOM:
        ;Not enough room in the relocation table so we are going to
        ;have to add a paragraph to the table.  As a result, we must
        ;read in the whole file after the relocation table and write
        ;it back out one paragraph down in memory.
        xor     cx, cx                  ;Move the file pointer to the end of
        mov     dx, word ptr [exehead+24]  ;the relocation pointer table.
        mov     ax, word ptr [exehead+6];size of relocation table in doubles
        add     ax, ax                  ;* 4 to get bytes
        add     ax, ax
        add     dx, ax                  ;add that to start of table
        push    dx
        mov     ax, 4200h
        int     21h

        pop     dx
        call    CALC_SIZE
        cmp     cx, 1
        je      LASTPAGE

        mov     dx, offset buffer
        call    READ_PAGE
        mov     dx, offset para
        call    READ_PARA
        call    DECFP_PAGE
        call    WRITE_PAGE
        call    MOVE_PARA
        dec     cx
        cmp     cx, 1
        je      LASTPAGE

MOVELOOP:
        mov     dx, offset buffer + 16
        call    READ_PAGE
        call    DECFP_PAGE
        call    WRITE_PAGE
        call    MOVE_PARA
        dec     cx
        cmp     cx, 1
        jne     MOVELOOP

LASTPAGE:
        sub     word ptr [lps], 16
        mov     cx, word ptr [lps]
        mov     dx, offset buffer + 16
        mov     ah, 3Fh
        int     21h
        push    cx
        mov     dx, cx
        neg     dx
        mov     cx, -1
        mov     ax, 4201h
        int     21h
        pop     cx
        add     cx, 16
        mov     dx, offset buffer
        mov     ah, 40h
        int     21h

        ;Got to adjust the file size since it will be used later
        add     word ptr cs:[9Ah], 16
        adc     word ptr cs:[9Ch], 0

        ;Increment the header size within the EXE header
        add     word ptr cs:[exehead+8], 1

        ;Change Page Count and Last Page Size in EXE header
        cmp     word ptr [exehead+2], 496
        jae     ADDPAGE
        add     word ptr [exehead+2], 16
        jmp     HAVEROOM

ADDPAGE:
        ;Adjust the header to add a page if the 16 additional bytes run
        ;over to a new page.
        inc     word ptr [exehead+4]
        mov     ax, 512
        sub     ax, word ptr [exehead+2]
        mov     dx, 16
        sub     dx, ax
        mov     word ptr [exehead+2], dx

HAVEROOM:
        mov     ax, word ptr [exehead+14] ;save orig stack segment
        mov     [hosts], ax
        mov     ax, word ptr [exehead+16] ;save orig stack pointer
        mov     [hosts+2], ax
        mov     ax, word ptr [exehead+20] ;save orig ip
        mov     [hostc], ax
        mov     ax, word ptr [exehead+22] ;save orig cs
        mov     [hostc+2], ax

        mov     cx, word ptr cs:[9Ch]   ;adjust file length to paragraph
        mov     dx, word ptr cs:[9Ah]   ;  boundary
        or      dl, 0Fh
        add     dx, 1
        adc     cx, 0
        mov     cs:[9Ch], cx
        mov     cs:[9Ah], dx
        mov     ax, 4200h               ;move file pointer to end of file
        int     21h                     ;plus boundary

        mov     cx, offset ENDGUARD2 - offset EXEGUARD  ;write code to end
        mov     dx, offset EXEGUARD                     ;of the exe file
        mov     ah, 40h
        int     21h

        xor     cx, cx                  ;Move file pointer to beginning of file
        xor     dx, dx
        mov     ax, 4200h
        int     21h

        ;adjust the EXE header and then write it back out
        mov     ax, word ptr cs:[9Ah]   ;calculate module's CS
        mov     dx, word ptr cs:[9Ch]      ;ax:dx contains orig file size
        mov     cx, 16                  ;CS = file size / 16 - header size
        div     cx
        sub     ax, word ptr [exehead+8];header size in paragraphs
        mov     word ptr [exehead+22], ax ;ax is now initial cs
        mov     word ptr [exehead+14], ax ;ax is now initial ss
        mov     word ptr [exehead+20], 0  ;initial ip
        mov     word ptr [exehead+16], ENDGUARD2 - EXEGUARD + 100h ;initial sp

        mov     dx, word ptr cs:[9Ch]   ;calculate new size file size
        mov     ax, word ptr cs:[9Ah]
        add     ax, offset ENDGUARD2 - offset EXEGUARD + 200h
        adc     dx, 0
        mov     cx, 200h
        div     cx
        mov     word ptr [exehead+4], ax
        mov     word ptr [exehead+2], dx
        add     word ptr [exehead+6], 2

        mov     cx, 1Ch                 ;Write out the new header
        mov     dx, offset exehead
        mov     ah, 40h
        int     21h

        ;modify relocatables table
        mov     ax, word ptr [exehead+6];Get the # of relocatables
        dec     ax                      ;Position to add relocatable equals
        dec     ax                      ;(# - 2)*4 + table offset
        mov     cx, 4
        mul     cx
        add     ax, word ptr [exehead+24]
        adc     dx, 0
        mov     cx, dx
        mov     dx, ax
        mov     ax, 4200h               ;move file pointer to position
        int     21h

        ;Use exehead as a buffer for relocatables.
        ;Put two pointers in this buffer, first points to ss in
        ;hosts and second points to cs in hostc.
        mov     word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
        mov     ax, word ptr [exehead+22]
        mov     word ptr [exehead+2], ax
        mov     word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
        mov     word ptr [exehead+6], ax
        mov     cx, 8
        mov     dx, offset exehead
        mov     ah, 40h                 ;Write the 8 bytes.
        int     21h
        mov     ah, 3Eh                 ;Close the file.
        int     21h

        ret                             ;Done!

;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
        ;dx holds the position in the file where we want to start reading.
        ;So, the amount to read in and write back out is equal to the size
        ;of the file minus dx.

        mov     cx, word ptr [exehead+2]
        mov     word ptr [lps], cx      ;Copy Last Page Size into lps
        mov     cx, word ptr [exehead+4];Copy Num Pages into cx

        cmp     dx, word ptr [lps]      ;If bytes to subtract are less than
        jbe     FINDLPS                 ;lps then just subtract them and exit
        mov     ax, dx
        xor     dx, dx
        mov     cx, 512
        div     cx                      ;ax = pages to subtract
        mov     cx, word ptr [exehead+4];dx = remainder to subtract from lps
        sub     cx, ax
        cmp     dx, word ptr [lps]
        jbe     FINDLPS
        sub     cx, 1
        mov     ax, dx
        sub     ax, word ptr [lps]
        mov     dx, 512
        sub     dx, ax

FINDLPS:
        sub     word ptr [lps], dx      ;Subtract start position and leave
                                        ;Num Pages the same

        ret

;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
        xor     cx, cx                  ;Move the file pointer back
        xor     dx, dx                  ;to the beginning of the file
        mov     ax, 4200h
        int     21h
        mov     cx, 1Ch                 ;read exe header (28 bytes)
        mov     dx, offset exehead      ;into buffer
        mov     ah, 3Fh
        int     21h

        ret                             ;return with cf set properly

;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
        push    ax
        push    cx

        mov     ah, 3Fh
        mov     cx, 512
        int     21h

        pop     cx
        pop     ax

        ret

;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
        push    ax
        push    cx

        mov     ah, 3Fh
        mov     cx, 16
        int     21h

        pop     cx
        pop     ax


        ret

;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
        push    ax
        push    cx
        push    dx

        mov     ah, 40h
        mov     cx, 512
        mov     dx, offset buffer
        int     21h

        pop     dx
        pop     cx
        pop     ax

        ret

;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
        push    ax
        push    cx
        push    dx

        mov     ah, 40h
        mov     cx, 16
        mov     dx, offset buffer
        int     21h

        pop     dx
        pop     cx
        pop     ax

        ret

;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
        push    ax
        push    cx
        push    dx

        mov     ax, 4201h
        mov     cx, -1
        mov     dx, -512
        int     21h

        pop     dx
        pop     cx
        pop     ax

        ret

;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
        push    ax
        push    cx
        push    dx

        mov     ax, 4201h
        mov     cx, -1
        mov     dx, -16
        int     21h

        pop     dx
        pop     cx
        pop     ax

        ret

;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
        push    cx

        mov     si, offset para
        mov     di, offset buffer
        mov     cx, 16
        rep     movsb

        pop     cx

        ret



;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
        call    GET_START

GET_START:
        pop     bp
        sub     bp, offset GET_START

        mov     ah, 9h                  ;DOS print string
        lea     dx, [bp + prompt]       ;Print the password prompt
        int     21h
        lea     di, [bp + guess]
        xor     cx, cx

READLOOP:
        mov     ah, 7h                  ;Read without echo
        int     21h
        inc     cx                      ;Count of characters entered
        stosb                           ;Store guess for comparison later
        cmp     cx, 10                  ;Limit guess to 10 chars including CR
        je      CHECKPASS
        cmp     al, 13                  ;Quit loop when CR read
        jne     READLOOP

CHECKPASS:
        lea     di, [bp + guess]        ;Setup for passwd checking loop
        lea     si, [bp +passwd]        ;Setup addresses for cmpsb
        xor     cx, cx                  ;Set counter to zero
        cld                             ;Tell cmpsb to increment si and di

CHECKLOOP:
        cmpsb                           ;Compare passwd with guess
        jne     FAIL                    ;Abort program if password is wrong
        inc     cx                      ;Increment counter
        cmp     cx, 8                   ;Only check first 8 chars
        jne     CHECKLOOP               ;Loop until you've read first 8

SUCCESS:
        mov     cx, 5
        cld
        lea     si, [bp + obytes]
        mov     di, 100h
        rep     movsb
        push    100h                    ;return from the jump to execute
        ret                             ;the host program

FAIL:
        mov     ah, 9h                  ;DOS print string
        lea     dx, [bp + badpass]      ;Print bad password msg
        int     21h
        mov     ax, 4C00h
        int     21h

prompt  DB      'password: ','$'
badpass DB      'Invalid password!','$'
passwd  DB      'smcrocks'
guess   DB      10 dup (0)
obytes  DB      0,0,0,0,0

ENDGUARD:


;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
        push    ax                      ;Save startup value in ax
        push    ds                      ;Save value of ds
        mov     ax, cs                  ;Put cs into ds and es
        mov     ds, ax
        mov     es, ax
        mov     bp, offset ENDGUARD2 - offset EXEGUARD
        mov     ax, [bp-4]

        mov     ah, 9h                  ;DOS print string
        lea     dx, [bp-57]             ;Print the password prompt
        int     21h
        lea     di, [bp-20]
        xor     cx, cx

EREADLOOP:
        mov     ah, 7h                  ;Read without echo
        int     21h
        inc     cx                      ;Count of characters entered
        stosb                           ;Store guess for comparison later
        cmp     cx, 10                  ;Limit guess to 10 chars including CR
        je      ECHECKPASS
        cmp     al, 13                  ;Quit loop when CR read
        jne     EREADLOOP

ECHECKPASS:
        lea     di, [bp-20]             ;Setup for passwd checking loop
        lea     si, [bp-28]             ;Setup addresses for cmpsb
        xor     cx, cx                  ;Set counter to zero
        cld                             ;Tell cmpsb to increment si and di

ECHECKLOOP:
        cmpsb                           ;Compare passwd with guess
        jne     EFAIL                   ;Abort program if password is wrong
        inc     cx                      ;Increment counter
        cmp     cx, 8                   ;Only check first 8 chars
        jne     ECHECKLOOP              ;Loop until you've read first 8

ESUCCESS:
        pop     ds
        mov     ax, ds
        mov     es, ax
        pop     ax

        cli
        mov     ss, word ptr cs:[bp-10]
        mov     sp, word ptr cs:[bp-8]
        sti

        xor     cx, cx
        xor     dx, dx
        xor     bp, bp
        xor     si, si
        xor     di, di
        lahf
        xor     ah, ah
        sahf


        jmp     dword ptr cs:[ENDGUARD2-EXEGUARD-6]


EFAIL:
        mov     ah, 9h                  ;DOS print string
        lea     dx, [bp-46]             ;Print bad password msg
        int     21h
        mov     ax, 4C00h
        int     21h

eprompt DB      'password: ','$'
ebadpass DB     'Invalid password!','$'
epasswd DB      'smcrocks'
eguess  DB      10 dup (0)
hosts   DW      0, 0
hostc   DW      0, 0
delta   DW      0

ENDGUARD2:


filter1 DB      '*.com',0
filter2 DB      '*.exe',0
bytes   DB      0,0,0,'CG'
exehead DB      28 dup (0)
buffer  DB      512 dup (0)
para    DB      16 dup (0)
lps     DW      0

END START
---------------------------END DOSGUARD.ASM------------------------------------



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                             Creating a User-Friendly Interface
                                             by S Sirajudeen


    Now a days, a programmer of any language has to include user friendly
features in his commercial software, since users desire user friendliness
for easy use. For example, Windows is the most popular OS due to its Graphical
User Interface.

     For an assembly language programmer who tries to develop a DOS-based
program, it is drudgery and challenging to incorporate even a few basic
features of graphical interface like that of Windows.

     Sometimes, in assembly language, the time taken to develop the core
of a software may be very less than writing code for its user interface. For
instance, assume that we're writing an addition program which displays a
dialog box to input two numbers and displays result in a dialog box. Here,the
dialog box is the user interface. What we have to do in this program is,
     * Displaying a dialog box.
     * Receiving the numbers to be added as string.
     * Checking the string whether it contains alphabets and graphics
       characters. If so, prompting the user to reenter the numbers.
     * Converting the ASCII digits into binary form.
     * Performing binary multibyte addition..
     * Converting sum which is binary into ASCII digits.
     * Displaying sum in a dialog box.
Our intention is only the addition of two numbers. But we have to spend more
time in the user interface design than for addition.

     As I say these things, you may become frustrated and decide to skip user
interface design. Still, in developing utilities or packages for commercial
purpose, a programmer will have to do these things to accomodate users. This
is why I present this article.

     This article will focus on user friendly features in DOS text mode.
In DOS text mode, user friendly means features such as menus, message box,
dialog box, list box, text window, radio button, status bar, mouse support etc.

     In this article, I will cover only an about message box and a dialog box.
However, knowledge of interrupts (for screen and mouse handling) is essential,
even for a C/C++ programmer, to incorporate user friendly features in a DOS
based program.

GETTING STARTED:
   Before going on, some things must be cleared.

 i) A text can be displayed in one of the following ways
         1) Direct access of video memory
         2) Using INT 21h
         3) Using INT 10h
    In the examples of this article, I have used the function 0Eh of INT 10h
    to display text.

ii)   To make the example programs as straightforward, I have used |, -
    and + as the box characters in the dialog box, since actual box characters
    are EXTENDED ASCII characters which are not allowed in a text article.

      The content of dialog box is labeled as DIALOG_BOX_TEXT.

      Before compiling this program, in the content of the dialog box,
   PLEASE REPLACE the characters |, - and + with the BOX CHARACTERS which
   are specified below.

   --------------------------------------
    ASCII code      Description
   --------------------------------------
      179           |  Vertical bar

      196          --  Horizontal bar

      218          |   Upper left corner

      191           |  Upper right corner

      192          |_  Lower left corner

      217          _|  Lower right corner
   --------------------------------------

EXAMPLE 1:
     First of all, we're going to put a zooming message box in our program. It
is an introduction to second example.

    You may be seen that some utlities such as Norton Utilities display
zooming message box to alert users.

    What this program does is
       - n boxes of different size, are continously displayed one after
         another for n seconds each. In this case, each time a box
         which is larger than previous one is displayed.
         It seems like the box is zooming.

         LOGIC:
            Assume that displaying boxes which are larger than previously
         displayed box, means enalarging/zooming the previously displayed box.
              i) Zoom box by n rows
             ii) Zoom box by n columns
            iii) Zooming box for n times

       - It displays horizontal and vertical shadows for the box
       - Finally displays text within the box

    What you will learn:
       i) Screen handling using BIOS interrupt 10h
      ii) An introduction to learn the second example.

Below is the source code of our simple program.
;; +------------------------------------------------------------------------+
;; | Program   : MSGBOX.ASM                                                 |
;; | Purpose   : Demonstration program about Message Box                    |
;; | Assembler : TASM                                                       |
;; +------------------------------------------------------------------------+

;; MACROS in this program    : @SetTextMode, @Cursor, @Display, @Window, @Delay
;; PROCEDURES in this program: Message_box, Window
;;///////////////////////////////////////////////////////////////////////;;
.386
MODEL USE16 TINY     ;; @Always must be TINY model

;;///////////////////////////////////////////////////////////////////////;;
DATASEG              ;; Initialize variables

RED    EQU 4fh       ;; @Color values
BLACK  EQU 0fh
BLUE   EQU 1fh

screen                EQU BLUE
shadow_colour         EQU BLACK
box_background_colour EQU RED

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
nl EQU  0Dh,0Ah
label dialog_box_text
db nl
db nl,'      +-------------------------+--------------------------------------+'
db nl,'      | ::/ \::::::.            |  Program to Display a Message Box    |'
db nl,'      | :/___\:::::::.          |                                      |'
db nl,'      | /|    \::::::::.        |     Written  By  S.SIRAJUDEEN.       |'
db nl,'      | :|   _/\:::::::::.      |   E-Mail: ssirajudeen@netscape.net   |'
db nl,'      | :| _|\  \::::::::::.    |                                      |'
db nl,'      | :::\_____\::::::::::.   |       Published in ASMJOURNAL        |'
db nl,'      | ::::::::::::::::::::::. | Internet: asmjournal.freeservers.com |'
db nl,'      |      AsmJournal         |                                      |'
db nl,'      +-------------------------+--------------------------------------+'
db nl,'      |  # If you have any comments or suggestions then please email me|'
db nl,'      |    at ssirajudeen@netscape.net                                 |'
db nl,'      +----------------------------------------------------------------+'
db nl,nl,nl,nl
count dw $-offset dialog_box_text

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
upper_x equ 08       ;; Upper left corner of the box to be zoomed
upper_y equ 37

lower_x equ 08       ;; Lower right corner of the box to be zoomed
lower_y equ 39

left_x db upper_x    ;; Variables to hold the UPPER LEFT coordinates of the
left_y db upper_y    ;; next box to be displayed

right_x db lower_x   ;; Variables to hold the LOWER RIGHT coordinates of the
right_y db lower_y   ;; next box to be displayed

shadow_vertical_left_x    db upper_x+1 ;; Don't Change!
shadow_vertical_left_y    db lower_y+1 ;; Coordinates to display the VERTICAL
shadow_vertical_right_x   db lower_x+1 ;; shadow of message box.
shadow_vertical_right_y   db lower_y+2

shadow_horizontal_left_x  db lower_x+1 ;; Don't Change!
shadow_horizontal_left_y  db upper_y+2 ;; Coordinates to display the HORIZONTAL
shadow_horizontal_right_x db lower_x+1 ;; shadow of message box
shadow_horizontal_right_y db lower_y+2

;;//////////////////////////////////////////////////////////////////////;;
UDATASEG
        DW 100H DUP (?)
MyStack LABEL WORD

;;--------------------------<  @SetTextMode  >------------------------;;
@SetTextMode MACRO
             mov ax,0003h
             int 10h
             ENDM  ;;End of macro

;;----------------------------<  @Cursor  >---------------------------;;
;;PURPOSE : Macro to move cursor
;;SYNTAX  : @Cursor <row>, <col>

@Cursor MACRO ROW,COL
        mov ah,02
        mov bh,00
        mov dh,ROW
        mov dl,COL
        int 10h
        ENDM    ;;End of macro

;;----------------------------<  @Display  >---------------------------;;
;;PURPOSE:   Macro to display a text
;;SYNTAX :   @DISPLAY <text width>, <text address>

@Display MACRO xcount, address
        LOCAL display_text
        mov cx, xcount          ;; Number of characters to be displayed
        mov bx, offset address
display_text:
        mov ah,0Eh              ;; Display the text
        mov al,byte ptr [bx]
        push bx
        mov bh,00
        mov bl,07h
        int 10h

        pop bx
        inc bx                  ;; Point to next character
        loop far ptr cs:display_text
        ENDM    ;;End of macro

;;-----------------------------<  @Window  >----------------------------;;
;;PURPOSE : Macro to display a window with a given color as background
;;SYNTAX  : @window <bacground  color>,
;;                  <Upper letf row of user window>, <Upper left column>,
;;                  <Lower right row of user window>, <Lower right column>

@window MACRO  color, lrow, lcol, rrow, rcol
        mov ah,06
        mov al,00
        mov bh, color     ;; Background Color
        mov ch, lrow
        mov cl, lcol
        mov dh, rrow
        mov dl, rcol
        int 10h
        ENDM    ;;End of macro

;;-----------------------------<  @Delay  >-----------------------------;;
@delay  MACRO
        mov ah,86h       ;; Execute a time delay
        mov dx,4500h ;;9000
        mov cx,0000h
        int 15h
        ENDM    ;;End of macro

;;/////////////////////////  MAIN PROGRAM  /////////////////////////////;;
CODESEG                  ;; This marks the start of executable code
        STARTUPCODE

        mov sp,offset MyStack
        push cs          ;; Initialize segment registers.
        pop ds
        push cs
        pop ss

        mov ah,0Bh       ;; Display screen border in WHITE color
        mov bx,0007h
        int 10h

        call message_box ;; Display the message box

        mov ax,4C00h     ;; Terminate the program.
        int 21h

;;////////////////////////////  Message_box  ///////////////////////////;;
Message_box PROC
        @SetTextMode
        @cursor 00,00                  ;; Position cursor at 00,00.
        @window screen,00,00,24,79     ;; @Clear screen

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
        mov cx,0008h  ;; Don't change!  Calculate how many times to zoom.
zoom:
        push cx       ;; @@Display a window which is zooming.
        call window
        pop cx

        loop zoom

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
        @display count, dialog_box_text

        ret
Message_box ENDP

;;///////////////////////////   Window   ///////////////////////////////;;
Window  PROC
  ;;Display a window with BLUE colour as background.
       @window box_background_colour, left_x, left_y, right_x, right_y

       dec byte ptr left_x
       sub cl,5
       mov byte ptr left_y,cl

       inc byte ptr right_x
       add dl,5
       mov byte ptr right_y,dl

;;---------------------------------------------------------------------;;
  ;;Display a horizontal shadow.
       @window shadow_colour,shadow_vertical_left_x,shadow_vertical_left_y,
               shadow_vertical_right_x,shadow_vertical_right_y

       dec byte ptr shadow_vertical_left_x
       add cl,5
       mov  byte ptr shadow_vertical_left_y,cl

       inc byte ptr shadow_vertical_right_x
       add dl,5
       mov  byte ptr shadow_vertical_right_y,dl

;;--------------------------------------------------------------------;;
  ;;Display a horizontal shadow.
       @window shadow_colour,shadow_horizontal_left_x, shadow_horizontal_left_y,
               shadow_horizontal_right_x,shadow_horizontal_right_y

       inc byte ptr shadow_horizontal_left_x
       sub cl,5
       mov byte ptr shadow_horizontal_left_y,cl

       inc byte ptr shadow_horizontal_right_x
       add dl,5
       mov byte ptr shadow_horizontal_right_y,dl

;;--------------------------------------------------------------------;;
       @delay

       ret
Window ENDP

END
;;////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\;;

EXAMPLE 2:
    Well, next we're going to put a DIALOG BOX in our program.

    What it does is:
       - Displays a dialog box with YES and NO buttons
       - Supports button selection using mouse
            (i) Checks for mouse installation
           (ii) Shows mouse pointer
          (iii) Captures button click of the left mouse button
       - Checks for keyboard input
          (i) Checks whether EXTENDED keys has pressed
         (ii) Checks whether ENTER or TAB key has pressed
       - Toggles button selection, on presssing TAB, LEFT ARROW key or RIGHT
         ARROW key.
       - On pressing ENTER key or clicking OK/YES button, displays different
         messages according to button selection and terminates.

    What we will learn from this example is:
       (i) Mouse handling
      (ii) Screen handling using BIOS interrupt 10h
     (iii) Key board handling using BIOS interrupt 16h
      (iv) Idea of user interface design

    I made the following program very straightforward and ignored code
optimization to reduce complexity.
;; +-------------------------------------------------------------------------+
;; | Program   : DLGBOX.ASM                                                  |
;; | Purpose   : Demonstration program about Dialog Box with YES & NO button |
;; | Features  : Supports mouse for button selection                         |
;; | Assembler : TASM                                                        |
;; | Required Knowledge: INT 21h, INT 10h, INT 16h, INT 33h & Scan Code      |
;; +-------------------------------------------------------------------------+

;; MACROS in this program    : @Cursor, @Display, @window, @Yes  & @No
;; PROCEDURES in this program: Dialog_box
;;///////////////////////////////////////////////////////////////////////;;
.386
MODEL USE16 TINY     ;; @Always must be TINY model

;;///////////////////////////////////////////////////////////////////////;;
DATASEG              ;; Initialize variables

mouse db 'n'         ;; Flag to indicate the availability of mouse

mouse_x db 0         ;; Keep track of position of mouse cursor
mouse_y db 0

m_x dw 00
m_y dw 00

left_mouse_button db 0   ;; Flag updated on clicking the left mouse button

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
RED    EQU 4fh       ;; @Color values
CYAN   EQU 3fh
BLACK  EQU 0fh
BLUE   EQU 1fh
WHITE  EQU 7fh

box_height EQU 10
box_width EQU 46

left_x EQU 7      ;; Upper left corner of user window
left_y EQU 20

right_x EQU left_x+box_height-1 ;; Calculate lower right corner of user window
right_y EQU left_y+box_width-1

upper_left_row db left_x
upper_left_col db left_y

box_background_color EQU RED ; Background color of dialog box

nl EQU  0Dh,0Ah   ; New line


label dialog_box_text
db '+--------------- USER COMMENT ---------------+' ;Dialog box. The variable
db '|                                            |' ;dialog_box_text contains
db '|         Written By S.Sirajudeen            |' ;10 lines; width of each
db '|      E-mail: ssirajudeen@netscape.net      |' ;line is 46 characters.
db '|                                            |'
db '|       HAVE YOU ENJOYED THIS PROGRAM?       |' ;NOTE:
db '|                                            |' ;If you edit here, you
db '|              Yes #       No  #             |' ;should UPDATE the
db '|            #######     #######             |' ;text_width and
db '+--------------------------------------------+' ;text_line_count.
count dw $-offset dialog_box_text

text_line_count EQU 10    ;; Variable dialog_box_text contains 10 lines
text_width      EQU 46    ;; and width of each line is 46 characters

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
shadow EQU WHITE              ;; color of button shadow

;;NOTE:  Width of  'yes' and 'yes_button' should be same.
yes_button      db 17,' Yes ',16  ;; Displayed on YES button has selected
yes             db '  Yes  '
yes_horz_shadow db 7 dup(223)
yes_char_count EQU 7

;;NOTE:  Width of 'no' and 'no_button' should be same.
no_button      db 17,'  No ',16   ;; Displayed on NO button has selected
no             db '   No  '
no_horz_shadow db  7 dup(223)
no_char_count  EQU 7

vert_shadow    db 220

yes_x EQU right_x-2           ;; Coordinate where YES button to displayed
yes_y EQU left_y+(box_width/2)-yes_char_count-4 ;;32

no_x  EQU right_x-2           ;; Coordinate where NO button to displayed
no_y  EQU left_y+(box_width/2)+1 ;;44

select   EQU BLUE   ;; @Background color to highlight the button selection
unselect EQU BLACK

button db 'y' ;; @Flag to keep track of the button selection. If the value
              ;; is 'y', the YES button has selected; 'n' for the NO button.

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
label thank_you      ;; Message to be displayed upon YES button has pressed
db 07,'                        Written By S.SIRAJUDEEN',nl
db '4/55,L.M.BUILDING,KUMARESAPURAM,KUTHAPAR(PO),TRICHY-620013,TAMILNADU,INDIA'
db nl,'                      Email: ssirajudeen@netscape.net'
db nl,nl,'                            Thank you! Good-bye!!'
thank_you_count dw $-thank_you

label suggest        ;; Message to be displayed upon NO button has pressed
db 7h,'        If you have any comments or suggestions, then please mail me at'
db nl,'                               ssirajudeen@netscape.net'
db nl
suggest_count dw $-suggest

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
;;  -------------+-----------  When a key has pressed, it returns a code.
;; |Extended Keys| Scan Code | This code is called SCAN CODE.
;; |-------------+-----------| Alphanumeric keys, tab, space and escape
;; | Left Arrow  |    75     | keys return one byte code. But Extended
;; | Right Arrow |    77     | keys return two bytes code. The first byte
;; | Up Arrow    |    72     | always 0. The second is the actual scan code.
;; | Down Arrow  |    80     | Arrow keys, Home, End, PageUp, Page Down,
;;  -------------+-----------  Insert, Delete, Function keys, Pause/break,
;;                             Scroll Lock & Print Screen are called EXTENDED
;;                             KEYs.
LEFT_ARROW  equ 75  ;; Scan code of LEFT ARROW key is 75
RIGHT_ARROW equ 77  ;;      ,,      RIGHT ARROW keyis 77
TAB_KEY     equ 9   ;; Scan code of TAB key is 9
ENTER_KEY   equ 13  ;;      ,,      ENTER key is 13

;;//////////////////////////////////////////////////////////////////////;;
UDATASEG
        DW 50H DUP (?)
MyStack LABEL WORD

;;----------------------------<  @Cursor  >---------------------------;;
;;PURPOSE : Macro to move cursor
;;SYNTAX  : @Cursor <row>, <col>

@Cursor MACRO ROW,COL
        mov ah,02
        mov bh,00
        mov dh,ROW
        mov dl,COL
        int 10h
        ENDM    ;;End of macro

;;----------------------------<  @Display  >---------------------------;;
;;PURPOSE:   Macro to display a text
;;SYNTAX :   @DISPLAY <text width>, <text address>

@Display MACRO xcount, address
        LOCAL display_text
        mov cx, xcount          ;; Number of characters to be displayed
        mov bx, offset address
display_text:
        mov ah,0Eh              ;; Display the text
        mov al,byte ptr [bx]
        push bx
        mov bh,00
        mov bl,07h
        int 10h

        pop bx
        inc bx                  ;; Point to next character
        loop far ptr cs:display_text
        ENDM    ;;End of macro

;;----------------------------<  @window  >-----------------------------;;
;;PURPOSE : Macro to display a window with a given color as background
;;SYNTAX  : @window <bacground  color>,
;;                  <Upper letf row of user window>, <Upper left column>,
;;                  <Lower right row of user window>, <Lower right column>

@window MACRO  color, lrow,lcol, rrow, rcol
        mov ah,06
        mov al,00
        mov bh, color  ;;Background Color
        mov ch, lrow
        mov cl, lcol
        mov dh, rrow
        mov dl, rcol
        int 10h
        ENDM    ;;End of macro

;;------------------------<  @button_shadow  >--------------------------;;
;;PURPOSE ; Macro to pad the button with horizontal and vertical char to
;;          make it as 3D button.
@button_shadow MACRO
   @Cursor yes_x+1, yes_y+1           ;; Display horizontal shadow of YES button
   @Display yes_char_count, yes_horz_shadow

   @Cursor yes_x, yes_y+yes_char_count ;; Display vertical shadow
   @Display 1, vert_shadow

   @Cursor no_x+1, no_y+1             ;; Display horizontal shadow of NO button
   @Display no_char_count, no_horz_shadow

   @Cursor no_x, no_y+no_char_count    ;; Display vertical shadow
   @Display 1, vert_shadow

   ENDM

;;-----------------------------<  @Yes  >-------------------------------;;
;;PURPOSE : Macro to select the YES button.
;;          In other words, a window which is used as YES button is displayed

@Yes  MACRO
    mov button, 'y'                 ;; DON'T CHANGE! ;  Update flag
    @window select, yes_x, yes_y, yes_x, yes_y+(yes_char_count-1)
    @window unselect, no_x, no_y, no_x, no_y+(no_char_count-1)

    @Cursor yes_x,yes_y                 ;; Move cursor to YES button
    @Display yes_char_count,yes_button  ;; Display label of YES
    @Cursor no_x,no_y                   ;; Move cursor to NO button
    @Display no_char_count, no          ;; Display label of NO button

    ENDM    ;;End of macro

;;-----------------------------<  @No  >--------------------------------;;
;;PURPOSE : Macro to select the NO button
;;          In other words, a window which is used as NO button is displayed

@No  MACRO
    mov button, 'n'                  ;; DON'T CHANGE! ;  Update flag
    @window unselect,yes_x, yes_y, yes_x, yes_y+(yes_char_count-1)
    @window select, no_x, no_y, no_x, no_y+(no_char_count-1)

    @Cursor yes_x,yes_y
    @Display yes_char_count, yes
    @Cursor no_x,no_y
    @Display no_char_count, no_button

    ENDM    ;;End of macro

;;////////////////////////  MAIN PROGRAM   /////////////////////////////;;
CODESEG                   ;;This marks the start of executable code
        STARTUPCODE

        mov sp,offset MyStack
        push cs           ;;Initialize segment registers.
        pop ds
        push cs
        pop ss

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
        @window BLACK, 00, 00, 24, 79     ;;@Clear screen

        call Dialog_box   ;;Display the dialog box

display_thank_u:
        cmp button,'y'    ;; Check whether YES  button has pressed/clicked
        jne display_suggestion

        @Display thank_you_count, thank_you
        jmp _end

display_suggestion:       ;; NO button has pressed/clicked
        @Display suggest_count, suggest

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
_end:
        mov ax,4C00h       ;; Terminate the program.
        int 21h

;;///////////////////////////   Dialog_box   ////////////////////////////;;
Dialog_box PROC
     mov ax,0003 ;; Don't change! Set text mode in 3. Changing this mode
     int 10h     ;; causes different resolution. Mouse movement is converted
                 ;; into rows and columns based on the resolution of text mode.

     mov ax,00   ;; Reset mouse
     int 33h

     cmp ax,00   ;; Check for error
     je start

     mov ax,01   ;; Show mouse pointer
     int 33h

     mov mouse, 'y'

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
start:
     @window box_background_color,left_x,left_y,right_x,right_y ;;Display a BOX

     @Cursor left_x,left_y  ;; Move cursor to upper left corner of dialog box

     mov cx, text_line_count           ;; Display n lines as dialog box text
     mov bx, offset dialog_box_text    ;; Address of text
next_line:
     push bx                           ;; OUTER LOOP
     push cx

           mov cx,00                   ;; INNER LOOP
           mov cl, text_width
display_text:
           mov ah,0Eh                  ;; Display the text
           mov al,byte ptr [bx]
           push bx
           mov bh,00
           mov bl,07h
           int 10h

           pop bx
           inc bx
           loop far ptr cs:display_text ;; INNER LOOP

     pop cx
     pop bx

     mov dx,00               ;; Calculate address of next line
     mov dl, text_width
     add bx, dx

     inc byte ptr upper_left_row

     push bx
     @Cursor upper_left_row, upper_left_col ;; Move cursor to next line within
     pop bx                                 ;; dialog box
     loop far ptr cs:next_line              ;; OUTER LOOP

     @button_shadow

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
_yes:
     @Yes             ;; Select the YES button

     cmp left_mouse_button,01
     je _end_proc

     jmp mouse_check

_no:
     @No              ;;Select  the NO button

     cmp left_mouse_button,01
     je _end_proc

mouse_check:
     cmp mouse, 'y'  ;; Check whether mouse is available
     jne key_check

     mov ax,03       ;; Get mouse cursor position
     int 33h

     mov left_mouse_button,bl

     mov word ptr m_x,dx
     mov word ptr m_y,cx

mouse_button:
     and left_mouse_button, 01 ;; Check whether left mouse button has pressed

     cmp left_mouse_button, 01
     jne key_check

mouse_row:
     mov mouse_x,0         ;; Mouse movement is converted into rows and columns
                           ;; to calculate the position of mouse cursor
     cmp word ptr m_x,00
     je mouse_col

     mov ax,word ptr m_x   ;; In the text mode 3, to calculate the current ROW,
     mov bl,8              ;; divide the position value for VERTICAL movement
     div bl                ;; by 8.

     mov mouse_x, al

mouse_col:
     mov  mouse_y,0        ;; Mouse movement is converted into rows and columns
                           ;; to calculate the position of mouse cursor
     cmp word ptr m_y,00
     je key_check

     mov ax, word ptr m_y  ;; In the text mode 3, to calculate the current COLUMN,
     mov bl,8              ;; divide the position value for HORIZONTAL movement
     div bl                ;; by 8.

     mov mouse_y, al

mouse_yes:
     mov al, mouse_x
     cmp al, yes_x      ;; Check whether mouse has clicked anywhere on
     jne  mouse_no      ;; the row where YES button is displayed

     mov al, mouse_y

     cmp al, yes_y
     jb mouse_no

     cmp al, yes_y+(yes_char_count-1)
     ja mouse_no

     mov button, 'y'
     jmp _yes

mouse_no:
     mov al, mouse_x
     cmp al, no_x       ;; Check whether mouse has clicked anywhere on
     jne  key_check     ;; the row where NO button is displayed

     mov al, mouse_y

     cmp al, no_y
     jb key_check

     cmp al, no_y+(no_char_count-1)
     ja key_check

     mov button, 'n'
     jmp _no

key_check:
     mov ah,01           ;; @Check whether any character is in keyboard buffer
     int 16h
     jz mouse_check
     mov ah,08           ;; @Receive  character without echoing to  screen
     int 21h

     cmp al, TAB_KEY     ;; Check whether TAB key has pressed
     je _left

     cmp al,ENTER_KEY    ;; Check whether ENTER key has pressed.
     je _end_proc        ;; Exit program

     cmp al,00           ;; @Check whether any Extended Key has pressed.
     jne mouse_check
     mov ah,08
     int 21h

     cmp al, LEFT_ARROW  ;; Check whether LEFT ARROW key  has  pressed
     je _left

     cmp al, RIGHT_ARROW ;; Check whether RIGHT ARROW key  has  pressed
     je _right

     jmp  mouse_check

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
_left:
     cmp button,'y'
     je _no
     jmp _yes

_right:
     cmp button,'y'
     je _no
     jmp _yes

;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
_end_proc:

     @Cursor right_x+1, 0    ;; Move cursor below the dialog box

     mov ax,02               ;; Hide mouse cursor
     int 33h

     RET
Dialog_box ENDP              ;; End of procedure

END   ;; End of program
;;////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\;;

Now, we have written a superb user friendly program. If you want to embed the
above examples in your work, you may have to heavily change these programs,
but the basic principles will be the same.

   Please, e-mail me your comments and suggestions at ssirajudeen@netscape.net



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                            ASM Building Blocks
                                                            by Laura Fairhead


    Here are some simple but very powerful library routines, primarily
concerned with screen output. They all follow the same conventions:
  *  Routines preserve all registers that they are not specified to return.
  *  The direction flag (DF) should always be clear before calling.

    All code is presented in MASM format. I do not use very many of the
functions of this assembler so it should be trivial to assemble these under
a different one. I do, however, use OPTION SCOPED, this means that labels
within a PROC block are local to that PROC block (a double colon suffixed
label is given global scope though).

    First come the primitive routines. These are responsible for the actual
output and simply call DOS to do it. The name for this sort of thing is
called a 'wrapper' function. It does nothing in itself except afford a
particular interface to an application. If all your access to the OS is in
a small number of logical wrapper functions then porting your code to other
systems becomes a lot easier.

;pstrcx-    write CX characters to stdout
;           uses DOS function 040h
;
;entry:     DS:SI=string address
;           CX=length of string
;
;exit:      (no parameters are returned)

pstrcx  PROC NEAR

;assume that DOS can't handle a zero-byte write
;(I don't trust those M$ programmers)
        JCXZ don
        PUSH AX
        PUSH BX
        MOV AH,040h
        MOV BX,1            ;stdout is handle #1
        XCHG DX,SI
        INT 021h
        XCHG DX,SI
        POP BX
        POP AX
don:    RET

pstrcx  ENDP

    Note the use of XCHG. XCHG is an extremely useful instruction indeed,
even though there are those who wish to see it's death along with all
those other "horrible, odd-ball, x86 specific". XCHG in essence performs
two operations simultaneously, which is hideously useful considering
they are both MOV's, also if one of the registers is AX (or EAX in 32-bit
code) you get a lovely 1 byte instruction bonus.

    XCHG is in fact the real instruction hiding behind the psuedo-op NOP.
If you look at the opcode for a NOP, it is 090h, this is actually the
encoding for XCHG AX,AX, which since it has no effect on the machine state
whatsoever (except of course IP+=1) is ideally suited for this.

    I haven't looked back since adding putch to my library. I used to use
the sequence:-

    MOV DL,<char>; MOV AH,2; INT 021h

    Not only is the putch method much cleaner and more flexible it is
also saving bytes! Of course the pay-back is that this method adds clocks.
However if you think about it the wasted clocks are meaningless really.
Sending characters one at a time to stdout is rather like spelling out
a dictate to your secretary letter-by-letter. In a case where you want
more MIPS you should be looking at your higher level algorithm and not
the output routine, an INT takes a vast amount of time anyway...

;putch-     write single character to stdout
;           uses DOS function 02h
;
;entry:     AL=character to write
;
;exit:      (no parameters are returned)

putch   PROC NEAR

        PUSH DX
        XCHG DX,AX
        MOV AH,2
        INT 021h
        XCHG DX,AX
        POP DX
        RET

putch   ENDP


    Not hot on speed this strlen, it was written to be compact. You can
if you wish write MUCH faster code than this. I believe X-Bios2 presented
something along these lines in a previous APJ. However, the most important
thing here is certainly not speed, and again if you wanted speed on string
handling so badly, you should really not use asciiz at all; it was never
designed for that.

;strlen-    return length of asciiz string
;
;entry:     DS:SI=address of asciiz string
;
;exit:      CX=length of string

strlen  PROC NEAR

        PUSH AX
        XOR CX,CX
        DEC CX

lop:    INC CX
        LODSB
        CMP AL,1
        JNC lop

        SBB SI,CX
        POP AX
        RET

strlen  ENDP

    Now, already, we start getting serious payback for being so good.
The code virtually writes itself.....

;pstr-      write asciiz string to stdout
;
;entry:     DS:SI=address of asciiz string
;
;exit:      (no parameters are returned)

pstr    PROC NEAR

        PUSH CX
        CALL NEAR PTR strlen
        CALL NEAR PTR pstrcx
        POP CX
        RET

pstr    ENDP

;pstrcr-    write asciiz string to stdout with appended newline
;
;entry:     DS:SI=address of asciiz string
;
;exit:      (no parameters are returned)

pstrcr  PROC NEAR

        CALL NEAR PTR pstr
        JMP NEAR PTR outcr

pstrcr  ENDP


;outcr-     write newline to stdout
;
;entry:     (no entry parameters)
;
;exit:      (no parameters are returned)

outcr   PROC NEAR

        PUSH AX
        MOV AL,0Dh;CALL NEAR PTR putch
        MOV AL,0Ah;CALL NEAR PTR putch
        POP AX
        RET

outcr   ENDP


;pchn-      write repeated character to stdout
;
;entry:     AL=character
;           CX=repetitions  (0 is valid and does nothing)
;
;exit:      (no parameters are returned)

pchn    PROC NEAR

        JCXZ don
        PUSH CX
lop:    CALL NEAR PTR putch
        LOOP lop
        POP CX
don:    RET

pchn    ENDP


;pstrlcl-   output string DS:SI left justified in a field
;           of CL spaces
;
;           if the field width is smaller than the string length
;           then the string is simply output
;
;entry:     DS:SI=asciiz string
;           CL=field width
;
;exit:      (all registers preserved)

pstrlcl PROC NEAR

        PUSH AX
        PUSH CX
        CALL NEAR PTR pstr
        MOV CH,0
        XCHG CX,AX
        CALL NEAR PTR strlen
        SUB AX,CX
        JNA SHORT don
        XCHG CX,AX
        MOV AL,020h
        CALL NEAR PTR pchn
don:    POP CX
        POP AX
        RET

pstrlcl ENDP

    Note the use of JNA. If you look at the logic for the JNA branch
(not many people seem to do this) you find that it branches iff
CF=1 OR ZF=1, hence after the SUB if the result goes <=0

    You may notice that all the routine names are <= 8 chars. The reason
for this being that you can save each one as a seperate file, giving it
the name of the routine. This allows easy reference but has a drawback
or two:

    (i) you have to remember the dependencies when you INCLUDE them
   (ii) you end up with a LOT of files

    So far I haven't found either of these 'drawbacks' to be a serious
problem.

    I will be referring back to routines a lot in future articles; whenever
routines are required I will state it and the code shall have a list of
INCLUDE's for the routines to be included. In this manner it will be possible
to present quite untrivial programs within a reasonable amount of space.



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                  Converting Strings to Numbers
                                                  by Chris Dragan


   Many programs require user input, which is often numbers. For this purpose
there are library functions, like for example sscanf() in C. But in assembly
all has to be done by hand, even under Windows (with the exception of edit
controls - GetDlgItemInt() function).

   My last project required a flexible function for reading numbers stored as
strings. From this project I carried out a great function which handles most
of common number formats.

   The function expects esi register to point at a string, which is a number.
The string can have one of the following forms:

     10   decimal integer
     10D  decimal integer
     1010B     binary integer
     AH   hexadecimal integer (does not require leading zero)
     0XA  hexadecimal integer
     $A   hexadecimal integer
     12Q  octal integer
     12O  octal integer
     10F  float
     10.0 float
     10.0F     float
     1.0E+1F float
     1.E+1     float

   The string is required to have all letters (hex digits, number type
specifiers) uppercase. If a number is to contain lowercase letters, it has
to be converted before calling the function.

   The function returns in eax number type:
     - 0 if the number is invalid,
     - 1 if the number is a dword integer,
     - 2 if the number is a qword integer and
     - 3 if the number is a float.
The number is returned in edx (dword), ecx:edx (qword) or st(0) (float).
The number will be a qword integer if it exceedes 0xFFFFFFFF boundary.
Also notice that the number is assumed to be positive, '-' before the
number is not accepted and has to be handled externally.

   Floating point conversion is done using multiplication, not by means
of fbld instruction. This is because fbld instruction limits numbers to
19 characters, but the function can accept longer numbers if only they
are not too large/small.

   And here is the function. It was written (and tested) in TASM's ideal mode,
but it can be easily ported to MASM or NASM. The function preserves all
registers but eax, ecx and edx, which are used for return value.

; This helper macro checks if there was an error on the fpu

macro     chkfpu _endinglabel
               fxam
               fstsw     ax
               sahf
               jc   _endinglabel
endm

proc ConvertNumber uses edi

;---------------- Identify number format

          ; Search for 0 at the end
               mov  edi, esi
               or   ecx, -1
               xor  eax, eax
               cld
            repne scasb

          ; Move to the last character
               dec  edi
               dec  edi

          ; Is there anything ?
               cmp  esi, edi
               ja   __invalid

          ; Identify C-style and Pascal-style hexadecimals
               cmp  [byte esi+1], 'X'
               je   __c_hex
               cmp  [byte esi], '$'
               je   __pas_hex

          ; Identify other types using the last character
               movzx     eax, [byte edi]
               cmp  eax, 'H'
               je   __asm_hex
               cmp  eax, 'B'
               je   __binary
               cmp  eax, 'D'
               je   __decimal
               cmp  eax, 'Q'
               je   __octal
               cmp  eax, 'O'
               je   __octal
               cmp  eax, 'F'
               je   __float_clr

          ; Find a comma (distinguish between integer and float)
               not  ecx
               dec  ecx
               mov  eax, '.'
               mov  edi, esi
            repne scasb
               je   __float

;---------------- Process decimal integer

          ; Prepare
__decimal:          mov  [byte edi], 0
               mov  edi, esi
               xor  eax, eax

          ; Get a digit
__next_decimal:     movzx     ecx, [byte edi]
               inc  edi
               xor  edx, edx

          ; Zero ends the string
               test ecx, ecx
               jz   __finito

          ; Multiply the already loaded part by ten
               add  edx, 10
               mul  edx

          ; If an overflow occurs - the number is a quadword
               jo   __decimal_qword

          ; Check digit validity
               sub  ecx, '0'
               jc   __invalid
               cmp  ecx, 9
               ja   __invalid

          ; Add the digit
               add  eax, ecx

          ; Next digit or process a quadword if carry occurs
               jnc  __next_decimal
               jmp  __decimal_carry

;---------------- Decimal (appears to be greater than 0FFFF_FFFFh)

          ; Check digit validity
__decimal_qword:    sub  ecx, '0'
               jc   __invalid
               cmp  ecx, 9
               ja   __invalid

          ; Add the digit (qword addition)
               add  eax, ecx
__decimal_carry:    adc  edx, 0

          ; Load next digit
               movzx     ecx, [byte edi]
               inc  edi

          ; Check for ending zero
               test ecx, ecx
               jz   __finito

          ; Multiply high part by 10
               push eax
               mov  eax, edx
               mov  edx, 10
               mul  edx

          ; Number too large if an overflow occurs
               jo   __decimal_overflow

          ; Multiply low part by 10
               xchg eax, [esp]
               mov  edx, 10
               mul  edx

          ; Join high parts
               add  edx, [esp]

          ; Number too large if carry
               jc   __decimal_overflow

          ; Next digit
               add  esp, 4
               jmp  __decimal_qword

          ; Handle overflow
__decimal_overflow: pop  eax
               jmp  __invalid

;---------------- Process hexadecimal integer

          ; Was Pascal-style hex (leading '$')
__pas_hex:          lea  edi, [esi+1]
               jmp  __hex

          ; Was C-style hex (leading '0X')
__c_hex:       cmp  [byte esi], '0'
               jne  __invalid
               lea  edi, [esi+2]
               jmp  __hex

          ; Was asm-style hex (ending with 'H')
__asm_hex:          mov  [byte edi], 0
               mov  edi, esi

          ; Clear what will become the number
__hex:              xor  eax, eax
               xor  edx, edx

          ; Get a digit
__get_hex:          movzx     ecx, [byte edi]
               inc  edi

          ; Zero ends the string
               test ecx, ecx
               jz   __finito

          ; Number too large if the most significant nibble of edx
          ; is nonzero
               cmp  edx, 0FFFFFFFh
               ja   __invalid

          ; Multiply the already converted part by 16
               shld edx, eax, 4
               add  eax, eax ; to avoid shift (see lea below)

          ; Convert ASCII to digit
               sub  ecx, '0'
               jc   __invalid
               cmp  ecx, 9
               jna  __hex_ok
               sub  ecx, 7
               cmp  ecx, 9
               jna  __invalid
               cmp  ecx, 15
               ja   __invalid

          ; Add the digit
__hex_ok:      lea  eax, [eax*8+ecx]
               jmp  __get_hex

;---------------- Return integer

__finito:      mov  ecx, edx
               mov  edx, eax
               cmp  ecx, 1
               sbb  eax, eax
               add  eax, 2
               ret

;---------------- Process binary integer

          ; Prepare
__binary:      mov  [byte edi], 0
               xor  eax, eax
               xor  edx, edx
               mov  edi, esi

          ; Get a digit
__get_binary:       movzx     ecx, [byte edi]
               inc  edi

          ; Zero ends the string
               test ecx, ecx
               jz   __finito

          ; Shift everything left and add the digit
               shr  ecx, 1
               adc  eax, eax
               adc  edx, edx
               jc   __invalid

          ; Check digit validity and get next digit if OK
               cmp  ecx, '0' shr 1
               jne  __invalid
               jmp  __get_binary

;---------------- Process octal integer

          ; Prepare
__octal:       mov  [byte edi], 0
               xor  eax, eax
               xor  edx, edx
               mov  edi, esi

          ; Get a digit
__get_octal:        movzx     ecx, [byte edi]
               inc  edi

          ; Zero ends the string
               test ecx, ecx
               jz   __finito

          ; Check if there is a room for another digit
               cmp  edx, 1FFFFFFFh
               ja   __invalid

          ; Multiply the already converted part by 8
               shld edx, eax, 3

          ; Convert ASCII to number
               sub  ecx, '0'
               jc   __invalid
               cmp  ecx, 7
               ja   __invalid

          ; Add the digit
               lea  eax, [eax*8+ecx]
               jmp  __get_octal

;---------------- Invalid number

__invalid:          fninit
               xor  eax, eax
               ret

;---------------- Process integer part of a float

          ; Prepare (st0=0, st1=10)
__float_clr:        mov  [byte edi], 0
__float:       finit
               push 0300h ; mask off all interrupts
               fldcw     [word esp]
               push 10
               fild [dword esp]
               add  esp, 8
               fldz
               mov  edi, esi

          ; Get a digit
__get_integer:      movzx     ecx, [byte edi]
               inc  edi

          ; Zero ends the string
               test ecx, ecx
               jz   __float_ready

          ; Comma starts fraction part
               cmp  ecx, '.'
               je   __float_fraction

          ; Multiply the already converted part by 10
               fmul st, st(1)
               chkfpu    __invalid

          ; Convert ASCII to number
               sub  ecx, '0'
               jc   __invalid
               cmp  ecx, 9
               ja   __invalid

          ; Add the digit
               push ecx
               fiadd     [dword esp]
               add  esp, 4
               chkfpu    __invalid
               jmp  __get_integer

;---------------- Process fractional part of a float

          ; Prepare (st0=0, st1=1, st2=num, st3=10)
__float_fraction:   fld1
               fldz

          ; Get a digit
__get_fraction:     movzx     ecx, [byte edi]
               inc  edi

          ; Zero ends the string
               test ecx, ecx
               jz   __fraction_ready

          ; E starts exponent
               cmp  ecx, 'E'
               je   __fraction_ready

          ; Multiply the already converted part by 10
               fmul st, st(3)

          ; Multiply the divisor by 10
               fxch st(1)
               fmul st, st(3)
               fxch st(1)
               chkfpu    __invalid
               fxch st(1)
               chkfpu    __invalid
               fxch st(1)

          ; Convert ASCII to number
               sub  ecx, '0'
               jc   __invalid
               cmp  ecx, 9
               ja   __invalid

          ; Add the digit
               push ecx
               fiadd     [dword esp]
               add  esp, 4
               chkfpu    __invalid
               jmp  __get_fraction

;---------------- Process exponent part of a float

          ; Divide the fraction by the divisor
__fraction_ready:   fdivrp    st(1), st

          ; Add fraction to integer
               faddp     st(1), st

          ; E indicates start of exponent
               cmp  ecx, 'E'
               jne  __float_ready

          ; Prepare (st0=0, st1=num, st2=10)
               fldz

          ; Sign of the exponent
               xor  edx, edx
               cmp  [byte edi], '-'
               jne  __no_minus
               not  edx
               inc  edi
__no_minus:         cmp  [byte edi], '+'
               jne  __get_exponent
               inc  edi

          ; Get a digit
__get_exponent:     movzx     ecx, [byte edi]
               inc  edi

          ; Zero ends the string
               test ecx, ecx
               jz   __exponent_ready

          ; Multiply the already converted part by 10
               fmul st, st(2)
               chkfpu    __invalid

          ; Convert ASCII to number
               sub  ecx, '0'
               jc   __invalid
               cmp  ecx, 9
               ja   __invalid

          ; Add the digit
               push ecx
               fiadd     [dword esp]
               add  esp, 4
               chkfpu    __invalid
               jmp  __get_exponent

          ; Multiply by 10**exp (** is a power operation)
__exponent_ready:   test edx, edx
               jz   __positive_exp
               fchs
__positive_exp:     fldl2t; 10**x = 2**(x*log2(10))
               fmulp     st(1), st ;
               fld  st        ;
               frndint        ;
               fsub st(1), st ;
               fld1           ;
               fscale              ;
               fstp st(1)          ;
               fxch st(1)          ;
               f2xm1               ;
               fld1           ;
               faddp     st(1), st ;
               fmulp     st(1), st;
               fmulp     st(1), st

          ; Return float
__float_ready:      chkfpu    __invalid
               fstp st(1)
               mov  eax, 3
               ret
endp



And that is it. The function is not meant to work as fast possible and was
not optimized, but it does the task it has to do.














::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                      List Scan Library Routine
                                                      by Laura Fairhead


    Firstly let me introduce an auxillary routine this uses. It is
called 'scaws' and scans past white space. It is very simple, and the
definition of whitespace here is SPACE (020h) or TAB (09h):-

========START OF CODE======================================================

;
;scaws-     scan whitespace
;
;entry:     DS:SI=string
;           DF=0
;
;exit:      SI=updated to first non-whitespace character
;           AL=value of the character
;

scaws   PROC NEAR

;
;there is nothing to explain here but you might take note now
;that I always use the same label names in different PROC blocks,
;in MASM you can do this with OPTION SCOPED
;

lop:    LODSB
        CMP AL,020h
        JZ lop
        CMP AL,09h
        JZ lop
        DEC SI
        RET

scaws   ENDP


========END OF CODE========================================================

    'scalst' is basically a routine to scan-convert a list which can
consist of values and strings. The radix of the values must be set
before hand by calling 'scanur' as the routine uses 'scanu' to convert
values and doesn't set the radix itself. The syntax of the list is
almost the same as the list in DEBUG, where in fact I got the idea from.
You have from 0+ data items, optionally seperated by commas. Whitespace
can be used freely as a delimitor and no delimitors are necessary where
there is no need for them (eg: between a value and a string).

    The routine takes several parameters, the address of your string
(DS:SI), the address of somewhere to store the converted data (ES:DI),
the size of the data store (CX) and the size of a unit (AL). The unit size
can be byte (AL=1), word (AL=2), dword (AL=4).

    Each data item, as in value/string character, is zero-padded to the
unit size for storing. Also values are checked that they are in range for
the unit size. This method therefore allows us to have those silly
word strings.

    Here are some examples, all of these assume that we had set the
radix = 010h (by calling 'scanur' with AL=010h) :-

    Calling with AL=1, and our string=1 2 3 "ABC" yields:-

    01 02 03 41 42 43

    Calling with AL=4, and our string="0"1FE08 2 yields:-

    30 00 00 00 08 FE 01 00 02 00 00 00

    Calling with AL=2, and our string=9A06 87"DEF" yields:-

    06 9A 87 00 44 00 45 00 46 00

    Calling with AL=2, and our string="ABC"FE0FE 0 1 2 yields:-

    ERROR! CF=1        (FE0FE>FFFF)

    A particularly powerful feature of this routine is that it takes
a parameter giving the size of your data store (in bytes). This means
that it will be impossible for the program to be crashed because there
was too much data. Programmers are generally too lazy to do this sort of
range checking, and much to their woe as one particularly wily hacker
attack called 'crashing the stack' has taught.

    Example; if we called with AL=2, CX=4 and string=1 9 F

    ERROR! CF=1        (01 00 09 00 0F 00 > 4bytes)

    As an aside, the function is not entirely the same as DEBUG's list
scanner. With DEBUG the strings are always converted to byte lists, no
matter what the unit size is. It is trivial to modify the routine to
work in this way.

    One last note is that the end of the list is the first invalid
character in the string, this not being an error of course since it is
the responsibilty of the controlling parser to decide this based on the
context; eg: DEBUG might check for a semicolon comment on the end of
the line, though as a matter of fact it doesn't. A premature ending (ie:
0 byte appearing inside the quotes of a string token) will abort with
error, thus;

    AL=1, string=0A 98"unterminated string  yields:-

    ERROR! CF=1         (unterminated string)


========START OF CODE======================================================

;
;scalst-    data list scan/convert routine
;
;entry:     DS:SI=string
;           ES:DI=store
;           CX=#bytes size of store
;           AL=unit size (1=byte,2=word,4=dword)
;           DF=0
;
;           "scanur" must have been called at least once previously
;           in order to set the radix of scanned values
;
;        !! entry parameters are not validated and invalid entry
;        !! parameters will cause undefined behaviour
;
;exit:      CF=1=>error (parse/overflow)
;           CF=0=>okay, then:
;               ZF=1=>no data scanned, ie: CX=0
;               ZF=0=>data scanned
;           SI=updated to the first invalid character
;           DI=updated to the end of converted data + 1
;           CX=#bytes converted data (invalid on overflow error)
;
;note:      requires routines "scaws" and "scanu"
;

scalst  PROC NEAR

;
;initialise stack frame
;[BP-4] (dw) size mask
;            =000000FFh for unit size 1
;            =0000FFFFh for unit size 2
;            =FFFFFFFFh for unit size 4
;[BP-6] (w)  unit size
;[BP-8] (w)  original data offset DI
;
;EAX is preserved and the main loop is entered
;
        ENTER 8,0
        PUSH EAX
        CBW
        MOV [BP-6],AX
        NEG AL
        AND AL,3
        SHL AL,3
        PUSH CX
        XCHG CX,AX
        OR EAX,-1
        SHR EAX,CL
        POP CX
        MOV [BP-4],EAX
        MOV [BP-8],DI
        JMP SHORT inlop

;
;main loop head
;  ignore any whitespace and skip the optional comma
;
lop:    CALL NEAR PTR scaws
        CMP BYTE PTR [SI],','
        JNZ SHORT ko
        INC SI

;
;main loop entry
;  ignore any whitespace and if a value token is recognised
;  write it to data store and continue loop
;
inlop:  CALL NEAR PTR scaws
ko:     CALL NEAR PTR scanu
        JC SHORT don
        JZ SHORT ko2
;
;  check that the value is in range for the unit size, if not
;  abort here with an error
;
        CMP [BP-4],EAX
        JC SHORT don
        CALL NEAR PTR wracc
        JMP lop

;
;  no value was present so check for a string
;
ko2:    CMP BYTE PTR [SI],022h
        CLC
        JNZ SHORT don
;
;  get string into data store
;
        INC SI
        XOR EAX,EAX

lop1:   MOV AL,[SI]
;
;  unterminated string causes an error abort, LODSB is not used for the
;load in order to ensure that [SI] will point to the invalid character
;
        CMP AL,1
        JC SHORT don
        INC SI
        CMP AL,022h
        JZ lop
        CALL NEAR PTR wracc
        JMP lop1

;
;  exit point for 'wracc' routine below, clean-up the stack
;
err0:   POP EAX

;
;  main exit point. the carry flag is preserved as this is used
;  for both error and normal exits. the number of bytes stored
;  is calculated into CX, the INC/DEC ensuring ZF=1 if this was zero
;
don:    LAHF
        MOV CX,DI
        SUB CX,[BP-8]
        SAHF
        INC CX
        DEC CX
;
;  restore the only corrupted register and 'LEAVE'
;
        POP EAX
        LEAVE
        RET

;
;wracc- write datum in accumalator to data store
;  AL/AX/EAX is written to the data store depending on the unit size.
;  throughout the routine DI is the offset into the data store and
;  CX is the #bytes left in it. these are updated but if there are
;  insufficient bytes remaining in the store we abort with error, taking
;  care to clear the 4 bytes (AX + return address) off the stack first
;
wracc:  PUSH AX
        MOV AX,[BP-6]
        SUB CX,AX
        JC err0
        CMP AL,2
        POP AX
        JZ SHORT ko0
        JNS SHORT ko1
        STOSB
        RET
;
;  note that 066h STOSW = STOSD
;
ko1:    DB 066h
ko0:    STOSW
        RET

scalst  ENDP

========END OF CODE========================================================



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                               Using the RTC
                                                               by Jan Verhoeven


Here are some routines to use the RTC/CMOS chip for serious timing. It's
an introductory tutorial, so you'll be given more than enough opportunity to
experiment with timing via this method.


About the hardware.
===================
The RTC chip used to be a Motorola MC 146818A chip, but nowadays you
will either find a Dallas 1287 or 1387 style chip, or it is embedded in
the chipset. So far for romance... :o)

I will describe the Dallas DS 1287 since this is the configuration which
is most common for many years now, and the majority of the features are
the same as for the other chips.

The DS 1287 is a clock/RAM with a Lithium battery inside the package.
That's why it stays so big: the battery needs space. If the system is
powered on, the RTC gets its power from the powersupply. When the PC is
off, the RTC goes into power-down mode and slowly drains the Lithium
cell. Expected life for the battery is around 10 years.

The DS 1287 has 64 storage locations, 14 of which are clock and control
registers and the remaining 50 are battery-backed general purpose RAM
cells. This is were the CMOS setup of your PC stores it's system setup
data.

The programmable clock can issue an interrupt, which can be triggered by
three independent events: time of day, periodic signal or end of clock-
update.

The 14 registers inside the DS 1287 are:

    address         purpose
    -------         ---------------------------------------
       0            current value of seconds
       1            alarm setting for seconds
       2            current value of minutes
       3            alarm setting for minutes
       4            current value of hours
       5            alarm setting for hours
       6            Day of the week [Sunday = 1]
       7            Day of the month
       8            month                   [0..12]
       9            year of this century    [0..99]
      10            Control register A
      11            Control register B
      12            Control register C      [read-only]
      13            Control register D      [read-only]

If you want to know the time of day, or any other date related data,
just select the RTC chip and request the contents of the desired
register.

The alarm registers can be set to generate long-time periodical
interrupts, or for having the chip give a signal when it's time for your
nap. The alarm rate ranges from seconds to weeks.
And since these alarm registers are almost never used, they can also be
used for storing some data for your own software. PTS Partition Manager
for example uses these registers to keep track of where it was, while
reformatting the hard disk. If there is a power-fail, it will just
continue where it left off.

In the PC, the RTC chip is hidden from the programmer. It can only be
accessed in an indirect way. The trick is to first select a register
location and then access that one register as follows:

        mov     al, <register number>
        out     70h, al         ; select <register number>

        in      al, 71h         ; for a READ operation
        out     71h, ah         ; for a WRITE operation

So, we use port 70h for selecting a register or storage location and use
port 71h for doing the actual access to that register. A bit tedious,
but that's how the PC was designed in the first place.

In "old style" RTC chips the century is maintained in software. It
resides in a RAM cell, offset 32h/50d, so it will not be affected by a
year-rollover from 99 to 00. If you update it with a short piece of code
on January first 2000, your PC will be ready for many, many, moons to
come.


The control registers.
======================
Registers A, B, C and D are the registers that control the working of
the RTC clock. They have various functions and register D uses just a
singe bit, which is also read-only....
But this chip is well engineered and all registers have a significant
(although not always logical) influence on the operation of it.


Register A: Timing control.
---------------------------
Register A is layed out as follows:

    bit     function
    ---     ------------------------------------------------------------
     7      UIP bit: Update In Progress. When there's a ONE in this flag
            the timing registers are being updated and it is not safe to
            read them. Better to wait until this flag is cleared.
            This one bit is read-only!

    4-6     DV0-DV2: these three bits control the on-chip oscillator. Do
            not experiment too much with this setting. There is only ONE
            valid combination for these three bits: 010.

    0-3     RS0 - RS3: These are the four Rate Selector bits. They
            determine how often the IRQ pin is activated. The following
            table shows the meaning of the different values.

            RS3  RS2  RS1  RS0    Frequency [Hz]    Period [ms]
            ---  ---  ---  ---    --------------    -----------
             0    0    0    0           ---             ----
             0    0    0    1           256             3.906
             0    0    1    0           128             7.813
             0    0    1    1          8192             0.122
             0    1    0    0          4096             0.244
             0    1    0    1          2048             0.488
             0    1    1    0          1024             0.977
             0    1    1    1           512             1.953
             1    0    0    0           256             3.906
             1    0    0    1           128             7.813
             1    0    1    0            64            15.625
             1    0    1    1            32            31.25
             1    1    0    0            16            62.5
             1    1    0    1             8           125.0
             1    1    1    0             4           250.0
             1    1    1    1             2           500.0

           The default value in the average IBM PC is 0110 or 1024 Hz.
           Since no IRQ is enabled, you will not notice any difference
           if you change the value.


Register B: Internal operation control.
---------------------------------------
This is the most important register for controling operation of the RTC
chip. Register A determines timing and oscillator parameters, but the B-
register determines how the system will notice these conditions.
In a normal PC, only bit 1 (24/12) is set. All other bits are cleared.

    bit     function
    ---     ------------------------------------------------------------
     7      SET : If you determine to write a ONE in this bit position,
            the clockregisters will not be updated anymore. Only when
            this bit is ZERO, the clockregisters will be updated.

     6      PIE : The Periodic Interrupt Enable bit controls the IRQ
            pin. If this bit is ZERO, no IRQ will be given when the
            programmable frequency source (selected by RS0 - RS3) times
            out.
            You need to set this bit to a ONE to enable a periodic IRQ
            operation.

     5      AIE : Alarm Interrupt Enable. When this bit is ONE, the IRQ
            pin is activated when the alarm-time equals the actual time.

     4      UIE : "Update Ended" Interrupt Enable. When this bit is set
            to ONE, the IRQ line is asserted when the timing registers
            have changed contents.

     3      SQWE : Put a ONE in this bit to have the programmable
            interval timer (which is controlled by RS0 - RS3) output a
            square wave on pin 23 of the chip.
            Unfortunately this pin 23 is not connected in a PC so for us
            this bit has no meaning. But if you are man enough to bring
            pin 23 of the DS 1287 to the outside world, you can use it
            at will.

     2      DM : Data Mode. The timing registers can display their data
            in two different modes: binary and BCD. In the PC, this bit
            is always ZERO, meaning that BCD is the desired format.

     1      24/12 : Controls if hours are shown in 12 or 24 hours mode.
            Put a ONE inhere and you have 24 hours in a day. Clear this
            bit and you end up with two half days of 12 hours each. In
            the 12-hour mode, bit 7 acts as an AM or PM flag.

     0      DSE : Daylight Saving Enabled. Always leave this bit cleared
            to ZERO. Daylight saving time periods vary worldwide and the
            dates of change are determined by politicians and not by
            chipmakers. Unfortunately.


Register C: Interrupt sources.
------------------------------
Register C is a status-word only. The bits in this register are read-
only and only have menaing AFTER an IRQ was received.
Since there is just one IRQ pin on the RTC chip, the IRQ can have three
different sources and there's no way to know which one triggered it,
unless there was only one source enabled. The bits mean the following:

    bit     function
    ---     ------------------------------------------------------------
     7      IRQF : If this bit is ONE, one of the actual interrupt
            conditions was enabled and the interrupt condition was met.

     6      PF : Periodic interrupt Flag. If this bit is set, the source
            of this IRQ source was the programmable interval timer.

     5      AF : Alarm interrupt Flag. If this bit is set, the alarm
            condition was the same as the actual date/time.

     4      UF : The "Update Ended" interrupt Flag. If this bit is set,
            the IRQ was issued by an update of the timing registers.

Bits 0 - 3 are meaningless and will always be ZERO.


Register D: Battery status.
---------------------------
On the chip, there is a voltage reference that is constantly being
compared to the battery voltage. If the battery voltage drops below the
reference voltage, the battery is considered empty and bit 7 will be
SET.
If bit 7 is a ONE, the battery has been empty for some period of time
and hence the data in the timing registers and in the RAM locations MAY
have lost their meaning.

Bits 0 - 6 have no meaning in this register and will always return a
ZERO value.


Using the RTC internals.
========================
This, in a nutshell, is what the RTC chip is from the inside. I already
explained some lines above how to access the storage locations and the
timing registers of the DS 1287. This does not mean that everything will
also work the first time.

If you need to change a timing value, you must always first disable
register updates, even if you make sure that the changes you make to the
timing registers will well fit in an RTC timeslot. This means:

     -   access register B and set the SET flag
     -   change the timing registers
     -   access register B and clear the SET flag

Remember, there's not much intelligence inside a DS 1287. More recent
chips might do more tricks for the programmer, but the old beasties just
do as they were told.

In order to set the periodic interrupt rate, we use the following code:

  --- Begin ------------------------------------------- SetPIRate -----

  SetPIRate:                    ; Set Periodic Interrupt Rate
         mov   al, 0A           ; ah = rate to set
         out   070, al
         mov   al, ah
         out   071, al          ; and set it in register A
         ret

  ---- End -------------------------------------------- SetPIRate -----

This code is very straightforward. It relies on the fact that (in the
IBM PC) the contents of register A are always the same:

     bit  7      = read-only
     bits 4 - 6  = 010
     bits 0 - 3  = rate selector

So, it can set the value of bits 4 - 7 in the calling code. It is not
good programming, since we should:

     -      read in the contents of Register A
     -      clear bits 0 - 3
     -      OR in the new value
     -      write it back to register A


Inside the IBM PC.
==================
The IRQ pin of the RTC is connected to the Intel 8259 PIC (Programmable
Interrupt Controller, although "programmable" is too much honour for
this dumbo). In non-XT machines there are two of them, cascaded. This
means that the second one is connected to what used to be IRQ2. This
gives us a rather stupid PC IRQ priority list:

        IRQ     Priority        IRQ     Priority
        ---     --------        ---     --------
         0         0             8          2
         1         1             9          3
         2        10            10          4
         3        11            11          5
         4        12            12          6
         5        13            13          7
         6        14            14          8
         7        15            15          9

A lower number means a higher priority....

The RTC interrupt line is connected to PC-IRQ8. So it comes in third
place for being serviced. When enabled!

Normally IRQ8 is NOT enabled, so you will first have to settle that with
the PIC, which is far from easy to understand. I use the following code
to enable and disable the IRQ8 processing. Disabling this interrupt is
necessary after your program is unloaded from memory. If you don't do
this, the IRQ service routine vector might point to some random code or
data in the next program loaded (like Command.Com).

  ----------------------------------------------------- EnableIRQ8 ----

  EnableIRQ8:                   ; enable IRQ 8 in 8259
         push  ax
         in    al, 0A1          ; get IRQ mask word
         and   al, not bit 0
         out   0A1, al          ; enable IRQ 8
         pop   ax
         ret

  ----------------------------------------------------- EnableIRQ8 ----

Easy, isn't it? It took some nights to figure this out, 'cause the Intel
databooks are not that clear. I was glad to find some NEC databooks
since these shed some more light. In general, for older chips, NEC is a
good choice of databooks. They used to second source 80x86 chips for
Intel and are still known for their innovations they put into their V20
and V30 chips. The V25, a vastly improved 8088, was contaminated by 8
full banks of 14 registers. Luckily Intel did not copy this. What would
a 386 have been with 250 GP registers?

Here's the code for disabling IRQ8:

  --- Begin ------------------------------------------ DisableIRQ8 ----

  DisableIRQ8:                  ; disable IRQ 8 in 8259
         push  ax
         in    al, 0A1          ; get IRQ mask word
         or    al, bit 0
         out   0A1, al          ; disable IRQ 8
         pop   ax
         ret

  ---- End ------------------------------------------- DisableIRQ8 ----

Asserting IRQ8 will make the PC generate an INT 70h. So, we need to have
an INT 70h handler ready:

  --- Begin ------------------------------------------ NewIRQ8 --------

  L0:      mov   [IrqCount], ax           ; and store it
  L1:      mov   al, 020                  ; tell stupid PC that IRQ ends here
           out   020, al                  ; EOI to original PIC
           out   0A0, al                  ; EOI to cascaded PIC
           pop   ds, ax                   ; restore registers
           iret                           ; and get out

  NewIRQ8: push  ax, ds
        cs mov   ds, [DataSeg]            ; restore DS
           mov   al, 0C
           out   070, al
           in    al, 071                  ; clear interrupt flags
           test  [Flags], Running         ; are we running?
           jz    L1                       ; if not, get out
           test  [Flags], FastMode        ; Samplerate over 128 Sps?
           jz    >L2                      ; if not, scram
           or    [Flags], TimeOut         ; else set TimeOut flag
           jmp   L1

  L2:      mov   ax, [IrqCount]           ; medium to slow samplerates
           dec   ax                       ; are we at correct value?
           jnz   L0                       ; ... if not, wait some more
           or    [Flags], TimeOut         ; ... if so, set TimeOut flag,
           mov   ax, [MaxCount]           ; ... reload time constant register
           jmp   L0

  ---- End ------------------------------------------- NewIRQ8 --------

I like to do as little as possible in this kind of routines. In this
case I set a flag and rely on the abillities of the background program
to fork execution based on the state of that flag.

I hate the idea of having an INT routine that actually DOES things, but
which, for some obscure reason, cannot complete before the next INT
comes in. You'll be able to figure out what will happen in most cases.

If this routine sets a flag twice, I don't care too much. OK, I loose a
sample, but the program keeps running and it will still terminate when I
ask it to.

This routine:

  - saves registers on the user-stack
  - restores correct DS
  - accesses the FLAGS register in memory
  - consults these flags and acts upon them
  - eventually reaches L1 and here an EOI is sent to the PIC's
  - pops the stored registers from the userstack
  - returns with an IRET.

The PIC needs an EOI to enable lower priority interrupts. And since
there are two PIC's in modern PC's, there also must be two EOI's.

The following routine will enable the new IRQ8 handler:

  --- Begin ----------------------------------- EnableNewIRQ8 ---------

  EnableNewIRQ8:                  ; program the RTC chip to 1 kSps
           push  ax               ; and enable the 8259 PIC, channel 8
           mov   al, 0C
           out   070, al
           in    al, 071          ; check register C first
           mov   ah, 00100110xB
           call  SetPIRate        ; set PI rate to 1 kSps
           mov   al, 0B
           out   070, al
           mov   al, 01000010xB   ; enable the RTC interrupt pin
           out   071, al          ; and store it in RTC register B
           call  EnableIRQ8       ; enable the 8259 PIController
           pop   ax
           ret

  ---- End ------------------------------------ EnableNewIRQ8 ---------

And before going back to the OS of your choice, make sure there will be
no IRQ8's anymore coming this way:

  --- Begin ----------------------------------- ResetNewIRQ8 ----------

  ResetNewIRQ8:                   ; restore default values in RTC
           push  ax               ; and disable 8259 PIC, channel 8
           mov   al, 0A
           out   070, al          ; select register A
           mov   al, 00100110xB
           out   071, al          ; and set it back to PC default
           mov   al, 0B
           out   070, al
           mov   al, 00000010xB   ; disable interruptions from RTC chip
           out   071, al          ; via register B
           call  DisableIRQ8      ; handle the PIC
           pop   ax
           ret

  ---- End ------------------------------------ ResetNewIRQ8 ----------

In the big program these code fragments are from, I use two timer
interrupts:

  - the RTC timer is used for trigger-timing. When the RTC has set the
    right flag, the main program will sample the ADC and store the
    result in a buffer for later processing.

  - the internal PC klok which generates the 55 ms timing signals is
    used to set another flag. When this is set, the (DMM style) display
    is updated. The digital readout is updated about 3 times per second
    and the bargraph display is updated 18 times per second.

Therefore I also need a new IRQ0 handler:

  ---------------------------------------------------- NewIRQ0 --------

  L0:      pop   ds                       ; restore register
           jmp   [cs:OldIRQ0]             ; and update DOS clock

  NewIRQ0: push  ds                       ; new timer routine (18,2 Hz)
        cs mov   ds, [DataSeg]            ; restore DS
           test  [Flags], Running
           jz    L0                       ; if not running, eject!
           inc   [Counter]                ; else increment counter,
           or    [Flags], RefrshBar       ;  indicate "bargraph refresh"
           test  [Counter], 07            ; twice per second,
           IF  Z or  [Flags], RefrshDig   ;  indicate "digits update"
           jmp   L0                       ; and get out

  ---------------------------------------------------- NewIRQ0 --------

This new routine does the following:

  - check if the DMM is running,
  - if not, it makes no sense to set any flags,
  -   if running, set the "update bargraph display" flag,
  -   if running, check if it is time to update the digital readout,
  - restore DS register,
  - branch to previous IRQ0 handler.

In the initialisation routine, common to all my programs, I make sure
the right interrupt vectors are stolen:

  --- Begin ------------------------------------------ Init -----------

  init:    call  SetVars          ; init most import variables
           call  PowDown          ; make sure ADC is OFF
           call  ClkLo            ; prepare ADC for power-up
           call  ChkTime          ; measure minimum sample time
           call  MaxSps           ; determine maximum sample speed

           mov   ah, 0F
           int   010              ; determine existing video mode
           mov   [VidMode], al    ; store it
           mov   ax, 012
           int   010              ; set 640 x 480 graphics mode
           push  es
           mov   ax, 0351C        ; get old timervector
           int   021
           mov   w [OldIRQ0], bx
           mov   w [OldIRQ0+2], es
           mov   dx, offset NewIRQ0
           mov   ax, 0251C
           int   021              ; install new TIMER routine
           mov   ax, 03570
           int   021
           mov   w [OldClock], bx
           mov   w [OldClock+2], es
           mov   dx, offset NewIRQ8
           mov   ax, 02570
           int   021              ; install NewIRQ8 routine
           call  EnableNewIRQ8    ; and get it to work
           pop   es
           mov   ax, 0
           int   033              ; init mouse
           ShowMouse              ; this is a macro....

           call  FillScreen
           or    [Flags], RfrshBar + RefrPara + Upd8Digs
           call  ShowDig
           call  BrScale
           call  Update
           ret

  ---- End ------------------------------------------- Init -----------

Not much to explain about this INIT routine I guess.

So, on to the EXIT part of the software. Forget this, and the computer
will hang on random times afterwards....

  --- Begin ------------------------------------------ Exit -----------

  exit:    call  PowDown
           call  ResetNewIRQ8
           push  ds
           lds   dx, [OldIRQ0]
           mov   ax, 0251C
           int   021              ; restore timer vector
           pop   ds
           push  ds
           lds   dx, [OldClock]
           mov   ax, 02570
           int   021              ; restore realtime clock vector
           pop   ds

           mov   ah, 0
           mov   al, [VidMode]
           int   010              ; back to previous screenmode
           mov   ax, 0
           int   033              ; reset mouse and -driver
           mov   ax, 04C00
           int   021              ; and exit to DOS

  ---- End ------------------------------------------- Exit -----------

That's all you need to know to get started. The RTC chip has some nice
other possibillities. It can be programmed to interrupt each second. Or
any other number of seconds. It is a truly versatile chip with many
timing functions directly available to systems level programmers.

It might be a good idea to seacrh the web for a datasheet. A good
starting point will be www.dalsemi.com where PDF files will be available
for all DS 1287 style chips. Or else from ftp.dalsemi.com. The latest
versions of this chip that I know of is the DS 17887. This has a Y2K
compliant clock and over 8K of NV (=Non Volatile) RAM.

In the USA Dallas have an Automatic Datasheet FaxBack number:

        972 - 371 4441

Have fun exploiting the RTC chip, but be prepared to hit the reset
button now and then. Also, make a backup of the CMOS battery-backup RAM
onto a floppy disk! You'll have corrupted or erased these data before
you know it and it's always a bit of a shock if the system cannot even
find the C: drive anymore....



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                              Chaos Animation
                                                              by Laura Fairhead


    To assemble this program you are going to require most of the library
routines I have so far presented here. You can consider this an example
in just how easy it is to write software in assembler if you continue
to build and refine a library system. The program probably took me about
half an hour of work and most of that was making myself satisfied with
the niceness of the code:-


;issue #5
INCLUDE NUCONV.ASM


;issue #6
INCLUDE SCANU.ASM

;issue #7
;scalst list scanner
INCLUDE SCAWS.ASM

;random number generator
INCLUDE RAND.ASM

;ASM building blocks
INCLUDE PSTR.ASM
INCLUDE PSTRCR.ASM
INCLUDE PSTRCX.ASM
INCLUDE OUTCR.ASM
INCLUDE STRLEN.ASM
INCLUDE PUTCH.ASM


Overview
~~~~~~~~
    This is a simple but endearing graphical animation minature that
is based on the iterative function:-

    x' = x*x + y + a

    y' = b - x

    Where a,b are constants, x,y are old coordinates and x',y' are the
new ones. All values are taken to be in [0,1). That is the operations
are all performed modulo 1.

    If you haven't covered this in mathematics yet it is quite simple,
your function mod1 would be:-

    mod1(x) = x - int(x)

    This can all be done nicely within the bounds of 32-bit values,
simply view the binary point as being just before the MSbit.

    We only really have 4 values to keep since x',y' are the next x,y.

kaa     EQU kaera+1
kab     EQU kaa+4
kax     EQU kab+4
kay     EQU kax+4

    The EQU's at the program end are defining offsets for uninitialised
data that lies in the primary code segment. Here we have kaa<->a, kab<->b,
kax<->x, kay<->y

;EAX=x
        MOV EAX,DWORD PTR DS:[kax]
;EBX=b
        MOV EBX,DWORD PTR DS:[kab]
;EBX=b-x =y'
        SUB EBX,EAX
;ECX=y' for later use
        MOV ECX,EBX
;EBX=y, y'->y  (didn't I say XCHG is useful!)
        XCHG EBX,DWORD PTR DS:[kay]
;EBX=y+a
        ADD EBX,DWORD PTR DS:[kaa]
;EDX:EAX=x*x
;high dword is the first 32 b.p's....
        MUL EAX
;EBX=x*x+y+a =x'  (how much of the pattern is due to loss of accuracy here?)
        ADD EBX,EDX
;x<-x'
        MOV DWORD PTR DS:[kax],EBX

    Reasonably efficient, and we come out with the x,y coordinate pair
also in EBX,ECX.

    To jazz things up a little, instead of the basic idea:-
        (i)   set some random x,y,a,b
        (ii)  do our function on the x,y,a,b
        (iii) plot point on the screen representing x,y
        (iv)  go back to (ii)

    We implement a "trail". This is basically where we keep a store of
the last so many points drawn  (remember that classic WORM game??).
Then one end is added to and the other is deleted from. Points are all
plotted with XOR, especially since doing a second XOR will erase a
plotted point (so there is no erase routine).

    Furthermore for every point, 4-reflections of the point are plotted
to the screen. This gives you symmetry for free.


The plot routine
~~~~~~~~~~~~~~~~
    I'm going to first explain the plot routine before going into the
main code body. I had some fun writing it, but it also illustrates some
important points.

    We are using mode 011h. This is 640x480x2, ( 0280hx01E0h )

    With mode 011h you have of course only the one plane. You've got
bytes from +00h to +04Fh on each row representing 8-bit pixel groups.

    So given an x coordinate you need to take the 2 parts:-

        offset =x SHR 3
        bit    =x AND 3

    The y coordinate is just the one part, the offset:-

        offset =y *050h           (row=050h bytes)

    Now of course it is plain to see that the offset is simply the result
of multiplication by 5 and then shift left 4. (unless you work always in
decimal, ala 050h=5*010h)

    Oh, I LOVE the x86:-

        LEA SI,[EDX*4+EDX]

    This puts DX*5 straight into SI. Thats about 5 operations all in one go:)

    So then SI is shifted 4 left and the resultant offset y*050h is the y
component of the offset on screen of the pixel we want to plot.

    The routine keeps the x/y components apart because we want to plot
(x,y) (-x,y) (-x,-y) (x,-y). And as soon as they are put together for
one they need to be disassembled/reconstructed for the next.

    The x component, which is always in BX, is obviously created with
a shift right 3, however we first have to rescue the least significant
3 bits. They give the bit in the byte.

        MOV CL,BL
        MOV AX,0180h
        ROR AL,CL;ROL AH,CL

    Here I am getting AL with the bit set that corresponds to the the pixel
on screen. AH is being set up the opposite way around. Think of the screen
as four quadrants:-

                    |
             x+     |    x-
                y+  |
                    |
         -----------+-------------
                    |
                    |
                y-  |
                    |
                    |

    If our point starts in the x+y+ quadrant, we have the values to
draw that:-

      ( SHR BX,3  )

        XOR [SI+BX],AL

    Now to reflect the point x-wise, so it goes to x-y+, you only need
to get the x offset = 04Fh-x. Well x86 lets you do powerful things,
we don't need to mess; just negate BX to get the -x and add the 04Fh
in as a displacement. Of course the bit offset gets negated as well,
which is exactly why we have the two opposite masks in AL/AH:-

        NEG BX;XOR [SI+BX+04Fh],AH

    Next y is reflected, so we go to -x-y. This is the same thing
again, only the y coordinate will only affect the offset:-

        NEG SI;XOR [SI+BX+04Fh+01DFh*050h],AH

    And finally to +x-y:

        NEG BX;XOR [SI+BX+01DFh*050h],AL


Notes
~~~~~
    During the program run you can press any key to set different values
for the chaos function. Press ESC to abort. On abortion a message will
give you 2 hex d-words, these are the random number seed that generated
the last pattern you were watching. To see it again simply record the
values and invoke the program with the values on the command line:-

(ESC abort)

random seed=01234567 FEDCBA98      (program output)

KAOS 01234567 FEDCBA98             (invoke program with seed as parameter)

(chaos pattern displayed is the same as the one broken out of)

    The code is left undelayed and as such it may run too fast on a fast
machine. The optimum speed is for it to be only slighty over-fast. If
you want to achieve this you should add some sort of delay loop in. Alt-
ernatively just get out your old 386 and give it some work to do.

    Code is, as usual, is MASM format. Assemble to a COM file.

========START OF CODE======================================================

OPTION SCOPED
OPTION SEGMENT:USE16
.486

stksiz      EQU 0400h       ;stack size

kadatx      EQU 0C00h       ;#points length of trail

cseg SEGMENT BYTE

ASSUME NOTHING
ORG 0100h

kode PROC NEAR

;initialise, allocate memory and stack
        CLD
        MOV AH,04Ah
        MOV BX,OFFSET endof+0Fh
        SHR BX,4
        INT 021h
        JC errmem
        MOV SP,OFFSET stk+stksiz
;zero-terminate command line to facilitate
;parsing
        MOV SI,080h
        LODSB
        CBW
        XCHG BX,AX
        MOV [BX+SI],BH

;any parameters given?
        CALL NEAR PTR scaws
        CMP AL,0
        JZ SHORT ko0

;yes, so read 2 dwords as random seed
        MOV DI,OFFSET rndn
        MOV AL,010h
        CALL NEAR PTR scanur
        CALL NEAR PTR scanu
        JNA erripa
        STOSD
        CALL NEAR PTR scaws
        CALL NEAR PTR scanu
        JNA erripa
        STOSD
        JMP SHORT ko1

;no, so set random seed from system time
ko0:    CALL NEAR PTR rndseed
ko1:


lop2:

;set mode 011h, fade grey background
        MOV AX,011h
        INT 010h
        MOV EAX,040404h
        MOV BL,0
        CALL NEAR PTR spal

;save random seed so that kaos params can be restored
;by user
        MOV SI,OFFSET rndn
        MOV DI,OFFSET seed
        MOVSD;MOVSD

;set random params for function
        MOV DI,OFFSET kaa
        MOV CX,4
        lop1:
        CALL NEAR PTR rndgen32
        STOSD
        LOOP lop1

;initialise for plot trail
;   [kaera]=0 on the first pass of the store
;   [kaera]=-1 thereafter
;   [kaoff]=offset of store pointer

        MOV BYTE PTR DS:[kaera],0
        MOV WORD PTR DS:[kaoff],OFFSET kadat

lop0:

;iterate x,y
;   x'=x*x+y+a
;   y'=b-x
        MOV EAX,DWORD PTR DS:[kax]
        MOV EBX,DWORD PTR DS:[kab]
        SUB EBX,EAX
        MOV ECX,EBX
        XCHG EBX,DWORD PTR DS:[kay]
        ADD EBX,DWORD PTR DS:[kaa]
        MUL EAX
        ADD EBX,EDX
        MOV DWORD PTR DS:[kax],EBX

;x,y scale to screen bounds
;  gets the x,y [0,1) values into screen coordinate pair (BX,DX)
        SHR EBX,12
        LEA EBX,[EBX*4+EBX]
        SHR EBX,13

        MOV EDX,ECX
        SHR ECX,4
        SUB EDX,ECX
        SHR EDX,23

;do point
;  [kaera] is -1 on and after the store had become full for the first
;          time
        MOV DI,WORD PTR DS:[kaoff]
        TEST BYTE PTR DS:[kaera],-1
        JZ SHORT ko3
;unplot trail end point
        PUSH BX
        PUSH DX
        MOV BX,[DI]
        MOV DX,[DI+2]
        CALL NEAR PTR plo4
        POP DX
        POP BX

;current position is saved in store
ko3:    MOV AX,BX
        STOSW
        MOV AX,DX
        STOSW
;store ptr incremented wrapping at the end
        CMP DI,OFFSET kadat+kadatx*4
        JNZ SHORT ko4
        MOV DI,OFFSET kadat
        OR BYTE PTR DS:[kaera],-1

ko4:    MOV WORD PTR DS:[kaoff],DI

;current position is plotted
        CALL NEAR PTR plo4

;user
;  ESC aborts, any key sets a new function going
        MOV AH,0Bh
        INT 021h
        CMP AL,0
        JZ lop0

        MOV AH,7
        INT 021h
        CMP AL,01Bh
        JNZ lop2

;display random seed value and terminate
        MOV SI,OFFSET t0
        CALL NEAR PTR pstr
        MOV EAX,02083010h
        CALL NEAR PTR nuconvs
        MOV SI,OFFSET seed

;those instructions at the program start are never going to be
;executed again so use them as a temp workspace instead of kadat
;which could possibly be dangerous if somebody EQU's kadatx to
;some low value
        MOV DI,0100h
        PUSH DI
        LODSD
        CALL NEAR PTR nuconv
        MOV AL,020h
        STOSB
        LODSD
        CALL NEAR PTR nuconv
        MOV AL,0
        STOSB
        POP SI
        CALL NEAR PTR pstrcr

;screen mode is not put back to 02h you may wish to add
;a MOV AX,2;INT 010h here however I left it out because I
;see way too much of that mode

;program termination
terminat0:
        MOV AL,0
terminat:
        MOV AH,04Ch
        INT 021h

;error aborts
erripa:
        MOV SI,OFFSET terripa
        MOV AL,2
        JMP SHORT err
errmem:
        MOV SI,OFFSET terrmem
        MOV AL,1
err:
        PUSH SI
        MOV SI,OFFSET terr
        CALL NEAR PTR pstr
        POP SI
        CALL NEAR PTR pstrcr
        JMP terminat

terr:   DB "ERROR: ",0
terrmem:
        DB "memory allocation failure",0
terripa:
        DB "invalid parameter format",0

;program text (in it's entirely)
t0:     DB "random seed=",0

kode ENDP

;plo4-    4-way plot routine for mode 011h
;
;         plots 4 reflections of a single point on the mode 011h
;         screen these are (x,y) (-x,y) (-x,-y) (x,-y)
;
;         plots using XOR
;
;entry:   BX,DX=x,y coordinates
;
;exit:    SI,CL,AX,BX destroyed

plo4 PROC NEAR

        PUSH DS

;screen segment 0A000h
; for further comment please refer above
        PUSH 0A000h
        POP DS
        LEA SI,[EDX*4+EDX]
        SHL SI,4
        MOV CL,BL
        MOV AX,0180h
        ROR AL,CL
        ROL AH,CL
        SHR BX,3
        XOR [SI+BX],AL
        NEG BX
        XOR [SI+BX+04Fh],AH
        NEG SI
        XOR [SI+BX+04Fh+01DFh*050h],AH
        NEG BX
        XOR [SI+BX+01DFh*050h],AL
        POP DS
        RET

plo4 ENDP

;if you don't like my fade grey background you can delete this and
;the line that invokes it. However this is also the next library
;routine, so do cut/paste it into a file it will be used in future
;articles.

;spal-  set VGA DAC register via hardware
;entry: EAX=XXGGBBRR  (hex of course)
;
;           RR=red component
;           BB=blue component
;           GG=green component
;
;       don't forget that these values are <=03Fh
;
;       BL=DAC register to set
;
;
;exit:  (all registers are preserved)

spal PROC NEAR

;the code here is straightforward so I shall add no comment apart
;from a small moan:( I have used direct hardware access instead
;of the BIOS calls to affect the palette since square one, I'm
;not unreasonable in my desire to program the hard-metal of the machine,
;however the quality of the BIOS graphics routines is absolutely
;despicable. If you've ever tried using them you will know what I'm
;talking about.
;
        PUSH EAX
        PUSH DX
        MOV DX,03C8h
        CLI
        XCHG BX,AX
        OUT DX,AL
        INC DX
        XCHG BX,AX
        OUT DX,AL
        SHR EAX,8
        OUT DX,AL
        SHR EAX,8
        OUT DX,AL
        STI
        POP DX
        POP EAX
        RET

spal ENDP

;library routines
        INCLUDE RAND.ASM
        INCLUDE SCAWS.ASM
        INCLUDE SCANU.ASM
        INCLUDE NUCONV.ASM

        INCLUDE PSTR.ASM
        INCLUDE PSTRCR.ASM
        INCLUDE PSTRCX.ASM
        INCLUDE OUTCR.ASM
        INCLUDE STRLEN.ASM
        INCLUDE PUTCH.ASM
;data

kaoff   EQU $                   ;(w)  offset of trail store pointer (absolute)
kaera   EQU kaoff+2             ;(b)  flag indicating 1st trail pass
kaa     EQU kaera+1             ;(dw) a
kab     EQU kaa+4               ;(dw) b   kaos function parameters
kax     EQU kab+4               ;(dw) x
kay     EQU kax+4               ;(dw) y
kadat   EQU kay+4               ;(*)  store space for trail data
seed    EQU kadat+kadatx*4      ;(qw) copy of initial random number seed
stk     EQU seed+8              ;(*)  stack space
endof   EQU stk+stksiz          ;[endofprogram]

cseg ENDS

END FAR PTR kode

========END OF CODE========================================================














::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                   Inline Assembler With Modula
                                                   by Jan Verhoeven


I don't want to start a compiler-war in the assembler programmer's journal,
but I do want to show some nice in-line assembly routines for FST Modula-2.
FST (or Fitted Software Tools) was a shareware Modula-2 compile made by Roger
Carvalho. He eventualy gave up the concept of shareware and made his final
version freeware. If you look carefully you can find this package in many
software repositories like Simtel. Also the FreeDOS website used to harbor
this final version.

For this Modula-2 compiler I used my VGA routines (see previous issues) and
some in-line assembly to give this compiler a way to do graphics modes.

I uploaded the full sources to SimTel some months (or years?) ago, so if you
would like to have a detailed look at it, go there and look for it.

Modula-2 is despised by many, but it is the most structured language ever
made. And that's also probably the reason why most coders refuse to use it.
You must follow the compiler, whatever you do. A high price, but the result is
that Modula-2 programs seldomly crash. They can bail-out in the middle of the
program, but they will not hang due to a pointer or indexing error.

Anyway, here's my addition to this marvelous language:

---------------------------------------------------------------------------
IMPLEMENTATION MODULE VgaLib;

PROCEDURE   SHL (x, y : CARDINAL) : CARDINAL;       (*  Shift left x, y bits.  *)

VAR result  : CARDINAL;

BEGIN
    ASM
        MOV  AX, x
        MOV  CX, y
        AND  CX, 15         (* Mask off lower nybble    *)
        JCXZ ok             (* Get out if no shift.     *)
        SHL  AX, CL
    ok: MOV  result, AX     (* Store result.    *)
    END;
    RETURN result;
END SHL;

---------------------------------------------------------------------------

The only "drawback" is that the in-line code must be 8088 style. So you won't
be eable to use MMX instructions, but almost no-one ever needs those.

FST Modula-2 offers direct access to (values of) variables. Neat. Makes the
in-line feature very convenient to use.

---------------------------------------------------------------------------

PROCEDURE   SetColour (Colour : CHAR);  (*  Define colour to work with.     *)

BEGIN
    ASM
        MOV  DX, 03C4H          (* VGA controller port  *)
        MOV  AH, Colour
        MOV  AL, 2
        OUT  DX, AX
    END;
END SetColour;
---------------------------------------------------------------------------

Compare the following routine with the one I entered for the VGA-12h code in
A86 assembly language format. There's some Modula-2 overhead, but the actual
plotting is done in ASM, for speed-reasons.

---------------------------------------------------------------------------
PROCEDURE   Plot (VAR InWin : WinData);     (*  Plot point on CurX, CurY. *)

VAR x, y    : CARDINAL;

BEGIN
    x := InWin.CurX + InWin.TopX;
    y := InWin.CurY + InWin.TopY;

    ASM
        MOV  AX, 0A000H
        MOV  ES, AX         (* Set up segment register *)
        MOV  CX, x
        AND  CX, 7          (* Which bit to plot? *)
        MOV  AH, 80H
        SHR  AH, CL         (* Compose plotting mask *)
        MOV  AL, 8
        MOV  DX, 03CEH
        OUT  DX, AX         (* Set plottingmask *)
        MOV  AX, y          (* Calculate offset in Video RAM *)
        MOV  BX, AX
        ADD  AX, AX
        ADD  AX, AX
        ADD  AX, BX         (* AX := 5 * Y *)
        MOV  CL, 4
        SHL  AX, CL         (* AX := 16 * 5 * Y *)
        MOV  BX, x
        SHR  BX, 1
        SHR  BX, 1
        SHR  BX, 1
        ADD  BX, AX         (* plus X / 8 *)
        MOV  AL, ES:[BX]
        MOV  AL, 0FFH
        MOV  ES:[BX], AL    (* and plot it *)
    END;
END Plot;


PROCEDURE   DrawH (VAR InWin : WinData; Flag : BOOLEAN);
    (*  Draw a horizontal line from CurX, CurY for DeltaX pixels. *)

VAR Index, Stop, x, dx, y, Kval             : CARDINAL;
    Emask, Lmask, Val                       : CHAR;

BEGIN
    IF Flag THEN        (* Flag = TRUE => Plot, else UnPlot *)
        Val := 0FFX;
    ELSE
        Val := 0X;
    END;
    IF InWin.DeltaX < 18 THEN
        FOR Index := 0 TO InWin.DeltaX DO       (* For short lines *)
            Plot (InWin);
            INC (InWin.CurX);
        END;
    ELSE
         x := InWin.TopX + InWin.CurX;          (* For long lines *)
         y := InWin.TopY + InWin.CurY;
        dx := InWin.DeltaX;
        ASM
            MOV  AX, 0A000H
            MOV  ES, AX         (* Set up segment register *)
            MOV  CX, x
            AND  CX, 7
            MOV  BX, 8
            SUB  BX, CX
            MOV  AL, 0FFH
            SHR  AL, CL
            MOV  Emask, AL      (* compose plotting mask *)
            MOV  CX, dx
            SUB  CX, BX
            MOV  AX, CX
            AND  AX, 7
            PUSH AX             (* Save L-val *)
            SUB  CX, AX
            SHR  CX, 1
            SHR  CX, 1
            SHR  CX, 1
            MOV  Kval, CX
            MOV  AL, 0
            POP  CX             (* retrieve L-val *)
            JCXZ L0
            MOV  AL, 080H
        L0: DEC  CX
            SAR  AL, CL
            MOV  Lmask, AL

            MOV  AX, y              (* Calculate offset in Video RAM *)
            MOV  BX, AX
            ADD  AX, AX
            ADD  AX, AX
            ADD  AX, BX             (* AX := 5 * Y *)
            MOV  CL, 4
            SHL  AX, CL             (* AX := 16 * 5 * Y *)
            MOV  BX, x
            SHR  BX, 1
            SHR  BX, 1
            SHR  BX, 1
            ADD  BX, AX             (* plus X / 8 *)

            MOV  AH, Emask
            MOV  DX, 03CEH
            MOV  AL, 8
            OUT  DX, AX             (* Set plotting mask *)

            MOV  AL, Val
            MOV  AH, ES:[BX]
            MOV  ES:[BX], AL        (* Do the plotting ... *)

            INC  BX
            MOV  CX, Kval
            JCXZ L2
            MOV  AX, 0FF08H
            OUT  DX, AX
            MOV  AH, Val
        L1: MOV  AL, ES:[BX]
            MOV  ES:[BX], AH
            INC  BX
            LOOP L1
        L2: MOV  AH, Lmask
            MOV  AL, 8
            OUT  DX, AX
            MOV  AL, ES:[BX]
            MOV  AL, Val
            MOV  ES:[BX], AL
        END;
        INC (InWin.CurX, dx);
    END;
END DrawH;


PROCEDURE   PlotChar (VAR InWin : WinData; Letter : CHAR);
            (*  Plot character on InWin.(CurX,CurY).    *)

VAR xpos, ypos, MapOfs, VGApos, VGAseg, Pmask       : CARDINAL;
    Cval                                            : CHAR;

BEGIN
    IF Letter = 0AX THEN
        INC (InWin.CurY, 16);           (* Process LF *)
        RETURN;
    END;
    IF Letter = 0DX THEN
        InWin.CurX := InWin.Indent;     (* Process CR *)
        RETURN;
    END;
    IF InWin.CurX >= InWin.Width - ChrWid THEN
        InWin.CurX := InWin.Indent;
        INC (InWin.CurY, 16);
    END;
    xpos := InWin.CurX + InWin.TopX;
    ypos := InWin.CurY + InWin.TopY;
    VGApos := 80 * ypos + SHR (xpos, 3);
    VGAseg := 0A000H;
    MapOfs := ORD (Letter) * 16;
    ASM
        PUSH ES             (* save ES *)
        MOV  CX, xpos
        AND  CX, 7
        MOV  Cval, CL       (* nr of bits "off center" *)
        MOV  BX, 0FF00H
        SHR  BX, CL
        MOV  Pmask, BX      (* mask to use for left and right halves *)
        MOV  AX, BX
        MOV  AL, 8
        MOV  DX, 03CEH
        OUT  DX, AX         (* set plotting mask for left part *)
        MOV  CX, 16
        MOV  BX, VGApos
        LES  SI, BitMap     (* here are the pixels that make the tokens *)
        ADD  SI, MapOfs
    L0: PUSH CX
        LES  AX, BitMap     (* load ES, AX is just scrap *)
        MOV  AH, ES:[SI]    (* load pattern *)
        MOV  CL, Cval
        SHR  AX, CL         (* compose left half *)
        MOV  ES, VGAseg
        MOV  AL, ES:[BX]
        MOV  ES:[BX], AH    (* and "print" it *)
        ADD  BX, 80         (* point to next row *)
        INC  SI             (* and next pixel pattern *)
        POP  CX
        LOOP L0             (* repeat until done *)
        MOV  AX, Pmask
        CMP  AL, 0          (* if Cval = 0 => perfect allignment *)
        JE   ex             (*   skip second half *)
        XCHG AH, AL         (* else repeat the story once more *)
        MOV  AL, 8
        OUT  DX, AX         (* set up mask for right half *)
        MOV  CX, 16
        SUB  BX, 1279       (* 16 x 80 - 1 *)
        SUB  SI, CX
    L1: PUSH CX
        LES  AX, BitMap
        MOV  AH, ES:[SI]
        MOV  AL, 0
        MOV  CL, Cval
        SHR  AX, CL
        MOV  ES, VGAseg
        MOV  AH, ES:[BX]
        MOV  ES:[BX], AL
        ADD  BX, 80
        INC  SI
        POP  CX
        LOOP L1
    ex: POP  ES
    END;
    INC (InWin.CurX, ChrWid);   (* point to next printing position *)
END PlotChar;
---------------------------------------------------------------------------

And here is the promised solution for the "make a box-drawing routine" problem
of the previous issue. OK, the solution is in Modula-2, but since this is such
a clear to understand language it will be no big deal to port this code to
assembly language format.

---------------------------------------------------------------------------
PROCEDURE   MakeBox (InWin : WinData);
            (*  Make a box on screen starting at (TopX, TopY).  *)
BEGIN
    InWin.CurX := 0;
    InWin.CurY := 0;                        (* Make sure pointers are correct *)
    InWin.DeltaX := InWin.Width - 1;
    InWin.DeltaY := InWin.Height - 1;       (* setup parameters for drawing lines *)
    SetColour (InWin.BoxCol);
    DrawH (InWin, TRUE);                    (* draw horizontal line *)
    DrawV (InWin);                          (* draw vertical line   *)
    InWin.CurX := 0;
    InWin.CurY := 1;                        (* adjust coordinates   *)
    DrawV (InWin);                          (* draw last vertical line  *)
    DEC (InWin.CurY);
    INC (InWin.CurX);                       (* adjust coordinates once more *)
    DrawH (InWin, TRUE);                    (* draw final line  *)
END MakeBox;

END VgaLib.
---------------------------------------------------------------------------











::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
                                                 Assembly on the Alpha Platform
                                                 by Rudolf Seemann


ASSEMBLING ON ALPHA PART I
--------------------------
In this first article I will discover how to use functions written in alpha
assembler in a program written in C. The example I give is a rather simple
one. There are many things to know about alpha. This text shows that it is
quite simple to use assembler on alpha.

Introduction
------------
The heart of the alpha architecture is a 64-bit RISC processor with 32 integer
($0 to $31) and 32 floating point registers ($f0 to $f31). Its operation codes
can be classified by the number of its operands:
  class     opcode
  operate   opcode Ra,Rb,Rc     # Ra operation Rb -> Rc
            opcode Ra,number,Rc # Ra operation number (0-255) -> Rc
  memory    opcode Ra,Disp(Rb)  # load/store contents saved in memory address
                                # Rb + offset Disp in register Ra
  branch    opcode Ra,label     # branch if Ra = true to label
  PAL       opcode number       # opcodes for the operating system

The Usage Convention of register is listed in the following table. Saved
Registers are such whose contents will not be lost if a function is called.
The function will save such registers if it uses them.

  int reg      Usage Convention           Saved
  ---------------------------------------------
  $0           Integer function result    No
  $1-$8        Conventional scratch regs  No
  $9-$14       General uses               Yes
  $15 or $fp   Frame pointer              Yes
  $16-$21      Integer arguments by value No
  $22-$25      Conventional scratch regs  No
  $26          Return address register    Yes
  $27          Procedure value (pointer)  No
  $28 or $at   reserved for system        No
  $29 or $gp   Global pointer             No
  $30 or $sp   Stack pointer              Yes
  $31          Zero (not modifiable)      n/a

  float reg    Usage Convention                Saved
  --------------------------------------------------
  $f0          floating point function result  No
  $f1          Imaginary part function result  No
  $f2-$f9      General uses                    Yes
  $f10-$f15    Conventional scratch regs       No
  $f16-$f21    Floating point args by value    No
  $f22-$f30    Conventional scratch regs       No
  $f31         Zero (not modifiable)           n/a

Data Types are specified by suffixes (like q for quadword, l for longword).
Most integer operations only know these two suffixes. Floating point operations
know both: s and t.

Integer Data types:
  Type         Bits         signed range          unsigned range
  ---------------------------------------------------------------------
  Byte         8            -128 to 127           0 to 255
  Word         16           -32768 to 32767       0 to 65535
  Longword     32           -2147483648 to        0 to 4294967295
                             2147483647
  Quadword     64           -9228372036854775808  0 to
                             9228372036854775807  18446744073709551615

Floating Point Data Types:
  Type         Magnitude                         Precision
  ----------------------------------------------------------------
  S-floating   1.175 x 10^-38 to 3.403 x 10^38    6 decimal digits
  T-floating   2.225 x 10^-308 to 1.798 x 10^308  15 decimal digits

If you want to use 64-bit numbers in the c-programming language (gcc), use
(long) or (long int). (int) is 32 bits long.

The following example was tested on an SX164 with SuSE Alpha Linux 6.3 (Kernel
2.2.13).

The Example
-----------
My c-program calls the assembler function div which divides the first argument
given to it by the second one. The arguments will be put in the integer
registers $16 and $17 by convention. So all we have to do is to divide register
$16 by $17. The alpha does not know any division for integer. There is a pseudo-
opcode for integer-division but I will show how to convert an integer to a
floating point number, do the division in the floating point registers and
convert it back to integer. Finally the result will be put by convention in
register $0 where the c-program expects it to be.

Compiling the source codes
--------------------------
gcc -c div.s
gcc -o div divide.c div.o


Source of the C-program
-----------------------
/* divide.c */
#include <stdio.h>
int main()
{
  long int a,b,c; /* long int is 64 bits long */
  a=1111; /* a random number */
  b=14;     /* second random number */
  c=div(a,b); /* div is a function written in assembler code */
  /* div returns the value of a / b */
  printf("c is %d\n",c);
  exit(0);
}
-------------------------------------------------- cut here


Source of the Assembler-Program: div.s
-------------------------------------------------- cut here
     .title div divides two arguments and returns the result
     .data               # Data section
temp1:    .quad 0             # temporary variable
temp2:    .quad 0             # temporary variable
temp3:    .quad 0             # temporary variable
     REGS = 1                 # How many registers have to be saved
     STACK = REGS             # this registers will be put on the stack
     FRAME = ((STACK*8+8)/16)*16 # Stack size

     .text               # text section
     .align 4
     .set noreorder      # disallow rearrangements
     .globl div          # these 3 lines mark the
     .ent div            # mandatory function
div:                     # entry
     ldgp $gp,0($27)     # load the global pointer
     lda $sp,-FRAME($sp) # load the stack pointer
     stq $26,0($sp)      # save our own exit address
     .frame $sp,FRAME,$26,0  # describe the stack frame
     .prologue 1
     stq $16,temp1       # save register $16 (first argument)
     stq $17,temp2       # save register $17 (second argument)
     ldt $f2,temp1       # load 1st argument in floating point register
     ldt $f3,temp2       # load 2nd argument in floating point register
     cvtqt $f2,$f2       # convert integer to floating point
     cvtqt $f3,$f3       # convert integer to floating point
     divt $f2,$f3,$f4    # $f4 <-- $f2 / $f3
     cvttq $f4,$f4       # convert floating point to integer
     stt $f4,temp3       # store integer
     ldq $0,temp3        # load integer in integer register
done:     ldq $26,0($sp) # restore exit address
     lda $sp,FRAME($sp)  # Restore stack level
     ret $31,($26),1     # Back to c-program
     .end div            # Mark end of function
-------------------------------------------------- cut here



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
                                                           Direct Draw Examples
                                                           by X-Calibre


As a follow-up to the Direct Draw article in APJ#5, here are two complete
DirectDraw sample programs. The first uses an 8-bit palette, while the
second uses a 32-bit (truecolor) palette. To compile these, you will need to
obtain Ddraw.inc ( http://asmjournal.freeservers.com/files/Ddraw.inc.html )
for the necessary DirectDraw definitions.


;Ddplasma8.asm_________________________________________________________________
;---------------------------------------;
;    DDRAW Plasma Demo                  ;
;                                       ;
;    Author :         X-Calibre         ;
;    ASM version :    Ewald Snel        ;
;    Copyright (C) 1999, Diamond Crew   ;
;                                       ;
;    http://here.is/diamond/            ;
;---------------------------------------;

    TITLE WIN32ASM EXAMPLE
    .486
    .MODEL FLAT, STDCALL
    option casemap :none

;-----------------------------------------------------------;
;                WIN32ASM / DDRAW PLASMA DEMO               ;
;-----------------------------------------------------------;

    INCLUDE \masm32\include\windows.inc

  ; -----------------------------------
  ; Note that the following is the
  ; include file written by Ewald Snel.
  ; -----------------------------------
    INCLUDE \masm32\include\ddraw.inc

    INCLUDE \masm32\include\gdi32.inc
    INCLUDE \masm32\include\kernel32.inc
    INCLUDE \masm32\include\user32.inc

    includelib \masm32\lib\gdi32.lib
    includelib \masm32\lib\ddraw.lib
    includelib \masm32\lib\kernel32.lib
    includelib \masm32\lib\user32.lib


    WinMain    PROTO :DWORD,:DWORD,:DWORD,:DWORD
    WndProc    PROTO :DWORD,:DWORD,:DWORD,:DWORD
    nextFrame  PROTO
    initPlasma PROTO

RETURN MACRO arg
    IFNB <arg>
        mov            eax, arg
    ENDIF
    ret
ENDM

LRETURN MACRO arg
    IFNB <arg>
        mov            eax, arg
    ENDIF
    leave
    ret
ENDM

FATAL MACRO msg
    LOCAL @@msg
    .DATA
    @@msg        db        msg, 0
    .CODE

    INVOKE MessageBox, hWnd, ADDR @@msg, ADDR szDisplayName, MB_OK
    INVOKE ExitProcess, 0
ENDM


.DATA?

hWnd            HWND                ?        ; surface window
lpDD            LPDIRECTDRAW        ?        ; DDraw object
lpDDSPrimary    LPDIRECTDRAWSURFACE ?        ; DDraw primary surface
ddsd            DDSURFACEDESC       <?>      ; DDraw surface descriptor
ddscaps         DDSCAPS             <?>      ; DDraw capabilities


palette            dd        256 dup (?)
table              dd        512 dup (?)
lpDDPalette        dd        ?


.DATA

ddwidth            EQU        320                ; display mode width
ddheight           EQU        200                ; display mode height
ddbpp              EQU        8                  ; display mode color depth

phaseA             dd         0
phaseB             dd         0

factor1            EQU        -2
factor2            EQU        -1
factor3            EQU         1
factor4            EQU        -2

red                dd        500.0
green              dd        320.0
blue               dd        372.0

scale1             dd        2.0
scale2             dd        128.0
scale3             dd        256.0
scale4             dd        127.0

szClassName        db            "DDRAW Plasma Demo", 0    ; class name
szDisplayName      EQU            <szClassName>            ; window name
color              dd            0

wc                WNDCLASSEX    < SIZEOF WNDCLASSEX, CS_HREDRAW OR CS_VREDRAW,
                                   OFFSET WndProc, 0, 0, , 0, 0, , 0,
                                                         OFFSET szClassName, 0
>

.CODE

start:

    INVOKE GetModuleHandle, NULL
    INVOKE WinMain, eax, NULL, NULL, SW_SHOWDEFAULT
    INVOKE ExitProcess, eax

;-----------------------------------------------------------;
;                Calculate Next Plasma Frame                ;
;-----------------------------------------------------------;

nextFrame    PROC
    push        ebx
    push        esi
    push        edi

    mov            ecx , ddheight                ; # of scanlines
    mov            edi , [ddsd.lpSurface]        ; pixel output

@@scanline:
    push        ecx
    push        edi

    mov            esi , [phaseA]
    mov            edx , [phaseB]
    sub            esi , ecx
    and            edx , 0ffH
    and            esi , 0ffH
    mov            edx , [table][4*edx][256*4]
    mov            esi , [table][4*esi]        ; [x]  +  table0[a + y]
    sub            edx , ecx                    ; [y]  +  table1[b]
    mov            ecx , ddwidth                ; [x] --> pixel counter

@@pixel:
    and            esi , 0ffH
    and            edx , 0ffH
    mov            eax , [table][4*esi]
    mov            ebx , [table][4*edx][256*4]
    add            eax , ebx
    add            esi , factor3
    shr            eax , 1
    inc            edi
    add            edx , factor4
    dec            ecx
    mov            [edi][-1] , al
    jnz            @@pixel

    pop            edi
    pop            ecx
    add            edi , [ddsd.lPitch]            ; inc. display position
    dec            ecx
    jnz            @@scanline

    add            [phaseA] , factor1
    add            [phaseB] , factor2

    pop            edi
    pop            esi
    pop            ebx

    ret
nextFrame    ENDP

;-----------------------------------------------------------;
;                Initalize Plasma Tables                        ;
;-----------------------------------------------------------;

initPlasma    PROC

    LOCAL @@i :DWORD
    LOCAL @@r :DWORD
    LOCAL @@g :DWORD
    LOCAL @@b :DWORD
    LOCAL temp :DWORD


    mov            [@@i] , 0

    .WHILE @@i < 256

        mov            edx , [@@i]

; Calculate table0 value

        fldpi
        fimul        DWORD PTR [@@i]
        fmul        REAL4 PTR [scale1]
        fdiv        REAL4 PTR [scale3]
        fsin
        fmul        REAL4 PTR [scale4]
        fadd        REAL4 PTR [scale2]
        fistp        DWORD PTR [table][4*edx]

; Calculate table1 value

        fldpi
        fimul        DWORD PTR [@@i]
        fmul        REAL4 PTR [scale1]
        fdiv        REAL4 PTR [scale3]
        fcos
        fmul        REAL4 PTR [scale2]
        fadd        REAL4 PTR [scale2]
        fldpi
        fmulp        st(1), st
        fmul        REAL4 PTR [scale1]
        fdiv        REAL4 PTR [scale3]
        fsin
        fmul        REAL4 PTR [scale4]
        fadd        REAL4 PTR [scale2]
        fistp        DWORD PTR [table][4*edx][4*256]

; Calculate palette value

        xor            eax , eax

        FOR comp, <red, green, blue>
            fldpi
            fimul        DWORD PTR [@@i]
            fmul        REAL4 PTR [scale1]
            fdiv        REAL4 PTR [comp]
            fcos
            fmul        REAL4 PTR [scale4]
            fadd        REAL4 PTR [scale2]
            fistp        DWORD PTR [temp]
            shl            eax , 8
            or            eax , [temp]
        ENDM

        bswap          eax
        shr            eax, 8
        mov            [palette][4*edx] , eax
        inc            [@@i]

    .ENDW

      ; Set palette
      DDINVOKE        CreatePalette, lpDD, DDPCAPS_8BIT or DDPCAPS_ALLOW256,
                                      ADDR palette, ADDR lpDDPalette, NULL
     .IF eax != DD_OK
          FATAL "Couldn't create palette"
     .ENDIF

     DDSINVOKE       SetPalette, lpDDSPrimary, lpDDPalette
     .IF eax != DD_OK
          FATAL "Couldn't set palette"
     .ENDIF

    ret

initPlasma    ENDP

;-----------------------------------------------------------;
;                WinMain  ( entry point )                   ;
;-----------------------------------------------------------;

WinMain PROC hInst     :DWORD,
             hPrevInst :DWORD,
             CmdLine   :DWORD,
             CmdShow   :DWORD

    LOCAL msg  :MSG

; Fill WNDCLASSEX structure with required variables

    mov            eax , [hInst]
    mov            [wc.hInstance] , eax
    INVOKE         GetStockObject , BLACK_BRUSH
    mov            [wc.hbrBackground] , eax

    INVOKE RegisterClassEx, ADDR wc


; Create window at following size

    INVOKE CreateWindowEx, 0,
                            ADDR szClassName,
                            ADDR szDisplayName,
                            WS_POPUP,
                            0, 0, ddwidth, ddheight,
                            NULL, NULL,
                            hInst, NULL
    mov            [hWnd] , eax

    INVOKE ShowWindow, hWnd, SW_MAXIMIZE
    INVOKE SetFocus, hWnd
    INVOKE ShowCursor, 0


; Initialize display

    INVOKE DirectDrawCreate, NULL, ADDR lpDD, NULL
    .IF eax != DD_OK
        FATAL "Couldn't init DirectDraw"
    .ENDIF

    DDINVOKE SetCooperativeLevel, lpDD, hWnd, DDSCL_EXCLUSIVE OR DDSCL_FULLSCREEN
    .IF eax != DD_OK
        FATAL "Couldn't set DirectDraw cooperative level"
    .ENDIF

    DDINVOKE SetDisplayMode, lpDD, ddwidth, ddheight, ddbpp
    .IF eax != DD_OK
        FATAL "Couldn't set display mode"
    .ENDIF

    mov            [ddsd.dwSize] , SIZEOF DDSURFACEDESC
    mov            [ddsd.dwFlags] , DDSD_CAPS
    mov            [ddsd.ddsCaps.dwCaps] , DDSCAPS_PRIMARYSURFACE
    DDINVOKE CreateSurface, lpDD, ADDR ddsd, ADDR lpDDSPrimary, NULL
    .IF eax != DD_OK
    FATAL "Couldn't create primary surface"
    .ENDIF

    call        initPlasma

; Loop until PostQuitMessage is sent
    .WHILE 1

        INVOKE PeekMessage, ADDR msg, NULL, 0, 0, PM_REMOVE

        .IF eax != 0
            .IF msg.message == WM_QUIT
                INVOKE PostQuitMessage, msg.wParam
                .BREAK
            .ELSE
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            INVOKE GetFocus

            .IF eax == hWnd

                mov            [ddsd.dwSize] , SIZEOF DDSURFACEDESC
                mov            [ddsd.dwFlags] , DDSD_PITCH

                .WHILE 1
                    DDSINVOKE mLock, lpDDSPrimary, NULL, ADDR ddsd, DDLOCK_WAIT, NULL

                    .BREAK .IF eax == DD_OK

                    .IF eax == DDERR_SURFACELOST
                        DDSINVOKE Restore, lpDDSPrimary
                    .ELSE
                        FATAL "Couldn't lock surface"
                    .ENDIF
                .ENDW

                DDINVOKE WaitForVerticalBlank, lpDD, DDWAITVB_BLOCKBEGIN, NULL

                call        nextFrame

                DDSINVOKE Unlock, lpDDSPrimary, ddsd.lpSurface

            .ENDIF
        .ENDIF
    .ENDW

    .IF lpDD != NULL

          .IF lpDDSPrimary != NULL
            DDSINVOKE Release, lpDDSPrimary
            mov            [lpDDSPrimary] , NULL
        .ENDIF

         DDINVOKE Release, lpDD
        mov            [lpDD] , NULL

    .ENDIF

    LRETURN msg.wParam

WinMain ENDP

;-----------------------------------------------------------;
;                Window Proc  ( handle events )                ;
;-----------------------------------------------------------;

WndProc PROC hWin   :DWORD,
             uMsg   :DWORD,
             wParam :DWORD,
             lParam :DWORD

    .IF uMsg == WM_KEYDOWN
        .IF wParam == VK_ESCAPE
            INVOKE PostQuitMessage, NULL
            RETURN 0
        .ENDIF
    .ELSEIF uMsg == WM_DESTROY
        INVOKE PostQuitMessage, NULL
        RETURN 0
    .ENDIF

    INVOKE DefWindowProc, hWin, uMsg, wParam, lParam

    ret

WndProc ENDP

END start
;End_Ddplasma8.asm_____________________________________________________________


;Ddplasma32.asm________________________________________________________________
;---------------------------------------;
;    DDRAW Plasma Demo                  ;
;                                       ;
;    Author :         X-Calibre         ;
;    ASM version :    Ewald Snel        ;
;    Copyright (C) 1999, Diamond Crew   ;
;                                       ;
;    http://here.is/diamond/            ;
;---------------------------------------;

    TITLE WIN32ASM EXAMPLE
    .386
    .MODEL FLAT, STDCALL
    option casemap :none

;-----------------------------------------------------------;
;                WIN32ASM / DDRAW PLASMA DEMO               ;
;-----------------------------------------------------------;

    INCLUDE \masm32\include\windows.inc

  ; -----------------------------------
  ; Note that the following is the
  ; include file written by Ewald Snel.
  ; -----------------------------------
    INCLUDE .\ddraw.inc

    INCLUDE \masm32\include\gdi32.inc
    INCLUDE \masm32\include\kernel32.inc
    INCLUDE \masm32\include\user32.inc

    includelib \masm32\lib\gdi32.lib
    includelib \masm32\lib\ddraw.lib
    includelib \masm32\lib\kernel32.lib
    includelib \masm32\lib\user32.lib


    WinMain    PROTO :DWORD,:DWORD,:DWORD,:DWORD
    WndProc    PROTO :DWORD,:DWORD,:DWORD,:DWORD
    nextFrame  PROTO
    initPlasma PROTO

RETURN MACRO arg
    IFNB <arg>
        mov            eax, arg
    ENDIF
    ret
ENDM

LRETURN MACRO arg
    IFNB <arg>
        mov            eax, arg
    ENDIF
    leave
    ret
ENDM

FATAL MACRO msg
    LOCAL @@msg
    .DATA
    @@msg        db        msg, 0
    .CODE

    INVOKE MessageBox, hWnd, ADDR @@msg, ADDR szDisplayName, MB_OK
    INVOKE ExitProcess, 0
ENDM


.DATA?

palette            dd        256 dup (?)
table              dd        512 dup (?)
hWnd            HWND                ?        ; surface window
lpDD            LPDIRECTDRAW        ?        ; DDraw object
lpDDSPrimary    LPDIRECTDRAWSURFACE ?        ; DDraw primary surface
ddsd            DDSURFACEDESC       <?>      ; DDraw surface descriptor
ddscaps            DDSCAPS          <?>      ; DDraw capabilities


.DATA

ddwidth            EQU        320                ; display mode width
ddheight           EQU        200                ; display mode height
ddbpp              EQU        32                 ; display mode color depth

phaseA             dd         0
phaseB             dd         0

factor1            EQU        -2
factor2            EQU        -1
factor3            EQU         1
factor4            EQU        -2

red                dd        500.0
green              dd        320.0
blue               dd        372.0

scale1             dd        2.0
scale2             dd        128.0
scale3             dd        256.0
scale4             dd        127.0


szClassName        db            "DDRAW Plasma Demo", 0    ; class name
szDisplayName    EQU            <szClassName>            ; window name
color            dd            0

wc                WNDCLASSEX    < SIZEOF WNDCLASSEX, CS_HREDRAW OR CS_VREDRAW,
                                  OFFSET WndProc, 0, 0, , 0, 0, , 0,
                                                        OFFSET szClassName, 0 >


.CODE

start:

    INVOKE GetModuleHandle, NULL
    INVOKE WinMain, eax, NULL, NULL, SW_SHOWDEFAULT
    INVOKE ExitProcess, eax

;-----------------------------------------------------------;
;                Calculate Next Plasma Frame                ;
;-----------------------------------------------------------;

nextFrame    PROC
    push        ebx
    push        esi
    push        edi

    mov            ecx , ddheight                ; # of scanlines
    mov            edi , [ddsd.lpSurface]        ; pixel output

@@scanline:
    push        ecx
    push        edi

    mov            esi , [phaseA]
    mov            edx , [phaseB]
    sub            esi , ecx
    and            edx , 0ffH
    and            esi , 0ffH
    mov            edx , [table][4*edx][256*4]
    mov            esi , [table][4*esi]        ; [x]  +  table0[a + y]
    sub            edx , ecx                    ; [y]  +  table1[b]
    mov            ecx , ddwidth                ; [x] --> pixel counter

@@pixel:
    and            esi , 0ffH
    and            edx , 0ffH
    mov            eax , [table][4*esi]
    mov            ebx , [table][4*edx][256*4]
    add            eax , ebx
    add            esi , factor3
    shr            eax , 1
    add            edx , factor4
    and            eax , 0ffH
    add            edi , 4
    mov            eax , [palette][4*eax]
    dec            ecx
    mov            [edi][-4] , eax
    jnz            @@pixel

    pop            edi
    pop            ecx
    add            edi , [ddsd.lPitch]            ; inc. display position
    dec            ecx
    jnz            @@scanline

    add            [phaseA] , factor1
    add            [phaseB] , factor2

    pop            edi
    pop            esi
    pop            ebx

    ret
nextFrame    ENDP

;-----------------------------------------------------------;
;              Initalize Plasma Tables                      ;
;-----------------------------------------------------------;

initPlasma    PROC

    LOCAL @@i :DWORD
    LOCAL @@r :DWORD
    LOCAL @@g :DWORD
    LOCAL @@b :DWORD
    LOCAL temp :DWORD


    mov            [@@i] , 0

    .WHILE @@i < 256

        mov            edx , [@@i]

; Calculate table0 value

        fldpi
        fimul        DWORD PTR [@@i]
        fmul        REAL4 PTR [scale1]
        fdiv        REAL4 PTR [scale3]
        fsin
        fmul        REAL4 PTR [scale4]
        fadd        REAL4 PTR [scale2]
        fistp        DWORD PTR [table][4*edx]

; Calculate table1 value

        fldpi
        fimul        DWORD PTR [@@i]
        fmul        REAL4 PTR [scale1]
        fdiv        REAL4 PTR [scale3]
        fcos
        fmul        REAL4 PTR [scale2]
        fadd        REAL4 PTR [scale2]
        fldpi
        fmulp        st(1), st
        fmul        REAL4 PTR [scale1]
        fdiv        REAL4 PTR [scale3]
        fsin
        fmul        REAL4 PTR [scale4]
        fadd        REAL4 PTR [scale2]
        fistp        DWORD PTR [table][4*edx][4*256]

; Calculate palette value

        xor            eax , eax

        FOR comp, <red, green, blue>
            fldpi
            fimul        DWORD PTR [@@i]
            fmul        REAL4 PTR [scale1]
            fdiv        REAL4 PTR [comp]
            fcos
            fmul        REAL4 PTR [scale4]
            fadd        REAL4 PTR [scale2]
            fistp        DWORD PTR [temp]
            shl            eax , 8
            or            eax , [temp]
        ENDM

        mov            [palette][4*edx] , eax
        inc            [@@i]

    .ENDW

    ret

initPlasma    ENDP

;-----------------------------------------------------------;
;                WinMain  ( entry point )                   ;
;-----------------------------------------------------------;

WinMain PROC hInst     :DWORD,
             hPrevInst :DWORD,
             CmdLine   :DWORD,
             CmdShow   :DWORD

    LOCAL msg  :MSG

; Fill WNDCLASSEX structure with required variables

    mov            eax , [hInst]
    mov            [wc.hInstance] , eax
    INVOKE         GetStockObject, BLACK_BRUSH
    mov            [wc.hbrBackground] , eax

    INVOKE RegisterClassEx, ADDR wc


; Create window at following size

    INVOKE CreateWindowEx, 0,
                            ADDR szClassName,
                            ADDR szDisplayName,
                            WS_POPUP,
                            0, 0, ddwidth, ddheight,
                            NULL, NULL,
                            hInst, NULL
    mov            [hWnd] , eax

    INVOKE ShowWindow, hWnd, SW_MAXIMIZE
    INVOKE SetFocus, hWnd
    INVOKE ShowCursor, 0


; Initialize display

    INVOKE DirectDrawCreate, NULL, ADDR lpDD, NULL
    .IF eax != DD_OK
        FATAL "Couldn't init DirectDraw"
    .ENDIF

    DDINVOKE SetCooperativeLevel, lpDD, hWnd, DDSCL_EXCLUSIVE OR DDSCL_FULLSCREEN
    .IF eax != DD_OK
        FATAL "Couldn't set DirectDraw cooperative level"
    .ENDIF

    DDINVOKE SetDisplayMode, lpDD, ddwidth, ddheight, ddbpp
    .IF eax != DD_OK
        FATAL "Couldn't set display mode"
    .ENDIF

    mov            [ddsd.dwSize] , SIZEOF DDSURFACEDESC
    mov            [ddsd.dwFlags] , DDSD_CAPS
    mov            [ddsd.ddsCaps.dwCaps] , DDSCAPS_PRIMARYSURFACE
    DDINVOKE CreateSurface, lpDD, ADDR ddsd, ADDR lpDDSPrimary, NULL
    .IF eax != DD_OK
    FATAL "Couldn't create primary surface"
    .ENDIF


    call        initPlasma

; Loop until PostQuitMessage is sent

    .WHILE 1

        INVOKE PeekMessage, ADDR msg, NULL, 0, 0, PM_REMOVE

        .IF eax != 0
            .IF msg.message == WM_QUIT
                INVOKE PostQuitMessage, msg.wParam
                .BREAK
            .ELSE
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            INVOKE GetFocus

            .IF eax == hWnd

                mov            [ddsd.dwSize] , SIZEOF DDSURFACEDESC
                mov            [ddsd.dwFlags] , DDSD_PITCH

                .WHILE 1
                    DDSINVOKE mLock, lpDDSPrimary, NULL, ADDR ddsd, DDLOCK_WAIT, NULL

                    .BREAK .IF eax == DD_OK

                    .IF eax == DDERR_SURFACELOST
                        DDSINVOKE Restore, lpDDSPrimary
                    .ELSE
                        FATAL "Couldn't lock surface"
                    .ENDIF
                .ENDW

                DDINVOKE WaitForVerticalBlank, lpDD, DDWAITVB_BLOCKBEGIN, NULL

                call        nextFrame

                DDSINVOKE Unlock, lpDDSPrimary, ddsd.lpSurface

            .ENDIF
        .ENDIF
    .ENDW

    .IF lpDD != NULL

          .IF lpDDSPrimary != NULL
            DDSINVOKE Release, lpDDSPrimary
            mov            [lpDDSPrimary] , NULL
        .ENDIF

         DDINVOKE Release, lpDD
        mov            [lpDD] , NULL

    .ENDIF

    LRETURN msg.wParam

WinMain ENDP

;-----------------------------------------------------------;
;                Window Proc  ( handle events )                ;
;-----------------------------------------------------------;

WndProc PROC hWin   :DWORD,
             uMsg   :DWORD,
             wParam :DWORD,
             lParam :DWORD

    .IF uMsg == WM_KEYDOWN
        .IF wParam == VK_ESCAPE
            INVOKE PostQuitMessage, NULL
            RETURN 0
        .ENDIF
    .ELSEIF uMsg == WM_DESTROY
        INVOKE PostQuitMessage, NULL
        RETURN 0
    .ENDIF

    INVOKE DefWindowProc, hWin, uMsg, wParam, lParam

    ret

WndProc ENDP

END start
;End_Ddplasma32.asm____________________________________________________________





I had mail problems last time... I don't think the example program from
the DDRAW tut ever reached you... and now you were looking for a Windows
article for issue #6... Maybe you can put the example in there... It's
Win32, and it would also double as a sequel to the article of issue #5
:)
Well, there's 2 examples actually... They look the same on screen, but 1
displays how to use 8 bit palette mode (like good old mode 13h), where
the other shows 32 bit truecolor mode...
I also included the original DDRAW.INC, so people can assemble the
sources themselves...
I hope this time it reaches you, and that I could have been of help to
you,
X-Calibre

WINDOWS



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD
                                                        Enter fbcon
                                                        by Konstantin Boldyshev


Many of Linux users have heard something about fbcon. It is becoming more
and more popular, mostly because of capability of getting graphics on
usual terminal without X. How to use graphic capabilities of fbcon?

The /dev/fb# devices represent frame buffer devices; they allow the frame
buffer of a video card to be read and written to by a user, and allow a
programmer to access the video hardware [and, more importantly, the video
memory] through ioctls and memory mapping.

The general approach to using fbcon is pretty simple:
  1) open /dev/fb0
  2) mmap /dev/fb0
  3) .. do the thing .. (use pointer returned by mmap to access videomemory)
  4) munmap /dev/fb0
  5) close /dev/fb0

I've taken one of my old DOS intros made in tasm, and rewritten it for nasm
and Linux/fbcon. At 408 bytes, This intro is the smallest implementation of
linear transformation with recursion (AFAIK).

Leaves.asm runs for about a minute and a half (depends on machine), and is
interruptible at any time with ^C. If everything is ok you should see two
branches of green leaves, and kinda wind blowing on them. It MUST be run only
in 640x480x256 mode (vga=0x301 in lilo.conf). You will see garbage or incorrect
colors in other modes.

Warning! Intro assumes that everything is ok with the system (/dev/fb0 exists,
can be opened and mmap()ed, correct video mode is set, and so on). So, if you
ain't root, check permissions on /dev/fb0 first, or you will not see anything.

The source is quite portable, you only need to implement putpixel() and initial-
ization part for your OS. To get the basic idea across, here is the fbcon
implementation in C:

//==========================================================================
// leaves.c : C implementation using /dev/fb0
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

typedef unsigned char byte;
typedef unsigned int  word;
typedef float dword;

#define MaxX 640
#define MaxY 480
#define VMEM_SIZE MaxX*MaxY

#define xc MaxX/2
#define yc MaxY/2
#define xmin0 100
#define xmax0 -xmin0
#define ymin0 xmin0
#define ymax0 -ymin0

#define colornum 8

int  h;
byte *p;

byte ColorTable[colornum] = { 0x00,0x00,0x02,0x00,0x00,0x02,0x0A,0x02 };
int color=0;

dword f=MaxY/(ymax0-ymin0)*3/2;
dword x1coef=MaxX-MaxY*4/9-yc;
dword y1coef=MaxY/4+xc;
dword x2coef=MaxY*4/9+yc;
dword x0=110;

dword a=0.7;
dword b=0.2;
dword c=0.5;
dword d=0.3;

void putpixel(word x,word y,byte color)
{
    *(p+y*MaxX+x) = color;
}

void leaves(dword x,dword y,byte n)
{
 word x1,y1;

 if (n>0)
  {
  y1=f*x+y1coef;

  putpixel(x1coef-f*y,y1,ColorTable[color]);
  putpixel(f*y+x2coef,y1,ColorTable[color]);

  if (++color>colornum-1) color=0;

  leaves(a*x+b*y,        b*x-a*y,     n-1);
  leaves(c*(x-x0)-d*y+x0,d*(x-x0)+c*y,n-1);

 }
}

int main(void)
{
 int i;

     p=mmap(0,VMEM_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,open("/dev/fb0",O_RDWR),0);

     for (i=0;i<VMEM_SIZE;i++) *(p+i) = 0;

     leaves(0,0,28);

     munmap(p,VMEM_SIZE);
     close(h);
}
//--------------------------------------------------------------------------EOF

Here is the asm source. It is quite short and self-explaining :) Well, actually
the source is badly optimized for size, contains some Linux-specific tricks,
and can be hard to understand. Please refer to the C source for areas that need
clarification

NOTE:     The following source was taken from asmutils and requires asmutils
        macros (*.inc), available from http://linuxassembly.org;
        you can also download binary there (in samples archive)

To compile leaves.asm:
          $ nasm -f elf leaves.asm
          $ ld -s -o leaves leaves.o

;==========================================================================
;Copyright (C) 1999 Konstantin Boldyshev <konst@voshod.com>
;
;leaves        -    fbcon intro in 408 bytes
;
;Ah, /if haven't guessed yet/ license is GPL, so enjoy! :)

%include "system.inc"

%assign SIZE_X 640
%assign SIZE_Y 480
%assign DEPTH 8
%assign VMEM_SIZE SIZE_X*SIZE_Y

%define MaxX 640.0
%define MaxY 480.0
%define xc MaxX/2
%define yc MaxY/2
%define xmin0 100.0
%define xmax0 -xmin0
%define ymin0 xmin0
%define ymax0 -ymin0

CODESEG
;al  -    color
putpixel:
     push edx
        lea    edx,[ebx+ebx*4]     ;computing offset..
        shl    edx,byte 7     ;multiply on 640
     add  edx,[esp+8]    ;
     mov  [edx+esi],al   ;write to frame buffer
     pop  edx
_return:
        ret

; recursive function itself
leaves:
        mov    ecx,[esp+12]
        test   cl,cl
     jz   _return
        mov    [esp-13],cl
        mov    eax,[edi]
        push   ecx
        sub    esp,byte 8
     mov  edx,esp

     fld  dword [ebp+16] ;[f]
     fld  st0
     fld  st0
     fmul dword [edx+16]
     fadd dword [ebp+24] ;[y1coef]
     fistp     dword [edx]
        mov    ebx,[edx]
     fmul dword [edx+20]
     fsubr     dword [ebp+20] ;[x1coef]
     fistp     dword [edx]
        call   putpixel
     fmul dword [edx+20]
     fadd dword [ebp+28] ;[x2coef]
     fistp     dword [edx]
        call   putpixel
     inc  edi
        cmp    edi,ColorEnd
        jl     .rec
     sub  edi,byte ColorEnd-ColorBegin

.rec:
     fld  dword [ebp+4]  ;[b]
     fld  dword [ebp]    ;[a]
     fld  st1
     fld  st1
     fxch
     fmul dword [edx+16]
     fxch
     fmul dword [edx+20]
     fsubp     st1
     fstp dword [edx-8]

     fmul dword [edx+16]
     fxch
     fmul dword [edx+20]
     faddp     st1
     dec  ecx
        push   ecx
        sub    esp,byte 8
     fstp dword [esp]
        call   leaves         ;esp+12
     mov  edx,esp
     fld  dword [ebp+12] ;[d]
     fld  dword [edx+28]
     fld  dword [ebp+8]  ;[c]
     fld  dword [ebp+32] ;[x0]
     fsub to st2
     fld  st3
     fld  st2
     fxch
     fmul st4
     fxch
     fmul dword [edx+32]
     faddp     st1
     fstp dword [edx-8]

     fxch
     fmulp     st2
     fxch st2
     fmul dword [edx+32]
     fsubp     st1
     faddp     st1
        push   ecx
        sub    esp,byte 8
     fstp dword [esp]
        call   leaves
        add    esp,byte 12*2+8
        pop    ecx
.return:
        ret

;------------------------------------- main()
START:
;prepare structure for mmap on the stack
     mov  edi,VMEM_SIZE
     mov  esi,esp
     mov  [esi-16],edi                  ;.len
     mov  [esi-12],byte PROT_READ|PROT_WRITE ;.prot
     mov  [esi-8],byte MAP_SHARED            ;.flags
     mov  [esi],edx                ;.offset

;init fb
     mov  ebp,Params
     lea  ebx,[ebp+0x2C] ;fb-Params
     sys_open EMPTY,O_RDWR

     test eax,eax        ;have we opened file?
     js   exit

     mov  [esi-4],eax    ;mm.fd
     lea  ebx,[esi-20]
     sys_mmap

     test eax,eax        ;have we mmaped file?
     js   exit

     mov  esi,eax

;clear screen
     mov  ecx,edi
     mov  edi,esi
     xor  eax,eax
     rep  stosb

;leaves
     lea  edi,[ebp+0x24] ;ColorBegin-Params
        push   byte 28        ;recursion depth
     push eax
     push eax
        call   leaves

;close fb
     sys_munmap esi,VMEM_SIZE
     sys_close [mm.fd]

exit:
     sys_exit

;----------------------------Parameters
Params:

a    dd   0.7
b    dd   0.2
c    dd   0.5
d    dd   0.3

f    dd   0xc0400000     ;MaxY/(ymax0-ymin0)*3/2
x1coef    dd   0x433b0000     ;MaxX-MaxY*4/9-yc
y1coef    dd   0x43dc0000     ;MaxY/4+xc
x2coef    dd   0x43e28000     ;MaxY*4/9+yc
x0   dd   112.0

ColorBegin:
     db   0,0,2,0,0,2,10,2
ColorEnd:

fb   db   "/dev/fb0";,NULL

END
;===========================================================================EOF

More information on the frame buffer device can be found in the Linux kernel
documentation [ usually /usr/src/linux/Documentation ] files framebuffer.txt,
internals.txt, matroxfb.txt, tgafb.txt, and vesafb.txt. The /dev/fbcon# ioctls
are defined in /usr/include/linux/fb.h .

Enjoy the demo!
















::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::................................ASSEMBLY.LANGUAGE.SNIPPETS
                                                                      TOHEX
                                                                      by Ronald

;Summary:       Convert hexadecimal digits to ASCII
;Compatibility: PowerPC platform
;Notes:         Reads 3 parameters in R1-R3
;               R1 = Number to convert to ASCII representation
;               R2 = Number of LSD's of R1 to convert
;               R3 = Address to store ASCII representation of number to
;               R31 = Temp register that holds 4 bits
;               Note that R1 is ruined during execution
.global TOHEX, TOHEX_LOOP, LT_TEN, STEP_OVER, TOHEX_EXIT
TOHEX:
        cmpwi R2, 0
        be TOHEX_EXIT
TOHEX_LOOP:
        andi. R31, R1, 15
        cmpwi R31, 10
        blt LT_TEN
        addi R31, R31, 'A'-10
        b STEP_OVER
LT_TEN:
        ori R31, R31, '0'
STEP_OVER:
        srwi R1, R1, 4
        subi R2, R2, 1
        stbx R31, R2, R3
        cmpwi R2, 0
        bne TOHEX_LOOP
TOHEX_EXIT:
        blr

                                                                     Hex2ASCII
                                                                     by cpuburn
;Summary:       Converts
;Compatibility: K7
;Notes:         This
;      While doing some light reading of the AMD K7 Athlon Optimization
;Manual, I came across one of the neatest hex-to-ASCII converters
;I've ever seen:

Example 5 - Hexadecimal to ASCII conversion
(y=x < 10 ? x + 0x30: x + 0x41):

MOV AL, [X]  ;load X value
CMP AL, 10   ;if x is less than 10, set carry flag
SBB AL, 69h  ;0..9 -> 96h, Ah.. h -> A1h...A6h
DAS          ;0..9: subtract 66h, Ah.. h: Sub. 60h
MOV [Y],AL   ;save conversion in y

                                                          MMX ltostr
                                                          by Cecchinel Stephan
;Summary:       Convert long [dword] value to an ASCII string
;Compatibility: MMX
;Notes:         Converts a number in EAX to an 8 bytes hexadecimal string
;               at [edi]
;               14 clocks on a Celeron-333
Sum1:      dd 0x30303030, 0x30303030
Mask1:    dd 0x0f0f0f0f, 0x0f0f0f0f
Comp1:    dd 0x09090909, 0x09090909
Hex32:
        bswap eax
        movq mm3,[Sum1]
        movq mm4,[Comp1]
        movq mm2,[Mask1]
        movq mm5,mm3
        psubb mm5,mm4
        movd mm0,eax
        movq mm1,mm0
        psrlq mm0,4
        pand mm0,mm2
        pand mm1,mm2
        punpcklbw mm0,mm1
        movq mm1,mm0
        pcmpgtb mm0,mm4
        pand mm0,mm5
        paddb mm1,mm3
        paddb mm1,mm0
        movq [edi],mm1
        ret



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::...........................................ISSUE.CHALLENGE
                                                              by Laura Fairhead


Challenge
~~~~~~~~~
Write a program that takes a snapshot of a text screen and writes it
to a file. It should work in any text mode and lines should be terminated
with newlines in the file so that it can easily be viewed in a standard
editor. ( 04Dh = 77 bytes )

Solution
~~~~~~~~
If you want to assemble this just remember FS = 064h, as MASM can't cope
with legal x86 code. Then just replace the (single) offset 0148h with
some name, then data is the filename at the end "SNAP",0. Obviously
the B's prefixing the addresses mean "BYTE PTR", and ALL the numbers
are in HEX.

=Z10 0
=NSUC0.COM
=L
0000004D
=U100 147
1CB6:0100 B8 30 11          MOV     AX,1130
1CB6:0103 32 FF             XOR     BH,BH
1CB6:0105 CD 10             INT     10              ;DL=rows-1
1CB6:0107 B4 0F             MOV     AH,0F
1CB6:0109 CD 10             INT     10              ;AH=columns
1CB6:010B 0E                PUSH    CS              ;1st BIOS call
1CB6:010C 07                POP     ES              ;corrupts ES
1CB6:010D 52                PUSH    DX              ;
1CB6:010E 50                PUSH    AX              ;set B[BP+1]=columns
1CB6:010F 8B EC             MOV     BP,SP           ;    B[BP+2]=rows
1CB6:0111 BA 48 01          MOV     DX,0148         ;open (CREATE) file
1CB6:0114 33 C9             XOR     CX,CX           ;name "SNAP"
1CB6:0116 B4 3C             MOV     AH,3C
1CB6:0118 CD 21             INT     21
1CB6:011A 93                XCHG    BX,AX           ;handle stays in BX
1CB6:011B 33 F6             XOR     SI,SI           ;SI read screen offset
1CB6:011D BA 80 00          MOV     DX,0080         ;DX data store in PSP
1CB6:0120 B8 00 B8          MOV     AX,B800
1CB6:0123 8E E0             MOV     FS,AX           ;FS screen segment
1CB6:0125 8B FA             MOV     DI,DX           ;outer loop rows
1CB6:0127 0F B6 4E 01       MOVZX   CX,B [BP+0001]  ;miss out the attribute
1CB6:012B 64 AD             FS: LODSW               ;byte, copying to
1CB6:012D AA                STOSB                   ;DS:080
1CB6:012E E2 FB             LOOP    012B
1CB6:0130 B8 0D 0A          MOV     AX,0A0D         ;n/l on row end
1CB6:0133 AB                STOSW
1CB6:0134 8B CF             MOV     CX,DI
1CB6:0136 2B CA             SUB     CX,DX           ;CX=data length
1CB6:0138 B4 40             MOV     AH,40           ;write row to file
1CB6:013A CD 21             INT     21
1CB6:013C FE 4E 02          DEC     B [BP+0002]     ;loop for row count
1CB6:013F 79 E4             JNS     0125
1CB6:0141 66 58             POP     EAX             ;clean-up stack
1CB6:0143 B4 3E             MOV     AH,3E           ;close file
1CB6:0145 CD 21             INT     21
1CB6:0147 C3                RET                     ;go CS:0 !
=D148 14C
1CB6:0148 53 4E 41 50 00                                  SNAP
=Q

If you've never seen the 2 BIOS calls before then you'd better take a look
at ralf brown's legendary interrupt list.

You may always overide the source segment DS: on a string instruction,
but you cannot override the destination segment ES: ever.

It's left as an exercise for you to incorporate error handling (since there
is none) and still better the length of this code ;)



::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::.......................................................FIN

Top

Assembly Programming Journals: Previous123456 — 7 — 89Next