ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Adam's Assembler Tutorial 1.0 ÇÄ¿ º º ³ º PART IX º ³ ÈÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Revision : 1.0 Date : 19-12-1996 Contact : blackcat@faroc.com.au http://www.faroc.com.au/~blackcat Note : Adam's Assembler Tutorial is COPYRIGHT, and all rights are reserved by the author. You may freely redistribute only the ORIGINAL archive, and the tutorials should not be edited in any form. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, this tutorial really is late out and I feel particularly guilty about it. Nonetheless, I've included yet another demo program with this edition so that makes up for it a little bit. Just a word about last Tutorial Eight's demo program - FIRE!. After obtaining a later version of TASM, I discovered that FIRE! doesn't like compiling all that well, so make sure you have the latest revision of Tutorial Eight (V 1.4) with the bugfix. You can always get all the latest revisions of the tutorials straight from ftp.faroc.com.au in the /pub/blackcat/programming/tutorials directory, and this is highly reccommended. Anyway, on we go with: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ FILE I/O ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Sooner or later, you will want to mess around with files. All you have to bear in mind here is that everything is HANDLE BASED. Those of you who have used or experimented with XMS will realize exactly what I mean by handles, but if you don't, then here's a quick summary: * You open/create a file. * You get a 16-bit unsigned integer to reference it with. How hard is that? Note: Back in the days before DOS 2, you had to use File Control Blocks to reference your files. (You've probably seen FCBS=xxxx sometime before in CONFIG.SYS files, and this is to support programs that were designed for XT's.) We can forget all about FCBs now, as they are pretty much obsolete. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Opening A File: ( Interrupt 21H ) AH = 3DH AL = operation type: 0 = Read only operation; 1 = Write only operation; 2 = Read/write operation. DS:DX = Filename Returns: If successful, the carry flag is cleared to zero, and the file handle is returned in AX. However, if something went wrong, the carry flag is set to one, and the error code returned in AX. For a list of all the error codes, see the table further down. Now, after all that, an example: .MODEL SMALL .STACK 200H .DATA FileName DB "EXAMPLE.TXT$" Error DB "Uh oh$" .CODE START: MOV AX, @DATA ; Point AX to the data segment MOV DS, AX ; AX --> DX MOV DX, OFFSET FileName ; Put offset of the file to open in DX MOV AH, 3DH ; Open file MOV AL, 00H ; Read only INT 21H JC Problem ; Something happen? ; Here you would get the handle from AX, and do some stuff JMP Done ; Nope Problem: MOV DX, OFFSET Error ; Uh oh MOV AH, 09H INT 21H Done: MOV AX, 4C00H ; Jump back to DOS - closing any open INT 21H ; files. Slack, but we don't know how ; to close files yet. END START ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Okay... simple enough I hope. Now, suppose we want to create a new file? It's just another simple subfunction of interrupt 21H. This is how you do it: Creating A New File: ( Interrupt 21H ) AH = 3CH CX = file type: 0 = Normal file; 1 = Read only; 2 = Hidden file; 4 = System file; DS:DX = Filename Returns: As before, if successful, the carry flag is cleared to zero, and the file handle is returned in AX. Note that you must watch out for existing files before creating a new file of the same name. DOS will not check if a file of the same name already exists, and overwrites the old one. Before you create a new file - try to open the file first. If you get error code 2 in AX, (file does not exist), then go ahead and create a new file. If you don't get error 2, you'll be overwriting an existing file! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ As you should know from experience with high-level languages, you must close your files before you end your program. (Actually, function 4CH closes all open files anyway, but that's a slack way of going about things.) To close an open file, you should do this: Closing A File: ( Interrupt 21H ) AH = 3EH BX = file handle Returns: Yet again, any errors are reflected in the carry flag and AX. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Finally, error codes. Just checking CF to see if anything went wrong will certainly let us know if anything is amiss, but we'd really like more detail. Examining AX after an error is detected is the way to go, and AX could contain any one of the following codes: ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Error Code ³ Explanation º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º 00H ³ Unknown error º º 01H ³ Invalid function number º º 02H ³ File not found º º 03H ³ Path not found º º 04H ³ Too many open files º º 05H ³ Access denied º º 06H ³ Invalid handle º º 07H ³ Control blocks destroyed º º 08H ³ Out of memory º º 09H ³ Bad control block address º º 0AH ³ Invalid environment º º 0BH ³ Invalid format º º 0CH ³ Invalid access code º º 0DH ³ Invalid data º º 0EH ³ Unkown error º º 0FH ³ Invalid drive º º 10H ³ Cannot remove current directory º º 11H ³ Device not the same º º 12H ³ No more files available º º 13H ³ Disk is write-protected º º 14H ³ Bad unit º º 15H ³ Drive not ready º º 16H ³ Unknown command º º 17H ³ CRC error º º 18H ³ Bad structure length º º 19H ³ Seek error º º 1AH ³ Invalid medium º º 1BH ³ Sector not found º º 1CH ³ Printer out ** º º 1DH ³ Write error º º 1EH ³ Read error º º 1FH ³ General failure º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ** I know, I don't get it either. I guess it's in there because DOS treats everything as a file. Phew! All courtesy of the good old DOS technical reference. Some of the ones up there were pretty obscure - there's only a few you really need to remember. Some of my *favourites* are: Sector not found, Seek error and CRC error half way through a stack of arjed diskettes. Gee that brings back memories. :) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Okay, so we've seen how to create, open and close files. Now let's do something with 'em. To read some bytes from a file, you must use function 3FH. Assuming you've already opened the file you want to read from, you can now use a bit of code like below: MOV AH, 3FH ; Read byte(s) MOV BX, Handle ; Fileto work on MOV CX, BytesToRead ; How much to get MOV DX, OFFSET WhereToPutThem ; An array or variable INT 21H JC DidSomethingGoWrong ; Check for an error If you are having problems grasping any of this - don't worry too much. Just go back over the examples above and see what sense you can make of it. Next tutorial we'll press on with sprites - (and loading them from disk) - so you'll get to see a good example. Okay... now, writing to a file. Much the same as reading, we now use function 40H. Some code to write a byte would look like: MOV AH, 40H ; Write byte(s) MOV BX, Handle ; Fileto write to MOV CX, BytesToWrite ; How much to write MOV DX, OFFSET WhereToWriteFrom ; Where the data is coming from INT 21H JC DidSomethingGoWrong ; Any errors? Well, that just about concludes file I/O for this tutorial. Although not a major component of Assembly language programming, file I/O is nonetheless an important concept to get a grip on. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ CALLING ASSEMBLER FROM C/C++ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ I suppose it's about time I covered linking assembler with C. Personally, I prefer doing VGA coding in an Assembler/Pascal combination. However, C has its place, and linking with C is an important aspect we should cover. You should have realized that you can start entering Assembler code into your C program like so: /* Your C code goes here */ asm { /* */ /* Your assembler code goes here */ /* */ } /* Your C code resumes here */ Now, considering that we can insert Assembler straight into C code, why would we bother to write external code? The answer is fairly simple. By using external routines, we have code that is faster to execute, faster to compile, can use some of Turbo Assembler's special features - such as ideal mode, and may even be portable to other languages. Writing external code for C is fairly simple, and is thankfully more easy than writing external code for Pascal. (See Tutorial Seven). As you noticed back in Tutorial seven, we had to declare the code and data segments ourselves using the somewhat messy SEGMENT directive. This is due to the way that Pascal likes to organise memory, and there is only one way we can get around the problem - we can use the TPASCAL model. Unfortunately, TPASCAL is an antiquated and outdated way of going about things, so we have to put a bit of work in. I'm not going to mention TPASCAL ever again, so we can safely forget about the gritty details. Note that none of this applies to us in C - we can quite happily use our nice simple assembler skeletons. There are a few restrictions placed upon us by most compilers though: þ The compiler uses SI and DI to store register variables. If you have used register variables in your code, remember to push and pop SI and DI in your external code. þ The compiler probably will not push and pop CS, DS, SS and BP so ensure that you are careful if you alter any of these registers. Other than those little snippets, there is little else we need to bear in mind. Let's blaze! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Okay... now we're going to write a small external routine and link it to C. Let's take a look at a basic skeleton that just puts some text onto the screen. ============================ LIBRARY.ASM ============================= .MODEL SMALL .DATA Message DB "Well looky here - we got ourselves some text$" .CODE PUBLIC _sample ; --------------------------------------------------------------------------- ; ; void sample(); ; _sample PROC NEAR ; Declare a near procedure MOV AH, 00H ; Set video mode MOV AL, 03H ; Mode 03H INT 10H MOV AH, 09H ; Print a string MOV DX, OFFSET Message ; DS:DX <-- Message INT 21H RET ; Outta here! _sample ENDP END ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well.... nothing too tricky there. Now, what about the C code that goes along with it? ============================= EXAMPLE.C ============================== extern void sample(); int main() { sample(); return 0; } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ And to compile the lot, the line below'll do the job. C:\> TCC EXAMPLE.C LIBRARY.ASM Of course, if you are using another flavour of C then replace TCC with whatever command line interpreter you have. It's also possible to get C to recognise variables declared in Assembler, and the following skeleton details how this is done: ============================ LIBRARY.ASM ============================= .MODEL SMALL .DATA PUBLIC _YourVariable ; Declare an external variable _YourVariable DW 9999 ; Make that variable a word with a value of 9999 .CODE END ============================= EXAMPLE.C ============================== extern int YourVariable; int main() { printf("The Assembler external variable is: %d", YourVariable); return(0); } Again, compile this with: TCC EXAMPLE.C LIBRARY.ASM But what about passing parameters to your routines? We could do this the hard way like we did with Pascal, or alternatively we could use the ARG directive. ARG is brilliant, because it greatly simplifies things -- but it has some shortcomings. Namely, in every routine you need an additional three instructions. If you want speed and don't mind a bit of hard work, work with the stack directly like we did in Tutorial Seven. Here's how you use ARG: ============================ LIBRARY.ASM ============================= .MODEL SMALL .DATA .CODE PUBLIC _putpixel ; Declare the external procedure ; --------------------------------------------------------------------------- ; ; void putpixel(int x, int y, char color, int location); ; _putpixel PROC NEAR ARG X : Word, Y : Word, Color : Byte, Location : Word PUSH BP ; Save BP MOV BP, SP ; BP *must* equal SP for ARG to work MOV AX, [Location] ; Parameters can now be accessed easily MOV ES, AX MOV BX, [X] MOV DX, [Y] MOV DI, BX MOV BX, DX SHL DX, 8 SHL BX, 6 ADD DX, BX ADD DI, DX MOV AL, [Color] MOV ES:[DI], AL POP BP ; BP needs to be restored! RET _putpixel ENDP END ============================= EXAMPLE.C ============================== extern void putpixel(int x, int y, char color, int location); int main() { asm { mov ax, 0x13 int 0x10 } putpixel(100, 100, 12, 0xa000); sleep(2); asm { mov ax, 0x03 int 0x10 } return(0); } Not all that tricky, huh? However, if you choose to write external routines because you want the speed Assembler can give, then access the stack the hard way. Those extra pops and pushes can really add up if your putpixel routine gets called 320x200 times! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ AN INTRODUCTION TO MACROS ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Macros are one of the most powerful features you have at your disposal when you are working with Assembler. Often you will find yourself repeating the same few lines of code over and over again when writing larger programs. You don't want to go to the trouble of creating a procedure -- which would slow down the code, but you don't want to keep repeating yourself. The answer.... MACROS. A macro is just a set of instructions that are given a name by which it will be referred to in the code. You can define a macro like this: MyMacroName MACRO ; ; Your instructions go here ; ENDM MyMacroName And from then on, whenever you put MyMacroName in your code, the instructions contained within the macro will be assembled in place of the macro name. NOTE: It is probably best to declare any macros just before you declare the data segment. For clarity, place all your macros in another text file and then use INCLUDE to include the macros. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Macros may also have parameters, making them especially handy. For example, I have often used DOS function 09H to put a string on the screen. I could make the programs I write more easy to read at first glance by creating the following macro: PutText MACRO TextParam MOV AH, 09H ; TextParam is the parameter -- NOT MOV DX, OFFSET TextParam ; a variable. Replace TextParam with INT 21H ; whatever name you choose. ENDM PutText Then, assuming in the data segment I had declared a string like this: AString DB "This is a string$" I could display that string by writing: PutText AString NOTE: When you are working with macros, be careful to observe what registers they change. If in doubt, push and then pop any registers you feel may be affected. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Although that simple macro wasn't really anything special, macros have many other uses. I'm not going to say anything more on macros for now, but I'll use them from time to time in future demo programs, and you'll learn other techniques you can put to good use. Anyway, on with what I've been wanting to do: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ THE DEMO PROGRAM ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ At first I was going to release this tutorial without a demo program, but seeing as I've been a bit too lazy for too long, (and also because a friend of mine just made a demo like this), I've decided to include a plasma. Plasmas can be a bit tricky in places -- it took me a while to get the whole thing working properly because of a problem I had with my lookup table. But if you follow the algorithm set out below, you shouldn't have any problems. *** Before you begin, you'll need FOUR temporary variables in your code. *** In Assembler this can get a bit tricky because you will often find yourself running out of registers. You could declare some bytes up in the data segment, but it's faster to use registers. These four temporary variables will only hold numbers between 0 and 255, so they only need to be BYTES. In the algorithm, I refer to these temporary variables as Temp1, *** Temp2, Temp3 and Temp4. *** The algorithm looks like this: þ Create a lookup table This is basically just one long sine wave. You can experiment by using a cosine wave instead, or alter the amplitude of the function you are using. I created my lookup table using the following expression: For W := 1 To 512 Do SinTable[W] := Round(Sin(W / 255 * Pi * 2) * 128); (SinTable is an array of 512 BYTES) þ Initialize the palette I personally like to make my palettes after I see the demo running with the standard palette. That way, by making certain colors dark and others very light, the result is exactly the way I want it. I've found the best way to do this is to grab the screen when the demo is running with a program like Screen Thief, then load up that picture in a paint program that lets you alter the palette. Once you get the palette how you want it, save it to disk as a COL file (if possible) and then write a little program to read in the COL file and write the file so it looks like PLASMA.DAT. Remember, Screen Thief is shareware, so if you use it, send the author some money huh? Loop (1): Before you start plotting the first row, you must: * Zero out Temp4; * Decrement Temp3 by two; You can experiment with Temp3 -- the larger the number you subtract from it, the faster the plasma will 'move'. You now move onto Loop (2). Loop (2): At the start of each row you must: * Increment Temp4 by one; * Let Temp1 = Sintable[Current row + Temp3]; * Let Temp2 = SinTable[Temp4]; You now move onto Loop (3). Loop (3): For every pixel on the current row you must: * Work out the color of that pixel to be plotted; The color value of that pixel is quite simply: SinTable[Temp1 + Temp2] + SinTable[Current row + Temp2] Unfortunately, this is a little harder to work out in Assembler and ends up taking several lines of code!! * Increment Temp1 by one; * Increment Temp2 by one; After doing an entire row, you then loop back to Loop (2). Once you have done all the rows (200), you can then loop back to loop (1). Of course, you'll also want to put a check for retrace in there, and checking to see if someone hit a key would be a good idea!! NOTE: For those that didn't know, the VGA has a status register that is worth paying particular attention to. This is register 03DAH, and by checking its various bits we can see what's happening right now with the VGA. (For those who want to know exactly what all the other bits are for, you should obtain a copy Ralf Brown's Interrupt List. This is available from my homepage and contains a complete list of all the interrupts, registers and much more.) Anyway, we are only interested in bit four of 03DAH which lets us know is a retrace is in progress. If we can access the VGA while the electron gun in the monitor is retracing it's path to the top of the screen -- we get fast, flicker-free access. What's more, because retrace happens every 1/80th of a second ON ALL COMPUTERS, we have a method of locking our demo to a particular speed on all machines. To check if retrace is happening, we simply examine bit four. If bit four is set, a retrace is now in progress. However, we don't know just how far into a retrace it is and we don't know how much time of flicker-free access we have. The solution is to check again for a retrace so we can be sure that we are at the START of one. I've used a retrace in the code to basically make sure the demo runs at the same speed on all machines. (More or less anyway). Also note that my plasma is more of a plasma variant. You can, and are encouraged to alter the code -- (try incrementing the temp values instead of decrementing, changing the amount the temp values are decremented by or changing the way the color value is found. Also try changing the palette, because this can make the plasma appear completely different). It is possible to create all sorts of effects by only making simple changes, so experiment... be creative! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, that just about concludes things for this tutorial. I'm not exactly sure what I'll be putting in Tutorial Ten -- sprites will be covered, but I can see the tutorials leaning towards demo effects and how you can code them in standalone Assembler. Until next time, -- Adam. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is getting a bit old now, but is still amusing: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ If Operating Systems Were Beers... DOS Beer: Requires you to use your own can opener, and requires you to read the directions carefully before opening the can. Originally only came in an 8-oz. can, but now comes in a 16-oz. can. However, the can is divided into 8 compartments of 2 oz. each, which have to be accessed separately. Soon to be discontinued, although a lot of people are going to keep drinking it after it's no longer available. Mac Beer: At first, came only a 16-oz. can, but now comes in a 32-oz. can. Considered by many to be a "light" beer. All the cans look identical. When you take one from the fridge, it opens itself. The ingredients list is not on the can. If you call to ask about the ingredients, you are told that "you don't need to know." A notice on the side reminds you to drag your empties to the trashcan. 3.1 Beer: The world's most popular. Comes in a 16-oz. can that looks a lot like Mac Beer's. Requires that you already own a DOS Beer. Claims that it allows you to drink several DOS Beers simultaneously,but in reality you can only drink a few of them, very slowly, especially slowly if you are drinking the Windows Beer at the same time. Sometimes, for apparently no reason, a can of Windows Beer will explode when you open it.. OS/2 Beer: Comes in a 32-oz can. Does allow you to drink several DOS Beers simultaneously. Allows you to drink Windows 3.1 Beer simultaneously too, but somewhat slower. Advertises that its cans won't explode when you open them, even if you shake them up. You never really see anyone drinking OS/2 Beer, but the manufacturer (International Beer Manufacturing) claims that 9 million six-packs have been sold. 95 Beer: You can't buy it yet, but a lot of people have taste-tested it and claim it's wonderful. The can looks a lot like Mac Beer's can, but tastes more like Windows 3.1 Beer. It comes in 32-oz. cans, but when you look inside, the cans only have 16 oz. of beer in them. Most people will probably keep drinking Windows 3.1 Beer until their friends try Windows 95 Beer and say they like it. The ingredients list, when you look at the small print, has some of the same ingredients that come in DOS beer, even though the manufacturer claims that this is an entirely new brew. NT Beer: Comes in 32-oz. cans, but you can only buy it by the truckload. This causes most people to have to go out and buy bigger refrigerators. The can looks just like Windows 3.1 Beer's, but the company promises to change the can to look just like Windows 95 Beer's - after Windows 95 beer starts shipping. Touted as an "industrial strength" beer, and suggested only for use in bars. Unix Beer: Comes in several different brands, in cans ranging from 8 oz. to 64 oz. Drinkers of Unix Beer display fierce brand loyalty, even though they claim that all the different brands taste almost identical. Sometimes the pop-tops break off when you try to open them, so you have to have your own can opener around for those occasions, in which case you either need a complete set of instructions, or a friend who has been drinking Unix Beer for several years. VMS Beer: Requires minimal user interaction, except for popping the top and sipping. However cans have been known on occasion to explode, or contain extremely un-beer-like contents.