Note on this tutorial

I wrote this tutorial many years ago (2001-2002). Because of the large number of links pointing to it I've left it up. However, I no longer support it. Please do not contact me asking for help with QBASIC, ASM, or other programming issues. Also, please do not contact me about minor errors in the text: the text of this tutorial is mostly unedited, and only the presentation and markup have been significantly updated (to valid XHTML 1.0 strict). If there are major errors which significantly interfere with readability, though, please feel free to bring them to my attention - Billy Wenge-Murphy
This tutorial is (c) 2001-2007 Billy Wenge-Murphy. All rights reserved. It may not be copied, reproduced, or redistributed in any form without permission. If you wish to share it, please link to it instead of reposting it.

3.5 - Special Segments

We know that all your variables, and all the code that makes up your final program reside in memory. Variables usually in the Data Segment and code, surprisingly enough, in the Code Segment [btw - just like the register DS points to the Data segment, CS points to the code segment. But, let's not worry about that right now]. Well, there are other various specific segments of memory, though they don't have registers associated with them. One of these segments of memory holds all the data for what's stored on the screen. Depending on whether you're using only text, or using graphics, the location may change around. For simplicity right now, we'll talk about text.

Our last program printed text on the screen using an interrupt. In all our previous programs, we didn't specify what screen mode we wanted to use. So by default, it uses a text screen mode. This means that the screen is set up then so you can only put text onto it.

So how do we specify the screen mode? There's an interrupt that'll change the screen mode for us. Take a look at this program:

.MODEL SMALL
.STACK 200H
.CODE
START:
Mov ax, 0003h
int 10h

mov ax, 4c00h
int 21h

END START

You may notice this is a bit different than how the previous programs used interrupts. Here, put a value only in ax. The change screen mode function is function 0 of interrupt 10. So, to use it, we must put 0 in ah. And this function requires that you put the screen mode you want in al. Since ax consists of ah and al, i just moved a value straight into ax. Now ah should contain 00, and al should contain 03. Therefore, we'll call the screen mode function, and change to screen mode 3. Screen mode 3 however, is the default screen mode, so this doesn't accomplish much.

Now that we're absolutely sure we're using the screen mode we want, we can write stuff to the segment where what's on screen is stored. This segment has an Absolute Address. This means that, unlike variables that may change around their address every single time you run a program, an absolute address is always in the same place.
The only catch in this case is that with the screen mode, it's a different absolute address for different screen modes. For the screen mode we're using, the segment is B800 [That's hexadecimal of course].

As an example, say we had run that previous program that prints "I'm a string" on the screen.
The letter "I" would be stored at B800:0000, or offset 0 in segment B800. Actually, it would be a number code for the letter I. Every letter on the keyboard, along with numerous other things, have a numeric code assigned to them. We saw this in an earlier example that put a smiley face on the screen - It's code was the number 1. Well, the code for the letter I is 73. Not to be confused with a lowercase i, which has code number 105. It would be difficult to remember everything in this code - all 256 of them - so just look at this chart:

ASCII chart ASCII chart

Anyway, the code for "I" would be at B800:0000 - 73 [49 hexadecimal]
The code for apostrophe would be at B800:0002 - 39 [27h]

But why isn't the code for apostrophe at B800:0001? It is only one byte long after all. And therefore being the second character on the screen it should be the second byte.
The answer is that text can have different colors. And after each byte containing a numeric code for a character, there's a byte with the numeric code of what color that character should be. Since by default the print string function of int 21 prints with the color grey, B800:0001 should have the number 7 stored at it - 7 is the numeric color code for grey.

Now let's put all this information to use in this next program:

.MODEL SMALL
.STACK 200H
.CODE
START:
mov ax, 0003h
int 10h

mov bx, 0b800h
mov es, bx

mov bx,0
mov ah, 1
mov es:[bx], ah

mov ax, 0100h
int 21h

mov ax, 4c00h
int 21h

END START

The first two lines are two change to the text screen mode. We covered this further up on this page.

From here, it gets a little complicated. We want to put text on the screen, so we want to put things into the segment B800. And the top left corner of the screen, which is essentially the 'beginning' of the screen, is stored at offset 0. We're gonna need to get a Segment Register to point to the segment we want. We're going to use ES, which is the 'extra segment'. I'm not sure exactly, but i don't think it's used for anything specific - i think it's just an extra segment register used to point to whatever segment you want, unlike DS and CS which point to your data and your code.
But since you can't move numbers directly into the segment, we must put it into a register first. So, we put it into bx with:

mov bx, 0b800h

For one reason or another, you have to put that first 0 on there. Rest assured, that does actually mean B800 - It sort of drops that first 0, but it's required.
After the segment's in bx, we put it into ES. Now it gets a little bit tricky:

mov es:[bx], ah

What exactly does this mean. Well, this is another way that we can move things around in memory. Firstly we know that a segment and it's offset are separated by a colon, so it must have something to do with a segment and offset. ES, since it's on the left side of the colon, is the segment. So instead of saying B800, we can actually put B800 in a register, and tell the computer to look at what's in the register to find what offset we want. Then so far we've deduced that we're trying to put a number at the segment represented by whatever is in es.

Since [bx] is after the colon, it must be the offset that we're moving to. This is similar to what we did with ES. But for registers that aren't segment registers, we must put them in brackets - [] - to specify that we want to use them as an offset. This is a "Mov" instruction, so if we just left bx without brackets, we'd be saying we actually want to put something in bx. So, two lines before this was this line:

mov bx,0

The offset of the upper-left corner of the screen is 0. And if bx is acting as our offset, we should make bx equal 0. So all in all, we now know what this command means:

mov es:[bx], ah

It means 'move' what's in ah to the segment and offset that es and bx point to. And ah contains 1 - this is the numeric code for the smiley face character of text.

These next 2 lines are also new:

mov ax, 0100h
int 21h

This interrupt waits for you to hit a key. That way you can have a chance to see what happens when you run the program.

4.1 - Loops & Line Labels

Line Labels are a very simple idea. When you use a label, you give a name to a specific part of the program.
As you'll see later, you can use this name to jump around in your program - In fact, it'll also come in useful right now.

What if in the last program you didn't want to print just 1 smiley face on the screen. Say you wanted to print 100. Well, it would be a very long program, because you would constantly have to change bx - you'd have to add 2 to it every time you wanted to print the smiley face in a different place. This isn't necessarily true. By using what's called a "loop" we can print those 100 smiley faces by only adding a couple of lines of code. Let's see what this new program would look like:


.MODEL MEDIUM
.STACK 200H
.CODE
START:
mov ax, 0003h
int 10h

mov bx, 0b800h
mov es, bx

mov bx, 0
mov ah, 1

mov cx, 100

startloop:
mov es:[bx], ah
add bx, 2
loop startloop

mov ax, 0100h
int 21h

mov ax, 4c00h
int 21h

END START

This new program makes use of the 2 new things we're learning here. Firstly, startloop: means that that line in the program is called startloop. Like a variable, you can call a label almost anything you want. Make sure that you're aware of the colon after it - this is what clarifies for the compiler that it's a label.

What a loop basically does is does a set of instructions over and over again. To make a loop we start with a label. This will be the start of the loop (BTW: A label doesn't always have to start a loop, it can just be a label for a part in your program. But in this case, it does start the loop). Then we put the instructions to be repeated on the lines after the label. When we've typed all the lines that should go in the loop, we need one more line to close the loop. This is the command LOOP.
Notice that here it says Loop startloop. We must tell where we want to loop back to. Since startloop is the beginning of the loop in this case, then we should use Loop startloop.
We probably don't want the loop going on forever, so there must be some way to specify how long the loop lasts - there is. CX is used as the loop counter. Before the start of the loop, you must put a number in cx. Then, every time your program runs across the command LOOP, it subtracts one from cx before looping. If cx is 0, then the loop ends.
That's really all there is to the loop command.

Now, just one more thing we added to this program. Inside the loop is this:

add bx, 2

This pretty much explains itself - it adds the number 2 to bx. Recall that bx will point the offset 0 at the beginning of the program. And this loop is going to be done 100 times. We don't want it to put the smiley face character at offset 0 100 times; By adding 2, bx points to the offset of the next character on the screen.

4.2 - Doing something useful: Graphics

Up until this point, we've used only the text mode for output. This is all well and fine for learning purposes but not particularly useful. So now it's time that we used one of the screen modes suited towards graphics. This is mode 13h. It has a resolution of 320x200x256. That means 320 pixels wide, 200 pixels tall, and 256 colors on screen at once. Though not great, it can do some pretty nice graphics. It's a start anyway. DOS has some interrupts for dealing with graphics, but there's no point in using them because drawing pixels to the screen is very easy. It's very much like the last section.

In the previous section, the screen started at offset 0 of segment B800. Likewise, the segment for mode 13 starts at offset 0 of segment A000. Also in the previous section, we potentially had to write 2 bytes per character. In this mode, each byte of data written to the screen will draw only one pixel [a dot]. So, all data is only a color - because, a dot always looks like a dot, there's nothing else to store but the color of the dot. Let's see an example program for drawing some pixels:

MODEL MEDIUM
.STACK 200H
.CODE
START:
mov ax, 0013h
int 10h

mov bx, 0A000h
mov es, bx

mov bx, 0
mov ah, 1

mov cx, 64000

startloop:
mov es:[bx], ah
inc bx
loop startloop

mov ax, 0100h
int 21h

mov ax, 4c00h
int 21h

END START

Surprised? It's almost the exact same as before but with minor changes for mode 13. Now we use

mov bx, 0A000h
mov es, bx

because the screen starts at segment A000. Also, the loop counter has been changed to

mov cx, 64000

That's because this program is intended to fill the whole screen with dots. Since there's 320x200 pixels, do the math: 320 * 200 = 64000 [note that * is used as a symbol for multiplication. FYI, when typing * usually denotes multiplication, / for division, and ^ for exponents: 2^3=8, and so on...].
Then, the loop itself is much the same. Move a byte to A000, add to bx to go to the next offset, loop again. Notice that

inc bx

is used in place of

add bx, 2

because we want to put a byte in every single offset, since every single offset corresponds to a pixel. Inc bx then, adds only one to bx. INC can be thought of as INCrememnt or even INCrease, if it helps.

add bx, 1

would have been valid here too, but i think inc is faster, and it's just good programming technique to do the more logical thing. Likewise, this would work:

inc bx
inc bx

in place of the add bx, 2 in the previous example, but why when you can just use add!?

Well, i know that that blue screen is ultra exciting. Let's try drawing the whole screen, but with all 256 colors at once to make it more interesting. Simply add this after inc bx:

inc ah

So, first time through the loop we draw a blue pixel. Then we move to the next pixel and draw one with color 2, the next with color 3, and so on. I'm fairly sure that when you use an inc for a register that's already at it's max value - hence, using inc when ah = 255 - that it loops back around to 0. This is how it turned out for me, anyway. You should see a colorful pattern on your screen when you run this.

4.3 - A faster way

Now we'll look at a quicker, cleaner way at filling the screen with pixels. We do this with the command STOSB. Stosb is used for exactly what we did in the last program - storing bytes at a location in memory. To use it, we must first set where to store the bytes. This is stored in ES:DI. Then it's just a matter of putting a value in al and calling STOSB.

START:
mov ax, 0013h
int 10h

mov bx, 0A000h
mov es, bx

xor di, di
xor al, al
mov cx, 64000

Startloop:
stosb
inc al
loop startloop

mov ax, 0100h
int 21h

mov ax, 4c00h
int 21h

END START

Well, same exact thing, but faster (i believe). It's not too noticeably faster, but when used over and over as part of a program it would pay off. This tutorial, as you can maybe tell, is leading towards the parts of ASM programming that will help you design games, which is mostly what I use it for, so it's the easiest for me to write about. Next, we'll get into drawing "sprites". A sprite is basically just a little image, like a person, enemy, ship, etc depending on what kind of game it's in. Our sprites will be stored in files and loaded in, so we'll need to load them in. This means learning how to open and read files.

4.4 - Graphics from file

Before we go onto to getting graphics from a file, let's try saving graphics in a file. Files aren't too hard to use. First we must open a file. Then, of course, we'll need the filename to do that. From there opening the file is just a matter of passing a few things to an interrupt. So to start, this should be in the DATA part of the program:

Filename db "spryte1.grh",0

Recall that db can be thought of as declare byte(s). Each character is a byte, and so is the number zero at the end. 0 is used as a terminator for the string. Much like printing text needs a $ at the end of the string, a filename must end in a 0 or 'null' character - It's therefore referred to as 'null terminated'. Notice that that line is NOT

Filename db "spryte1.grh0"

When the 0 is inside the quotes it becomes text. It's no longer a terminator because instead of being 00h in hex, it's 40h. Now it's part of the filename instead a terminator of the string.
For simplicity, I have a sprite made for our program to load onto the screen. Download it before moving on.

Our program so far should look like this:

.MODEL MEDIUM
.STACK 200H
.DATA
Filename db "spryte1.grh",0
.CODE

START:

mov ax, 0013h
int 10h

mov ax, @data
mov ds, ax

Next, let's open the file. First, add these two lines to the data part:

Filehandle dw ?
Filebuffer db 256 dup (?)

When we open a file, it'll give us a number called a 'handle'. This way, whenever we want to read, write, etc with the file, we just use the number associated with the open file instead of giving it the entire filename again. We can 'open' many files at once, meaning we have access to them and no other programs do. So, we technically could have many different handles, one for each file. For now though, we only need this one. Filebuffer is where the contents are stored. We have this variable in order to actually load the file, making it quicker and easier to look at it's contents, rather than reading the file every time we need data from it.

You may wonder though what 'dup' is. Dup can be thought of as DUPlicate. Since we want a 256 byte long chunk of memory, we would normally have to write:
Filebuffer db 0,0,0,0,0,...... and so on, 256 times. Well, dup says duplicate the byte (in this case we don't specify exactly what value, we just put a ? to say that it doesn't matter, and the assembler i think will reserve the space leaving whatever used to be there) 256 times. Notice that the 256 comes before DUP, and the value to DUP after it in parenthesis ().
So, we have a place to but the handle and data, so let's get to it and open the file. The interrupt for opening a file is again int 21h, and it takes a few parameters.
AH = 3Dh specifies that we want to open a file
AL = the mode to open it in. Read only, write only, or both read and write.
We'll make AL=0, meaning read only. We can't change it while it's open for read only, but that's okay because we only want to load it.
DS:DX = Seg and offset to string holding the filename, an idea we're familiar with already.
So, just a few simple lines to open it:

mov ax, 3d00h
mov dx, OFFSET filename
int 21h
mov filehandle, ax

And it 'returns' the file handle to ax, meaning that it puts it in a register after it's done. We don't get to pick the handle, it decides for us. So, we just move ax, which now contains the handle, into our variable filehandle. Since we'll obviously need to use ax many more times throughout our program, the handle can't stay there.
Next, it's just another interrupt to read from our newly opened file.
AH = 3Fh specifies we want to read from the file.
BX = Handle. We must put the handle here to tell which file to read from
CX = how many bytes to read. in this case, 256 (16 pixels across, 16 down, 1Bpp - byte per pixel)
DS:DX = seg + offset of place to load to.
So, just another little segment of code:

mov ax, 3f00h
mov bx, filehandle
mov cx, 256
mov dx, OFFSET filebuffer
int 21h

Now that it's loaded, we'll introduce you to something very similar to what we just covered. It's a command called MOVSB. It's like STOSB, but it MOVeS Bytes around in memory. So, we'll move from the 'buffer' to the screen. To use this, we give an address to move to, and one to move from. DS:SI points to source, or where to move from. ES:DI will point to the destination, or where to move to. So, we'll point DS:DI to Filebuffer, and ES:DI to the screen:

mov si, dx
mov ax, 0a000h
mov es, ax
xor di, di

The first line is just a little short cut, because DX still contains the offset of Filebuffer from before. Ds already points to the right segment, no need to change it. xor di,di points di to the first pixel of segment 0a000h, the screen by making it 0. This is also a shortcut to

mov di, 0

I know I haven't introduced the stack, PUSH, and POP, but just bear with it because they're necessary for this. Sometimes it's better to omit a few smaller details until later in order to move to the bigger stuff quicker. For now, just think of PUSH as saving a register temporarily, and POP as getting that value back. Things that are PUSHed go onto the 'stack', and they're 'POPped' off of there as well. I'll go into detail of this later...
Anyway, this bit of code is a bit tricky:


mov cx, 16
startloop:
push cx
mov cx, 16
rep movsb
add di, 304
pop cx
loop startloop

Our loop draws one line, so we start by setting the loop counter in cx to 16. Then inside the loop we need to use cx again as a different counter, but it's already the loop counter! Well, we use PUSH CX to save it on the stack. Now, we can get back what value it had before the end of the loop so the loop will work. We want to move 16 bytes (one line) from the buffer to the screen. We set CX again to 16, and use REP MOVSB. What is rep? It means, "repeat the next instruction however many times CX says to". Now this is a little tricky as well: Each time we do MOVSB, it moves a byte, and automatically increases DI and SI by one. So, after 16 times, DI has changed by 16. There's 320 pixels in a row, and we want to point it to the next row before we draw it, so let's do the math: 320 - 16 = 304. By adding 304 to di, we point it to the first pixel of the next row down.
The final command POPs CX back, making it have whatever value it had last time it was pushed. You'll notice though that it's POPped, then we loop and immediately PUSH it again! What good does this accomplish? Well, be sure to remember that when loop, CX is decreased by one. At the start of the loop then, it's pushed as 16, then we pop it, still 16, and loop. It's decreased before we loop, and pushed as 15, and so on and so on.
REP also decreases CX by one every time it REPeats the instruction, that's why we must store and retrieve CX - it needs to be used as two separate loop counters. If we didn't PUSH it, the first time through CX would be 0 at the end of the loop because REP brought it down to 0.
So, finally, we have just these lines to wait for a key allowing us to see the sprite, and then to exit:

finish:
mov ax, 0100h
int 21h
mov ax, 4c00h
int 21h
END START

And that's it! When you run it, you should see a little Mario sprite in the top left corner of the screen. However, there's something wrong with him. His colors aren't right. Well, that's another topic VERY VERY important to graphics called the 'Palette'. And that's what we'll discuss next.

Mascot: Billy's Weird Cat Thing