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.html

Also 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.

Output a hexadecimal number as human-readable text

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

A program which turns on your mouse cursor

;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.

Download it here

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.

Pause the computer until a mouse button is pressed

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.

"Hello, world."

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.

Create a textual mouse "button"

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$'

Check for the presence of a mouse driver

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.$'

Return errorlevel based on presence of mouse driver

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

Create a graphical mouse cursor

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

A red pixel which follows mouse movement

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

Use LODSB to load data from a memory buffer into the accumulator

This program contains a 200-byte sequence of pixel colors. When the program is run, this 200-byte data block is loaded into memory. The program then loads the data from this buffer one byte at a time through LODSB, then puts the byte value on the screen to create a pixel of that color.
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

Display a raw image file

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

Reset dates on files

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

A TSR shell

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)

Maintain a "permanent pixel" on the screen

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)

Indicate the current video mode

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

Print the date of the BIOS

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

Draw a pixel in a graphics mode

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

Draw a line in a graphics mode

;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

Draw a single pixel in each color available in video mode 13

;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

Draw five rows of pixels in each color available in video mode 13

;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

Turn on all keyboard "lock"s

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

Turn off all keyboard "lock"s

;Move 80h to 40:17

MOV DS,40h
MOV BX,17h
MOV AX,80h
MOV [BX], AX

mov ax,4C00h  ;terminate program
int 21h

Wait for a particular keyboard scancode

;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

Show the contents of the circular queue keyboard buffer

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

Show command-line parameters

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

Turn on the PC internal speaker

;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

Turn off the PC internal speaker

;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

Change the pitch of the PC internal speaker

;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

Turn the first pilot in a Gunship 2000 roster into a 2nd Lieutenant

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.

Back to the main page