Disclaimer: I have no idea what I'm doing, please don't get too mad :3
Another disclaimer: Nerd Shit Ahead
Just over two years ago, I started development on my own little operating system, or rather x86 microkernel. I got it to a place where it could allocate memory, have a super crappy, untested standard library filled with bugs, and support very little hardware. But it was a start!
As usual for my projects, it was left to rot in my projects directory for most of two years, until I picked it up again in April this year (and stopped development, again, until a week ago). I now made it useful*!
Let me introduce the world to the horrible crimes I had to commit to get a QEMU window to play Bad Apple in poor quality:
CMake is cool (might be the first one to ever say this). Makefiles though are much simpler and seem to be the norm for hobby OS projects; likely for good reason.
Here's the issue: I don't know Makefiles too well. I can get CMake to build regular C++ projects just fine, but I'd have no idea how I could get it to assemble a kernel. I did the only reasonable thing in this situation...
... and invented my own build system in a shell script!
It's only 143 lines long and does most of the work, from assembling the boot source to linking libraries, making an ISO, verifying multiboot and launching a properly configured VM. Really the only quest up to the developer is getting a cross-compiler going - just a minor inconvenience.
Building an operating system is quite simple:
There are many ways to boot an operating system: You could, for example, start by writing your own bootloader. I don't hate myself enough for this yet, and luckily there's a solution much more pleasant: simply use Multiboot and GRUB!
The multiboot standard takes care of lots of things: it launches us straight into 32-bit protected mode, provides a memory map and framebuffer however we would like it, and is capable of lots more! We only need to put some magic constants into the binary, define a stack and call out to a C++ function, reducing the amount of required assembly code to a mesely 41 lines.
GRUB supports this without issues; both it and Multiboot are GNU inventions after all. It can take care of all the required hardware detection stuff and pass us whatever it found out. QEMU can boot the kernel binary without a bootloader at all thanks to Multiboot!
Before we can play Bad Apple, we first should be able to set up a heap. At the very least, we need some way to allocate another framebuffer to get double buffering working (you wouldn't want to put a megabyte onto the kernel stack, it's only 16KB...)
As mentioned previously, our kernel gets some memory information from the multiboot struct. My system goes through all memory map entries, finds the largest one and uses that to figure out where to place its heap. Just make sure you don't accidentally overwrite anything important...
There are many different algorithms for this task, and I chose the dumbest one: a simple bitmap allocator. I split the heap up into 64-byte chunks and keep a bitmap where each bit corresponds to one of these chunks. Finding an empty chunk is a linear search through the bitmap. It's neither fast nor efficient, but gets the system going. If you think this sounds a lot like early SerenityOS days, guess where I stole the allocator from.
There's no file system. No video codecs. No audio. There's barely even a functioning String class. But we can write pixels to a framebuffer, so that immediately gives me the horrible idea of somehow embedding Bad Apple into my kernel!
All we need to do is extract all frames and encode them somehow, in other words dump all frames pixel by pixel and concatenate them without any compression into a binary because I can't be bothered to do anything in my life properly.
I then learned a cool trick that converts the binary file to an elf32 object file that can be included into the kernel using objcopy, and manually copy that object file into the build directory where the scuffed build script can pick it up during the linking phase.
Now there are these three symbols available:
02b52400 g *ABS* 00000000 _binary_bad_apple_bin_size
02c58400 g .data 00000000 _binary_bad_apple_bin_end
00106000 g .data 00000000 _binary_bad_apple_bin_start
That was easy!
With these three magic symbols, the kernel can determine where the video is located and knows the size in bytes. Since the binary data is simply a 1:1 dump of all frames, there's no information about the video stream embedded into it - thus the resolution and number of frames must be hard-coded. Fortunately for me there's absolutely no decoding necessary, the only trick required is indexing into this massive, 44 million byte array.
Here's the formula: u8 pixel = data_start[frame * size_per_frame + (y * unscaled_width + x)];
Every byte in the binary data corresponds to one pixel, there is no color information.
Theoretically even something as simple as RLE could massively reduce size, as the Bad Apple video is simply black and white. See this excellent GPN22 presentation:
I, however, still can't be bothered.
The kernel gets a suitable write pointer from a decently smart Framebuffer class and draws every pixel of a frame into it, scales the entire thing up from 96x72 to the native resolution, and finally tells the framebuffer management to swap buffers (copy the backbuffer we just wrote to into the front buffer, aka The Screen)! Now wait for a couple of milliseconds without actually having a timer and repeat this process about 6000 times, and you'll get the entirety of Bad Apple in exceptionally low quality.
Earning a single fav on Fedi has never been easier.
Nothing. I don't want to touch this project anymore. Still, here are a couple ideas: