For more projects, click here
A 16-bits x86 DOS Assembly library that provides many useful functions for developing programs. It has both VGA grapics functions as well as general purpose utilities. The main purpose of this library was to be able to implement simple DOS games (in Assembly) using VGA (320x200, 256 colors) display.
Note The library was developed and tested using TASM 4.1 and DosBox
The library provides the following graphics capabilities:
- General purpose graphics utilities
- Manipulate color palettes
- Draw lines
- Draw rectangles (outline and filled)
- Draw closed polygons (outline only)
- Draw circles (outline and filled)
- Draw Bitmaps
- Images & screen manipulations
- Draw sprites and animation
- Support for Double buffering
The library contains an utility package with many useful macros and procedures such as:
- File management
- Keyboard management, including ISR support
- Mouse input
- Several Math and randomizer functions
- Memory management (allocation and deallocation on the heap)
- String operations
- Printing to the screen
- Support for playing sound
- Time functions for delays
To use the libraries, include the files in your code as follows:
CODESEG
include "UtilLib.inc"
include "GrLib.inc"
Note that UtilLib.inc MUST come before GrLib.inc
Here is a sample program that draws a line and waits for a key press, using double buffering:
.486
IDEAL
MODEL small
STACK 256
DATASEG
CODESEG
include "UtilLib.inc"
include "GrLib.inc"
start:
mov ax, @data
mov ds,ax
; Init library with double buffering flag on
mov ax, TRUE
ut_init_lib ax
; Allocate double buffering memory space
call AllocateDblBuffer
; set display to VGA mode
gr_set_video_mode_vga
; Set initial pen color
gr_set_color GR_COLOR_GREEN
; your code here - we will draw a line...
grm_DrawLine 10, 10, 200, 200
exit:
call WaitForKeypress
call ReleaseDblBuffer
gr_set_video_mode_txt
return 0
END start
Build the program using
tasm /zi main.asm
tlink /v main
The library provides many Procedures and Macros that you can use directly in your code. To make life easier (and have the code look more like a high level language), a wrapper MACRO was provided to many of the PROCs. Both options yield the same outcome.
For example, you can draw a line using the PROC
push x1
push y1
push x2
push y2
call GR_DrawLine
or using a wrapper MACRO
grm_DrawLine x1, y1, x2, y2
Make sure you pass the arguments in the right order. All procedures preserve register values, unless stated otherwise. Macros (not including wrapper macros), on the other hand, may alter register values. Check out their documentation for details.
At the beginning of your code, you need to initialize the library by calling:
CODESEG
; Init library
mov ax, FALSE
ut_init_lib ax
The flag shoule be set to TRUE if you intend to dynamically allocate memory in your program. See memory management
Note that TRUE and FALSE are defined by the library as 1 and 0.
The Tests folder includes a testing file that demonstrates the use of various parts of the library. the test program itself can be found at the root and is called main.asm
A simple TicTac game that uses many of the library's features, was added to the library. You can compile and run the game using:
tasm /zi tictac.asm
tlink /v tictac
The game itself can be found under Tests/tic folder and the main file is tictac.asm
The most basic macros handle drawing a single pixel on the screen, using direct memory access (not interrupt).
gr_set_pixel x, y, color - draws a pixel in the given (x,y) coordinates with the given color
gr_set_pixel_xy x,y - draws a pixel in the given (x,y) coordinates with the set color
Note that x, y and color cannot use the following registers: ax, bx, di, dx
This macro takes into account double buffering and will draw to the buffer (if set) instead of the video display. See Double Buffering for details.
It is highly recommended to use this macro whenever you want to draw to the VGA screen.
There are 3 ways to clear the screen, or a portion of the screen
clear_screen_vga - macro that very efficiently clears the entire screen (VGA mode)
GR_ClearRect - procedure that clears a rectangle on the screen (VGA mode)
clear_screen_txt - macro that very efficiently clears the entire screen (TXT mode)
Drawing a line using a PROC:
push 50 ; x1
push 60 ; y1
push 120 ; x2
push 180 ; y2
call GR_DrawLine
or using a MACRO:
grm_DrawLine 50, 60, 120, 180
You can either draw a rectangle outline or a filled one.
Outline using a PROC:
push 50 ; x (top)
push 60 ; y (top)
push 120 ; width
push 180 ; height
call GR_DrawRect
or using a MACRO:
grm_DrawRect 50, 60, 120, 180
Filled using a PROC:
push 50 ; x (top)
push 60 ; y (top)
push 120 ; width
push 180 ; height
call GR_FillRect
or using a MACRO:
grm_FillRect 50, 60, 120, 180
You can either draw a circle outline or a filled one.
Outline using a PROC:
push 150 ; X center
push 100 ; Y center
push 40 ; Radius
call GR_DrawCircle
or using a MACRO:
grm_DrawCircle 150, 100, 40
Filled using a PROC:
push 150 ; X center
push 100 ; Y center
push 40 ; Radius
call GR_FillCircle
or using a MACRO:
grm_FillCircle 150, 100, 40
To draw a polygon, you need to define an array and pass its pointer along with the number of points in the array. The array should contain points (x and y) as follows:
DATASEG
_poly dw 5,30,100,50,200,100
This is an array with the following points:
- _poly[0] = (5,30)
- _poly[1] = (100,50)
- _poly[2] = (200,100)
Using a PROC:
push 3 ; there are 3 points
push offset _poly ; pointer to _poly array
call GR_DrawPolygon
or using a MACRO:
mov ax, offset _poly
grm_DrawPolygon 3, ax
The library support only 8-bit bitmaps in v3. To load a bitmap, you need to define the following variable:
_bmp_file db "asset\\b.bmp",0
_bmp db BMP_STRUCT_SIZE dup(0)
This will allocate the memory for the BMP headers (struct). Memory allocation for the pixels' data will be allocated in RAM when loading the data.
You must use "call FreeProgramMem" before trying to load images or you will get an out of memory situation and the allocation will fail. Note that you can implicitly call it by passing TRUE to ut_init_lib:
mov ax, TRUE
ut_init_lib TRUE
To load a BMP image, call:
mov dx, offset _bmp_file
mov cx, offset _bmp
push dx ; path offset
push ds ; path segment
push cx ; bitmap struct offset
push ds ; bitmap struct segment
call LoadBMPImage
or using a MACRO
mov dx, offset _bmp_file
mov cx, offset _bmp
grm_LoadBMPImage dx, ds, cx, ds
After loading the bitmap, you can draw it on the screen
mov cx, offset _bmp
push cx ; bitmap struct offset
push ds ; bitmap struct segment
push 000Ah ; xTop = 10
push 000Bh ; yTop = 11
call DisplayBMP
; instead, you can tile the image on the screen using
push cx
push ds
call TileBmp
or using MACROS:
mov cx, offset _bmp
grm_DisplayBMP cx, ds, 000Ah, 000Bh
; or
grm_TileBmp cx, ds
You must free the bitmap memory (when it is not needed anymore) by calling
mov cx, offset _bmp
push cx ; bitmap struct offset
push ds ; bitmap struct segment
call FreeBmp
or using MACROS:
grm_FreeBmp cx, ds
The library provides methods for extracting color palettes of BMP files and storing them in a new file (binary) as well as load a palette file into a buffer (possibly the palette of a BMP structure).
Here is an example of saving a palette to a file:
mov dx, offset _bmp_file
mov ax, offset _bmp
grm_LoadBMPImage dx, [_dss], ax, [_dss]
mov ax, offset _bmp
mov bx, offset _paletteFile
grm_SavePalette ax, [_dss], bx
and loading it to a buffer:
push offset _paletteFile
push ds
push offset _palette
push ds
call LoadPalette
The library supports sprites, which is an image with multiple frames. Sprites are a great way to create animation or to hold multiple variants of an image. See an example.
Since sprites are standard BMP files, you load them normally and then can display any frame you select.
DATASEG
_sprite_w equ 30
_sprite_frames equ 6
_sprite_file db "asset\\sprite1.bmp",0
_sprite db BMP_STRUCT_SIZE dup(0)
CODESEG
mov dx, offset _sprite_file
mov ax, offset _sprite
push dx ; path address
push ds ; path segment
push ax ; struct address
push ds ; struct segment
call LoadBMPImage
push 0 ; frame index
push ax ; BMP struct address
push ds ; BMP struct segment
push 0064h ; x coordinate
push 0064h ; y coordinate
push _sprite_w ; width of a single frame
push _sprite_frames ; number of frames in BMP
call PlaySpriteInPlace
or using macros:
CODESEG
mov dx, offset _sprite_file
mov ax, offset _sprite
grm_LoadBMPImage dx, ds, ax, ds
grm_PlaySpriteInPlace 0, ax, ds, 0064h, 0064h, _sprite_w, _sprite_frames
Take a look at TestMySprite function as an example for playing animation using sprites.
Here is an example of a sprite image
The library provide functions for copying a portion of the screen to a buffer and visa versa (from a buffer to the screen).
grm_SaveScreen memAddress, memSeg, x, y, w, h
grm_WriteScreen memAddress, memSeg, x, y, w, h
The library includes other utilities that help developing assembly programs.
store_sp_bp - macro that stores and sets BP value. Called at the beginning of PROC
restore_sp_bp - macro that restores BP, SP values. Called at the end of PROC
return - macro for returning control to DOS with a code
cmpv - macro that compare two memory variables
movv - moves a WORD from one memory to another via the stack
The library stores the original data segment (DS register) using a global variable _dss that you can access at any time if you need to restore its value.
push [_dss]
pop ds
The library provides 3 different ways to delay your program:
If you want your program to halt for a given number of seconds call the Sleep procedure
push 3 ; sleeps for 3 seconds
call Sleep
or using MACROS:
utm_Sleep 3
Delay program execution by X number of milliseconds. Actually, the argument is the number of 1/18 of a second
push 3 ; Delay for 3 * 1/18 of a sec
call Delay
Or:
utm_Delay 3
This function delays execution for given number of microseconds which are specified in 2 variables, one for the high order and one for the low order. For example, a 1 second delay (1 * 1,000,000 msec) is 000F 4240 and therfore the values will be
push 000Fh
push 4240h
call DelayMS
Or
utm_DelayMS 000Fh, 4240h
Another example, a 2 seconds delay is equal to (21,000,000) 001E 8480 and a 1 millisecond is (11000) 0000 03EB
Most keyboard functions are accessible via interrupts and the library focuses on some less trivial parts of handling the keyboard. A list of keyboard scan codes can be found at keymap.asm
Setting keyboard yypematic rate to defalt (repeat delay and rate)
call SetKeyboardRateDefault
Hold program execution until a key is pressed
call WaitForKeypress
Getting keyboard status
call GetKeyboardStatus
Returns: ZF = 0 if a _Key pressed (even Ctrl-Break) AX = 0 if no scan code is available AH = scan code AL = ASCII character or zero if special function _Key
Taking the pressed key out of the keyboard buffer
call ConsumeKey
call GetKeyboardFlags
Returns AL:
|7|6|5|4|3|2|1|0| AL or BIOS Data Area 40:17
| | | | | | | ---- right shift key depressed | | | | | |
----- left shift key depressed
| | | | | ------ CTRL key depressed | | | |
------- ALT key depressed
| | | -------- scroll-lock is active | |
--------- num-lock is active
| ---------- caps-lock is active
----------- insert is active
You can replace the default keyboard interrupt with your own by calling this procedure
push isr_address
call InstallKeyboardInterrupt
For example:
lea dx,[my_interr]
push dx
call InstallKeyboardInterrupt
You must restore the interrupt at the end of the program by calling
call RestoreKeyboardInterrupt
The library contains 2 sample implementations of a keyboard ISR.
- KeyboardSampleISR - implements a FIFO buffer and allows retrieving keys using getcISR. Note that this is not a complete implementation but merely an example. For instance, it does not convert the scancode to ASCII and does not treat SHIFT, CTRL and ALT combinations.
- KeyboardISREvents - a simple implementation that illustrates how to call the default keyboard handler
See TestKeyboardISR and TestSimpleISR
Most mouse functions are accessible via interrupts and the library focuses on some less trivial parts of handling the mouse.
Use these macros to show / hide the mouse
ShowMouse
HideMouse
Mouse position and button status can be retrieved by
GetMouseStatus
On return: CX = horizontal (X) position (0..639) DX = vertical (Y) position (0..199) BX = button status:
|F-8|7|6|5|4|3|2|1|0| Button Status
| | | | | | | | `---- left button (1 = pressed)
| | | | | | | `----- right button (1 = pressed)
`------------------- unused
After getting mouse coordinates from GetMouseStatus, you can translate them to VGA coordinates by calling
TranslateMouseCoords
mov cx, 3 ; horizontal
mov dx, 5 ; vertical
SetMousePosition
You can replace the default mouse interrupt with your own by calling this procedure
push ISR address
push ISR segment
push mask
call InstallMouseInterrupt
You must restore the interrupt at the end of the program by calling
call UninstallMouseInterrupt
The library provide a set of functions to access and manipulate files. The library was designed to handle a single file at any time using the global variables
_fHandle - stores the handle of the openned file
_fErr - stores the error value
push address_of_file_name
push segment_of_file_name
call fopen
or using MACROS:
utm_fopen address_of_file_name, segment_of_file_name
push address_of_file_name
push segment_of_file_name
call fnew
or using MACROS:
utm_fnew address_of_file_name, segment_of_file_name
call fclose
or using MACROS:
utm_fclose
push address_of_file_name
push segment_of_file_name
call fsize
or using MACROS:
utm_fsize address_of_file_name, segment_of_file_name
push length
push address_of_buffer
push segment_of_buffer
call fread
or using MACROS:
utm_fread address_of_buffer, segment_of_buffer
push length
push address_of_buffer
push segment_of_buffer
call fwrite
or using MACROS:
utm_fwrite address_of_buffer, segment_of_buffer
push address_of_file_name
push segment_of_file_name
call fdelete
or using MACROS:
utm_fdelete address_of_file_name, segment_of_file_name
push attribute
push address_of_file_name
push segment_of_file_name
call fchangeAttr
or using MACROS:
utm_fchangeAttr attribute, address_of_file_name, segment_of_file_name
You can do a fseek in a file very similar to the C function
grm_fseek SEEK_SET, 0, 40
Or:
push SEEK_SET
push 0
push 40
call fseek
The first parameter can be SEEK_CUR, SEEK_SET or SEEK_END. The second parameter is the high order of the offset and the third is the low order.
The library provides a few directory services, including:
mkdir OR utm_mkdir - create a directory
rmdir OR utm_rmdir - delete a directory
chdir OR utm_chdir - change a directory
The library provide some basic math related functions and macros
Before generating a random number, you need to initialize the number generator by calling
call RandomSeed
and then you can use
call RandomWord
or
call RandomByte
to get a random number in AX or AL
You can get the absoilute value of a number
gr_absolute number
The library allows managing (allocating, releasing) RAM memory. If you need to allocate dynamic memory, you must free unused memory at the beginning of your program by calling:
call FreeProgramMem
If you forget to call it, all memory allocations will fail on "out of memory".
Note that you can implicitly call it by passing TRUE to ut_init_lib:
mov ax, TRUE
ut_init_lib TRUE
Allocating a memory block is done by calling:
push size
call malloc
Note that the size is measured in Paragraphs and therefore need to be divided by 16 (bytes)
Return value: AX = segment address of allocated memory block (MCB + 1para) 0 on error BX = size in paras of the largest block of memory available 0 on error CF = 0 if successful = 1 if error Allocated memory is at AX:0000
Pass the segment address of the allocated memory block to release it
push segment
call mfree
The library maintains an internal list of up to 50 allocated blocks that can be freed by calling
call mfreeAll
Copies memory from one address to another
push from_address
push from_seg
push to_address
push to_seg
push length_in_bytes
call MemCpy
You can set a byte or word value to an entire memory block
mov ax, offset _blcok
push 10 ; length_in_bytes
push ds ; block_segment
push ax ; block_offset
push 0 ; value
call SetMemByte
and
mov ax, offset _blcok
push 5 ; length_in_words
push ds ; block_segment
push ax ; block_offset
push 0 ; value
call SetMemWord
You can make a beep with the following procedure. Note that the sound will continue until you stop it
push frequency
call Beep
utm_Sleep 2 ; wait 2 seconds
call StopBeep
Or with macros:
utm_Beep freq
utm_Sleep 2
utm_StopBeep
This part of the library supports writting text to the screen
call PrintDecimal - prints a value as a decimal number
call PrintChar - prints DL as a char
PrintCharNewLine - macro to print a char with new line
call PrintByte - prints DL (number between 0 and 15)
PrintByteNewLine - macro to print a byte with a new line
call PrintStr - prints a string. DS:DX pointer to string ending in "$"
PrintStrNewLine - macro to print a string with a new line
call PrintNewLine - prints a new line
call PrintSpace - prints a space char
call PrintHexByte - prints the LSB BYTE in HEX
call PrintHexByte - prints the WORD in HEX
call PrintCharVGA - prints a character on VGA display (DL: char, BL: color)
call PrintStrVGA - prints a string to the VGA screen
push x
push y
call SetCursorPosition
The library contains several basic string manipulations functions that accept null terminating string
Strlen - Calculates length of string ending with NULL
StrlenDollar - Calculates length of string ending with '$'
Strcpy - Copies string s2 into string s1
Strncpy - Copies given number of chars from string s2 into string s1.
Strcat - concatenate 2 strings
Strchr - Searches for the first occurrence of the character in the given string
Strcmp - Compares the string pointed to, by str1 to the string pointed to by str2.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- Some ideas were taken from https://github.com/itay-grudev/assembly-dos-gx
- README file created using Dillinger