Skip to main content

A Python-native operating system development framework for building bootable x86 and x86_64 systems. Aetherix supports both legacy BIOS and modern UEFI firmware, generates machine code directly through its own native encoder, and provides high-level APIs alongside low-level instruction control.

Project description

Aetherix

Aetherix

A Python-native operating system development framework for building real, bootable x86 and x86_64 systems.

Aetherix generates machine code directly through its own native x86 encoder — no nasm, no ld, no external assembler toolchain. It supports two genuinely separate boot paths, legacy BIOS (16‑bit real mode → 32‑bit protected mode, MBR) and modern UEFI (PE32+, 64‑bit long mode, GPT/FAT32) — and gives you both a batteries-included high-level API and raw, byte-level instruction control when you need it.

License: AGPL v3 Python 3 Platform: x86 / x86_64

from aetherix import Project

with Project("MyOS") as os:

    @os.kernel_entry
    def main(prog, drivers):
        drivers.vga.clear(prog)
        drivers.vga.print_string(prog, "Hello, world!")
        prog.hlt()

    os.build("myos.img")
qemu-system-i386 -drive file=myos.img,format=raw

Or write it to a USB stick: sudo dd if=myos.img of=/dev/sdX bs=4M status=progress conv=fsync (VirtualBox and VMware also accept the raw .img/.bin directly as a virtual disk.)


Table of contents


Why Aetherix

Most "write your own OS" tutorials hand you fragile shell scripts wired around nasm and a linker. Aetherix takes a different approach:

  • No external assembler. Machine code is emitted directly by a small C encoder core (native/encoder.c), driven from Python. Building an image needs nothing beyond a C compiler at install time.
  • Two real, independently-verified boot paths. A classic BIOS/MBR path and a genuinely separate UEFI/GPT/FAT32 path — not one bolted onto the other.
  • Verification over vibes. Every primitive this project ships with has been checked byte-for-byte against a reference (Intel SDM / objdump), and the higher-level behavior (disk loading, keyboard input, graphics compositing, UEFI protocol calls) is confirmed by actually executing built images under CPU emulation (Unicorn, QEMU) — not just by disassembling them. See Verifying the output yourself.
  • Honest about scope. Aetherix tells you what's implemented and what's a documented extension point (HAL().scaffolded()) — it doesn't fake hardware support it doesn't have.
  • Two altitudes of API. High-level (drivers.vga.print_string(...)) for getting an OS running fast, and low-level (prog.mov16(AX, 0x1234)) for full instruction-level control when you need it.

Install

pip install aetherix

Requires a C compiler (gcc/clang/MSVC) on the machine building the package — the native encoder core is compiled once, either at install time or on first import for dev checkouts. Nothing beyond that is required to build an image — Aetherix emits its own machine code. You do need an emulator (QEMU / VirtualBox / VMware) or real hardware to run what you build.

python -c "import aetherix; print(aetherix.__version__)"

For image/graphics support (aetherix.imaging, used to prepare loading screens, backgrounds, and logos for aetherix.drivers.graphics), also install Pillow — a build-time-only dependency, not needed by the OS you build:

pip install "aetherix[imaging]"

Quick start

from aetherix import Project

with Project("MyOS") as os:

    @os.kernel_entry
    def main(prog, drivers):
        drivers.vga.clear(prog)
        drivers.vga.print_string(prog, "Hello, world!")
        prog.hlt()

    os.build("myos.img")
qemu-system-i386 -drive file=myos.img,format=raw

That's the entire pipeline — bootloader generation, kernel assembly, protected-mode switch, and disk image writing all happen inside os.build(...). For the complete API (drivers, the HAL, embedding files, disk images, raw Program control, and UEFI), see API.md.

Examples

Example What it demonstrates
hello_os.py Minimal: prints fixed messages, waits for one keypress, halts.
aether_shell.py A genuinely interactive OS: a live keyboard-echoing shell with a runtime VGA cursor (moves, wraps at end-of-row, handles Enter/Backspace/Escape/Shift), a speaker beep on F1, and real power control (F2 reboot, F3 QEMU/Bochs shutdown). Verified end-to-end by scripting keystrokes through CPU emulation (tests/test_emulation.py), not just by disassembling the output.
graphics_os.py VGA graphics (Mode 13h, 320×200, 256-color): a boot loading animation, a background image, and a composited logo. Uses aetherix.imaging to prepare ordinary PNGs at build time; ships with self-contained placeholder art so it runs with no external files. Verified pixel-for-pixel and palette-for-palette correct under CPU emulation.
uefi_hello.py A completely separate boot path: a genuine UEFI application (PE32+, 64-bit long mode) on a GPT+FAT32 disk image, booted by real, unmodified OVMF firmware in real QEMU. See UEFI support.
python aether_shell.py
qemu-system-i386 -drive file=build/aether_shell.img,format=raw
python graphics_os.py
qemu-system-i386 -drive file=build/graphics_os.img,format=raw
python uefi_hello.py
qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=build/uefi_hello.img,format=raw

graphics_os.py in action

Self-contained placeholder art (assets/generate_assets.py), so the example runs with no external files — swap in your own PNGs any time, aetherix.imaging handles resizing and color quantization for whatever you give it.

Boot loading animation (a real "video" — a sequence of frames shown in order with a delay between them):

Loading animation frame 1, 16% Loading animation frame 3, 33% Loading animation frame 5, 66% Loading animation frame 6, 100%

Home screen — background and logo, prepared together with imaging.load_images_shared_palette so both render correctly against VGA Mode 13h's single, screen-wide 256-color palette:

Gradient background image Composited home-screen logo

Full example source

hello_os.py — minimal Aetherix example
"""
hello_os -- minimal Aetherix example.

Boots to real mode, sets up segments, prints a message via BIOS, loads a
32-bit kernel that switches to protected mode, clears the VGA text screen,
prints a message directly to video memory, waits for a keypress via the
PS/2 controller, then halts.

Run:
    python examples/hello_os.py

Then boot the produced image in an emulator, e.g.:
    qemu-system-i386 -drive file=hello_os.img,format=raw
"""
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from aetherix import Project, regs

with Project("HelloOS", boot_message="HelloOS bootloader starting...") as os_:

    @os_.kernel_entry
    def main(prog, drivers):
        drivers.vga.clear(prog, attr=drivers.vga.WHITE_ON_BLACK)
        drivers.vga.print_string(prog, "Hello from Aetherix!", row=0, col=0,
                                  attr=drivers.vga.GREEN_ON_BLACK)
        drivers.vga.print_string(prog, "Press any key to continue...", row=2, col=0)
        drivers.keyboard.wait_key_scancode(prog)
        drivers.vga.print_string(prog, "Key received. System halted.", row=4, col=0,
                                  attr=drivers.vga.YELLOW_ON_BLACK)
        prog.hlt()

    os_.add_file("readme.txt", "This file is embedded in the disk image via AVFS.\n")

    out_dir = Path(__file__).resolve().parent.parent / "build"
    out_dir.mkdir(parents=True, exist_ok=True)
    out = os_.build(str(out_dir / "hello_os.img"))
    print(f"Built: {out} ({out.stat().st_size} bytes)")
    print(f"Kernel sectors: {(len(os_.kernel.assemble()) + 511) // 512}")
    print("Boot it with: qemu-system-i386 -drive file=hello_os.img,format=raw")
aether_shell.py — a genuinely interactive example OS
"""
aether_shell -- a genuinely interactive example OS.

Unlike hello_os.py (which prints a few fixed, compile-time-known messages
and waits for one keypress), this boots into a live loop: it reads your
keyboard input in real time -- Shift and all -- and echoes it to the
screen using a runtime VGA cursor (aetherix.drivers.terminal) that
actually moves, wraps at the end of a row, and responds to
Enter/Backspace. There is no fixed script of what appears where; what
shows up on screen depends on what you type.

Controls:
    (letters, digits, punctuation, space)   echoed as typed
    Shift                                    uppercase letters, shifted symbols
    Enter                                    move to the next line
    Backspace                                erase the previous character
    F1                                        beep the PC speaker
    F2                                        reboot (hardware reset)
    F3                                        shutdown (QEMU/Bochs only --
                                               see aetherix.drivers.power)
    Esc                                       goodbye message, then halt

Run:
    python examples/aether_shell.py

Then boot the produced image in an emulator, e.g.:
    qemu-system-i386 -drive file=aether_shell.img,format=raw

Or verify it under CPU emulation without needing QEMU installed:
    pip install unicorn
    python tests/emulate.py build/aether_shell.img
"""
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from aetherix import Project, regs

with Project("AetherShell", boot_message="AetherShell starting...") as os_:

    @os_.kernel_entry
    def main(prog, drivers):
        vga = drivers.vga
        kb = drivers.keyboard
        term = drivers.terminal
        power = drivers.power

        kb.init(prog)  # zero the Shift-state flag
        vga.clear(prog)
        vga.print_string(prog, "AetherShell v0.2 -- a live Aetherix demo OS",
                          row=0, col=0, attr=vga.GREEN_ON_BLACK)
        vga.print_string(prog, "Type (Shift works!). Enter=newline  Backspace=erase",
                          row=1, col=0)
        vga.print_string(prog, "F1=beep  F2=reboot  F3=shutdown  Esc=halt",
                          row=2, col=0)
        term.init(prog, start_row=4)

        prog.label("shell_loop")
        kb.wait_key_scancode(prog)  # blocks; raw scancode (make OR break) ends up in AL

        prog.cmp8(regs.AL, kb.SCANCODE_ESCAPE)
        prog.jcc(regs.JZ, "shell_halt")
        prog.cmp8(regs.AL, kb.SCANCODE_ENTER)
        prog.jcc(regs.JZ, "shell_enter")
        prog.cmp8(regs.AL, kb.SCANCODE_BACKSPACE)
        prog.jcc(regs.JZ, "shell_backspace")
        prog.cmp8(regs.AL, kb.SCANCODE_F1)
        prog.jcc(regs.JZ, "shell_beep")
        prog.cmp8(regs.AL, kb.SCANCODE_F2)
        prog.jcc(regs.JZ, "shell_reboot")
        prog.cmp8(regs.AL, kb.SCANCODE_F3)
        prog.jcc(regs.JZ, "shell_shutdown")

        # Shift press/release: update the flag keyboard.read_char also
        # uses, then go straight back to waiting -- Shift never prints.
        prog.cmp8(regs.AL, kb.SCANCODE_LSHIFT)
        prog.jcc(regs.JZ, "shell_shift_on")
        prog.cmp8(regs.AL, kb.SCANCODE_RSHIFT)
        prog.jcc(regs.JZ, "shell_shift_on")
        prog.cmp8(regs.AL, kb.SCANCODE_LSHIFT | kb.BREAK_BIT)
        prog.jcc(regs.JZ, "shell_shift_off")
        prog.cmp8(regs.AL, kb.SCANCODE_RSHIFT | kb.BREAK_BIT)
        prog.jcc(regs.JZ, "shell_shift_off")

        # Any other break code (bit 7 set) -- ignore, don't echo on release.
        prog.test_al(kb.BREAK_BIT)
        prog.jcc(regs.JNZ, "shell_loop")

        # Dispatch any other recognized key, Shift-aware: compare the
        # scancode against each known entry and, on a match, print
        # whichever of its two (compile-time-known) characters matches
        # the current runtime Shift state. Same technique
        # keyboard.read_char uses internally, inlined here because this
        # loop also needs to catch Enter/Backspace/F-keys in the same
        # pass, which read_char deliberately doesn't return for.
        all_codes = sorted(set(kb.SCANCODE_SET1_ASCII) | set(kb.SCANCODE_SET1_SHIFTED))
        for code in all_codes:
            skip_label = prog.unique_label("key_skip")
            use_shifted_label = prog.unique_label("key_use_shifted")
            unshifted_ch = kb.SCANCODE_SET1_ASCII.get(code, kb.SCANCODE_SET1_SHIFTED.get(code))
            shifted_ch = kb.SCANCODE_SET1_SHIFTED.get(code, unshifted_ch)

            prog.cmp8(regs.AL, code)
            prog.jcc(regs.JNZ, skip_label)
            prog.load8(regs.BL, kb.SHIFT_STATE_VAR)
            prog.cmp8(regs.BL, 0)
            prog.jcc(regs.JNZ, use_shifted_label)
            term.putchar_imm(prog, unshifted_ch)
            prog.jmp("shell_loop")
            prog.label(use_shifted_label)
            term.putchar_imm(prog, shifted_ch)
            prog.jmp("shell_loop")
            prog.label(skip_label)

        prog.jmp("shell_loop")  # unrecognized key -- ignore and keep waiting

        prog.label("shell_shift_on")
        prog.store8(kb.SHIFT_STATE_VAR, 1)
        prog.jmp("shell_loop")

        prog.label("shell_shift_off")
        prog.store8(kb.SHIFT_STATE_VAR, 0)
        prog.jmp("shell_loop")

        prog.label("shell_enter")
        term.newline(prog)
        prog.jmp("shell_loop")

        prog.label("shell_backspace")
        term.backspace(prog)
        prog.jmp("shell_loop")

        prog.label("shell_beep")
        drivers.speaker.beep(prog, frequency_hz=880, duration_iterations=1_500_000)
        prog.jmp("shell_loop")

        prog.label("shell_reboot")
        vga.print_string(prog, "Rebooting...", row=24, col=0, attr=vga.YELLOW_ON_BLACK)
        power.reboot(prog)

        prog.label("shell_shutdown")
        vga.print_string(prog, "Shutting down...", row=24, col=0, attr=vga.YELLOW_ON_BLACK)
        power.shutdown(prog)

        prog.label("shell_halt")
        vga.print_string(prog, "Goodbye! System halted.", row=24, col=0, attr=vga.YELLOW_ON_BLACK)
        prog.hlt()

    out_dir = Path(__file__).resolve().parent.parent / "build"
    out_dir.mkdir(parents=True, exist_ok=True)
    out = os_.build(str(out_dir / "aether_shell.img"))
    print(f"Built: {out} ({out.stat().st_size} bytes)")
    print(f"Kernel sectors: {(len(os_.kernel.assemble()) + 511) // 512}")
    print("Boot it with: qemu-system-i386 -drive file=aether_shell.img,format=raw")
graphics_os.py — boot animation, background, and composited logo
"""
graphics_os -- an example OS with a real boot animation, a background
image, and a home-screen logo, using VGA Mode 13h (320x200, 256-color).

Boot flow:
  1. Loading screen: plays a short progress-bar animation (a real "video"
     -- a sequence of frames shown in order with a delay between them).
  2. Home screen: a full-screen background image with a logo composited
     on top (two separate images, blitted to different screen regions).
  3. Halts, leaving the home screen displayed.

Uses placeholder art generated by examples/assets/generate_assets.py --
run that first (or just run this script, which does it for you if the
assets are missing). Swap in your own PNGs any time; aetherix.imaging
handles resizing and color quantization for whatever you give it.

Run:
    python examples/graphics_os.py

Then boot the produced image in an emulator, e.g.:
    qemu-system-i386 -drive file=graphics_os.img,format=raw

Or verify it under CPU emulation without needing QEMU installed:
    pip install unicorn
    python tests/emulate.py build/graphics_os.img
(the emulator prints the VGA text buffer, which will be blank here since
this example only uses graphics mode -- see tests/test_emulation.py for
how the framebuffer/palette are checked instead.)
"""
import sys
import subprocess
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from aetherix import Project, imaging
from aetherix.drivers import graphics

ASSETS_DIR = Path(__file__).resolve().parent / "assets"

if not (ASSETS_DIR / "background.png").exists():
    print("Generating placeholder assets...")
    subprocess.run([sys.executable, str(ASSETS_DIR / "generate_assets.py")], check=True)

# -- Prepare assets at build time (resizing/quantizing happens here, not
#    at boot -- the kernel only ever handles already-prepared bytes) --
loading_frames = [
    imaging.load_image(ASSETS_DIR / f"loading_{i}.png") for i in range(6)
]

# background and logo will be on screen AT THE SAME TIME, so they must
# share one 256-color palette -- VGA Mode 13h has a single palette for the
# whole screen, not one per image (see imaging.load_images_shared_palette).
background, logo = imaging.load_images_shared_palette([
    (ASSETS_DIR / "background.png", graphics.GFX_WIDTH, graphics.GFX_HEIGHT, True),
    (ASSETS_DIR / "logo.png", 64, 64, False),
])

LOGO_X = (graphics.GFX_WIDTH - logo.width) // 2
LOGO_Y = (graphics.GFX_HEIGHT - logo.height) // 2

with Project("GraphicsOS", graphics_mode=graphics.MODE_13H) as os_:

    @os_.kernel_entry
    def main(prog, drivers):
        # 1. Loading animation -- plays once, then falls through.
        drivers.graphics.play_frames(prog, loading_frames, delay_iterations=3_000_000, loop=False)

        # 2. Home screen -- background, then a logo composited on top.
        drivers.graphics.show_image(prog, background, x=0, y=0)
        drivers.graphics.show_image(prog, logo, x=LOGO_X, y=LOGO_Y)

        prog.hlt()

    out_dir = Path(__file__).resolve().parent.parent / "build"
    out_dir.mkdir(parents=True, exist_ok=True)
    out = os_.build(str(out_dir / "graphics_os.img"))
    print(f"Built: {out} ({out.stat().st_size} bytes)")
    print(f"Kernel sectors: {(len(os_.kernel.assemble()) + 511) // 512}")
    print("Boot it with: qemu-system-i386 -drive file=graphics_os.img,format=raw")
assets/generate_assets.py — placeholder art used by graphics_os.py
"""
Generates placeholder art for examples/graphics_os.py using Pillow, so the
example is runnable with no external image files. Swap these PNGs for your
own artwork any time -- graphics_os.py just calls aetherix.imaging.load_image
on whatever's in this directory.

Run: python examples/assets/generate_assets.py
"""
from pathlib import Path

from PIL import Image, ImageDraw

OUT_DIR = Path(__file__).resolve().parent


def make_loading_frames(n=6, size=(320, 200)):
    """A simple progress-bar boot animation, frame by frame."""
    frames = []
    for i in range(n):
        img = Image.new("RGB", size, (10, 12, 30))
        draw = ImageDraw.Draw(img)
        draw.text((110, 70), "Aetherix", fill=(120, 200, 255))
        bar_w, bar_h = 200, 14
        bx, by = (size[0] - bar_w) // 2, 110
        draw.rectangle([bx, by, bx + bar_w, by + bar_h], outline=(80, 90, 120))
        fill_w = int(bar_w * (i + 1) / n)
        draw.rectangle([bx, by, bx + fill_w, by + bar_h], fill=(90, 200, 140))
        draw.text((bx, by + 20), f"Loading... {int(100 * (i + 1) / n)}%", fill=(180, 190, 210))
        frames.append(img)
        img.save(OUT_DIR / f"loading_{i}.png")
    return frames


def make_background(size=(320, 200)):
    """A simple vertical gradient desktop background."""
    img = Image.new("RGB", size)
    px = img.load()
    top = (20, 30, 60)
    bottom = (70, 40, 90)
    for y in range(size[1]):
        t = y / (size[1] - 1)
        r = int(top[0] + (bottom[0] - top[0]) * t)
        g = int(top[1] + (bottom[1] - top[1]) * t)
        b = int(top[2] + (bottom[2] - top[2]) * t)
        for x in range(size[0]):
            px[x, y] = (r, g, b)
    img.save(OUT_DIR / "background.png")
    return img


def make_logo(size=(64, 64)):
    """A simple circular logo icon to overlay on the home screen."""
    img = Image.new("RGB", size, (0, 0, 0))
    draw = ImageDraw.Draw(img)
    draw.ellipse([2, 2, size[0] - 2, size[1] - 2], fill=(90, 170, 255), outline=(230, 240, 255))
    draw.text((size[0] // 2 - 4, size[1] // 2 - 6), "A", fill=(15, 20, 40))
    img.save(OUT_DIR / "logo.png")
    return img


if __name__ == "__main__":
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    make_loading_frames()
    make_background()
    make_logo()
    print(f"Generated placeholder assets in {OUT_DIR}")
uefi_hello.py — a minimal, genuinely working UEFI application
"""
uefi_hello -- a minimal, genuinely working UEFI application.

This is a completely different boot path from the other examples in this
directory (hello_os.py, aether_shell.py, graphics_os.py), which all boot
via BIOS/MBR. UEFI firmware boots a PE32+ executable directly in 64-bit
long mode -- no 16/32-bit stages, no BIOS interrupts, no boot sector.

Demonstrates: console output, cursor control, and keyboard input (all via
real UEFI protocol calls -- ConOut/ConIn), plus locating an arbitrary
protocol by GUID and calling a method on it that this package doesn't
wrap with a dedicated helper (aetherix.uefi.boot_services/protocol --
the general extension mechanism for any UEFI protocol you need that
isn't built in yet).

This builds:
  - uefi_hello.efi   -- the PE32+ application itself
  - uefi_hello.img   -- a complete GPT+FAT32 disk image containing it at
                         \\EFI\\BOOT\\BOOTX64.EFI, ready to boot in QEMU+OVMF
                         or write to a real USB stick

Run:
    python examples/uefi_hello.py

Then boot it with QEMU + OVMF (get OVMF.fd from your Linux distro's
`ovmf`/`edk2-ovmf` package, or https://github.com/tianocore/edk2):
    qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=build/uefi_hello.img,format=raw

Or verify it without QEMU/OVMF installed, via real x86-64 CPU emulation:
    pip install unicorn
    python tests/emulate_uefi.py build/uefi_hello.efi

Or write it to a real USB stick (BACK UP THE STICK FIRST -- this
overwrites the entire device):
    sudo dd if=build/uefi_hello.img of=/dev/sdX bs=4M status=progress conv=fsync
Then boot from it on any UEFI PC with Secure Boot disabled (this app
isn't signed).
"""
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from aetherix.uefi.app import UefiApp
from aetherix.uefi import console, keyboard, diskimage
from aetherix import regs

app = UefiApp()


@app.entry
def main(prog, sys_table):
    console.clear_screen(prog, sys_table)
    console.set_cursor_position(prog, sys_table, 0, 0)
    console.println(prog, "Hello from Aetherix UEFI!", sys_table)
    console.println(prog, "", sys_table)
    console.println(prog, "This machine code was generated entirely by aetherix.uefi --", sys_table)
    console.println(prog, "no nasm, no ld, no C compiler toolchain for the .efi itself.", sys_table)
    console.println(prog, "", sys_table)
    console.println(prog, "Press any key to continue...", sys_table)
    keyboard.read_key(prog, sys_table, regs.R12, regs.R13)

    console.set_cursor_position(prog, sys_table, 0, 8)
    console.println(prog, "Key received. Returning control to firmware.", sys_table)


out_dir = Path(__file__).resolve().parent.parent / "build"
out_dir.mkdir(parents=True, exist_ok=True)

efi_path = app.build(str(out_dir / "uefi_hello.efi"))
print(f"Built: {efi_path} ({efi_path.stat().st_size} bytes)")

img_path = diskimage.write_disk_image(efi_path.read_bytes(), str(out_dir / "uefi_hello.img"))
print(f"Built: {img_path} ({img_path.stat().st_size} bytes)")
print("Boot it with: qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=uefi_hello.img,format=raw")

What you actually get (read before you assume too much)

Building a full production OS with GPU, USB, camera, printer, and network driver stacks is a multi-year effort even for large teams. Aetherix does not claim to hand you that. What it genuinely gives you:

  • A verified-correct real-mode bootloader: segment setup, BIOS teletype output, A20 line enable, BIOS LBA disk loading with retry, and a far jump into your kernel.
  • A verified-correct 16→32-bit transition: flat GDT construction, lgdt, CR0.PE set, far jump into a 32-bit code segment — computed with exact, non-guessed offsets.
  • Working drivers for hardware that's actually reachable without an existing OS or bus/protocol stack underneath you:
    • VGA text-mode framebuffer (clear screen, print strings).
    • PS/2 keyboard — full Scancode Set 1 (letters, digits, punctuation, Tab), Shift-aware (uppercase, shifted symbols), plus a high-level read_char helper. Extended keys (arrows, Insert/Delete/Home/End/Page Up/Down) aren't covered yet.
    • PC speaker — PIT-driven beep with an actual audible duration.
    • Power controlrestart (soft, jump back to kernel start), reboot (hard, keyboard-controller reset pulse — works on real hardware and every mainstream emulator), and an honestly-scoped shutdown/sleep_until_keypress (QEMU/Bochs magic port, not real ACPI).
    • Terminal — a runtime VGA cursor (not just compile-time-fixed text): moves, wraps at end-of-row, supports newline/backspace.
    • Graphics — VGA Mode 13h (320×200, 256-color): palette upload and image blitting, so a kernel can show a loading screen, a background, and composited images, not just text.
  • A minimal embedded read-only file archive format (AVFS) for bundling files into the disk image at build time.
  • A Python API designed so both styles work: high-level (drivers.vga.print_string(...)) and low-level (prog.mov16(AX, 0x1234), straight machine-code control).

What's not implemented (and why)

GPU (beyond VGA Mode 13h — no modern framebuffer/GOP/VBE/hardware acceleration/3D), USB, Ethernet/networking, printer, fan control, battery telemetry, and camera all need real bus enumeration or protocol stacks (PCI, a USB host controller driver, ACPI, etc.) that don't exist yet in this codebase. Rather than fake them, aetherix.drivers.hal.HAL lists them as named, documented extension points:

from aetherix import HAL

hal = HAL()
print(hal.implemented())   # ['vga', 'keyboard', 'speaker']
print(hal.scaffolded())    # ['gpu', 'usb', 'ethernet', 'printer', 'fan', 'battery', 'camera']

Accessing a scaffolded driver raises NotImplementedError with an explanation of what it needs, rather than silently doing nothing. There is also no file manager, task manager, calculator, or custom in-OS programming language yet, no Caps Lock, and no real ACPI shutdown/sleep. Building any of these is exactly the kind of contribution this project is scoped for — see CONTRIBUTING.md for a concrete, tractability-ordered list of realistic next drivers and opcodes.

UEFI support

Everything above (Project, BootSector, Kernel, aetherix.drivers) is the BIOS boot path. UEFI is a genuinely different architecture, not an extension of it: firmware loads a PE32+ executable directly into 64-bit long mode — no 16/32-bit stages, no BIOS interrupts, no MBR boot sector. It boots from a GPT-partitioned disk with a FAT32 EFI System Partition, not a flat binary. I/O goes through UEFI protocol function-pointer calls (Microsoft x64 calling convention), not int instructions. The two APIs cannot be mixed — pick one per OS you're building.

aetherix.uefi is a separate subsystem built for this from scratch:

  • A minimal 64-bit long-mode instruction set (Program(bits=64)) — REX-prefixed mov/add/sub/cmp, RIP-relative lea, indirect calls through a register, full RAX–R15 register range.
  • A PE32+ writer (aetherix.uefi.pe) producing a genuinely valid EFI_APPLICATION image (position-independent by construction, so no .reloc section is needed).
  • UefiApp (aetherix.uefi.app) — generates the Microsoft x64 ABI prologue/epilogue around your entry function.
  • Console I/O (aetherix.uefi.console, aetherix.uefi.keyboard) — ConOut/ConIn calls, UCS-2 encoded as the spec requires.
  • A generic protocol-call extension mechanism (aetherix.uefi.protocol, aetherix.uefi.boot_services) — locate_protocol + protocol.call let you drive any UEFI protocol (Graphics Output, Block I/O, File System, or anything else) by GUID and byte offset, with no changes needed to this package.
  • GPT + FAT32 (aetherix.uefi.gpt, aetherix.uefi.fat32, aetherix.uefi.diskimage) — a real, from-scratch disk image builder: protective MBR, primary + backup GPT with correct CRC32 checksums, and a FAT32 ESP containing \EFI\BOOT\BOOTX64.EFI.

Scope: this targets UEFI applications — code that uses Boot Services and returns control to firmware, like a shell command. It is not a full custom OS loader (that means calling ExitBootServices, taking over the memory map, and setting up your own GDT/IDT/paging — see CONTRIBUTING.md if you want to build that on top of this).

from aetherix.uefi.app import UefiApp
from aetherix.uefi import console, diskimage

app = UefiApp()

@app.entry
def main(prog, sys_table):
    console.clear_screen(prog, sys_table)
    console.println(prog, "Hello from Aetherix UEFI!", sys_table)

efi_path = app.build("build/uefi_hello.efi")
diskimage.write_disk_image(efi_path.read_bytes(), "build/uefi_hello.img")
qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=build/uefi_hello.img,format=raw

Verified at every layer, independently, not just by this codebase's own logic checking itself:

Layer How it's verified
PE file file recognizes it as PE32+ executable (EFI application) x86-64
Machine code objdump -M intel -m i386:x86-64
Real execution A Unicorn (UC_MODE_64) harness simulating EFI_SYSTEM_TABLE/ConOut/ConIn/BootServices (tests/emulate_uefi.py, tests/test_uefi.py)
GPT gdisk -l disk.img — "No problems found"
FAT32 fsck.vfat and mtools (mdir/mcopy), including byte-for-byte file extraction
End-to-end Real, unmodified OVMF firmware in real QEMU boots the image and prints the exact expected text

Full API details (console output, keyboard input, the protocol extension mechanism, disk image building) are in API.md, section 18.

Architecture

aetherix/
  asm.py            # two-pass label/jump resolver over the native encoder (16/32/64-bit)
  regs.py           # register / condition-code constants
  context.py        # Project: the batteries-included entry point (BIOS path)
  imaging.py        # build-time image loading/quantization (needs Pillow)
  boot/realmode.py  # BootSector builder (512-byte MBR-style boot sector)
  kernel/
    pmode.py        # flat GDT construction
    builder.py      # Kernel: 16-bit trampoline + GDT + 32-bit body
  drivers/
    vga.py, keyboard.py, speaker.py, terminal.py, graphics.py, power.py   # implemented
    hal.py                                                                 # driver registry + extension points
  fs/simplefs.py    # AVFS embedded file archive
  image/diskimage.py # boot sector + kernel + AVFS -> flat .img/.bin
  uefi/              # separate boot path -- see "UEFI support" above
    app.py           # UefiApp: MS x64 ABI prologue/epilogue + PE assembly
    console.py       # ConOut protocol calls (output, cursor control)
    keyboard.py      # ConIn protocol calls (key input)
    protocol.py      # generic protocol-method-call extension mechanism
    boot_services.py # LocateProtocol (find a protocol interface by GUID)
    guid.py          # EFI_GUID mixed-endian encoding (shared by gpt.py too)
    pe.py            # PE32+ writer
    gpt.py, fat32.py, diskimage.py  # GPT + FAT32 ESP disk image builder
native/
  encoder.c         # the actual x86 instruction encoder (C, no deps)

Every layer is independently usable — build a raw Program and hand-assemble arbitrary real/protected-mode code without ever touching Project, or add a new driver module and register it with HAL.

CLI

aetherix new MyOS          # scaffold a starter project script
aetherix info               # show implemented vs. scaffolded hardware support

Verifying the output yourself

Don't take byte correctness on faith — disassemble it:

head -c 512 myos.img > boot.bin
objdump -D -b binary -m i8086 boot.bin      # boot sector, 16-bit
dd if=myos.img of=kernel.bin bs=512 skip=1
objdump -D -b binary -m i386 kernel.bin      # (after the trampoline offset)

Or go further and actually execute it under CPU emulation — this is how real bugs (like the multi-chunk LBA disk load issue, see CHANGELOG.md) were caught, since disassembly alone proves the bytes are plausible x86, not that the boot sequence completes:

pip install unicorn
python tests/emulate.py myos.img

This prints the real-mode boot message, the disk reads that occurred (LBA, sector count, destination), and the final VGA text buffer contents — i.e. what a real screen would show, without needing QEMU/VirtualBox. tests/test_emulation.py runs this automatically as a regression test whenever unicorn is installed. The UEFI path has its own equivalent (tests/emulate_uefi.py) — see UEFI support.

Documentation

  • API.md — the complete usage guide: every layer from Project down to raw Program instructions, the HAL, embedding files (AVFS), disk images, writing a custom bootloader/kernel, the full UEFI subsystem, a common-errors table, and a full worked example.
  • CONTRIBUTING.md — how to add a native encoder opcode or a new driver, code style (including the RSP/R12 and RBP/R13 ModRM gotchas), and a concrete, tractability-ordered roadmap of realistic next contributions.
  • CHANGELOG.md — project history, including real bugs found and fixed during development and how each was verified.

Contributing

The most valuable contributions right now are new drivers and new instruction-encoder opcodes, following the existing pattern in aetherix/drivers/vga.py/keyboard.py/speaker.py and native/encoder.c. See CONTRIBUTING.md for the full guide, code style, and a roadmap ordered roughly by tractability — from small (serial port, extended keyboard scancodes) to substantial (a real writable filesystem, ACPI parsing, a full custom UEFI OS loader).

License

AGPL-3.0-or-later.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aetherix-0.1.0.tar.gz (215.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aetherix-0.1.0-py3-none-any.whl (198.1 kB view details)

Uploaded Python 3

File details

Details for the file aetherix-0.1.0.tar.gz.

File metadata

  • Download URL: aetherix-0.1.0.tar.gz
  • Upload date:
  • Size: 215.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for aetherix-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0624632cb572bc1ad35bcc3e87bc2b92bb08e0f5b0aaaaef1b1d64487377de53
MD5 3c4826ca93c665200ac2ac017a3a77f1
BLAKE2b-256 46d1e51a4f016663241bbf70369df72cc456bba6be1697b4de56f786942a6e9d

See more details on using hashes here.

File details

Details for the file aetherix-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: aetherix-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 198.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for aetherix-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2c7b0e45ed31aab53ab544838169c225d5d475f48a79b577b6f518d6bf99b704
MD5 34e57bfd879f8b9a60c9e1ddf8871f4a
BLAKE2b-256 98aa808e13ea88ce11008c5514fa4c605b4145ec07100781775285d17590efb7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page