What everybody really needs is a good, thorough introduction to assembler which explains the basics before going deeper into technical info. One good online tutorial of this kind I've found is here:
http://udgftp.cencar.udg.mx/ingles/tutor/Assembler.htmlAlso check The x86 Assembly Language Webring, which as you might guess from the name is a webring about assembly language for x86 chips, which refers to the Intel x86-series, the 286, 386, 486, etc. Pentiums are also included.
And check http://www.riverland.net.au/~mdunn for a few examples of neat asm tricks, like how to control the speaker, etc. (This is where I learned how to make a TSR.)
I should, however, include some material of my own. So, in my own style of simply jumping into an example and then explaining how everything works, here are some small programs in assembler to do various things.
MOV AL, 050h ;Change this to whatever you like for testing. ;Convert the value in AL into a 2-digit hexadecimal number and output the ;result to the screen. ;We do the left ASCII character first, but converting it destroys ;the contents of AX... PUSH AX ;...so let's save AX so we can restore it later. SHR AL,4 ;AL now contains the high nibble of the original number. ADD AL,30h ;If this nibble was 0 through 9, it's now been converted to ASCII. CMP AL,39h ;Is the character 9 or less? JLE PRINTFIRSTHEXCHAR ;If so, it's ready to be printed. ADD AL,7 ;Otherwise, it's A-F, and needs to be bumped up by 7. ;(There are 7 characters between the number 9 and the capital letter A in the ;ASCII table.) PRINTFIRSTHEXCHAR: MOV AH,0Eh ;Let's use INT 10,E to output AL as ASCII text to the screen. MOV BH,0 ;Page number, for text modes. INT 10h POP AX ;Now let's restore AX and do it all over again for the low nibble! AND AL,0Fh ;Zero out the high nibble, leaving only the low one... ;...and the rest is the same as above. ADD AL,30h CMP AL,39h JLE PRINTSECONDHEXCHAR ADD AL,7 PRINTSECONDHEXCHAR: MOV AH,0Eh MOV BH,0 INT 10h MOV AX,4C00h ;Terminate program INT 21h
;Turns on mouse cursor MOV AX,01 INT 33h MOV AX,4C00h INT 21h
The first line of this program is simply a comment. In assembler, a semicolon begins a comment, and the comment then continues through to the rest of that line.
The MOV AX,01 line puts a value of 01 (in other words, simply 1) into AX, which is a CPU register. Registers are small temporary storage areas in your CPU used for data processing.
The line INT 33h calls software interrupt number 33, which is used for mouse functions. (Software interrupts are mini-functions built into your computer.) INT 33 gets all its instructions from AX. Whatever we've specified for AX will tell INT 33 what to do. In this case, we've specified 1, which means we're telling INT 33 to turn on the mouse cursor. That's exactly what it will do now.
The final two lines are simply here for the purpose of program termination. INT 21 gets its instructions from AH, and when we feed 4C to it, that tells it to turn off the program. (This is important. If we omitted these last two lines from this program, the mouse cursor would be turned on, but the program would then stop running and lock up the computer.)
By now, you are no doubt wondering: How do I know that INT 33 controls the mouse and that MOV AX,01 will tell it to turn on the cursor? Sure, it's easy enough if you know that, but where might you find that information?
The answer is very simple: External references. Whole books have been written on INTs and registers and the MOVs that run them. Get a book from your local library that explains it all. Better yet, buy one. If you want to become a serious programmer, you will be referring to it constantly.
If you don't want to buy a book, or are simply too lazy to go to a library, you can also easily find complete INT lists on the Internet. In fact, from right here, you can download a program called HelpPC 2.10, which is a reference program chock-full of useful information for programmers and advanced computer users.
You can also get HelpPC from Simtel at the following link:
ftp://ftp.simtel.net/pub/simtelnet/msdos/info/helppc21.zip
Now that you have a program telling you everything you need to know, you may be wondering exactly how to create assembler programs. The small program listed above looks interesting enough, perhaps, but how do you run it? Just type it in at the computer's command line? Answer: No. You must use a compiler. Just like BASIC and C and Java, assembler is still not pure, raw machine code, and so you must use a program to translate it so the computer can run it.
If you use MS-DOS or Windows 95, you already have a program which can compile assembler instructions: Debug. It comes with MS-DOS and Windows 95. At the command prompt, simply type debug and press ENTER. You'll get a hyphen for a prompt. (Debug is remarkably unintuitive, but makes up for this with power.) Now, if you want to create the program above, you might as well name the program before you start. This can be done by typing simply n filename where "filename" is the name you want to give the file. (Use .COM for the extension, since this is a program.) N is Debug's command for specifying the file name. Most of Debug's commands are one character, actually. (I did say it was unintuitive.) Now you can type A and press ENTER. This is Debug's command for "assemble", and you can now begin typing in the program, pressing ENTER at the end of each line, of course. (Ignore the first line when typing it in, since it's just a comment anyway.) Once you're done, make a note of the number at the end of the prompt. That number shows how many bytes you've entered so far. (Minus 100. Notice that it starts at 100, so the actual number of bytes is 100 less.) For this particular program, you will see that it is 9 bytes long. Now, press ENTER to exit assembling and return to the hyphen prompt. One minor quirk of Debug is, you must specify the byte size of the programs you write before you save them. So, type RCX (short for Register CX). This will have Debug show you the current value of register CX (it will be 0), and the prompt will become a colon, indicating it wants you to type a new value for CX. Type 9 and press ENTER (since we're writing 9 bytes). Now, just type w (for "write") to save the program. All done. You can now type q (for "quit") to exit Debug.
The program is now in the current directory. If you want to see your mouse cursor, just run the program (make sure you have a mouse driver loaded first). You should now see it and be able to move it with the mouse.
By the way, notice the INT numbers above have an H after them. This is because some assembler compilers assume if you just use plain numbers that they should be converted into their hexadecimal values. Debug doesn't but A86, a very popular compiler, does. Basically, it would use INT 21 where it's supposed to use 33 (33 decimal = 21 hex) and 15 where it's suppoed to use 21 (21 decimal = 15 hex). Putting the H after clears up any problems that might come up with this.
Oh, and if you want to turn the cursor back off, here's a program which does just that:
MOV AX,02 INT 33h MOV AX,4C00h INT 21h
As you can see, this program is almost identical. The only difference is, a value of 2 in AX tells INT 33 to turn the cursor off.
MOV AX,5 ;5 in AX tells INT 33 to get mouse button info INT 33h ;get mouse button info CMP AX,1b ;this compares AX (button status) with 1 JL 0100h ;if no button is pressed, back to start ;AX will be zero while no button is pressed. JL means, if the first ;operand is less than the second, make the jump! mov ax,4C00h ;terminate program int 21h
The operation of this program should be fairly simple and obvious.
This next program prints out the canonical program test message: "Hello, world." Just about everybody's first program in BASIC or C is a program that prints out this message. Here's one in Assembler that does it
MOV AX,SEG MESSAGE MOV DS,AX MOV DX,OFFSET MESSAGE ;These three lines make DS:DX form a pointer to the segment:offset of ;MESSAGE. Note that AX is used as a go-between for the segment, ;because some assembler compilers don't allow you to MOV immediate ;values into segment registers (like DS) directly. Some do, some don't. ;Just to avoid problems, instead of MOV DS,SEG MESSAGE, we do ;that to AX and then MOV DS,AX. mov ah,0009 ;9 in AH makes INT 21 print the string int 21h ;referenced in DS:DX. mov ax,4C00h ;terminate program int 21h MESSAGE: DB 'Hello, world.$'
This program should also be fairly obvious. It introduces some new concepts, but just study it and I'm sure you'll figure out what everything does.
And now, yet another mouse program. This whole program is completely self-explanatory. It's bigger than the ones above, but it's well-commented.
;Gives you an on-screen button to click on with the mouse, and ;proceeds only when you do! mov ah,06h ;this calls SCROLL SCREEN UP mov al,00h ;AL specifies how many lines... 0 just clears the screen mov ch,00h mov cl,00h mov dh,24h mov dl,80h int 10h ;The above lines just clear the screen. mov ah,02h ;Set cursor position mov dh,0Bh ;row mov dl,22h ;column int 10h ;Actually does it. :) MOV AX,SEG MESSAGE MOV DS,AX MOV DX,OFFSET MESSAGE mov ah,0009 ;9 in AH makes INT 21 print the string int 21h ;referenced in DS:DX. ;So, we have printed BUTTON right where we want it!!! mov ax,0001 int 33h ;Now, that's got the mouse on. MOUSLOOP: MOV AX,5 ;5 in AX tells INT 33 to get mouse button press info INT 33h ;get mouse button info CMP AX,1b ;this compares AX (button status) with 1 JNE MOUSLOOP ;unless LEFT button ONLY is pressed, go back to MOUSLOOP ;(Only the left button will work for this... Not the right.) MOV AX,3 INT 33h ;Now we're getting the POSITION info through INT 33,3! ;CX now is mouse horizontal position. 0 - 639 ;DX now is mouse vertical position. 0 - 199 CMP CX,270 JL MOUSLOOP ;If CX is less than 270 (left edge of button), go back CMP CX,315 JG MOUSLOOP ;If CX if more than 400 (right edge), go back CMP DX,85 JL MOUSLOOP ;If DX is less than 85 (top), go back CMP DX,95 JG MOUSLOOP ;If DX is more than 110 (bottom), go back ;The numbers above for the button dimensions are approximations, but ;should be good enough. mov ax,0002 int 33h ;This just hides the cursor. Otherwise it would stay on when the program ;ended! :) mov ax,4C00h ;terminate program int 21h MESSAGE: DB 'BUTTON$'
Often, a program wants to simply check if a mouse driver has been installed or not. (Usually on startup of a program which requires a mouse, so the program can abort with a "No mouse driver" error if needed.) Here's a program which displays an error message if there is NO mouse driver, and exits silently if a driver was detected.
MOV AX,0000 INT 33h CMP AX,0000 JNE Ender ;Code here to perform if mouse driver is not installed. MOV AH,09 MOV DS,SEG Message MOV DX,OFFSET Message INT 21h Ender: mov ax,4C00h ;terminate program int 21h Message DB 'ERROR: Mouse driver was not detected.$'
A simple addition to the previous program which returns an errorlevel based on the presence of a mouse driver: Returns 0 if the driver was detected, or 1 if no driver was detected. (In DOS, an errorlevel of 0 is the standard code to signify "no error.")
;Checks for mouse driver, returns errorlevel of 0 if no error, returns ;errorlevel of 1 if error (i.e. mouse driver not installed) MOV AX,0000 INT 33h CMP AX,0000 JNE Itsok ;Code here to perform if mouse driver is not installed. MOV AL,01 ;Return errorlevel of 1 (indicating an error) JMP Ender Itsok: MOV AL,00 ;Return errorlevel of 0 (no problem) Ender: MOV AX,4C00h ;terminate program INT 21h
Definitely a bit longer and more complicated than the previous programs, but in my opinion, well worth the effort.
;Turn on mouse graphics cursor and make it look like a pointing hand. mov ah,00h mov al,13h int 10h ;The above three lines switch to 256 VGA! MOV AX,09h ;Tells INT 33 to set mouse cursor MOV BX,0Ah ;horizontal hot spot (-16 to 16) MOV CX,00h ;vertical hot spot (-16 to 16) MOV ES,SEG Curlook MOV DX,OFFSET Curlook ;ES:DX = pointer to screen and cursor masks (16 byte bitmap) INT 33h ;SET MOUSE CURSOR!!! MOV AX,01h INT 33h ;Turn it on! mov ax,4C00h ;terminate program int 21h ;NOTE: Each word of the following defines one row. ;There are 16 pixels in one row. ;There are 2 bytes for each row. ;1 word is 2 bytes, or 16 bits. Each bit of the word sets ;that pixel to either on or off. ;(The cursor is 16 by 16 pixels.) ;(The "xB"s indicate the number is in binary.) ;Screen mask: ;If a bit is 1, then the background beneath that pixel will show. ;If a bit is 0, then the background beneath that pixel will be hidden ;by the cursor when it's over that area. ;The second byte in each row is the leftmost 8 pixels. The first byte ;is the rightmost 8 pixels. (Don't ask me why they're switched around ;like that.) ;(In general, these bits should just be the exact opposite of their ;cursor counterparts. That way, the cursor covers up what's under it, ;and nothing around the cursor is obscured.) Curlook DB 11111111xB, 11111001xB DB 11111111xB, 11110000xB DB 11111111xB, 11110000xB DB 11111111xB, 11110000xB DB 11111111xB, 11110000xB DB 11111111xB, 11110000xB DB 00000111xB, 11000000xB DB 00000011xB, 10000000xB DB 00000011xB, 10000000xB DB 00000111xB, 11000000xB DB 00000111xB, 11000000xB DB 00001111xB, 11100000xB DB 00011111xB, 11110000xB DB 00011111xB, 11111000xB DB 00011111xB, 11111000xB DB 00011111xB, 11111000xB ;Whew! :) That's just the screen mask... Here comes the cursor mask! ;(If a bit is 1, that means that pixel for the cursor will be on. ;If a bit is 0, that means that pixel for the cursor will be off.) ;The second byte in each row is the leftmost 8 pixels. The first byte ;is the rightmost 8 pixels. (Don't ask me why they're switched around ;like that.) DB 00000000xB, 00000110xB DB 00000000xB, 00001111xB DB 00000000xB, 00001111xB DB 00000000xB, 00001111xB DB 00000000xB, 00001111xB DB 00000000xB, 00001111xB DB 11111000xB, 00111111xB DB 11111100xB, 01111111xB DB 11111100xB, 01111111xB DB 11111000xB, 00111111xB DB 11111000xB, 00111111xB DB 11110000xB, 00011111xB DB 11100000xB, 00001111xB DB 11100000xB, 00000111xB DB 11100000xB, 00000111xB DB 11100000xB, 00000111xB
Here's a pleasant little program which shows how you can make graphics that respond to mouse movement. In this case, the "graphics" are only a single pixel, but the point here is to show how to capture mouse movement, not how to draw fancy graphics.
The reason why this is important is because although we saw above how to use INT 33,9 to create our own graphical mouse cursor, INT 33,9 is not capable of producing color mouse cursors; it can only make black-and-white mouse cursors. If you want to make a color mouse cursor, there is no interrupt function for this in the standard Microsoft-compatible mouse driver for DOS, meaning that rather than using a simple plug-in interrupt call to create your mouse cursor, you will have check the mouse location using INT 33,3 (which returns the location of the mouse pointer on the screen) and then draw your own mouse cursor there.
Implementing a working mouse cursor with this would basically mean making the mouse cursor into a sprite which moves across the screen but leaves the graphics on the screen as they were after it leaves that part of the screen. There are many tutorials available on how to do sprite graphics, so I've skipped that information here to focus on how to capture the mouse movement and make the pixel change position on the screen accordingly. This resulted in a surprisingly meditative sort of experience, considering how simple the code is. The program also checks on each loop for a press of the Q key to quit.
mov ah,00h mov al,13h int 10h ;The above three lines switch to mode 13h, which is 256-color VGA MAINLOOP: ;Let's check to see if the user pressed Q to quit. IN AL,60h ;Read from keyboard input port into AL CMP AL,10h ;The "Q" key has a scancode of 10h JE QUITTER ;If AL equals 10h, the user pressed Q, so jump to the quit routine mov ax,3 ;Get mouse position (and also button status, which is not used here) int 33h ;Now CX and DX contain the X and Y positions of the mouse: ;CX = horizontal (X) position 0 - 639 ;DX = vertical (Y) position 0 - 199 ;First, let's fix CX, since it is actually twice as wide as we need it to be. ;(Our video mode is 320x200, but CX has a resolution of 640 pixels.) ;We'll load CX into AX (since we need to use AX for division), ;then divide AX by 2 and move AX back into CX. MOV AX,CX MOV BX,2 ;Here, I'd like to just use DIV BX to divide AX by 2. ;(We could also use DIV BL to do 8-bit division, but that would smash ;everything up if the user moves the mouse too far to the right, since our ;screen is 320 pixels wide, but an 8-bit register only goes up to 256.) ;However, there's another small detail we need to pay attention to here, ;namely the DX register, which will probably cause this program to crash with ;a divide overflow error if we don't attend to it. ;The DIV BX instruction divides the 32-bit value in DX:AX by BX. ;Having DX be anything other than 0 or 1 is probably going to cause a divide ;overflow, because the results won't fit into the 16-bit AX register, which ;would make the program crash. ;In any case, we want to divide only what's in AX, not DX, ;so let's set DX to 0. ;However, DX still contains our important Y-value of the mouse location, so ;we preserve DX by PUSHing it first, then POPing it when we're done with it. PUSH DX MOV DX,0 DIV BX ;FINALLY! POP DX MOV CX,AX ;CX has been divided by 2 ;Now let's multiply DX by 320, since each line is 320 pixels long, and so the ;memory location needs to be increased by 320 for each vertical row. MOV AX,320 MUL DX ;AX now contains DX times 320. The vertical location in video memory is ;correct, so let's add in the horizontal value still contained in CX... ADD AX,CX ;...and store the final value of the screen location in BX. MOV BX,AX MOV DS,0A000h ;VGA memory-mapped video segment MOV AL,40 ;40 decimal is red! MOV [BX],AL ;Writes a red pixel to the current screen location stored in BX. JMP MAINLOOP QUITTER: mov ax,4c00h ;terminate program int 21h
mov ah,0 mov al,13h int 10h ;The above 3 lines set video mode 13h MOV BX,0 ;Start from pixel 0 MOV CX,200 ;Number of times to go through the loop MOV SI,OFFSET MyBuffer ;LODSB loads the byte at DS:SI into AL LOOPER: PUSH CS POP DS ;Sets DS to be the current program segment CLD ;Clear direction flag so that SI gets incremented by LODSB LODSB ;Load the byte at DS:SI into AL and increment SI MOV DS,0A000h ;VGA memory-mapped video segment MOV [BX],AL ;Put the data onto the screen in the form of a pixel INC BX ;Move to the next pixel DEC CX CMP CX,0 JNE LOOPER mov ax,4C00h ;terminate program int 21h MyBuffer DB 40 DUP (40, 49, 33, 44, 37) ;Creates 40 copies of this 5-byte sequence (which corresponds to the colors ;red, green, blue, yellow, magenta), for 200 bytes in memory
By extension of the above program, this program reads a raw image file (one in which each pixel is a single byte of the file) and prints it to the screen. This program assumes a 320x200-pixel image and thus uses a 64000-byte data buffer, but if you're using a different picture size, you can adjust the size of this buffer easily. Don't forget to set the file name in the last line (here it's set to "IMAGE.RAW").
mov ah,0 mov al,13h int 10h ;The above 3 lines set video mode 13h MOV AH,3Dh ;open file using handle MOV AL,0 ;read only MOV DS,SEG Filename MOV DX,OFFSET Filename INT 21h ;AX is now the file handle MOV BX,AX ;File handle MOV AH,03Fh ;Read from file using handle MOV CX,64000 ;Number of bytes to read MOV DS,SEG MyBuffer MOV DX,OFFSET MyBuffer INT 21h ;MyBuffer should now contain the 64000 bytes of the image file MOV CX,64000 ;Number of bytes to process MOV BX,0 ;Start from pixel 0 MOV SI,OFFSET MyBuffer LOOPER: PUSH CS POP DS ;Sets DS to be the current program segment CLD ;Clear direction flag so that SI gets incremented by LODSB LODSB ;Load the byte at DS:SI into AL and increment SI MOV DS,0A000h ;VGA memory-mapped video segment MOV [BX],AL ;Put the data onto the screen in the form of a pixel INC BX ;Move to the next pixel DEC CX CMP CX,0 JNE LOOPER mov ax,4C00h ;terminate program int 21h MyBuffer DB 64000 DUP (?) Filename DB 'IMAGE.RAW', 0
That was fun, but now let's move away from the mouse. Here's a utility to reset dates on files to January 1, 1980 at 12:00:00 AM (which is the earliest date DOS supports on files). Very handy for when you want to forget about when something happened.
;The following 5 lines just clear the carry flag (CF), which is important, ;because clearing the carry flag makes INT 21,3D return the file handle ;instead of an error code. PUSHF ;transfer 16-bit flag register onto stack POP AX ;AX now contains the flag register contents AND AX,0FFFEh ;sets rightmost bit (carry flag) to 0 PUSH AX POPF MOV AH,3Dh ;open file using handle MOV AL,1 ;write only MOV DS,SEG filename MOV DX,OFFSET filename INT 21h ;AX should now contain the file handle. MOV BX,AX ;transfer file handle to BX where it belongs MOV AH,57h ;set file date/time using handle MOV AL,1 ;set (not read) date and time MOV CX,0 ;12:00:00 AM MOV DX,21h ;January 1, 1980 (0000000000100001) INT 21h MOV AH,3Eh ;close file using handle ;BX already contains the appropriate file handle from the previous operation, ;so we don't need to re-set it. INT 21h mov ax,4C00h ;terminate program int 21h ;Edit the line below as appropriate to reflect the actual filename of the ;file whose date you want to reset. filename DB 'newfile.txt', 00
Here's a TSR shell. It provides the framework for what you need to make a program that stays resident in memory (now you just need to put the function that the memory-resident program should perform at the indicated point):
;HOW TO MAKE A TSR: ;STEP 1. Get the interrupt vector of INT 8. CLI ;turn off hardware interrupts MOV AL,8h ;specifies INT 8 MOV AH,35h ;get interrupt vector INT 21h ;STEP 2. Make INT C8 behave like INT 8. ;(INT C8 isn't used by anything anyway.) MOV DX,BX PUSH ES POP DS ;the above three lines set DS:DX to whatever ES:BX was MOV AL,0C8h MOV AH,25h ;set INT C8's vector (makes INT C8 behave like INT 8) INT 21h ;STEP 3. Set INT 8's vector to your own interrupt function. MOV DX,OFFSET TSRINT PUSH CS ;push the Code Segment... POP DS ;...to make DS equal this routine's segment MOV AL,8 MOV AH,25h ;set INT 8 to behave like your new interrupt function INT 21h ;STEP 4. Do INT 21,31 and you're done. MOV AL,0 ;exit code (for batch files) MOV DX,0FFh ;how much memory is reserved for this TSR MOV AH,31h ;make it into a TSR now INT 21h TSRINT: ;THIS IS THE ACTUAL CODE OF THE INTERRUPT ITSELF: CLI ;disable interrupts PUSHA ;save current registers status for later ;YOUR INTERRUPT ROUTINE HERE! This is where the code of the actual interrupt ;function (the process which the TSR should keep repeating while it's in ;memory) should go. ;A reminder: PUSHA is used above to save the registers so they can be easily ;restored to their former states with POPA later. However, PUSHA and POPA ;only affect (E)AX, (E)CX, (E)DX, (E)BX, (E)SP, (E)BP, (E)SI, and (E)DI. They ;do NOT affect any of the segment registers (CS, DS, SS, or ES), meaning that ;if your routine uses these registers, you will also need to save them by ;PUSHing them before this interrupt routine, and POPing them after it's over. POPA ;restore old register status (so everything's the same as before) INT 0C8h ;do the old INT 8 as well, otherwise what should be INT 8 never gets done PUSH AX MOV AL,20h OUT 20h,AL ;this sends an End-Of-Interrupt signal to the 8259 int controller POP AX STI ;re-enable interrupts (opposite of CLI) IRET ;interrupt return (signifies that the interrupt is over)
Although the TSR shell above is a good starting point, it doesn't actually do anything. The program below is probably one of the more amusing little assembler programs that I've made, because it's a TSR that does something that's constantly apparent: It switches to graphics mode 13h, then puts a single yellow pixel on the screen. What's perhaps even more interesting is loading this program and then experimenting with other programs which use different video modes; in some video modes, this TSR causes several pixels in a horizontal row to become corrupted (since some graphics modes don't use the simple one-pixel-to-one-byte memory mapping that mode 13h uses).
;A TSR which actually does something! This little program will use its ;TSR functionality to create an annoying pixel on the screen that WILL ;NOT go away no matter how much you try to scroll it off or CLS it ;away. (Well, switching to a non-graphics video mode will clear it up.) mov ah,00h mov al,13h int 10h ;The above 3 lines put you in video mode 13 ;STEP 1. Get the interrupt vector of INT 8. CLI ;turn off hardware interrupts MOV AL,8h ;specifies INT 8 MOV AH,35h ;get interrupt vector INT 21h ;STEP 2. Make INT C8 behave like INT 8. MOV DX,BX PUSH ES POP DS ;the above three lines set DS:DX to whatever ES:BX was MOV AL,0C8h MOV AH,25h ;set INT C8's vector (makes INT C8 behave like INT 8) INT 21h ;STEP 3. Set INT 8's vector to your own interrupt function. MOV DX,OFFSET TSRINT PUSH CS ;push the Code Segment... POP DS ;...to make DS equal this routine's segment MOV AL,8 MOV AH,25h ;set INT 8 to behave like your new interrupt function INT 21h ;STEP 4. Do INT 21,31 and you're done. MOV AL,0 ;exit code (for batch files) MOV DX,0FFh ;how much memory is reserved for this TSR MOV AH,31h ;make it into a TSR now INT 21h TSRINT: CLI ;disable interrupts PUSHA ;save current registers status for later PUSH DS ;save DS too, since we use it ;PIXEL-DRAWING ROUTINE FOLLOWS! MOV DS,0A000h MOV BX,35100 ;location of pixel on screen. Adjust to taste. MOV AX,44h ;44h is yellow MOV [BX],AX POP DS ;pop DS... POPA ;...and everything else, so it's just like it used to be. INT 0C8h ;do the old INT 8 as well PUSH AX MOV AL,20h OUT 20h,AL ;send an End-Of-Interrupt signal to the 8259 int controller POP AX STI ;re-enable interrupts IRET ;interrupt return (signifies that the interrupt is over)
This programs outputs a single byte which indicates the current video mode. This can be useful since I actually do not know any other way of figuring out what video mode you're currently in; you could also create a TSR which does something like this on command to figure out what video mode a particular program is using as it's running
;Show current video mode. This programs outputs one character, which ;is an extended ASCII character corresponding to the video mode. The ;character's hex value is the current video mode. MOV DS,40h MOV SI,49h ;DS:SI now points to 40:49, which is the location of the current video ;mode MOV DL,[SI] ;DL = character to output MOV AH,2 INT 21h ;print the character mov ax,4C00h ;terminate program int 21h
A standard PC BIOS stores its release date as an 8-byte string in memory location FFFF:5. This program prints that string.
;Print from ROM, starting at FFFF:5 for 8 characters ;(This shows the date of the ROM BIOS) MOV DS,0FFFFh MOV SI,5 MOV CX,8 mainloop: MOV DL,[SI] ;DL = character to output MOV AH,2 INT 21h ;print the character INC SI ;move SI to the next byte LOOPNZ mainloop ;this loops 8 times, because CX was set to 8 mov ax,4C00h ;terminate program int 21h
By default, DOS prompts are text-only, meaning you can't draw graphics on the screen with them. However, you can change your graphics mode with INT 10,0. This program starts off by switching to the famous mode 13 (320x200 256-color VGA, a very popular video mode for amateur DOS games) and then draws a single pixel to demonstrate the graphical capabilities of this mode.
;Draws a single yellow pixel in the upper-left. ;Writes directly to video memory which begins at A000:0 mov ah,00h mov al,13h int 10h ;The above three lines just switch to 320x200 256-color VGA. mov ds,40960 ;a000h = 40960 decimal mov ax, 44h ;44h is yellow! ;) mov bx,0000 mov [bx],ax ;This defaults to DS:[whatever], and since whatever here is bx, it moves ;AX into DS:BX!!! mov ax,4C00h ;terminate program int 21h
;By extension, draws a yellow line in the upper-left. ;A good example of how to efficiently use INC, CMP, ;and a conditional jump for repetitive tasks. mov ah,00h mov al,13h int 10h ;The above three lines just switch to 320x200 256-color VGA. mov ds,40960 ;a000h = 40960 decimal mov ax, 44h ;44h is yellow! ;) mov bx,0000 START: mov [bx],ax inc bx cmp bx,20 JL START ;This waits until BX reaches 20, then exits! mov ax,4C00h ;terminate program int 21h
;prints all possible 256 colors in a row, in order from 1 to FF, ;along the top of the screen. mov ah,00h mov al,13h int 10h ;The above three lines just switch to 320x200 256-color VGA. mov ds,40960 ;a000h = 40960 decimal mov bx,0000 mov ax, 00h ;Start from color number 0, since 0 is also a valid color. MAINLOOP: mov [bx],ax inc bx inc ax cmp ax,0FFh JNG MAINLOOP mov ax,4C00h ;terminate program int 21h
;Prints all possible 256 colors in a row, 5 times (thus making 5 rows). ; ;A single pixel is a little hard to discern, so I thought it would be useful ;to make the color bands larger than 1 pixel. ;However, 320x200 VGA has only 320 pixels horizontally, so there's no ;possibility to make 256 colors larger horizontally and still keep them all ;in one row. Thus, I went for the vertical option. mov ah,00h mov al,13h int 10h ;The above three lines just switch to 320x200 256-color VGA. mov ds,40960 ;a000h = 40960 decimal mov bx,0000 mov ax, 00h ;Start from color number 0, since 0 is also a valid color. MAINLOOP: mov [bx],ax add bx,320 ;Above we use the decimal value of the horizontal resolution to go down a row mov [bx],ax add bx,320 mov [bx],ax add bx,320 mov [bx],ax add bx,320 mov [bx],ax sub bx,1280 ;And now subtract 4 times the horizontal res to go up 4 rows inc bx inc ax cmp ax,0FFh JNG MAINLOOP mov ax,4C00h ;terminate program int 21h
This program turns on Caps Lock, Num Lock, and Scroll Lock.
;Move F0h to 40:17 MOV DS,40h MOV BX,17h MOV AX,0F0h MOV [BX], AX mov ax,4C00h ;terminate program int 21h
;Move 80h to 40:17 MOV DS,40h MOV BX,17h MOV AX,80h MOV [BX], AX mov ax,4C00h ;terminate program int 21h
;Keep scanning the keyboard input port (port 60h). This watches for ;whatever keys are pressed. If the key is the A key (which has a scan ;code of 1Eh), the program exits. This demonstrates how to directly view ;what keys are being pressed. looper: IN AL,60h ;get most recent scan code CMP AL,1Eh ;is it a? JE ender JMP looper ender: MOV AX,4C00h ;terminate program INT 21h
Apparently, memory location 40:1E is supposed to contain a "circular queue keyboard buffer." I never did figure out exactly how this works or what format the contents of this location are supposed to take, but it is interesting to run this program and watch a few historical keystrokes show up.
;40:1E is the location of the 32-byte circular queue keyboard buffer MOV DS,0040h MOV SI,001Eh MOV BH,0001h MOV AH,0Eh MOV CX,32h MainLoop: MOV AL,[SI] INT 10h INC SI LOOP MainLoop mov ax,4C00h ;terminate program int 21h
At some point in time, you may wish to make a program which makes use of command-line parameters. This program lets you experiment with parameters, as it simply prints out the parameters you give it at runtime.
;Show the command-line parameters given to the program MOV AH,03h INT 10h ;This will return: Cursor row: DH, Cursor column: DL MOV SI,80h CMP B[SI],00h JE Ender ;The above three lines quickly check if *any* parameters were given. ;If not, an abrupt quit is made. ;(The number of bytes in the parameters is stored in 80h) MOV SI,82h ;From 82h on is where the command-line parameters are MOV CX,1h ;For INT 10,A's sake MainLoop: MOV AH,0Ah ;Tells INT 10 to print a character MOV AL,[SI] ;Loads a character into AL CMP AL,0Dh ;If the character is a carriage return, end JE Ender ;(Because that's the end of the parameters) INT 10h ;Prints character in AL INC SI ;Move to next character INC DL MOV AH,02h INT 10h ;The above three lines move the column one to the right JMP MainLoop ;keep going until it hits a carriage return Ender: mov al,0 ;Amusingly, it causes problems for INT 21,4C if AL has some ;screwy value, so we reset it just before finishing up. mov ax,4C00h ;terminate program int 21h
;Turns on the internal speaker. MOV AL,0B6h OUT 43h,AL ;Most sources say you're supposed to send B6 hex to port 43 before ;turning on the speaker. While it seems to work fine even if you don't, ;you might as well. IN AL,61h ;Sets AL to port 61's current value OR AL,11b ;Sets last two bytes of AL to 1 OUT 61h,AL ;This makes 61 exactly the way it was, but with the two-byte change! ;That will turn on the speaker! MOV AX,4C00h ;terminate program INT 21h
;Turns off the internal speaker. IN AL,61h ;Sets AL to port 61's current value XOR AL,11b ;Sets last two bytes of AL to 0 OUT 61h,AL ;This makes 61 exactly the way it was, but with the two-byte change! ;That will turn off the speaker! MOV AX,4C00h ;terminate program INT 21h
;Changes the tone of the computer's speaker. (It must have ;already been turned on before.) MOV AH,11 MOV AL,01011101xB OUT 42h,AL MOV AL,AH OUT 42h,AL mov ax,4C00h ;terminate program int 21h
Gunship 2000's excellent add-on, Islands And Ice, only lets you use its mission editor after you have attained a rank of at least 2nd Lieutenant (2LT). This is, frankly, outrageous, since you should be able to make your own missions at any time. This program is a simple way of dealing with this problem. It edits Gunship 2000's roster file, ROSTER.DAT, so that the first pilot in the roster is made a 2nd Lieutenant. If you want to change a pilot other than the first one in the roster, you can just edit the number of bytes which the program moves forward in the file. Currently it is set to 54h.
;Set byte 54h of file to 5. This will make you a 2nd Lieutenant (2LT), ;which in turn will let you use the mission builder. MOV AH,3Dh ;open file using handle MOV AL,01 ;write only MOV DS,SEG FILENAME MOV DX,OFFSET FILENAME INT 21h ;AX is now the file handle MOV BX,AX ;moves AX (the file handle) into BX MOV AH,42h ;move file pointer MOV AL,0 MOV CX,0 ;high order word of number of bytes to move MOV DX,54h ;low order word of number of bytes to move ;Move 54h bytes, since that's where the byte we want is INT 21h ;BX is still the file handle MOV AH,40h ;write to file using handle MOV CX,1 ;number of bytes to write MOV DS,SEG BUFFER MOV DX,OFFSET BUFFER INT 21h mov ax,4C00h ;terminate program int 21h FILENAME db 'ROSTER.DAT', 00 ;The 00 on the end is to terminate the filename with a 00 character, ;making it an ASCIIZ string. BUFFER db 5 ;BUFFER is what will actually get written to the file.
Well, that's all for now. If you have any advice or comments, please e-mail me. Thanks.