[back to main site]
Snowdrop OS - a homebrew operating system from scratch, in assembly language
This is the development log of Snowdrop OS. 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)

day 152
Began writing a debug system call which dumps the values of all registers to video ram.

day 153
Finished the "dump registers" call. It now also displays up to 4 bytes at the top of the stack, while not crossing segment boundaries.

day 154
Added a scheduler interrupt which allows a task to exit, but keep its resources.
It will be useful for programs which provide services to other programs via the interrupts they register.
Decided to change approach from a "exit and keep" interrupt to a "set task lifetime" interrupt, with the task invoking the same interrupt as when exiting normally.
This keeps the app contract unchanged, not increasing its complexity further.
Since this was implemented with an additional task state entry, I was thankful to my past self for leaving a number of unused bytes in each task state entry; it made this change painless.
Implemented a "set task lifetime" system service interrupt.
TODO: NOOP handlers for custom user interrupts
TODO: "get current handler" call
TODO: finish install/call example

day 155
Added NOOP custom user interrupt handlers.
Finished install/call example apps.

day 156
Improved the example chained interrupt handler to properly maintain all registers when invoking the previous (old) interrupt handler.
This required some meticulous stack operations.

day 157
Further improved the chained interrupt handler by reducing its complexity by a third.

day 158
Improved chained interrupt handler by reducing its size by another third. This was possible by no longer using the DS register to access memory.
Spent some time re-learning stack frames, since that is relevant material to my recent work involving stack manipulation via BP.

(the eighth version of Snowdrop OS was released here)

day 159
I intend to make Snowdrop OS more approachable for game development. As such, I've decided to create a software sprites library that is easy to use by consumers.
Currently, the mousegui app already shows an object (in this case the mouse pointer) that can be moved "over" a background.
I've used it as the starting point, and am now in the process of generalizing some of the app's routines.

day 160
Continued work on the sprite library. It now has enough functionality that I transitioned the mousegui app to use the library.

day 161
Started adding a simple parallel (LPT) port driver. I'm not yet sure how far I'll take it.
I can now output bytes through the parallel port and see them in a dump file in Bochs. I haven't yet tried it on real hardware.

day 162
Factored out graphics mode initialization and restoration logic into a graphics module.
Moved non-sprite specific code into the graphics module.
Renamed and commented all graphics and sprites functions written so far.
Added sprite hide/show routines.

day 163
Cleaned up and commented parallel port driver code. Also registered an interrupt handler which sends bytes to the parallel port.
Began writing an app which outputs a byte to the parallel port, while letting the user modify the byte's value.
Adding overlapping sprites revealed some issues with how background rectangles were saved and restored.
In response, I made fundamental changes to how background rectangles are saved.

day 164
The sprites of the "sprite demo" app now move in a cycle.
The "parallel port test" app now also allows rotation of the bits of the number being output.
Removed the "wait for port ready" step, since it made real hardware freeze.
The parallel port driver now reports when the computer does not have a parallel port.

day 165
I built a small circuit which connects 8 red LEDs to the data pins of the parallel port.
Through the "parallel port test" app, the LEDs can now be turned off or on!
I'd like to be able to load graphics from BMP files, so sprites can be created from them.
I've started reading about the BMP format.
I'm initially going to not support multiple palettes - just the default VGA palette.
I found that nobody has made a 256-colour test BMP which has a correct (matching that on a computer) VGA palette.
I've corrected a test BMP whose palette was almost VGA-proper. It is now suitable as my test BMP file. It is also a good starting point to create further BMPs, as it has the correct VGA palette.
Started working on a test app which displays my test BMP image.

day 166
Cleaned up "parallel port test" app.
Fixed reading mismatched height and width from BMP headers.
Experimented with BMPs created with both GIMP and MSPaint.

day 167
Wrote a function which reverses the order of all horizontal pixel lines of a loaded BMP file. This is so that they are in the same order as when copied to video memory.
Snowdrop OS can now render a 256 colour BMP file! However, palette loading is not yet supported.

day 168
Modified the "sprite test" app to load sprite data from a BMP file.
Introduce a vsync procedure in the graphics module. This is meant synchronize with the monitor's vertical retrace, preventing sprites from flickering.

day 169
Began work on loading palettes from BMP files.
I can now load 256-colour BMP palettes, but there's a mismatch between 8-bit BMP palette entries and 6-bit VGA palette entries. This causes images to look very strange at this time.
Converting 8-bit colour to 6-bit colour was as simple as shifting each palette colour right by 2 bits.
I'm having an issue with BMP files because they may or may not contain a so-called "colour space information".
Resolved my previous issue by relying on the "remaining header bytes" header entry, which helps offset correctly to the beginning of the palette data.
Factored out newly-added graphics and BMP routines into their respective modules.
Moved 8-bit to 6-bit conversion to the BMP-specific routine, fixing a bug in the save-then-load palette workflow.
Added a routine which fades the screen to black via palette modification.
Simplified palette loading and saving routines.
Cleaned up kernel initialization order. This was long overdue.

day 170
Split out the fade effect routine into a new "graphics effects" module, so apps who don't need effects don't waste memory on the effects module's buffers.

day 171
Made the fade effect much smoother by relying on subtraction rather than bit shifts.
Tested loading of BMP images created by various editors (GIMP and MS Paint).
Created BMP test image.
Converted all sprite code to work with 16x16 pixels sprites rather than 8x8. The problem now is that they flicker. It takes too long to render 50 sprites of 256 pixels each. I have to find a solution to this.
I've tested my flickering sprite code on multiple systems, and the results are strange: Old laptop doesn't flicker, new laptop does. Bochs VM doesn't flicker, VMWare does...
I'm starting to think this is related to whether vsync is properly raised.

day 172
I'm trying to update all sprite code so that it works with any size square sprites (up to a limit).
I've hit a problem where it seems like I can't use all the memory I'm setting aside for sprites. Something is overflowing somewhere, and it's very difficult to troubleshoot.
Reverted to a "last known good". It will probably be easier to start again than to get out of the rabbit hole I am in.
While making progress again, I identified the issue to be a loop index which wasn't preserved.
Fixed a small issue whereby sprites near the edges of the screen became garbled.
Tested "destroy sprite" functionality.
The sprite library now works with square sprites of any size up to 16x16 pixels. This means that they can moving objects as large as characters and as small as bullets.
TODO: Don't crash when the parallel port is not available

day 173
Simplified background generation routine for the "sprite test" app.
Factored out "allocate memory or exit" routine, since it is common to several apps.
While working on making the parallel port driver perform a NOOP when the port is not present, I found that simply entering the "send byte" interrupt freezes computers without the serial port.
The issue was a simple oversight on my part: the handler wasn't installed when no parallel port was present.
Introduced a "get parallel port driver status" interrupt, to keep consistent with other driver status interrupts I've introduced so far. Updated test app to take driver status into account.
TODO: Factor out the high-level workflow of displaying a bitmap on screen, directly from a BMP file.

day 174
Clean up and document new features.

(the ninth version of Snowdrop OS was released here)