[Today’s random Sourcerer profile: https://sourcerer.io/hoganmcdonald]

The Princess is in Another Castle

In my last retro-themed article, I explored the joy of using a somewhat modern (at least in DOS terms) GCC compiler named DJGPP to write protected-mode DOS applications and games that would run on any Intel 80386 processor or higher running any DOS variant (MS-DOS, DR-DOS, or even FreeDOS).

This time I wanted to get a bit closer to the metal. Sure, we were poking values directly into video memory, but the executable was hefty in size and required a 386. A 386 can run Windows 95 (albeit slowly), so I wanted something more old-school. I recalled the first computer that really taught me how to program — a Tandy 1000 with an 8086 processor and one megabyte of RAM — and pondered the multitude of good, complex software titles it could run.

While having a version of GCC for a retro computer certainly has practical use, a good chunk of the fun with retro computing isn’t practical. If we wanted practical, sensible computers, we’d all be running Linux laptops, right? No — I wanted something completely impractical yet flexible and easily accessible.

DOS is Hard

DOS was never known for its ease of installation. Some older computers, especially 80286 CPUs and below, were not entirely 100% IBM-PC compatible. Because of this, they would provide a special basic driver layer called IO.SYS (or IBMBIO.COM on IBM machines) that was tailored specifically to that motherboard and BIOS. Simply formatting a drive with a pure MS-DOS system disk on an older computer may not make it bootable. Newer versions of DOS on 386 processors and above addressed this, but nevertheless DOS wasn’t (and still isn’t) anywhere near as accessible as modern operating systems.

The FreeDOS project has worked tirelessly to provide a stable retro operating system for as many computers as possible, even IBM-XT. Installation is generally a breeze as long as you can boot from a CD-ROM drive. Booting from a floppy and then installing via CD-ROM is certainly possible but a bit more challenging for new DOS users. If you have an old machine in your closet, installing FreeDOS and putting it to work is certainly an excellent task for a rainy weekend.

Closer Still

Despite the excellent API that FreeDOS and other DOS systems provide, many developers, especially in the 80’s, needed more performance than DOS (and even BIOS) routines could provide. As long as these programs were the only application running on the system and no conflicting drivers interfered, things generally went fine. Displaying text and graphics on the screen is faster with direct copies to memory than via DOS and BIOS calls.

These programs became known as “ill-behaved” by Microsoft because they bypassed everything and make direct calls to the IO ports and moved data in memory in potentially dangerous ways. This made them tricky to virtualize under Windows, but that doesn’t concern us here. Accessing the bare metal is part of the fun!

To DOS or Not to DOS

Installing MS-DOS today may not be a possibility on some systems. Finding the original installation disks for ancient systems is a challenge at best, and while many have no issues downloading so-called “abandonware”, I can’t ethically advocate that to others.

This legal quandary is even worse for developers who wish to release software and games for DOS-based systems. Sure, many will have some kind of DOS installed, but others, especially those who use Compact Flash IDE adapters for hard drives or have no floppy drives or disks, may find this a challenge. FreeDOS can be an excellent alternative in this situation, but I wanted a solution that required no operating system and could run without any commands or configuration from the user.

Don’t Copy That Floppy

I’ve always wanted to write an operating system. I might yet someday, but that’s a monumental task that generally involves a lot of people. When researching options for assembly language programs that would run on older machines, I ran across Mihail Szabolcs’s FloppyBird game on GitHub. I was amazed by what I saw.

FloppyBird is an entire single-button arcade-scroller type game that is written entirely in assembly. It treats the user to vibrant VGA graphics solely by fiddling directly with the hardware. No two hundred megabyte graphic driver installs necessary!

FloppyBird is an impressive enough feat, but I was most enthralled by the boot sector that Mihail added in front of the main program. The boot sector on PCs is limited to 512 bytes, so the code must be astonishingly compact. Usually a boot sector loader reads the next few sectors of code from the disk in a safe area of RAM and jumps into that code. FloppyBird does just that, allowing for a game to be played on any machine without a hard drive or any kind of operating system.

I needed the boot sector code but didn’t need the VGA game, so I cloned most of the boot sector and several low-level routines and wrapped them into a thinner loader named Retrokern. I then wrote a very basic character movement algorithm to display how a character could move around the screen. I suspect it wouldn’t be hard to port the Legend of the Sourcerer Ruby game I wrote a few months back to use this system.

Getting the Boot

If you want to run Retrokern and its demo on your machine, just write it to a USB stick via Rufus in Windows or various GUI tools in Linux. I’m hesitant to document how to write a USB stick via Linux with “dd” because if you don’t use the right drive letter you can overwrite data, so if you aren’t sure how to do this, please use a GUI tool. Once your USB key is ready, put it in your system then press the boot selection key (usually ESC, F8, F9, F12, or Del) at boot and choose your USB key. If you don’t have USB on the computer in question, you will have to use a floppy disk, CD-ROM, or Compact Flash IDE adapter.

If you have a computer with a UEFI BIOS (most systems built in the past 3–5 years will have this), you may have to disable UEFI or enable legacy support to boot Retrokern. Nearly every BIOS allows this. You may also have to disable Secure Boot, if enabled. You can always re-enable these things later.

Assembling the Basics

You can browse the full code here:

https://github.com/sourcerer-io/retrokern

The code is documented but I wanted to highlight some interesting sections. Before I do that, I’ll need to cover some assembly basics. If you already know assembly, you can skip the next three paragraphs.

Assembly instructions tend to involve two “variables”, or registers. These variables are actually CPU registers, tiny bits of extremely fast memory inside the CPU. Rather than reference bytes of memory each time data is needed to be accessed, the CPU can store very small amounts of data to be processed. In the 8086’s case, these registers are 16 bits long. The Intel 386 CPU increased this to 32 bits, and this increased to 64-bit with the AMD64 and Intel 64-bit extensions.

These tiny variables are often used in counting, function calls, and math. For example, the “i” integer that is often used as a counting variable is often compiled to the “cx” (or ecx in 32-bit terminology) register. This allows the CPU to increment or decrement this counter very rapidly without having to store this value in main memory.

The “mov” instruction moves data from memory to a register, from register to register, or specific values into a register (or most combinations of these). Once that data is in a register, you can use operands like “cmp” (compare) or “test” to perform “if” statements on these value and “jz” (jump if zero), “jne” (jump if not equal), or “je” (jump if equal) to another part of code.

In this section:

xor ax, ax

int 0x16 ; AL will have ascii code

; if up

cmp al, K_UP

je .key_up

We call the BIOS interrupt 16 with the ax register being 0 to request a key from the keyboard. The value is returned in the al register. We then take that value and run “cmp” instructions on it to see if it matches various keyboard scan codes. In this case, we test for K_UP, which is previously defined as the “w” key, and if it matches then jump to the “.key_up” section of code.

Note that “xor ax, ax” is a tiny bit faster than “mov ax, 0”. Both pieces of code do the exact same thing, but “xor” is resolved earlier in the CPU’s execution pipeline than the “mov” instruction. Plus, “xor” is cooler.

Please Interrupt Me

CPUs can process interrupt requests, either called from external devices like the keyboard, or via software. An interrupt signals the processor to stop what it is doing and process the routine associated with the interrupt. Once this is finished, it returns and continues executing code.

Software interrupts are done via the “int” call, whereas hardware interrupts are triggered via circuitry on the motherboard. Either way, the interrupt vector table is referenced. The first kilobyte of RAM provides a table of jump addresses to routines in BIOS (or in software, if later defined) to handle these events.

Part of Your World

Moving data around is great, but how do we access that data? In the Retrokern, we primarily use BIOS calls. These are calls hard coded into the ROM of your computer. But these routines are just other assembly language programs that perform even lower-level access, usually by manipulating memory addresses or using I/O ports.

For example, in the boot sector we call BIOS interrupt 0x13 to read disk data. However, in speaker.asm (which is unused in our game example and copied from the original FloppyBird system) uses input and output ports to produce a beep on systems equipped with a motherboard speaker. Unfortunately, motherboard speakers are rare these days, but almost all retro computers will have them.

As you might expect, these input and output commands are labeled “in” and “out” and can load data from a port into a CPU register for processing. This, in concert with shoveling data in and out of ranges of memory, responding to requests from IRQ lines (hardware interrupt vectors), and utilizing DMA (direct memory access) channels, is how your computer interacts with the outside world.

Popping the Kernel

The example included in Retrokern is trivial but can be easily expanded upon to create a full fledged game. Some basic subroutines are included, but I’ll expand on these as time goes on to provide more functionality out of the box. What isn’t there can easily be done via BIOS calls, direct memory access, or input and output port instructions. I’ve assembled a list of reference links at the end of this article.

The boot loader currently loads just over 9 sectors from the disk, but this can be modified by adjusting the boot sector code definitions. That said, the current boot sector code is limited to dealing with one segment (i.e. 65535 bytes, or 64k), but with a carefully constructed loop and multi-segment addressing this can be expanded to load at least 500k more of code and data.

If you need more than that, you’ll have to enter protected mode. Usually bootloaders call a second-stage program to do that because you only have 512 bytes in the boot sector. But, if you’re going to do that, you might as well use DJGPP as mentioned in my previous article on retro computing.

500k doesn’t sound like a lot, but when assembly language is concerned, it is generally plenty for most games and software applications, especially in 16-bit mode. The challenge comes in with extensive amounts of data. Your code can fit nicely in a 64k segment, but high resolution graphics are another story. Game designers in the 80’s and 90’s used plenty of tricks like insane levels of compression, using vectors instead of raster images, and loaded images from disk into RAM when needed.

Why Retro?

Using older technology and playing older games is fun. It isn’t always the most practical solution, but it certainly is entertaining. When programmers and designers were constrained on resources, their creativity flourished. Working in a limited environment can tease incredibly clever and innovative techniques out of any developer.

If you have an old computer, here’s another reminder to get it out of the closet, turn it on, and prepare to have some fun. I’m sure you’ll find it can do something useful, and if not, well, there’s always DOOM.

Further Reading

Ralph Brown’s Interrupt List

X86 Assembly

8086 BIOS and DOS Interrupts