Snowdrop OS - my operating system from scratch, in assembly language
Snowdrop OS now has a dedicated page here.

This page is obsolete and no longer updated.

-/++/: :+shhy/
+hNMMMMMmy: ---:://osyyyyhhoo/:- -+dNMMMMNNNy:
:+osshdhyyyyyhdNMMMMNNdo: :+yhddmmhyyyhhhhhho:
:ssss+ -:/+osyssydmddhssysys+yyy+yhhy+:::/::::::::
-sysssso: --:::/++++shhhs:/oss++o+syyo/:::::----:://-
/ssooosso+: --::-- ----:/osso+oossss+:----:::::::::::/:
/sooosssso+++: ---:::::::::::-://::--:::::::::::://+++++/
version 1 - initial version, single tasking, shell, aSMtris
version 2 - PS/2 mouse driver and mouse test apps
version 3 - basic multi-tasking support and virtual display support
version 4 - FAT12 driver write/delete, file manager, text editor
version 5 - serial port driver, formatting utilities, file copy support
version 6 - multiplayer snake game (over serial port)
version 7 - slide show presentation app

Snowdrop OS was born of my childhood curiosity around what happens when a PC is turned on, the mysteries of bootable disks, and the hidden aspects of operating systems. It is a 16-bit real mode operating system for the IBM PC architecture. I designed and developed this homebrew OS from scratch, using only x86 assembly language.

I have created and included a number of utilities, including a file manager and a text editor. I also ported one of my DOS games to it. After all, what kind of an operating system doesn't have games?

The Snowdrop OS and the apps are distributed as both a floppy disk (1.44Mb) image, as well as a CD-ROM image. The images contain the following, all programmed from scratch:
  • a boot loader which loads the kernel into memory
  • a kernel which sets up interrupt vectors to be used by user apps, and then loads the startup app
  • user apps, including a shell (command line interface), utilities, test apps, and aSMtris, my Tetris clone

I hope that Snowdrop can serve other programmers who are looking to get a basic understanding of operating system functions. Like my other projects, the source code is fully available, without any restrictions on its usage and modification.


IMG floppy disk image - write this bootable image to a real 1.44Mb floppy disk, or mount it as a virtual floppy drive in a virtual machine such as Bochs or VMWare

ISO CD-ROM disk image - write this bootable image to a real CD-ROM, or mount it as a virtual drive in a virtual machine such as Bochs or VMWare

Snowdrop OS source code package - grab this if you wish to inspect the boot loader, kernel, or apps source code, or if you wish to modify Snowdrop OS. Tested under Windows XP 32bit. All tools required to build Snowdrop OS are included, and no configuration is required. Just download, unzip anywhere, and run "make.bat". See the "building" section below for more information.

Additionally, the floppy disk image above can be used to create a bootable USB drive. For more information, check the README.txt file in the project root directory.

Implementation details

I chose to use exclusively assembly language because of two reasons:
  • it keeps the process of creating binaries extremely simple, only requiring the assembly step
  • assembler is one of my favourite hobby programming languages, giving me a break from the high level software I write for my day-to-day job

I've made sure to comment my source code well, focusing on the more algorithmically-complex pieces. Other than the boot loader, where I was strapped for space, I kept the code on the verbose side, as opposed to the efficient/minimalistic side. If you run across a confusing piece of code, send me an email to the address on the homepage of this website.

There are four subdirectories inside the src directory:
  • apps - contains source code for user apps, such as aSMtris, etc. (one file per app)
  • kernel - the entry point is in the kernel.asm file; it inlines all other files in this directory. These files make up Snowdrop OS's kernel. The kernel provides access via interrupts to the functionality defined in most files in this directory
  • loader - the Snowdrop OS boot loader. It is responsible for loading the kernel from disk, and then transferring control to it
  • static - static files, copied verbatim to the generated disk image

aSMtris is my Tetris clone which I wrote a while ago. It, too, was written in x86 assembly language, and initially targeted MSDOS. It made use of several functions of int 21h, the MSDOS services interrupt. Once Snowdrop OS was functional, I added kernel-offered services such as random number generation and delay. This allowed me to port aSMtris to Snowdrop OS, by removing the reliance on MSDOS int 21h, and replacing those calls with calls to Snowdrop OS interrupts.

Also, I've kept a day-by-day (or rather, evening-by-evening) development log describing various issues I ran into. It doesn't have much technical value, but could be an interesting read. I've included it in here, in the "Development log" section below.

Regarding the tools I've used (Nasm, cdrtools, etc.), I've used an online meta virus scanner to scan the Snowdrop OS package for possible viruses in the tools I downloaded, of the 55 scanners, one lesser-known scanner reported a possible threat. Given that industry-recognized ones like Bitdefender, AVG, Symantec, Kaspersky, etc. detected nothing, I believe that the threat report is a false positive. Regardless, I suggest you always work in a virtual machine when you work with tools whose origin is hard to verify.

Building Snowdrop OS

To build Snowdrop OS, simply download the source code package from the "downloads" section above. I recommend using Windows XP 32bit, since that's what my development environment was.

Requirements for building Snowdrop OS
Works on: Windows XP 32bit, Windows 98, Windows 95
Doesn't work on: Windows 7 64bit

The tools used to build Snowdrop may work on other Windows versions; just give it a try! However, I think that it is unlikely that they'll work on 64bit versions.

Build process
Building the Snowdrop OS is simple - run the "make.bat" file in the project's root directory, and then locate the generated Snowdrop OS disk images inside the "disk" directory.

The following are created:
  • "output" directory - will contain all generated binaries, such as kernel, apps, and boot loader, as well as static files, such as the kernel configuration file
  • "disk" directory - will contain Snowdrop OS disk images, such as the floppy disk image and the CD-ROM image

Warning: if you have Cygwin installed, the generation of the CD-ROM image may fail (however, the floppy image creation should still succeed). The reason for this is that cygwin1.dll is included with the cdrtools (which create the CD-ROM image from the floppy image) so that it works on systems without Cygwin. The provided cygwin1.dll may conflict with the installed Cygwin.

Extending Snowdrop OS

You are encouraged to modify and extend Snowdrop OS with your own system calls (provided by the kernel), or by creating apps for it. I used the Nasm assembler for all the development, but you can use whichever one you prefer.

To extend the Snowdrop kernel:
  • implement your service routine in a separate source file
  • include your source file at the bottom of src\kernel\kernel.asm
  • inspect the kernel_init_interrupts procedure in src\kernel\kernel.asm to see how each interrupt handler is registered, and register your own

Writing your own Snowdrop OS app

Writing your own app targeting Snowdrop OS is very similar to writing a COM program in DOS. The main exception is that you cannot rely on int 21h, which is DOS-specific.

Take a look at the source code of the "hello" app in src\apps\hello.asm. It is the most bare-bones program which fully respects the Snowdrop OS app contract.

Then take a look at build\assemble.bat to see how to assemble your program using Nasm. The binary has to have a .APP extension in order to be recognized as an app.

Make your own program bootable
You can also decide to have your app run automatically upon booting by specifying its name in the SNOWDROP.CFG kernel configuration file.

Version 2 - PS/2 mouse driver and mouse test apps

Snowdrop OS has been updated to include a mouse driver, which allows applications to rely on standard PS/2 mice. As with everything around it, I have designed and written the driver from scratch, mainly from PS/2 specifications. I have used two real computers and two virtual machines as my testing environment.

The driver has two conceptual layers. The first layer handles the communication and capture of raw mouse data from the mouse hardware, via IRQ12. The second layer translates the raw data into an absolute location and applies acceleration for quicker mouse motions.

A user application which needs the mouse will first initialize the mouse manager via an interrupt (see interrupt table below), passing in the dimensions of the bounding box within which the mouse cursor can move. Usually, these dimensions will be the width and height of the whole screen. This is called "managed" mode, and the driver will ensure that the mouse never leaves the user-defined bounding box.
For example, when using graphics mode 13h, whose resolution is 320x200, the mouse's horizontal location will be between 0 and 319, inclusive. See test applications MMANAGED and MOUSEGUI for usages of "managed" mode.

Optionally, a consumer application can gain access to raw mouse data, allowing it to track the location of the mouse itself. See the test applications MINTRRAW and MINTRPOLL to see how to obtain raw (delta X, delta Y, button status) mouse data.

Version 3 - Multi-tasking support (scheduler, memory manager, virtual displays)

Snowdrop OS has been updated to support basic multi-tasking. This required a few kernel components to be added to the Snowdrop OS kernel:

The memory manager is responsible with the allocation and deallocation of memory. I kept it simple, only allowing operation on entire 64kb memory segments. Prior to its existence, programs would simply use the next segment from where they were loaded, by virtue of only one task running at a time.
Multiple, concurrent tasks invalidated the assumption of the ready availability of the memory past where a task was loaded.

The second component I added was a virtual display driver. This was needed because I wanted tasks to have their own, isolated display. At a low level, I had to implement my own direct-to-memory string output routines, complete with cursor management and scrolling. These routines more or less replace int 16h, the BIOS screen services interrupt. At a high level, the driver supports switching between the virtual displays, keeping only one of them active (on-screen) at a time.

The third component I implemented was a scheduler. The scheduler is in charge of managing tasks by handling requests for task addition, exit, and yield. The scheduling mechanism uses a non-preemptive (cooperative), round robin approach. Starvation (that is, running out of tasks) will cause the kernel to lock the CPU. This introduces the requirement that the startup app never exit.

Version 4 - FAT12 driver write/delete support, file manager, text editor

Snowdrop's FAT12 file system driver has received support for writing and deleting files. While the delete functionality was straightforward to implement, writing a file was among the most algorithmically-difficult pieces I've implemented for Snowdrop OS. It involved working with all parts of a FAT12 file system: FAT linked list, root directory, 12bit cluster references, and the data area.

The file deletion capability has allowed me to create the File Manager app, which provides a UI for the user to list and delete files on the disk. The File Manager required integer-to-string routines for displaying 32bit numbers, which were interesting to implement.

Similarly, the file write capability has opened the door for the Text Editor. For the first time, files could be created on the disk from within Snowdrop OS!

Testing FAT12 driver writes and deletes took some time because I wanted consistency between Snowdrop, and Microsoft OSes (MSDOS6.22, Windows XP). Any change to the disk (file creation or deletion) was required to keep the disk in a good state between operating systems.

These changes have also been tested on real hardware, on both floppy disks and USB keys.

Version 5 - serial port driver, formatting utilities, file manager copy support

With this version, I wanted Snowdrop OS to have the ability to replicate itself on other disks, starting with unformatted disks. Since it still assumes a 1.44Mb floppy disk, formatting such a disk is as simple as raw-writing a pre-generated blank FAT12 image.

The next step in the workflow is writing Snowdrop's boot loader to the disk's boot sector. What follows is the transfer of core Snowdrop files, such as kernel, configuration, etc. At this point, the user can copy whatever apps he chooses, as well as edit the configuration file on the new disk.

At every step of this process, I've tested the disk in a few other operating systems, to ensure that they see the disk as "proper".

To accomplish copying apps (and other files) to the new disk, the File Manager has been updated to support file copy operation.

The second addition in this version was support for serial port communications. I have created a simple serial port driver, which allows programs to hook an interrupt handler to receive bytes from the serial port. An additional kernel service interrupt allows blocking writes to the serial port.

An accompanying test app hooks into the "read byte from serial" interrupt to read, as well as send keystrokes via serial. This was then used to test communication between two instances of Snowdrop, as well as between Snowdrop and HyperTerminal running in Windows XP. The tests took place on both virtual and real hardware.

Version 6 - multiplayer snake game (over serial port)

Last version's serial port functionality inspired me to make a small multiplayer game.

No Snakes! is a two-player game, played over the serial port. It follows a client-server model, whereby it runs in server mode on one computer and in client mode on the other. In client mode, the game sends the direction chosen by the player, and receives the current positions of each snake, as well as whether the game is over.

In server mode, the game enforces game rules, updating snake positions and determining the winner and the loser.

This is my first multiplayer game, and my approach was to test client and server separately as much as possible. Both client and server were required for testing towards the end, which is when I switched to using two older laptops.

Version 7 - slide show presentation app

I had a guest lecture ahead of me, and decided to use Snowdrop OS to give my presentation. I thought that the students may find it more interesting that way, compared to seeing a run-of-the-mill PowerPoint slide show.

The Presenter app was relatively easy to create, using the text editor app as a starting point.


Here are some photos of Snowdrop OS in action, on real hardware.

In one of the boot-up video takes, reading the FAT had to be retried. I wrote the retry code, but could never test it fully, because there were no physical drive failures, and I could find no way to simulate a failure in software.

Essentially, this failure on real hardware validated my retry code. The "reset!" indicates a retry, and after that, the kernel loaded successfully.

After finding the BOOT utility in the DOSBox emulator, I was able to boot Snowdrop OS on my BlackBerry PlayBook.

Snowdrop OS interrupt list

The Snowdrop OS kernel offers services to be used by applications via the following interrupt handlers. If you decide to write your own app, you can call these.
Most of them are not re-entrant.

int 80h - Print string to screen

WRITES DIRECTLY TO VIDEO MEMORY. Not suitable for tasks which need to write to
their own isolated virtual display.

DS:SI - pointer to first character of zero-terminated string


int 81h - Load file from FAT12 disk

The disk from which the OS booted is used.

DS:SI - pointer to 11-byte buffer containing file name in FAT12 format
example: for "BEAR.APP", the buffer would contain "BEAR APP"
ES:DI - pointer to where file data will be loaded, rounded up to
nearest 512 bytes
AL - result (0=success, 1=not found)

int 82h - Convert string to upper case

DS:SI - pointer to first character of zero-terminated string

int 83h - Clear keyboard buffer


int 84h - Get system timer ticks count

The system timer begins ticking as soon as the kernel initializes.
By default, the system timer ticks approximately 18.2 times a second.

AX - current system timer ticks count

int 85h - Cause delay (busy wait)

By default, the system timer ticks approximately 18.2 times a second.

CX - number of system timer ticks to delay

int 86h - Get next random number

AX - next random number

int 87h - Load FAT12 root directory

The disk from which the OS booted is used.
Each 32-byte FAT12 directory entry has the following format:

Bytes Content
0-10 File name (8 bytes) with extension (3 bytes)
11 Attribute - a bitvector. Bit 0: read only. Bit 1: hidden.
Bit 2: system file. Bit 3: volume label. Bit 4: subdirectory.
Bit 5: archive. Bits 6-7: unused.
12-21 Reserved (see below)
22-23 Time (5/6/5 bits, for hour/minutes/doubleseconds)
24-25 Date (7/4/5 bits, for year-since-1980/month/day)
26-27 Starting cluster (0 for an empty file)
28-31 File size in bytes

ES:DI - pointer to where the root directory will be loaded
AX - number of 32-byte FAT12 root directory entries loaded

int 88h - Dump memory to screen

WRITES DIRECTLY TO VIDEO MEMORY. Not suitable for tasks which need to write to
their own isolated virtual display.

DS:SI - pointer to first byte to dump
CX - number of bytes to dump

int 89h - Play sound on internal speaker

The sound is sustained until the speaker is muted via int 8Ah
Frequency number can be obtained from the following table:

Note Frequency Frequency #
C 130.81 9121
C# 138.59 8609
D 146.83 8126
D# 155.56 7670
E 164.81 7239
F 174.61 6833
F# 185.00 6449
G 196.00 6087
G# 207.65 5746
A 220.00 5423
A# 233.08 5119
B 246.94 4831
Middle C 261.63 4560
C# 277.18 4304
D 293.66 4063
D# 311.13 3834
E 329.63 3619
F 349.23 3416
F# 369.99 3224
G 391.00 3043
G# 415.30 2873
A 440.00 2711
A# 466.16 2559
B 493.88 2415
C 523.25 2280
C# 554.37 2152
D 587.33 2031
D# 622.25 1917
E 659.26 1809
F 698.46 1715
F# 739.99 1612
G 783.99 1521
G# 830.61 1436
A 880.00 1355
A# 923.33 1292
B 987.77 1207
C 1046.50 1140

AX - frequency number of sound to play

int 8Ah - Stop internal speaker output

This interrupt is used to turn off the speaker after a sound has played
for a desired period of time.


int 8Bh - Mouse event callback

Receives 3 bytes of raw mouse data upon a mouse event taking place.
This handler can be overridden by a custom handler for access to raw mouse data.
Once overridden, "managed" mode via interrupt 8Fh (see below) becomes unavailable.

BH - bit 7 - Y overflow
bit 6 - X overflow
bit 5 - Y sign bit
bit 4 - X sign bit
bit 3 - unused and indeterminate
bit 2 - middle button
bit 1 - right button
bit 0 - left button
DH - X movement (delta X)
DL - Y movement (delta Y)

int 8Ch - Poll raw mouse data

Returns the last-received raw mouse event data.

BH - bit 7 - Y overflow
bit 6 - X overflow
bit 5 - Y sign bit
bit 4 - X sign bit
bit 3 - unused and indeterminate
bit 2 - middle button
bit 1 - right button
bit 0 - left button
DH - X movement (delta X)
DL - Y movement (delta Y)

int 8Dh - Get mouse driver status

Returns the status of the mouse driver.

AL - 1 when driver is loaded, 0 otherwise

int 8Eh - Print byte

Prints the value a byte to the screen (hexadecimal).
WRITES DIRECTLY TO VIDEO MEMORY. Not suitable for tasks which need to write to
their own isolated virtual display.

AL - byte to print

int 8Fh - Poll mouse manager

Polls the mouse manager, receiving button status and mouse location in
user-specified coordinates. See interrupt 90h below for how those
coordinates are specified.

AL - bits 3 to 7 - unused and indeterminate
bit 2 - middle button current state
bit 1 - right button current state
bit 0 - left button current state
BX - X position in user coordinates
DX - Y position in user coordinates

int 90h - Initialize mouse manager

After calling this, a consumer program will be able to poll the mouse
manager (via interrupt 8Fh above), to get the mouse location and button status.

BX - width of the bounding box within which the mouse cursor will move
DX - height of the bounding box within which the mouse cursor will move

int 91h - Allocate memory

Allocates a memory segment.

AX - 0 when the allocation succeeded
BX - segment number of newly allocated segment, when successful

int 92h - Free memory

Frees a memory segment, making it available for allocation.

BX - segment to free

int 93h - Scheduler task add

Adds a task to the scheduler, preparing it for execution.

BX - segment containing the app to be added as a task
AX - task ID of newly added task

int 94h - Scheduler task yield

Causes the currently running task to yield to another. Used when a running app
reaches a point in its execution when it's sensible to yield to another app.


int 95h - Scheduler task exit

Causes the currently running task to exit. Must be called by all apps whose
execution has ended.


int 96h - Activate virtual display of task

Makes the virtual display of the specified task active.

AX - ID of task whose virtual display is to be made active

int 97h - Write string to current task's virtual display

Suitable for writing to a task's own isolated virtual display, without
interfering with the displays of other tasks.

DS:SI - pointer to first character of zero-terminated string

int 98h - Write character to current task's virtual display

Suitable for writing to a task's own isolated virtual display, without
interfering with the displays of other tasks.

DL - ASCII character to write

int 99h - Scheduler task get status

Returns the status of the specified task.

AX - task ID
AX - task status as follows: 0FFFFh = "not present"

int 9Ah - Scheduler get current task ID

Returns the ID of the currently running task.

AX - task ID of currently running task

int 9Bh - Power off computer

Attempts to put the computer in a power off state via APM.

AX - error code as follows:
0 = installation check failed
1 = real mode connection failed
2 = APM driver version 1.2 unsupported
3 = state change to "off" failed

int 9Ch - Delete file from disk

Deletes the file with the specified name from the disk.
Performs no operation if file does not exist.

DS:SI - pointer to zero-terminated file name string in FAT12 format

int 9Dh - Write file to disk

Writes the file with the specified name and contents to the disk.

DS:SI - pointer to 11-byte buffer containing file name in FAT12 format
ES:DI - pointer to file contents buffer (cannot cross 64kb boundaries)
CX - size of file content in bytes
AX - status as follows:
0 = success
1 = failure: maximum number of files reached
2 = failure: disk full

int 9Eh - Set cursor position

Sets the position of the cursor in the current display.

BH - row
BL - column

int 9Fh - Write attribute to display

Writes the specified number of attribute bytes to the current display.

DL - attribute value
CX - number of attribute bytes to write

int 0A0h - Clear screen

Clears current display, moves cursor to top left, and sets
attributes to gray text on black background.


int 0A1h - Get free file slots count

Returns the number of free file slots on the disk.

CX - number of free file slots

int 0A2h - Convert 32bit unsigned integer to string

Adds ASCII 0 terminator at the end of the string.

DX:AX - number to convert, no larger than 655,359,999 (270FFFFFh)
DS:SI - pointer to buffer where the result will be stored
(must be a minimum of 16 bytes long)
BL - formatting option, as follows (for input 834104):
0 - no formatting, eg: "000834104"
1 - leading spaces, eg: " 834104"
2 - leading spaces and commas, eg: " 834,104"
3 - no leading spaces, eg: "834104"
4 - no leading spaces with commas, eg: "834,104"

int 0A3h - Get free disk space amount

Returns the amount of available disk space, in bytes.

DX:AX - amount of available disk space, in bytes
(least significant bytes in AX, most significant bytes in DX)

int 0A4h - Read keyboard input from user

Reads characters from the keyboard into the specified buffer.
It reads no more than the specified limit, and adds a terminator character
of ASCII 0 at the end, once the user presses Enter to finish inputting.
The specified buffer must have enough room for one more character than the
specified count.
Echoes typed characters to the current task's virtual display.

CX - maximum number of characters to read
ES:DI - pointer to buffer (must fit CX+1 characters)

int 0A5h - Get string length

Returns the length of a ASCII 0-terminated string.

DS:SI - pointer to string
BX - string length, not including terminator

int 0A6h - Convert 8.3-format file name to FAT12 format

Converts a 8.3 dot file name to a fixed-size, padded, upper-case
FAT12-compliant file name.
Must contain a dot.
Must have between 1 and 8 characters before the dot.
Must have between 1 and 3 characters after the dot.
Example: "abcd.000" is converted to "ABCD 000"
"" is converted to "ABCDEFGHAAA"

DS:SI - pointer to 8.3 dot file name
ES:DI - pointer to buffer to hold the resulting FAT12 file name

int 0A7h - Dump virtual display data

Dumps the virtual display of the current task.
Dumps the vram if the specified virtual display is the
active virtual display.
The buffer must be able to store 4000 bytes (80 columns x 25 rows x 2 bytes).

ES:DI - pointer to where the virtual display will be dumped
(must be able to store 4000 bytes)

int 0A8h - Dump characters to current task's virtual display

Prints a specified number of characters from a string to the
current task's virtual display.

DS:SI - pointer to beginning of dump source
CX - number of characters to print

int 0A9h - Validate 8.3-format file name

Checks whether the specified 8.3 dot file name is valid:
Must contain one dot.
Must have between 1 and 8 characters before the dot.
Must have between 1 and 3 characters after the dot.
Example: "abcd.000"

DS:SI - pointer to 8.3 dot file name
AX - 0 when the file name is a valid 8.3 file name

int 0AAh - Get cursor position

Gets the cursor position in the current task's virtual display.

BH - row
BL - column

int 0ABh - Format disk

Formats the current disk with the specified FAT12 image.
Snowdrop OS's default FAT12 image is expected to be first read via 0B1h.

DS:SI - pointer to FAT12 image

int 0ACh - Write boot loader to disk

Writes the Snowdrop OS boot loader to the current disk.


int 0ADh - Get serial port driver status

Returns the status of the serial port driver.
(Default settings: COM1, 9600 bits per second, 8 data bits, no parity,
1 stop bit, no flow control)

AL - 1 when driver is loaded, 0 otherwise

int 0AEh - Serial port "read data" user interrupt

Receives a byte whenever one is available to read from the serial port.
User applications are meant to define their own handlers for this interrupt,
to handle bytes available for reading from the serial port.
(Default settings: COM1, 9600 bits per second, 8 data bits, no parity,
1 stop bit, no flow control)

AL - byte read from serial port

int 0AFh - Serial port send data (blocking)

Blocks until the serial port is ready to send.
Once that happens, it sends the specified byte.
(Default settings: COM1, 9600 bits per second, 8 data bits, no parity,
1 stop bit, no flow control)

AL - byte to send

int 0B0h - Install interrupt handler

Installs the specified interrupt handler, returning a pointer to the old
(previous) interrupt handler.

AL - interrupt number
ES:DI - pointer to interrupt handler to install
DX:BX - pointer to old interrupt handler

int 0B1h - Read FAT12 image

Reads a complete floppy FAT12 image in preparation for a disk format
operation via int 0ABh.

ES:DI - pointer to where file data will be loaded
(must be able to hold at least 32kb)
(must not cross any 64kb boundaries)
AL - status (0=success, 1=not found)

int 0B2h - Read screen character and attribute at position

Reads a character and attribute from the current
task's virtual display, from the specified location.

BH - location row
BL - location column
AH - attribute byte
AL - ASCII character byte

int 0B3h - Convert FAT12 format file name to 8.3-format file name

Converts a file name in FAT12 format to a 8.3 dot file name.
Example: "ABCD 000" is converted to "ABCD.000"
"ABCDEFGHAAA" is converted to "ABCDEFGH.AAA"
"ABC A" is converted to "ABC.A"

DS:SI - pointer to FAT12-compliant file name
ES:DI - pointer to buffer to hold the resulting 8.3 dot file name
(must be able to hold at least 13 characters)

Development log

This is the development log of Snowdrop OS, my homebrew operating system written in x86 assembly language. It was developed primarily during evenings, some longer and some shorter. Outside of this, I often spent time thinking about what to do next and how to do it.

day 1
Re-familiarized myself with boot loaders and BIOS booting process.
Read real-mode interrupt primer.
Start thinking about organization of segments (stack, etc.).
Start thinking about where applications will be loaded, and how they'll return to the OS.

day 2
Installed Bochs, a virtual machine provider which I hope to use as my development environment.
Put together toolchain - it assembles and creates boot loader floppy image from one script.
The floppy imaging tool tries to be extra smart and injects its own bootloader. dd'ing the image to insert my own bootloader simply corrupts things.
For now, I can either create a floppy image with files on it, or a bootable, empty floppy image.
About five different tools later, I can boot, as well as have valid files on the floppy image.
The problem could be that I'm missing the BPB boilerplate. I added it, and it boots in Bochs, but I can't read the FS from a DOS 6.22 VM. However, in VMWare, I can read the FS, but of course, doesn't boot.
It turns out I was missing one byte from the beginning of my BPB (BIOS Parameter Block). Once I added it, my floppy disk image booted in both Bochs and VMWare. It also became readable in both DOS and Windows XP. That one byte took 3 hours to find.

day 3
Of course, as soon as I try to copy even one file to the floppy image, it stops working (can't be read by DOS or Windows XP).
I reached a compromise where DOS no longer can read the floppy, but Windows XP can. Of course, it boots in both Bochs and VMWare.
I refused to give up and took a look at an online example of a bootloader which specifies the PBP in order to make a "proper" floppy image. While the FAT12 specification says that the first 3 bytes of the boot sector are ignored, this is not true. With a 0 there, DOS 6.22 doesn't recognize the floppy as valid. Instead, I changed the 0 to a nop (0x90) instruction, and it started working. I don't know if this is a DOS-specific thing, or not, but the generated floppy image now boots in Bochs and VMWare, and is recognized as a valid floppy disk in both Windows XP and DOS.
The build script now injects a few test files into the floppy image.
Started reading about reading from a floppy disk, since the plan is to read FAT12 and locate a file with a specific name which will contain the kernel.
Wrote some calls to help me debug FAT12 operations.

day 4
I don't know if I will be able to find a suitable debugger for Windows which will work with Bochs, so I'm going to code up some functions to dump registry values.
Found a good LBA-to-CHS translation routine and incorporated it in the code. I've tested it using a converter utility website. Looks like it's working well, but I'd like to clean it up again. The point of this routine is to be able to map a logical disk sector number (0 to n-1) to a physical Cylinder-Head-Sector tuple which is required by the BIOS's interrupt 13h (disk services). The ultimate purpose is for the loader to load the kernel.
The simple "print hex byte to screen" routine is already paying off.
TODO: Clean up LBS-to-CHS code (save results of division)
TODO: Make sure head/cylinder calculation makes total sense before moving on

day 5
I have a feeling I'll run out of space with the boot loader, so I'm refactoring the LBA-to-CHS function.
Saved 4 bytes by switching to using the stack rather than temporary registers.
LBA-to-CHS head/cylinder calculation makes sense fully now, and I've tested it using an online converter.
Saved 2 bytes from the LBA-to-CHS routine. This was possible because there's no point in preserving the ax register, since it will be set in preparation for the int 13h call anyway, right after returning from the LBA-to-CHS routine.
The code is now debug printing first three entries in the root directory.

day 6
Wrote the fragment which finds the root directory entry which belongs to the kernel file. The boot loader file size is getting uncomfortably close to 512 bytes. It will lose a bit of "weight" once I remove some of the text output functions. Found a damned bug, where if a file has the kernel file name, but a wrong last character, the code considers it the kernel.
The bug was fixed shortly after. Tested that kernel file is found regardless of whether there are other files around it on the floppy.
TODO: Convert the debug_print function to work without a character count (by expecting 0-terminated strings). Still allow character count to be specified, to dump chunks of memory more easily.

day 7
My floppy image hasn't been booting in VMWare for a while. Introduced a second build method, which builds the floppy as an flp image, using ImDisk.
Fixed a small but in the root directory search code which incorrectly left 2 bytes on the stack
I was printing out the 32-byte root directory entry to help me debug, and I was changing the DS register, but not changing it back. Since it was late and night and was tired, this mistake cost me one hour.

day 8
Learned about how FAT works in more detail. I can now load the first cluster of the kernel file.
Learned that the data area on FAT12 actually starts with cluster 2. This caused problems with offsetting to locate the first cluster storing the kernel file, before I found this fact online.

day 9
Got the loader (load then move to next cluster) code working, adapting an online example. However, it can be tidied up a bit.
Saved 3 bytes by removing a useless mov.
Saved 2 bytes by converting a mov to a dec.
Saved 3 bytes by removing another useless mov.
Saved 2 bytes by removing a push/pop pair and using a different register.
Saved 1 byte by using dec to compare to 0 a register whose values can only be 1 or 0.
I might want better messages and better floppy error handling, which is why I'm tightening up the code.

day 10
I've started writing out an example of the cluster chain calculation in comments. Basically, if I'm able to explain it, it means that I've understood it.
I'm trying to understand FAT12 bit packing, and while it's working, I'm uncomfortable that I don't fully understand it. I'm tired now, and will try again tomorrow.

day 11
This morning I found the missing link, which is IBM PC's little endianness. When you read the word 0x34 0x12 from memory, you get 0x1234. I've finished writing the example, and everything makes sense now.
I've been cleaning up the boot loader code, with cleaner comments. I also have to remove some debug/dump functions I no longer need. I'll probably add them somewhere else, perhaps a stand-alone file, to serve as examples.
In the mean time, I wrote a quick "hello world" kernel and verified that control is passed to the kernel correctly.
One thing I have not accounted for is floppy disk retries. Real floppy drives (as opposed to those emulated by VMWare and Bochs) often require read operations to be retried. Unless I find a computer with a floppy drive, I will leave it as it is.

day 12
Saved 5 bytes by converting segment register assignments to be done via the stack (push->pop instead of mov->mov).
I found that an old USB floppy disk drive I had worked as a boot device, so it's worth trying to get a real bootable floppy.
I downloaded dd for Windows (to copy the floppy image to an actual floppy), and because it doesn't show progress by default, I thought it kept getting stuck, and I kept cancelling the process and re-trying. However, after cancelling, I once tried to see if Windows can read the floppy. Although it had been cancelled half way through, the first sectors, which contain my test files did get copied, and the floppy worked.
After this, I realized dd has a --progress flag, which will show the copy progress in bytes. I created a floppy fully, and verified that the boot loader is loaded.
However, my code currently doesn't retry floppy disk reads, and it needs to, if it is ever to work off of a real floppy. I will get to work on this now.
Fixed a problem where BX was not zeroed, and by luck alone had a value of 0 from before the boot loader was entered.
I got the retry code around every int 13h call, and only have a few bytes to spare. This is because I wanted each int 13h call to display a string so that I could debug any failures which occurred on real hardware.

day 13
The loader now has retry code around every floppy operation, and every step is logged via a message to the screen.
Even better, I've tested the boot loader on 2 different floppy drives, on three different computers, and it worked everywhere. This was very good news, as I've been investing a lot of time in making sure the floppy image is as "proper" as possible.
I really wanted the boot loader to initialize all segments for the kernel, but I had to remove that code since I need all the bytes I can get, to have better messages.

day 14
Began working on the kernel. The loader only needs some clean up and comments, but is otherwise done.
The kernel registers its first interrupt, 80h, which prints a string. There will be more, dealing with the disk, most likely. They will be made available to apps.

day 15
I'm bringing some floppy drive functionality into the kernel, ultimately exposing it via interrupts. I had a choice between this and working on the user console (command line interface). I chose the floppy routines because they're still fresh in my head from the boot loader.
The kernel now exposes interrupt 81h, which loads a file from the floppy disk. I still have to decide where to transform a file name to the uppercase, blank space-padded version, as is stored in the root directory of the floppy. Currently it just loads a hardcoded file name.

day 16
More work around making the floppy drive code more reusable, by passing in a pointer to the destination where the file data will be loaded. Int 81h (load file) now returns the success status of the operation.
Decided to reduce the size of the kernel by having the console be a separate app, loaded on startup by the kernel.
Created a caller-app contract, which specifies values of registers, etc. that the consumer of an app must set before running an app. It also mentions rules the app must follow.
The kernel now loads and runs the "startup app" (whose name is still hardcoded).

day 17
Wrote some code around restoring whichever video mode was current when the kernel began executing. The idea is to reuse this code for the console app, so that it restores the video mode if any app exits with a different video mode current.
Cleaned up my build scripts and source directory.

day 18
I'm writing the routines needed to convert from a lower case string representation of an app's name to a FAT12 file name.
For a change of scenery, I opened up the aSMtris source code to look for calls to int 21h. Int 21h is the entry point into the system services provided by DOS. Since many hobbyist x86 assembly programs assume DOS is present (either natively, or via Windows's NTVDM), int 21h almost ubiquitously makes it into source code, forum posts, tutorials, etc. aSMtris is in the same bucket, but I want to make it run on Snowdrop, because every operating system needs games. As such, I must essentially implement specific int 21h functionality aSMtris uses.
Here are the TODOs for these:
TODO: implement a "clear keyboard buffer" interrupt
TODO: implement "get total timer ticks" interrupt by hooking int 8h
TODO: implement "get next random number" by relying on the get ticks interrupt above and seeding by current RTC seconds value
TODO: implement "print string at coordinates" interrupt
Implemented the "clear keyboard buffer" interrupt
Changed aSMtris "print string" routine to no longer rely on int 21h (since there is no DOS), and instead, rely on Snowdrop's int 80h, which also prints a string.
Hardcoded aSMtris's file name in the floppy load file routine. The kernel loaded it, and then ran it, but aSMtris filled the screen with characters. Quickly after, I realized int 21h expects strings to end in $, whereas Snowdrop expects them to end in a 0 (NULL) character. After updating all strings, aSMtris starts and shows Game Over immediately. This is because Snowdrop doesn't yet implement the routines needed for timing and random numbers.
It felt very satisfying to boot Snowdrop and run aSMtris on real hardware.

day 19
Finally removed the hardcoded file name inside the "load file" floppy disk routine
There's a bug in the floppy load file routine... When I try to load the config file, it loops infinitely, and this is not good.
The bug took one painful hour to fix. Not only was I overwriting the disk buffer, butchering the stored FAT, but I also wasn't popping all values on the stack when returning successfully from the config file parser.

day 20
Fixed an off-by-one bug which prevented 8-character startup app names from being considered valid.
Startup app names can now be specified using either lowercase or uppercase characters in the kernel config file.
Cleaned up all error messages.
Tested the config file reader code thoroughly.

day 21
Added to the app contract a clause on app exit. Upon startup, the caller specifies whether the app is allowed to exit or not. The reason for this is that in some instances, it is undesirable for an app to exit. One such example is the startup app (whatever that may be), which should never exit.
Updated aSMtris to adhere to the "allowed to exit" app contract clause.

day 22
Added an interrupt handler for interrupt 1C, which is invoked automatically by interrupt 8 (system timer interrupt).
Noticed that in Bochs, the system timer interrupt occurs significantly more often than 18.2 times a second. I fixed this by configuring Bochs's system timer to be bound to the host machine's system timer.
Rewrote the aSMtris delay routine to use Snowdrop's int 84h. aSMtris now runs as intended on Snowdrop OS, both on virtual machines and real hardware.

day 23
Started working on the console app, beginning with correctly handling "special" keys, such as backspace, enter, etc.
The console app now enforces app file name length limit, and is able to load files to memory.
If told to load the console app, it will do nothing.
Backspace and enter keys are now properly handled.

day 24
The console app can now load and run other apps.
TODO: better error messages for when apps can't be loaded.
Greatly improved console error messages.
Arrange source files in directories, so that the src tree is cleaner now.

day 25
Rewrote the spin wait delay routine in aSMtris into a much simpler version which has no irregularities (unlike the previous version), which ended up as a "delay" interrupt that the kernel exposes.

day 26
Introduced a "get random number" interrupt. Ultimately, Snowdrop will rely on both system timer and real time clock to generate random numbers, so that apps don't have to maintain and calculate their own random numbers.
TODO: use values read from the real time clock at boot time to seed the random number generator
Updated aSMtris to no longer calculate its own random numbers, and, instead, rely on the OS-provided interrupt.
TODO: adjust the random number formula to not alternate between even and odd numbers
TODO: manually test aSMtris for random number fairness

day 27
Wrote a test program to help me analyze even/odd distribution across a few tens of thousands of random numbers. There is some variation, but nothing too bad.
Started writing the random number initialization routine, which uses the CMOS RTC's current time.

day 28
Manually tested aSMtris for random number fairness.
The kernel now initializes the random number generator at boot time based on the RTC (real time clock). When used as a startup app, aSMtris now starts with different blocks every time, as it should.

day 29
Went through all source files and added a header which describes the purpose of the file, along with any contracts that the file must respect.
Went through all source files, trimming lines down to 80 characters and removing commented-out code.
Tested most functionality to ensure things still work after the cleanup.
Removed unnecessary files, including the FLP stack, which I haven't used in a while. It seems like the IMG stack produces the exact same results, but has the advantage that it requires no installed Windows programs. This means that I can distribute all tools needed to build the Snowdrop OS floppy image in a single zip which requires no installation.
I just realized that nasm (the assembler I use) optimizes by default. I prefer that the assembler generate exactly what I tell it to, so I turned on the switch -O0 which tells nasm to perform no optimizations. Immediately after doing so, the boot loader exploded in size to 523 bytes. That's 11 bytes I now have to trim.
Since the boot loader had generous amounts of space dedicated to user messages, I was able to shorten some of the messages to gain the 11 bytes back. I was glad I could avoid any further code changes to the boot loader, to keep things stable.
My source code is now assembled without any kind of modifications, such as optimizations.
Cleaned up the build scripts, deleting a bunch of REM'd out trials from back when I was still trying to get the floppy image bootable, as well as accessible via DOS and Windows.
Deleted about 60% of the tools I had downloaded, also from back when I was trying to properly build the floppy image. These tools were no longer used, and I only kept what I needed.

day 30
Implemented a kernel interrupt which loads the root directory into the specified memory location. This will be used by the APPS app to print the list of available apps.
Fixed a problem with the build script which erroneously copied the boot loader file to the floppy image.

day 31
Hooked up the "dump X bytes" routine to be accessible via an interrupt.
The apps app is now almost complete, and only needs some aesthetic changes.

day 32
Finished the apps app.
Wrote a mandatory hello app, which displays "Hello, world!". The hello app is meant to show a very basic example of a Snowdrop OS app which respects the app contract.
Made a few comments changes across the apps.
Started researching tools to convert the floppy image to an ISO CD-ROM image, so that I can also make a bootable Snowdrop CD-ROM as well.

day 33
mkisofs seems to be the most popular way of creating CD-ROM images. Unfortunately, mkisofs is a Linux tool, so I will try to find a Windows port.
Found a Cygwin build of the cdrtools suite, which includes mkisofs. Fortunately for me, this build included cygwin1.dll, which removes the requirement that Cygwin be properly installed. I was happy about this because it means that my build scripts continue to require no installed applications in Windows; everything needed to assemble and to create floppy and CD-ROM images of Snowdrop OS are encapsulated in the single Snowdrop OS download.
I've tested that I can boot a VMWare VM using the CD-ROM image I can now generate.
Burned the current Snowdrop CD-ROM image to physical media, and successfully booted it on real hardware.
Found a bug where when the kernel config file is not there, the kernel treats it as if it were.
It turned out to not be a bug; I had forgotten entirely to handle the "kernel config file not found" case. It was easy to fix.

day 34
Wrote an app which shuts down host computers which support APM (Advanced Power Management). It didn't take very long to get it working and I've always found it cool how software can shut down hardware.
Tested the shutdown app on real hardware.
TODO: in the console app, change tab characters to print out blanks.

day 35
The console app now prints blanks for tabs.
Began cleaning up the directory structure. The point here is to reduce the number of available build scripts, and to hide away the run scripts (those which I used for testing) in their own directory. People who download this will likely be more interested in building Snowdrop than seeing how I ran it, on my particular development environment. However, I am still keeping them for my own sake.
Added a scary "make sure you know what you're doing" message to the "write image to real floppy" script, since it can destroy media if the target drive is not the intended target floppy drive for Snowdrop.

day 36
Cleaned up the directory structure even more.
Added a kernel interrupt which offers internal speaker functionality.
Wrote a test app which plays Pachelbel's Canon in D.

day 37
Started creating a diagram showing both components and flow of the Snowdrop OS boot and initialization process.
Improved how cluster loading progress is shown by the boot loader.

day 38
Finished the sequence/flow diagram for Snowdrop OS.
The Canon in D app now prints to the screen the note being played.
Wrote the bear app which displays a bear.
Made further refinements to the build scripts, in preparation for release.

day 39
Moved most of the build script smarts inside the build directory, leaving only two scripts at the project root level: "build all" and cleanup scripts.
Started writing the documentation for Snowdrop OS, contained in the "docs" directory.
Documented all kernel-provided services (via interrupt handlers).
Documented the build process, along with potential issues.
Documented the basic steps to create apps for Snowdrop OS, including setting them as startup apps to run automatically after booting

day 40
Documented the recommended steps to extend the Snowdrop kernel.
Wrote an introductory readme.
Performed an online virus scan of the Snowdrop distributable package.
Generated the distributable floppy and CD-ROM disk images and tested them on two virtual machines, and on several real hardware computers.
Tested that the Snowdrop package works out of the box on a fresh installation of Windows XP 32bit.
Snowdrop OS is now ready for distribution!

(the first version of Snowdrop OS was released here)

day 41
Started writing the PS/2 mouse driver. I'm going off of various sources of PS/2 mouse documentation and guides.
I got the "reset" workflow to complete, but I'm still missing many pieces.

day 42
The difficulty here arises from the wildly different behaviours between PS/2 implementations of virtual machines, real hardware, etc. I'm getting different results all over the place.
Struggled for a whole day with the "get PS/2 status byte and then turn on IRQ12" step. For some reason, printing the PS/2 status byte - right after I asked for it - causes it to no longer be available to read. Since I had plenty of debug printing, this was a pickle to find.

day 43
Completed the initialization workflow, and am now looking at creating an IRQ handler.
I was happy to see that IRQ handlers for IRQ12 are registered the same way as all interrupt handlers Snowdrop OS already registers. The vector number for IRQ12 is 74h.

day 44
New problem: my IRQ12 handler seems to freeze after the first invocation, even with the proper EOI (End Of Interrupt) being acknowledged to the PIC. It turned out to be that I was not actually reading the PS/2 data port, causing no further IRQs to happen. Tested that the CPU wasn't actually halted by printing something from my timer interrupt handler.
Another difference: real hardware sends a lot more IRQ12s than Bochs. I'll have to see if differentiating by bit 5 of the status byte is enough.

day 45
On two virtual machines and two real machines, I get four different results. I am no longer confident that the initialization code will function properly on a variety of computers.
Mitigation strategy: allow user a few seconds during boot to disable the mouse driver. This is to ensure that Snowdrop OS remains functional despite driver-hardware incompatibilities.
Created the mouse interrupt contract. User programs who wish to use the mouse will register a handler which is called every time the driver accumulates 3 "state changed" bytes from the PS/2 mouse.
Wrote a routine which waits for user input (with a timeout), and doesn't load the mouse driver if the user wishes so.

day 46
Started on the idea of a "raw" stack, and a "managed" stack.
The raw stack is more complicated to work with, but gives a user program access to raw PS/2 mouse data.
The managed stack is simpler to use, implements a bounding box algorithm, and exposes less data to the consumer.
I'm trying to make it work in more than just 2 test computers. I'm adding timeouts, as that seems to be an accepted practice online. Initial results are not promising.

day 47
Now it works well in the 2 VMs, but not on real hardware (on one keyboard doesn't work, and on the other, mouse events are out of sync).
Still working well in 2 VMs, and only intermittently on a 1996 computer that has real, hardware-based PS/2.
It now works reliably on the 1996 computer! A longer timeout during mouse RESET command gave the mouse hardware more than enough time to reset itself.
To my very pleasant surprise, the above fix made it also work on a 2008 computer that has emulated PS/2 (via SMM).
These are great successes; my driver now works on 2 VMs as well as 2 real computers!
It continues to not work on a 2014 computer. I'm not very concerned, since with newer hardware I am at the mercy of their legacy PS/2 implementation. From what I've read, some such implementations are buggy.
Wrote a test program which exemplifies how to poll raw mouse data from a user program.

day 48
Wrote a test program which shows how to register a custom interrupt handler, in order to be notified when mouse events take place, and be given the raw mouse event data.
I'm learning about FPU (floating point) x87 opcodes, and how they work. This is the first time I've seen them, and they may be useful for the "managed" mouse mode, to perform divisions when converting to the user-specified coordinate system.
One important fact is that regardless of the coordinate system, a delta X or Y of 1 must always move the mouse position by 1, to allow for the most granular movements in any coordinate system. Higher delta values can scale according to the coordinate system.

day 49
Split out the mouse driver source code into two files: communication and higher-level callbacks and polling.
Wrote the mouse manager initialization interrupt.
Created the "poll mouse manager" contract, and wired up interrupts.
Implemented buttons state tracking in the mouse manager.
Added a test program which polls the mouse manager (button state and current location), and displays them on the screen.

day 50
Started on the code which translates mouse deltas to locations in user coordinates.

day 51
FPU opcodes and operating mode are different than those of the "regular" x86. The x87 FPU operates on a register stack, which I find more confusing than the more traditional "independent" registers paradigm.
I'm making headway into operations such as division and multiplication, needed for the translation to user coordinates.

day 52
Translation to user coordinates code is completed. I now have linear mouse movement tracking within a specified bounding box.
Fixed a bug where button clicks registered as +1 movements to the right and downwards.
TODO: Write a graphical example app, to test whether the mouse "feels" good, and adding acceleration if it doesn't.

day 53
I'm working on a "mouse GUI" app, which shows how to display a mouse pointer.
Improved the messages displayed by the mouse test apps.

day 54
Still working on the mouse GUI app.

day 55
More work on the mouse UI app. It's taking a bit longer because I am also writing new routines which draw bitmaps on the screen (for the mouse cursor).
I now have all the bitmap buffer manipulation routines I need to draw the mouse without "erasing" the background behind it.

day 56
The mouse pointer now moves on the screen. There is a wrapping issue I have to fix, whereby the mouse appears on the other side of the screen, when it's at the right-most edge.
The mouse is too fast. I have to slow it down.

day 57
Simplified mouse-to-box factor calculation by making cursor equally fast vertically and horizontally. This also made it "feel" better.
The mouse cursor no longer "wraps around" the screen when near the right-hand or bottom edges. The mouse GUI test program is now complete.
Introduced acceleration of the mouse cursor when the delta X or Y from the mouse hardware are above a certain threshold.
I will have to adjust the threshold and acceleration amount until they feel right.

day 58
Found a sweet spot where I'm happy with how the mouse feels.
Drew a diagram showing the components and interactions of the PS/2 mouse driver.
Documented and cleaned up mouse driver code.
Documented the new interrupts.

(the second version of Snowdrop OS was released here)

day 59
Spent some time thinking about how the scheduler would work, and what compromises are needed.
Tightened up kernel code to free up two 64kb memory segments for the memory manager.
Started writing the memory manager. So far it can allocate entire 64kb memory segments.

day 60
Wrote a memory allocation test app.
Started writing the scheduler.

day 61
The scheduler can now accept tasks, and start them. It still needs task interrupt/resume functionality.
Hooked up the code that starts the startup app to rely on the scheduler and memory manager rather than hardcode things.

day 62
The scheduler now supports task yielding. I don't yet have a good way to test it; I will once I spin off console2, which will start apps using the new memory manager and scheduler functionality, rather than just "calling far" into them, and expecting a "return far".
The only way I can test it right now is using one task (the console), which keeps yielding to itself.
TODO: Give each task its own stack.

day 63
Spun off console 2 app from the original console. It's still missing at least some error checking.
Started writing a small app to exemplify multi-tasking.

day 64
Minor cleanup around the code I recently added.
The two console apps behave differently with respect to programs returning. One calls into the program it loaded, expecting it to return upon completion (old app contract), the other registers the program as a task with the scheduler, then yielding to it. The scheduler later returns to the console either when a loaded app yields to it, or when all loaded apps have exited.
I'll probably upgrade all apps to respect the new contract, and then discontinue the first version of the console.
I'm also considering removing the "don't terminate" part of the app contract, as it isn't technically interesting. In the rare case that somebody may want an app other than the console to run and not terminate, they can provide a non-terminating version to run on startup.

day 65
Implemented "task exit" functionality, which allows a program (task) to notify the scheduler that it has finished.
Fixed a bug in console2 which caused a crash when memory could not be allocated for a new task.

day 66
Changed the scheduler to allocate each task its own stack.
Fixed a bug in the console which did not free memory when the app failed to load.
In other news, my mouse driver demonstration app no longer works for some reason...

day 67
Had a hard drive failure and lost some work.
Investigating why the mouse driver stopped working took me about six gruelling hours. It uncovered an issue with how the flags register was initialized when adding a new task to the scheduler.

day 68
Started working on a virtual display manager, so that each task receives its own virtual display.
Basically, once multi-tasking is implemented, most resources have to be virtualized. I will begin with the display, as an example.

day 69
Continued work on the virtual display manager.

day 70
Continued work on the virtual display manager.
Virtual displays are now initialized and can be recalled into video ram.
TO DO: inspect all places in kernel code where string operations (rep stosb, etc.) are used, and explicitly cld (with pushf/popf) right before.

day 71
Updated all kernel procedures which relied on string operations (rep, repe, etc.) to cld right before doing so, and to save/restore flags. This is so changes to the direction flag from user programs do not affect the operation of the kernel.
Started work on the "print string to virtual display" routine.
The "print string" routine is not trivial; it has to update the cursor, as well as write to video ram if the target virtual display is active.
This requires the introduction of the idea of an "active" virtual display, along with a way to "activate" a task's virtual display.
The "print string" routine has a bug in the scrolling code. Once scrolling occurs, no further characters are printed, for some reason...
Fixed the scrolling bug above.
TO DO: add support for the special characters 13 (carriage return) and 10 (line feed).

day 72
Added support for CR and LF in the "print string" routine.
Wrote a "move hardware cursor" routine which writes to the 6845 CRT controller to restore the position of the cursor when the active virtual display changes.

day 73
More work on wiring up the scheduler and virtual display manager into user-addressable interrupts.
Fixed some bugs which made everything crash upon trying to switch the active virtual display.
Wrote a "print character to virtual display" routine which relies on the existing "print string to virtual display" routine.
TO DO: write a "move cursor in virtual display" routine

day 74
More work on the virtual display manager.
Writing to an active virtual display will now write exclusively to the video ram. This is to cover the case of child tasks writing directly to video ram via BIOS, such as in the case of console-started apps that don't require their own virtual display.
With a few hard-coded interrupt calls, the console now starts an app and switches to its virtual display. The app writes a few strings and then exits, re-activating the console's virtual display. This is promising and I'm getting closer to my goal of having multi-tasking and virtual display support.
Wrote a "get hardware cursor position", needed for direct-to-vram string output.
TO DO: decide what happens when the task whose display is active exits (maybe store a task's parent task?)

day 75
Fixed an issue whereby the hardware cursor position was not saved when changing the active virtual display.
Migrated the console2 app to use strictly virtual display interrupts to output text, and away from BIOS "write text" interrupts.
Fixed a bug where a non-preserved register would cause a mysterious "0" to appear when the hardware screen scrolled.
While still using some hard-coded calls, the console2 loads and switches to the display of a test app, and then back to itself.
Added (rudimentary, but good enough for now) support for printing out the backspace character. As opposed to the BIOS version which just moves the cursor, my version also erases the previous character.
TO DO: consider moving an app's stack into the first 100 bytes or so (needs all apps to be ORGed differently)

day 76
Each task now stores a reference to its parent, that is, the task that started it. This lets the kernel switch to the display of the parent task of a task that's exiting, if the display of the exiting task is active.
New bug, as I'm transitioning out of debug hard-coded calls into changing out of the exiting task's display during a task exit call: cursor is not set properly, and I get some artifacts on the screen.
Spent on hour fixing the above bug. As with most bugs in assembly, it had to do with a register holding a different value than planned.
The only hard-coded debug calls now are the ones that set the active display on app load. The rest is all wired up.

day 77
Added logic to store IDs of tasks started by the console app.
Added logic to allow the user to switch between running tasks' virtual displays via ALT+F1 through ALT+F10.
Tested that virtual displays are switched properly upon task exit. Also verified that the user cannot switch to a task's virtual display once the task has exited.
Added a message informing the user of how to switch to the virtual display of a newly started task.

day 78
Factored out display driver functionality into 3 source files.
Deprecated the first version of the console app.
Deprecated the memory allocation test app.
Deprecated the first version of the multi-tasking test app.
Updated all apps to use the new "task exit" interrupt when their execution finishes.
Updated all apps to no longer rely on the now deprecated "should return" contract.
Updated all apps which change the video mode to also restore it to its initial value upon exiting.
Found a bug in how console2 tracks completed tasks. Reproduction: run the hello app a few times in a row.
Found a bug in the display driver. Reproduction: run the hello app a few times in a row.
Fixed the bug which prevented the re-use of task list slots in the console2 app.
A bunch of my apps don't output text properly because some of my older interrupts relied on BIOS. I'm converting these interrupts to use my display driver instead, for feature parity with my virtual display routines.
Also, my display driver works only in text mode, so I must change apps which use mode 13h to print text through BIOS.
TO DO: Change aSMtris and mousegui apps to use BIOS for text output in a graphics video mode.
TO DO: Change the apps app to no longer use next segment as data buffer. (either use an in-segment buffer, or allocate memory via the kernel).

day 79
Updated the aSMtris and mousegui apps to use BIOS for text output in a graphics video mode.

day 80
Updated the apps app to no longer allocate next segment as data buffer. FAT12 root directories have a fixed size, and will fit in the same segment as the app's code segment.

day 81
Added error checking in the console2 app when the user tries to switch to the display of a task that exited.
Cleaned up the multitasking test app.
After having observed bunch of people automatically reach for the arrow keys when aSMtris starts in front of them, I updated aSMtris to use left and right arrow keys to move the falling pieces.

day 82
Changed any remaining 0x syntax numbers to 0h syntax, for consistency.
Moved routines from kernel.asm into the kernel component source files which needed them. This eliminated two-way dependencies between source files.
Updated the Snowdrop app contract text in all source files.
Encountered a very frustrating bug whereby the graphical mouse test app stopped working. All other mouse apps worked, but not that one. This started happening after routine refactoring in unrelated areas.
Turns out I was only allocating one byte (instead of two) for the scheduler's initial FLAGS register value (upon starting a task). Two bytes are, of course, needed because FLAGS is a 16-bit register. While that single byte was just before a filename buffer, it overran into the first byte of the buffer, running into a happy value. As soon as I moved it into the scheduler, it overran into a bad value, causing undefined behaviour.

day 83
Documented the new interrupts, and updated the existing ones.
Cleaned up some code comments.

day 84
Drew a diagram showing the components and interactions of the multi-tasking eco-system.

day 85
Wrote some documentation.
Modified mousegui and aSMtris apps to switch to their own virtual display upon startup. This prevents the clobbering of the parent task's virtual display.
Renamed console2 to snowshell.
Began implementing a "drop piece" feature in aSMtris.

day 86
Finished the "drop piece" feature in aSMtris.
Also switched all controls to the arrow keys. aSMtris can now be played with only one hand. Space bar can still be used to rotate, allowing for two-handed play, as well.
Improved aSMtris's "last chance movement" code, to make the game feel smoother when a piece lands.
The mouse GUI test app doesn't work on real hardware, to my surprise and disappointment. At a first glance, it has something to do with the interrupts becoming disabled at some point. Sure enough, this app works in two virtual machines.
The problem was due to my test machine starting out with the interrupt flag disabled. That value was causing the scheduler to start apps with the interrupt flag off.
Packaged the new version of Snowdrop OS, and re-tested on real hardware.

day 87
Re-visited the APM power off code, in hopes of getting it to work on machines other than Bochs.
The power off code now works in Bochs, VMWare, and an old laptop (late 90s). Unfortunately, newer computers don't seem to have APM any longer, causing the code to fail.

day 88
Improved aSMtris to allow "last chance" movement after a drop.
Factored APM power off functionality into the kernel, offering it to consumers via a kernel interrupt. Updated the shutdown app.
Found a tool that can create bootable USB keys from my floppy image. I'm still experimenting with it, and may include it in the download pack.
Reverted aSMtris "last chance" after a drop, because I found it to cause me to make more mistakes.
Added to the tool set a tool which creates bootable USB keys from the Snowdrop OS floppy image.

day 89
Found a bug in aSMtris which I'm fixing before releasing: pieces which spawn right on top of a tall stack are not rendered, and the "last chance movement" counter takes longer than it should.

day 90
Fixed the aSMtris bug. The counter which allowed pieces to be slid was not initialized properly when the piece spawned right on top of a tall stack.

(the third version of Snowdrop OS was released here)

day 91
I want add delete/write capabilities to the FAT12 code, and shape it into a nicer file system driver.
Started on FAT12 "delete file" functionality.

day 92
Small cleanups to a run script.
Wrote the "mark root directory entry as deleted" part. I'm happy to see that booting a DOS image with the Snowdrop as the second floppy does not show the deleted file via "dir". Similarly, the APPS app no longer shows the deleted app.
Similarly, mounting the floppy image in Windows XP also no longer shows the deleted file, confirming the method.

day 93
Spent some time refactoring floppy code. The complexity and duplication was getting a bit out of hand, so it was time it got cleaned up and modularized into proper, reusable routines.
Completed the "delete file" workflow. It still needs some testing, but it looks like it's working for empty files, single-cluster files, and multi-cluster files.

day 94
More refactoring in the floppy code. It's time consuming and requires concentration, but it's satisfying when the resulting code is easier to read.
I finally broke down the large and scary "load file" routine into reusable parts, reducing its size to less than half of what is was.
Improved both "delete file" and "load file" to account for how zero-sized files store their first cluster number in the root directory entry.

day 95
I'm now writing routines meant to support the "write file" workflow.
Started with a simple "count free clusters", and was happy to see that the in-use and free cluster counts added up to the right total amount.
I think the best way to test either delete or write workflows will be when both are available.
I'm continuing on with "get free root directory entry" and "count free root directories".

day 96
Finished the "get free root directory entry" routine.
Finished the "count free root directories" routine.
Added better error messages for fatal floppy failures.
I now have all the parts I need to write the "write file" workflow.
Since the "write file" workflow is complex, I've started by writing out pseudo-code.
"load file" workflow now also returns the file size in bytes.

day 97
I've begun implementing the "write file" workflow.
After about two thirds in, I have a bug whereby every other free cluster is skipped, for some reason.
I fixed the bug. I was using a dummy value that exceeded 12 bits when building the cluster chain. This corrupted nearby cluster values in FAT, causing the cluster-skipping behaviour.

day 98
The "write file" workflow is now completed, but what I get is a crash, and no file yet.
The issue was an uninitialized pointer. Test file is now created. I can read its contents via DOS, but not via Windows XP, for some reason.
Another symptom of this problem is that Snowdrop's delete code dies trying to delete this file.
If I write a zero-sized file, the problem does not occur.
Snowdrop "delete file" code reads an unexpected value from FAT from the first cluster of a written file. The bug must be with how I update the FAT.

day 99
I've isolated the root cause of the FAT write bug to only odd FAT cluster entries. This is promising.
Fixed the bug. I had intended to shift left by a nibble (0xABCD to 0xBCD0), and shifted by one bit, instead of 4.
Windows XP now shows the written file properly, without any complaints.
A lot more testing is needed, including edge cases of full floppies, large files, etc.
Factored out the large FAT12 driver file into four.
Hooked up disk service interrupts and began writing a test app to exercise the disk routines.
Fixed a bug caused by a missed segment assignment.

day 100
Ran some basic tests of file creation on real hardware, via both USB and real floppy disk.
Wrote two test apps which fill the floppy data area (with large files), and which fill the root directory with many small files, respectively.

day 101
Wrapped vram and virtual "set cursor" functionality and exposed it via a new interrupt.
Implemented a "write attributes to screen" interrupt.
Updated the "get task status" interrupt to also return whether the task's display is currently active.
This interrupt can now be used by tasks that wish to take control, and not yield again, but only when their display is made active.
Implemented a "clear screen" interrupt.
Started work on a file manager, which can be used to browse and manage files.

day 102
Contributed further to the file manager, mainly around the UI.

day 103
More work on the file manager, focusing on paging.

day 104
More work on the file manager paging. Rudimentary page back/forward works now.

day 105
Added code to handle the "disk empty" case.
Re-structured code to allow re-initialization, effectively allowing user to work with multiple disks by swapping and re-initializing.
Fixed a multiple month-old bug which manifested itself if keys were pressed during kernel initialization.
The bug had no effect on some machines, froze the keyboard on others, and froze the mouse on others.
Tested the fix on virtual machines and real hardware.

day 106
The file manager can now delete the highlighted file, after waiting for user confirmation.
Initialization code is probably lacking something, as deletion from pages 2 and on bugs out the state of the program after re-initialization.
I was incorrectly restoring dirty register values after a re-initialization. The problem has been fixed.
Factored out box and box title drawing routines.
Continued work on the file manager UI.

day 107
More work on the file manager UI.
Implemented an unsigned 32bit "integer to string" routine.
The file manager now reports file sizes.
Extended the "integer to string" routine with two formatting options.

day 108
Fixed a buffer overrun bug in the "add commas" string format routine.
Fixed a number formatting bug whereby some commas were missing.
File manager now reports the amount of free disk space, relying on a newly-added kernel interrupt.
File manager now reports the amount of available file slots on disk.
Added indicator labels for previous and next pages.

day 109
Factored out "read line of text from user" functionality from the shell into an interrupt.
Unfortunately, despite my intentions, the "switch virtual display" workflow of the shell is preventing a bit of code re-use I was aiming for. The shell doesn't just read user input into lines of text; it also has to respond to "change virtual display" key commands.

day 110
Added two more formatting options.
Started working on a "page X of Y" label at the top of the file box, in the file manager.

day 111
Finished the "page X of Y" label, for the file manager.
The file manager is now complete for the time being.
Started work on the text editor app.

day 112
Implemented a string utility which converts 8.3 dotted file names to FAT12 space-padded uppercase format.
Started factoring out useful routines into a set of "common" source files that can be easily included in and re-used by Snowdrop OS apps.

day 113
Work continues on the text editor app. User file prompt is now finished.

day 114
Added a "dump virtual display to buffer" interrupt.
Added a "dump characters to virtual display" interrupt.
Spent half an hour debugging an issue caused by one wrong letter (used DI register instead of SI).

day 115
Wrote the transformation code which runs when the user saves the file.
Fixed a bug in "dump characters" routines which caused erratic behaviour for character counts of zero.
Fixed a bug in "read user input" routines which caused the escape key to be recorded as a key stroke.
TODO: disallow zero-length file names in the text editor.
TODO: write a 8.3 filename validator

day 116
Added an interrupt which validates 8.3 filename strings. I was pleased that I wrote it in one go, and the unit tests showed no bugs.
The text editor app now re-prompts user if file name input was invalid.
Factored out some re-usable screen functionality.
Bad news: sometimes, the File Manager app shows an extra, ghost file on the last page. This bug started manifesting itself as the size of the app changed due to the "common" routines changing.
A weird bug has surfaced. I have been testing the file manager with two hundred 10 byte files. Out of all these, one of the files, on page 5 of 14, shows a 3.5Mb size, instead of 10 bytes.

day 117
The bug from yesterday is proving to be nasty. Adding a byte before the "root directory" buffer shifts the bytes which represent the erroneous file size forward.
It's hard to debug, because I can't insert debug statements without running the risk of hiding the error.
Skipping some calls before the area where the file size is printed required padding with NOPs, to maintain the same binary size, to keep the error occuring.
This bug has already cost me a few hours, but I have a good lead.
I converted the 3Mb figure into bytes, and it they were 32, 49, 52, 0 (note that x86 is little endian).
I tried ASCII, and noticed that 32, 49, 52 represent the string " 14". During my 10 byte file test, I have 14 pages of files, so this was a hint to the routine which prints "page X out of Y".
Inspected the "page X out of Y" code, and if I change "itoa's" formatting option from 3 to 2, the problem goes away.
Looks like the culprit is the "remove leading zeroes" fragment, invoked when formatting an integer with options 3 and 4 (for interrupt 0A2h).

day 118
I have finally fixed the nasty bug from two days ago.
The fix involved changing a single letter. An incorrect data segment meant that the routine clobbered the caller's memory rather than use its own temporary buffer.
Improved the "dump X characters to screen" call to print question marks for non-printable characters such as backspace, line feed, etc.
Replaced aSMtris's ugly hand-drawn arrows with nicer, ASCII ones.

day 119
Brought the debug utilities to allow inclusion in apps as well.

day 120
Wired together existing "get cursor position" routines, and exposed this functionality via an interrupt.
Started working on cursor movement routines for the text editor.

day 121
Work continues on the text editor, on handling special keys, such as home, escape, etc.

day 122
More work on the text editor, around character printing.
Fixed a bug in the kernel's "output string routine", whereby a backspace character printed on column 0 would erroneously print a character rather than behave like backspace should.
The text editor can now save.
The text editor is good enough for now, as it does everything I had planned.

day 123
The two test apps which fill the disk with files now prompt the user for confirmation at the beginning.
Documented the new kernel service interrupts, and cleaned up source code comments.

(the fourth version of Snowdrop OS was released here)

day 124
I would like "format disk" functionality in Snowdrop OS.
Additionally, I want an utility which makes Snowdrop OS bootable disks, similarly to how the DOS SYS command works.
For "format disk", my plan is to use DOS or Windows to format a floppy disk, and then grab the first 33 sectors, saving them as a file that I can then raw-write from within Snowdrop.
For "make bootable", it will be like above, plus the 512-byte loader, plus straight file copying (kernel, configuration file, etc.).
Implemented a "format disk" service interrupt, and a small app to test it.
I don't yet have a proper FAT12 image, so I tested with a hand-created text file of the same length at the FAT12 image.

day 125
Used existing tools in my toolchain to make a script which generates a blank floppy disk image, and then extract the first 16.5kb.
The first 16.5kb of an empty 1.44Mb floppy disk image is sufficient to format a new floppy disk by simply raw-writing it starting at logical sector 0 on the disk.
I've always had a strong preference for very simple toolchains. Being able to generate the FAT12 image with no additional tools was very pleasant.

day 126
The format app now prompts the user for confirmation.
Wrote a "write boot loader" service interrupt.
Started on the "make boot" app, which makes a floppy disk bootable, and copies core Snowdrop OS files to it.

day 127
Refactored FAT12 driver initialization code.
The FAT12 driver now stores the disk's boot sector upon initialization to make OS core file transfer to a new disk easier.

day 128
The "make boot" app now copies the Snowdrop OS core files to a new disk, prompting the user to insert the correct disk at every step.
Although I still have to test on real hardware, the "make boot" app behaves well in virtual machines.
For now, I am happy with it, and am moving on to adding support for copying files to the file manager.

day 129
Implemented support for reading a file in the file manager.
Fixed an integer-to-string bug whereby after adding commas, the wrong few bytes of memory would be clobbered.
The cause was incorrectly nested push-pop pairs.
Implemented the "write loaded file" workflow.
Adjusted the "empty disk" workflow to take into account that a file may have been loaded from before.
Save must work even when the disk is empty, to support copying a file to another, empty disk.
File manager now reports an error message when it cannot allocate memory.
Created a test app which exercises 32bit integer-to-string formatting.
I decided to make this test app because I've found and fixed more bugs in those routines than I was comfortable with.

day 130
I decided I wanted basic support for serial port communications.
Interacting with the serial port is proving to be easy. I can already initialize, as well as transmit (via Bochs, to a file on the host computer) and receive (via HyperTerminal connecting to Bochs).

day 131
Arranged all serial port debugging scripts into their own directory inside runscripts\

day 132
Wrote a test app which registers a user interrupt handler to receive serial port bytes.

day 133
Added a "get serial driver status" system call, just like for the mouse driver.
This is because the user can choose to not load this driver, just like the mouse one.

day 134
Added a kernel interrupt that can be used by consumers to register custom interrupt handlers.
Converted kernel initialization and apps to rely on the above call to register their handlers.
The "receive bytes from serial port" app now also sends key presses over the serial port.
Wrote a small test app which sends a message over the serial port.

day 135
It looks like seemingly unrelated work has affected the file manager app, which crashes when it loads a file, in preparation for a copy.
The cause was a common library routine which destroyed a register, causing the file manager to clobber the kernel space, likely the FAT12 driver.
Inspected all common library routines for similar problems; found no additional occurrences.
My end-to-end format and "make boot" test includes starting from an unformatted disk, and finishing with a bootable Snowdrop OS disk, which boots into aSMtris.
The initial test has failed. All I get is a disk showing all sorts of 300Gb file entries.
It looks like the formatting utility reads the FAT12 image from the disk to which it is supposed to write it, rather than from the disk current when the operation started.
I'll have to fix that tomorrow.

day 136
Split formatting system call into a read-write pair.
After combinations of computers and wires, serial port communications finally worked on real hardware. The problem was either a faulty DB9 gender changer, or an overly sensitive USB-to-serial converter.
As soon as I used a no-frills null-modem cable, I had two 90s laptops talking to each other via the serial port.

day 137
Tested, organized, and documented the new scripts.
Documented the new kernel service interrupts.

(the fifth version of Snowdrop OS was released here)

day 138
Since Snowdrop OS can now communicate via the serial port, it's time to make a multiplayer game.
However I choose to handle the networking part, I need a way to buffer received bytes.
Implemented a byte-based circular queue to be used to store bytes received via the serial port.

day 139
Tested and cleaned up queue routines.
Started work on the multiplayer game. It will be a snake game where each player tries to not collide with the enemy, walls, or their own snake.

day 140
Work continues on the snake game. To begin, I want to be able to control the snake using the keyboard.

day 141
The "local" snake can now be moved with the arrow keys.
The "remote" snake can now be moved via HyperTerminal.
Began work on the networking routines.

day 142
Wrote a service interrupt which gets the ASCII character and attribute byte at a specified row-column location in the virtual display.

day 143
Implemented collision detection and transmission of "win" or "lose" packets.
I have been testing using HyperTerminal as the client, and looking at the bytes I receive, while sending directional keystrokes.
Implemented game won and lost workflows.
Implemented the game start sync for server mode.
When both snakes collide at the same time, the winner is now randomly chosen.

day 144
Started on the client mode of the game.
This is a crucial part of the development, as it is when the two bridge halves have too meet up and connect.

day 145
I've tested all I could without actually copying the executable to a floppy and trying it on my two laptops connected via serial cable.
In client mode, the game now updates snake positions based on data from the server.
In client mode, the game now handles "game over" packets.
Cleaned up code a bit.

day 146
Running the game on real hardware shows that the client redraws the snakes only sporadically.
The issue was that bytes were dequeued from the buffer and thrown away if the wrong procedure got to them first.
Fixed it by introducing a "queue peek" procedure and relying on that.
The snakes are now synchronized!
Fixed an issue where the client would render the snakes' heads in the position where they collided, overlapping borders, each other, etc.
Cleaned up colours and messages.
Added obstacles which cause you to lose the game on collision.
Users can now cancel out of "waiting for client/server", exiting the program.

day 147
Tested the "peek" queue functionality more thoroughly.
Documented new interrupts.

(the sixth version of Snowdrop OS was released here)

day 148
Started adjusting the text editor, to create a new slide show presentation app.

day 149
The Presenter app is now ready. It only took me a few hours to write, as I've used the text editor as a starting point.
Created a few sample slides.
Updated the apps app to list apps in multiple columns. This was necessary because the addition of the Presenter app, a single-column display would've scrolled off screen.

day 150
The Presenter app now lets the user specify the prefix of the slide file names, to allow loading of multiple presentations from the same disk.

day 151
The Presenter app now provides better error information to the user when the first slide cannot be found.
Added an interrupt which converts FAT12-format file names to 8.3 dot file names.

(the seventh version of Snowdrop OS was released here)
If you use the materials on this page, or any other page on this web site, you do so at your own risk. They are provided "as is". No warranty is provided or implied. I neither guarantee that the materials will work, nor that they will not be harmful in any way.

Snowdrop OS assembler and debugger

Electronic circuits - CMOS buffer

Electronic circuits - driving higher current loads through parallel port

Electronic circuits - interfacing a Nintendo NES from Snowdrop OS

Electronic circuits - 3-bit current buffered DAC

Electronic circuits - stepper motor driver controlled by Snowdrop OS

Electronic circuits - parallel port interface

Snowmine - a Minesweeper-like game for Snowdrop OS (in x86 assembly)

Storks - a matching game for Snowdrop OS (in x86 assembly)

Electronic circuits - interfacing with a 16x2 LCD via parallel port

Electronic circuits - square wave vs. sine wave (audio differences)

Electronic circuits - Catch That LED!

Electronic circuits - parallel port light show

Electronic circuits - the Annoizer (555 speaker circuit)

Intellivision development - Hotel Bunny

Coverage of my projects

Interviewed in the Retro Gamer magazine

My homebrew cartridges

ZX Spectrum development - Husband Chores (in Z80 assembly language)

No Snakes! - a multiplayer game over serial port

Sega Dreamcast development - Overbearing Burgers

Snowdrop OS - my operating system from scratch, in assembly language

libzx - ZX Spectrum game programming library (Z80 assembly language)

Compact Pong - game in C# for the Pocket PC (Windows Mobile 2003)

TOTP (time-based one-time password) authenticator in C# (.Net)

aSMtris - Tetris in assembly language (x86, 16-bit)

Balanced Diet (GBA) limited edition

Gameboy Advance development - Balanced Diet

Atari 7800 development - Poetiru

Arcade ROM hacking - Knights of the Round translation

PocketStation development - Pocket Worm

Sega Game Gear development - Burgers of Hanoi GG

Pokemon Mini development - Mini Cookie

Magnavox Odyssey2 development - Red Green

Sega Dreamcast VMU development - Raining Squares

Nintendo GameCube development - Mama Bear Puzzle

Nintendo Wii development - Groundhog Puzzle

Sega Saturn development - Saturnade

Atari Jaguar development - Jagmatch

Sega CD development - Blackjack CD

Nintendo 64 development - Don't Be Square

Commodore 64 development - Tube64

Sega 32x development - Eight Queens

WonderSwan (Mono) development - Swan Driving BW

WonderSwan Color development - Swan Driving

Animal Keeper - a JavaScript and HTML5 Canvas game

3DO development - Space Invaders Invaders

Sony PlayStation development - The 11th Power

Sony PSP development - Newton Voyage

Nintendo DS development - Geoincursion

Gold of the Kingdoms - an XNA/C# homebrew game

Blue Elf 2 309-in-1 JAMMA PCB - troubleshoot controls not working

Fractals in JavaScript and HTML5 Canvas

Angry Video Game Nerd (AVGN) theme song on the Gameboy Advance

Novice calligraphy - Gothic hand, with letter guide

Video compilation of my classic console homebrew games

Seven segment display circuit with the 4511 decoder and the 4029 counter

A simple Atari 2600 joystick tester circuit

555 timer and 4017 decade counter - traffic lights circuit

Catch That LED! - an electronic game circuit

Capacitor study circuit

BlackBerry PlayBook development - Sheepish Bearings (Native SDK, OpenGL)

Neo Geo Pocket Color development - NGCollector

Neo Geo development - Neo Thunder

Atari 5200 development - Shooting Gallery

ZX Spectrum development - simple input/graphics example

Vectrex development - Scalar Ownage

Nintendo Virtual Boy development - Real Danger

Gameboy Color development - Burly Bear vs. The Mean Foxes (GBC version)

Sega Master System development - Burgers of Hanoi

Colecovision development - Mowleco

TurboGrafx-16/PC Engine development - Alddee

Atari Lynx development - Catkanoid

Nintendo NES development - Invaders must die!

Atari 2600 development - Snappy (batari basic)

Super Nintendo development - Bucket

Gameboy Advance development - smgbalib library

Airplane Vegas slot machine

Sega Genesis development - Gen Poker

(2004) Project One - first university game programming club project

Gameboy development - Burly Bear vs. The Mean Foxes

(2006) RGB Overdose - university programming contest entry