Pong from scratch or my first steps in assembly
#Coding#OsDevPong from scratch
I really did write pong from scratch using only the functions provided by the bios. You could write the game onto an USB stick, boot from it and then play it.
My first assembly projects
At the end of the year 2015 I started learning assembly for fun. I still remember how I studied assembly and the corresponding C code during my Latin lessons at high school.
I started with translating the programming exercises from C PROGRAMMING A Modern Approach By K. N. King into assembly and some own projects, such as a little demonstration for a return address exploit or a program that prints the Finnish translation of the numbers from 0 to 99. At this point I still used the functions from the C standard library such as printf or scanf.
All the projects can be found in the repository kalehmann/x86_64_assembly_stuff on GitLab.
Going deeper
Soon after building my first simple programs for Linux, I started getting interested in writing software without using any of the functions from the C standard library or even the Linux syscall.
Of course this brings some new challenges. The bios only loads the first 512 bytes into memory. Additionally you need to do some initialization and the last two bytes should contain a boot signature, which leaves even less space for the real program.
You could either use some functions provided by the bios to load more of your program or stick to this. I haven chosen the latter.
Writing bootcode
Some people would call such software a bootloader. However, since my program does not load any additional stuff, I prefer the term bootcode.
It all started simple with things like printing “Hello, world!” on the screen using the bios functions provided by the interrupt 0x10. A great resource of information about the interrupt codes of the bios is the famous interrupt list of Ralph Brown.
Such a program looks like this:
This file can be compiled using
nasm -f bin -o bootcode.bin bootloader.asm
and then tested with qemu using
qemu-system-i386 -fda bootloader.bin
One the I decide to write a game. With all those limits I face while writing bootcode, my decision felt to pong, because it is really simple.
Implementing the game pong
The design of the code for the game is pretty simple. First comes the initialization the stack and the setup of the video mode.
In real mode the stack has its its own register, the stack register ss. Since the stack grows downwards, the stack pointer sp and the base pointer bp have to be initialize with a high value, that the can shrink to zero.
Setting up the stack may look like this:
The video mode is set using the interrupt 0x10,0 provided by the bios. 0x10,0 means calling the 16th interrupt of the bios with the ah register set to zero. This triggers the function for setting the video mode. The actual mode you want to set is passed in the al register. The code for setting the video mode looks like this:
There are several modes available, I choose the mode 0x13. That mode basically means 320x200 pixels and 256 colors. I think that hits the nail for the game.
After this follows the main loop of the game. There are several things that needs to be done in main loop:
- sleeping a short time to limit the speed of the game
- updating the position of the ball and the score when the ball hits the top or the bottom
- handling the user input and updating the positions of the two players
- redrawing the screen
Sleeping
Fortunately the bios provides for sleeping a short time, the interrupt 0x10,0x86 - wait for a given period. The period to wait is passed in dx:cx in microseconds.
For example if the game should run at 30 frames per second, the waiting period should be a 30th of a second - 33333 microseconds. The code would look like this:
Handling user input
Of course the bios also provides functionality to get keyboard input. The interrupt 0x16,1 can be used to check if there is a keystroke in the keyboard buffer and 0x16,0 gets that keystroke and removes it from the keyboard buffer.
The interrupt 0x16,1 sets the zero flag if no keystroke is available. When a keystroke is available, the interrupt 0x16,0 returns the bios scancode in ah and the ascii character in al.
This code checks if the left arrow key has been pressed:
Draw everything on the screen
The screen can be set all black using the set video mode code shown earlier. Drawing a rectangle on the screen is a bit trickier.
One way would be using the interrupt 0x10,0xc to set every single pixel. This interrupt takes the following arguments:
- the color of the pixel in al
- the number of the page to draw the pixel on in bh
- the x position of the pixel on the screen in cx
- the y position of the pixel on the screen in dx
A function for drawing a rectangle on the screen using this interrupt looks like this:
The alternative is writing directly into the video memory. The video memory is mapped to the RAM beginning at the address 0xA0000. In the video mode 0x13 the screen is mapped line wise into the video memory with one byte per pixel. The value of the byte determines the color of the pixel.
A function for drawing a rectangle directly into the video memory looks like this:
Printing on the screen
The scores of the two players gets printed on the screen during the game. To save memory, the score is not printed numeric. After 9 come the characters from a to z.
Printing on the screen can be done using the interrupt 0x10,0xe as already shown in the first example.
Putting it all together
With all this combined, the game pong can be implemented:
The game flickers a little bit. This brought me to nearly vomit at one point during the developing process.
The whole source of the game can been seen at the GitLab repository kalehmann/pong.
Note that the game can be run on real hardware, it may not always succeed. Some BIOS require a valid Bios Parameter Block, but pong does not have one as it takes too much memory.