Skip to content

webOS - Part One

My earliest memory of a Windows PC dates back to when I was 5. The information age had started to pick up steam and Microsoft was sporting it’s latest operating system, Windows 95 - a powerhouse for business, connectivity and leisure marketed as a must-have tool for the modern family. My parents who were operating a small business and raising a young family at the time certainly thought so and bought home the Compaq Presario 4160, a technological marvel for its time. This machine not only served me throughout most of my adolescent life, but also sparked a joy for technology that would be lifelong.

compaq_4160 I reference this because even at that young age, wanting to understand the ins and outs of my machine and how all the software operated was something that excited me. That’s why I recently decided to go deeper and attempt to simulate an operating system using a modern web language - these projects are often described as webOS projects and are generally used to learn and grow as a developers.

This project has been very rewarding for me, it has taught me a great deal on how larger teams make various decisions. For those who are at an intermediate level and looking to branch from one type of development to another (in my case it was web to Systems/OS) - these sorts of projects are a great way to think outside the box.

At the end of June 2025 I came across whispers that the Windows 11 Start Menu was built with React Native. After further research, it seemed to be some casual “clickbait” that surfaced every few months and nothing of note (the Start Menu itself is built with C++ and XAML with the recommendations feature using React Native). It was a nice little rabbit hole at the time however it did spark a small proof of concept which would later turn into an exciting project for me.

Growing up in the mid-90s early 2000s, the nostalgia of older Microsoft operating systems notably 95, 98 and XP brings me joy, reminiscing about aero icons and MSN Messenger dings after school I set myself a goal of visually recreating a Windows XP desktop environment in React - not the hardest front end styling exercise but one I came to enjoy.

It was around this time that my curiosity and current learning topics started to mingle - I had recently started reading Clean Architecture by Robert C. Martin (Uncle Bob), alongside working through some courseware from Princeton around Data Structures and Algorithms. which was taught in Java. Putting the two together I had fully bought into the OO paradigm and was itching to practice my knowledge and theory - as I started crafting my UI design, my systems thinking mindset set in - It was then I asked myself some fundamental questions.

  • “How does Windows get to the front end?”
  • “Is Windows different to other operating systems in how it achieves this?”
  • “How do we manage themes, preferences, start menu applications and other users?”

The more I asked these questions the more I realised this project was going to go beyond the scope of just a front end recreation.

I had made a decision to turn this small project into a “learning companion” giving me a higher level understanding of core components that go into an Operating System, to keep it simple it would be written in a language I am currently familiar and comfortable in (TypeScript). I wanted the project to push me to think outside of the box with my current language whilst introducing me to concepts found in different development practices (Kernel Development, Drivers, Firmware, OS etc. ).

I first needed to set myself some rules - after careful consideration these were the main ones.

  1. Pick a reference architecture.
  2. Choose a Paradigm.
  3. Stick to SOLID principles .
  4. Modularity and expansion prioritised in design and structure.
  5. Constrain realism to limitations of language and tools we are building in.
  6. Be open to changing these rules if it meets the goals of simulating our reference architecture.

These rules resulted in some early decisions. I first elected to align with the Object Oriented Paradigm for this project, this would mean some changes to frameworks and structure (more on that shortly). I also decided to choose the NT Architecture as my reference architecture considering I was already working on a Windows front end.

Some other “standards” I set for myself.

  • kebab-case naming convention with dot separators to identify our files with ease etc: example-file.class.ts
  • Separation of concerns including modularised type.ts and const.ts
  • Test driven development to milestone major functionality implementations with unit testing methodology.

I believe the above to be more foundational, I also realised the more important decisions were paradigm and reference architecture which helped shape and grow these standards organically.

In addition I chose some technologies to assist with the developer experience - the main ones being jest, prettier, tailwind, angular, ts-jest and jsdom.

  • jest with js-dom: Is the main testing suite being used as it allows for both functional unit tests and DOM related output validation.
  • prettier: Is used to ensure consistent formatting across our files.
  • tailwind: Is a utility class based CSS library allowing for us to have simple inline styling across our html components
  • angular: is an OO front end framework which we will discuss in more detail in the next section.

I had done a considerable amount of work making a functional front end using vite. I had built a replica of the Windows XP taskbar which visually matched the old Start Menu. I had even built multiple themes being served through a contextProvider, originally the vision was to allow for users to make their own themes to replicate other popular operating systems - the only issue, vite and React (whilst no issue mixing in OO principles after all everything is an object in javascript) are primarily better suited to the functional paradigm.

I had my first decision to make - one that I now believe to be the right one.

I elected to port my project in full to Angular. This was a great experience overall and my approach helped speed things up, if you are ever looking to port a project from one framework to another - my suggestion is to speak in the language of the Framework you are familiar with to an LLM (my current choice is Gemini for “pair programming ” purposes) - I’m not suggesting the responses are gospel however, questions like.

“What is the React useState equivalent in the Angular framework?”

Helped speed up finding the right reference materials for me to learn the basics of the framework. Other more nuanced techniques were reserved to reading documentation and self research.

Here I want to show how different the two frameworks are, let’s take the Windows XP Start Menu (see reference picture from my frontend).

webos_startmenu

The original functional component code (vite) looked like this:

StartMenu.tsx

src/applications/system/desktop-environment/components/StartMenu.tsx
import Divider from "../divider/Divider";
import PowerPanel from "./PowerPanel";
import StartMenuLeftPanel from "./StartMenuLeftPanel";
import StartMenuRightPanel from "./StartMenuRightPanel";
import { forwardRef } from "react";
import { useTheme } from "../../themes/ThemeContext";
interface StartMenuProps {
  isOpen: boolean;
}
const StartMenu = forwardRef<HTMLDivElement, StartMenuProps>(
  ({ isOpen }, ref) => {
    // 'ref' is the second argument provided by forwardRef
    const theme = useTheme();
    const startMenuTheme = theme.startmenustyles;
    const startMenuWrapperClasses = [
      startMenuTheme.wrapper.border,
      startMenuTheme.wrapper.color,
      startMenuTheme.wrapper.display,
    ]
      .filter(Boolean)
      .join(" ");
    const startMenuHeaderClasses = [
      startMenuTheme.startMenuHeader.border,
      startMenuTheme.startMenuHeader.color,
      startMenuTheme.startMenuHeader.display,
    ]
      .filter(Boolean)
      .join(" ");
    const innerMenuClasses = [
      startMenuTheme.innermenu.border,
      startMenuTheme.innermenu.display,
    ]
      .filter(Boolean)
      .join(" ");
    return (
      <div
        className={`${isOpen ? "absolute" : "hidden"} ${startMenuWrapperClasses}`}
        ref={ref}
      >
        <div className={startMenuHeaderClasses}>
          <img
            src={theme.startmenustyles.profilepicture.iconSrc}
            className={theme.startmenustyles.profilepicture.iconStyle}
            alt={theme.startmenustyles.profilepicture.iconAlt}
          />
          <span className={theme.startmenustyles.username.text}>
            Administrator
          </span>
        </div>
        <div className={innerMenuClasses}>
          <Divider dividerColor="orange" />
          <div className={theme.startmenustyles.pannelwrapper.display}>
            <StartMenuLeftPanel />
            <StartMenuRightPanel />
          </div>
          <PowerPanel />
        </div>
      </div>
    );
  },
);
export default StartMenu;

And the ported object oriented component (Angular) looks like this:

StartMenu.ts

src/applications/system/desktop-environment/components/StartMenu.ts
import { Component, inject, computed, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgOptimizedImage } from '@angular/common';
import { ThemeContextService } from '../../themes/theme-context';
import Divider from '../divider/divider';
import StartMenuLeftPanel from './start-menu-left-panel';
import StartMenuRightPanel from './start-menu-right-panel';
import StartMenuPowerPanel from './power-panel';
import { StyleHelpers } from '../../style-helpers';
@Component({
  selector: 'start-menu',
  standalone: true,
  imports: [
    CommonModule,
    NgOptimizedImage,
    Divider,
    StartMenuLeftPanel,
    StartMenuRightPanel,
    StartMenuPowerPanel,
  ],
  template: `
    <div [ngClass]="startMenuWrapperClasses">
      <div [ngClass]="startMenuHeaderClasses">
        <img
          [ngSrc]="profilePicture.iconSrc"
          [ngClass]="profilePicture.iconStyle"
          [alt]="profilePicture.iconAlt"
        />
        <span [ngClass]="startMenuUsernameClasses"> Administrator </span>
      </div>
      <div [ngClass]="startMenuInnerMenuClasses">
        <divider color="orange" />
        <div [ngClass]="startMenuPanelClasses">
          <start-menu-left-panel />
          <start-menu-right-panel />
        </div>
        <start-menu-power-panel />
      </div>
    </div>
  `,
})
export default class StartMenu {
  @Input() isOpen: boolean = false;
  private theme = inject(ThemeContextService);
  styles = computed(() => this.theme.theme().startmenustyles);
  get profilePicture() {
    return StyleHelpers.returnIconValues(this.styles().profilepicture);
  }
  get startMenuPanelClasses() {
    return StyleHelpers.joinStyleValues(this.styles().pannelwrapper);
  }
  get startMenuInnerMenuClasses() {
    return StyleHelpers.joinStyleValues(this.styles().innermenu);
  }
  get startMenuHeaderClasses() {
    return StyleHelpers.joinStyleValues(this.styles().startMenuHeader);
  }
  get startMenuUsernameClasses() {
    return StyleHelpers.joinStyleValues(this.styles().username);
  }
  get startMenuWrapperClasses() {
    const classes = [StyleHelpers.joinStyleValues(this.styles().wrapper)];
    if (!this.isOpen) {
      classes.push('hidden');
    } else {
      classes.push('absolute');
    }
    return classes.filter(Boolean).join(' ');
  }
}

Two completely different approaches ultimately leading to the same rendered component - I had moved from useContext to inject (and compute); props to @decorators; state to signals and of course was now returning classes instead of functions.

Whilst the conscious choice to port to Angular was made early this wasn’t necessarily due to one framework being better then the other. It aligned more to those opinions and rules I had set - in particular adhering to an OOP approach upfront.

A computer is made up of components, those components put together make it compute. So how do we simulate those components without losing focus on the overall project and its learning outcomes. My first task was to validate a configuration with some simulated core hardware components.

During my initial research I made a decision to forgo simulating a CPU at this stage - I made this decision as I saw the task as one that would consume a significant amount of early stage development that could cause the project to lose focus and fail to meet its initial learning goals..

Should I have included CPU I would need to consider, threading, concurrency, logic gate functionality, orchestrating kernel executions and much more. My approach was simple, allow for the structure to “plugin” a CPU at a later date and for now continue with the main goals.

Memory from a functionality perspective is fairly simple with the two main functions being read and write. (technically it is storing and releasing but for now let’s relegate to read and write).

Accessing memory has no linearity, allowing for non-sequential reads and writes to ensure fast access. As this is a simulation we were not going to get into the granularity of control logic, address decoders (at least in detail) or multiplexers. The function of our various memory modules was to meet and address a few points.

  • A way to simulate and limit concurrent running processes
  • Abstracted pointers to Hexadecimal values (Uint8Array was used for this).
  • Simple read and write that could eventually be accessed through our win32API (More on this in part two of this article in the near future).

The first technical decision made was to limit the simulated storage parameters to be kilobytes (kb/1024*1024) noting I would scale this up in the front end environments for rendering/visual purposes. The reason was due to the constraints for object creation lengths in JavaScript. Having a Uint8Array with a length that equated to the amount of bytes in a GB would not be acceptable or even required for this project.

As mentioned I made a choice of a Uint8Array to represent our “address decoder” (using the term very loosely) because it supports the working with arbitrary binary data. Should I decide to dip my toes into WASM and decoding binary data then we have a good typed array to support this in the future!

I also decided to not implement GDI level simulation which meant limiting the GPU to memory functionality, this means that both the GPU and RAM classes would be identical for now separated in name.

ram.class.ts

src/simulated-hardware/ram/ram.class.ts
// GPU and RAM have the same functions at this stage.
// technical decision to use kb instead of mb to simulate actual memory reads within javascript
export class RAM {
  public totalMemoryKB: number;
  public memoryContent: Uint8Array;
  public usedMemoryKB: number = 0;
  public availableMemoryKB: number = 0;
  public totalBytes: number;
  constructor(memoryKB: number) {
    this.totalMemoryKB = memoryKB;
    this.availableMemoryKB = this.totalMemoryKB;
    this.totalBytes = this.totalMemoryKB * 1024;
    this.memoryContent = new Uint8Array(this.totalBytes);
  }
  public allocateMemory(expectedMemoryUsageKB: number): number | null {
    if (expectedMemoryUsageKB <= 0) {
      console.warn('RAM: Cannot allocate 0 or negative memory');
      return null;
    }
    if (expectedMemoryUsageKB >= this.availableMemoryKB) {
      console.warn(
        `RAM: Not enough RAM Memory available. Requested ${expectedMemoryUsageKB}KB, but only ${this.availableMemoryKB}KB remaining.`,
      );
      return null;
    } else {
      const allocatedAddressBytes = this.usedMemoryKB * 1024;
      this.availableMemoryKB -= expectedMemoryUsageKB;
      this.usedMemoryKB += expectedMemoryUsageKB;
      console.log(
        `RAM: Allocated ${expectedMemoryUsageKB}KB of memory. Used: ${this.usedMemoryKB}KB, Remaining: ${this.availableMemoryKB}KB`,
      );
      return allocatedAddressBytes;
    }
  }
public readMemory(addressBytes: number, lengthBytes: number): Uint8Array {
    const totalBytes = this.totalMemoryKB * 1024;
    if (addressBytes < 0 || addressBytes + lengthBytes > totalBytes) {
      console.error(
        `RAM: Memory read out of bounds. Address: ${addressBytes}, Length: ${lengthBytes} bytes. Total Size: ${totalBytes} bytes.`,
      );
      return new Uint8Array(0);
    }
    return this.memoryContent.subarray(
      addressBytes,
      addressBytes + lengthBytes,
    );
  }
  public writeMemory(addressBytes: number, data: Uint8Array): void {
    const totalBytes = this.totalMemoryKB * 1024;
    if (addressBytes < 0 || addressBytes + data.length > totalBytes) {
      console.error(
        `RAM: Memory read out of bounds. Address: ${addressBytes}, Data Length: ${data.length} bytes. Total Size: ${totalBytes} bytes.`,
      );
      return;
    }
    this.memoryContent.set(data, addressBytes);
  }
  public getAvailableMemory(): number {
    return this.availableMemoryKB;
  }
  public getUsedMemory(): number {
    return this.usedMemoryKB;
  }
}

Whilst the storage device would also take the same functions a small addition was added to the constructor to ensure I could adapt the class to have different type properties. I did this by using a StorageType export. I was also initially considering how to check “port compatibility” and this structure would help me address that across components.

storage/types.ts

src/simulatedHardware/storage/types.ts
export const StorageTypes = {
  DISC_DVD: 'DISC_DVD',
  IDE_HDD_ONE: 'IDE_HDD_ONE',
  IDE_HDD_TWO: 'IDE_HDD_TWO',
  IDE_HDD_THREE: 'IDE_HDD_THREE',
  USB_FLASHDRIVE: 'USB_FLASHDRIVE',
};
export type StorageType = (typeof StorageTypes)[keyof typeof StorageTypes];

storage-device.class.ts

src/simulated-hardware/storage/storage-device.class.ts
constructor(storageType: StorageType, memoryKB: number) {
if (memoryKB <= 0) {
console.error('STORAGE: Cannot create a storage device with no memory.');
this.availableMemoryKB = null;
this.totalMemoryKB = 0;
this.totalBytes = 0;
this.memoryContent = new Uint8Array(0);
this.hardwareProfile = storageType;
return;
}
this.totalMemoryKB = memoryKB;
this.totalBytes = this.totalMemoryKB * 1024;
this.availableMemoryKB = this.totalMemoryKB;
this.memoryContent = new Uint8Array(this.totalBytes);
this.hardwareProfile = storageType;
}

The motherboard serves as a I/O board that verifies if its slots are occupied with valid devices and returns a class with “what’s plugged in”. I decided to use custom interfaces to construct types that would ensure the compatibility of certain slot types.

This approach allows me to come back to this module and expand on the idea in the future. For now the pcieOne and pcieTwo ports as per pcConstructorProperties take a pcieSlotConfig type which would offer us with either a null value (nothing plugged in) or an interface that’s gpu or storage. The idea here is fairly straight forward. The motherboard can ultimately have anything plugged in but at a minimum it needs GPU, Storage and RAM to be a valid construction of a motherboard otherwise returning a configurationError.

motherboard/types.ts

src/simulated-hardware/motherboard/types.ts
import { GPU } from '../gpu/gpu.class';
import { RAM } from '../ram/ram.class';
import { StorageDevice } from '../storage/storage-device.class';
interface GpuPcieProperties {
  type: 'gpu';
  profile: GPU;
}
interface StorageProperties {
  type: 'storage';
  profile: StorageDevice;
}
export type PCIeDevice = GPU | StorageDevice | null;
export type pcieSlotConfig = GpuPcieProperties | StorageProperties | null;
export interface pcConstructorProperties {
  dimSlotOne?: RAM;
  dimSlotTwo?: RAM;
  dimSlotThree?: RAM;
  dimSlotFour?: RAM;
  ideOne?: StorageProperties;
  ideTwo?: StorageProperties;
  sataOne?: StorageProperties;
  pcieOne?: pcieSlotConfig;
  pcieTwo?: pcieSlotConfig;
}

motherboard.class.ts

src/simulated-hardware/motherboard/motherboard.class.ts
import { RAM } from '../ram/ram.class';
import { StorageDevice } from '../storage/storage-device.class';
import type { pcConstructorProperties } from './types';
import type { PCIeDevice } from './types';
export class MotherBoard {
  public dimSlotOne: RAM | null = null;
  public dimSlotTwo: RAM | null = null;
  public dimSlotThree: RAM | null = null;
  public dimSlotFour: RAM | null = null;
  public ideOne: StorageDevice | null = null;
  public ideTwo: StorageDevice | null = null;
  public sataOne: StorageDevice | null = null;
  public pcieOne: PCIeDevice | null = null;
  public pcieTwo: PCIeDevice | null = null;
  constructor({
    dimSlotOne,
    dimSlotTwo,
    dimSlotThree,
    dimSlotFour,
    ideOne,
    ideTwo,
    sataOne,
    pcieOne,
    pcieTwo,
  }: pcConstructorProperties) {
    if (!dimSlotOne && !dimSlotTwo && !dimSlotThree && !dimSlotFour) {
      this.configurationError(`Missing RAM`);
    }
    if (pcieOne?.type !== 'gpu' && pcieTwo?.type !== 'gpu') {
      this.configurationError(`Missing GPU`);
    }
    if (
      !ideOne &&
      !ideTwo &&
      !sataOne &&
      pcieOne?.type !== 'storage' &&
      pcieTwo?.type !== 'storage'
    ) {
      this.configurationError(`Missing Storage`);
    }
    if (dimSlotOne) this.dimSlotOne = dimSlotOne;
    if (dimSlotTwo) this.dimSlotTwo = dimSlotTwo;
    if (dimSlotThree) this.dimSlotThree = dimSlotThree;
    if (dimSlotFour) this.dimSlotFour = dimSlotFour;
    if (ideOne && ideOne.type == 'storage') this.ideOne = ideOne.profile;
    if (ideTwo && ideTwo.type == 'storage') this.ideTwo = ideTwo.profile;
    if (sataOne && sataOne.type == 'storage') this.sataOne = sataOne.profile;
    if (pcieOne) this.pcieOne = pcieOne.profile;
    if (pcieTwo) this.pcieTwo = pcieTwo.profile;
  }
  private configurationError(errorMessage: string) {
    throw new Error(`PC: ${errorMessage}`);
  }
  public currentConfig() {
    return {
      dimSlotOne: this.dimSlotOne,
      dimSlotTwo: this.dimSlotTwo,
      dimSlotThree: this.dimSlotThree,
      dimSlotFour: this.dimSlotFour,
      ideOne: this.ideOne,
      ideTwo: this.ideTwo,
      sataOne: this.sataOne,
      pcieOne: this.pcieOne,
      pcieTwo: this.pcieTwo,
    };
  }
}

I introduced the concept of firmware when I was separating concerns related to verifying an option ROM and the master boot record. In a normal scenario an operating system would initially be installed off external boot media with a valid boot signatures however in this scenario our simulation would assume that the operating system was “pre-installed” and these installation steps had been completed. I made a choice to relegate the master boot record to a “firmware” function, this would allow for us to create different signatures, should we make a Linux inspired OS in the future we could dynamically load a Linux MBR onto a Storage device.

To create the master boot record the approach I took wasn’t dissimilar to how it would work in a real operating system. The first step was to set some variables.

  • diskSectorSize - This would be the smallest size allocated for each sector, similar to a real device we chose 512 as the smallest divisible number.
  • pointerVariables - These variables are the memory addresses reserved to identify characteristics of the the boot record we have:
    • 446: Pointer which returns if partition is Active or Inactive.
    • 450: Partition type (NTFS, FAT etc.)
    • 510 and 511: These pointers are reserved to let the BIOS know if the partition has a boot signature.
    • 328 and 329: For the simulation I reserved pointer 328 and 329 to be our OS Signature, in a real operating system unreserved bytes in the MBR would store the bootloader software. As I was going straight into kernel mode depending on signature found I simplified the logic to match - it also means we can make different operating systems without needing to build a bootloader for each one.
  • partitionVariables: To accompany the above memory addresses the partition variables are set into two categories.
    • Storage Specific - File Type, Active/Inactive
    • OS Specific - Boot Signature Values, OS Signature Values (For our simulation we only have the Windows ones for now.)

const.ts

src/firmware/storage/const.ts
// MBR Constants
export const diskSectorSize = 512;
// MBR - Pointers
export const pointerBootablePartition = 446;
export const pointerPartitionType = 450;
export const pointerBootSignatureOne = 510;
export const pointerBootSignatureTwo = 511;
export const pointerOSSignatureOne = 328;
export const pointerOSSignatureTwo = 329;
// MBR - Flags/Data
export const partitionActive = 0x80;
export const partitionInactive = 0x00;
export const partitionTypeNTFS = 0x07;
export const partitionTypeFat32 = 0x0c;
// MBR - OS Sigantures
export const osBootSignatureOne = 0x55;
export const osBootSignatureTwo = 0xaa;
export const osSignatureBindowsXDOne = 0xff;
export const osSignatureBindowsXDTwo = 0xee;

With the values set now it was time to translate this to a class structure - I opted to take four variables for the constructor as captured by the types file:

  • partitionType: a simple union type that would translate different filesystem types.
  • sectorSize: a number representing the number of bytes per sector.
  • osSingatureOne and osSignatureTwo: our unique system implemented to invoke the correct operating system from BIOS. types.ts
src/firmware/storage/types.ts
type MasterBootRecordPartitionType = 'ntfs' | 'fat32';
export interface MasterBootRecordTypeConstructorProperties {
  partitionType: MasterBootRecordPartitionType;
  sectorSize: number;
  osSignatureOne: number | null;
  osSignatureTwo: number | null;
}

Finally I combined the variables and put together the MasterBootRecord class which when constructed would return a Uint8Array which could then be directly written into the previous StorageDevice class.

The class when constructed would first ensure the constructor arguments were within to our original architecture specifications (512 byte length, active partition, valid OS signature and valid filesystem type). Once checks are complete it sets the this.masterBootRecord property to the typed array with our crucial pointers having the correct corresponding data.

I also included a validateMasterBootRecord function which would return true if all our specifications were met else returning false.

master-boot-record.class.ts

src/firmware/storage/master-boot-record.class.ts
import type { MasterBootRecordTypeConstructorProperties } from './types';
import {
  pointerBootSignatureOne,
  pointerBootSignatureTwo,
  pointerBootablePartition,
  pointerOSSignatureOne,
  pointerOSSignatureTwo,
  pointerPartitionType,
  partitionActive,
  partitionInactive,
  partitionTypeFat32,
  partitionTypeNTFS,
  osBootSignatureOne,
  osBootSignatureTwo,
} from './const';
export class MasterBootRecord {
  public masterBootRecord: Uint8Array;
  constructor({
    partitionType,
    sectorSize,
    osSignatureOne,
    osSignatureTwo,
  }: MasterBootRecordTypeConstructorProperties) {
    this.masterBootRecord = new Uint8Array(sectorSize).fill(0x00);
    if (sectorSize !== 512) {
      this.masterBootRecord[pointerBootablePartition] = partitionInactive;
      throw new Error('Master Boot Record: Sector Size not supported.');
    }
    this.masterBootRecord[pointerBootablePartition] = partitionActive;
    if (!partitionType) {
      this.masterBootRecord[pointerBootablePartition] = partitionInactive;
      throw new Error('Master Boot Record: Missing Partition Type');
    }
    if (partitionType === 'ntfs') {
      this.masterBootRecord[pointerPartitionType] = partitionTypeNTFS;
    } else if (partitionType === 'fat32') {
      this.masterBootRecord[pointerPartitionType] = partitionTypeFat32;
    }
    if (!osBootSignatureOne || !osBootSignatureTwo) {
      this.masterBootRecord[pointerBootablePartition] = partitionInactive;
      throw new Error(
        'Master Boot Record: Cannot create a master boot record without boot signature',
      );
    }
    this.masterBootRecord[pointerBootSignatureOne] = osBootSignatureOne;
    this.masterBootRecord[pointerBootSignatureTwo] = osBootSignatureTwo;
    if (!osSignatureOne || !osSignatureTwo) {
      this.masterBootRecord[pointerBootablePartition] = partitionInactive;
      throw new Error('Master Boot Record: No OS Signature provided.');
    }
    this.masterBootRecord[pointerOSSignatureOne] = osSignatureOne;
    this.masterBootRecord[pointerOSSignatureTwo] = osSignatureTwo;
  }
  public validateMasterBootRecord(): boolean {
    if (!this.masterBootRecord) return false;
    return (
      this.masterBootRecord.length === 512 &&
      this.masterBootRecord[pointerBootablePartition] === partitionActive &&
      (this.masterBootRecord[pointerPartitionType] === partitionTypeNTFS ||
        this.masterBootRecord[pointerPartitionType] === partitionTypeFat32) &&
      this.masterBootRecord[pointerBootSignatureOne] === osBootSignatureOne &&
      this.masterBootRecord[pointerBootSignatureTwo] === osBootSignatureTwo &&
      this.masterBootRecord[pointerOSSignatureOne] !== 0x00 &&
      this.masterBootRecord[pointerOSSignatureTwo] !== 0x00
    );
  }
}

Finally we can create a custom const for a “Windows” master boot record (if you haven’t noticed yet my simulated OS working title is Bindows XD).

windows-master-boot-record.ts

src/firmware/storage/windows-master-boot-record.ts
import { MasterBootRecord } from './master-boot-record.class';
import { osSignatureBindowsXDOne, osSignatureBindowsXDTwo } from './const';
export const windowsMasterBootRecord = new MasterBootRecord({
  partitionType: 'ntfs',
  sectorSize: 512,
  osSignatureOne: osSignatureBindowsXDOne,
  osSignatureTwo: osSignatureBindowsXDTwo,
});

Now that I have explained the logic behind how pointers and data is set when dealing with storage - I can expand on the GPU’s role. For now I have decided to use Angular as a “Graphical Device Interface Service” rather then building something custom to simulate the OS GDI Layer. There is however a crucial role a GPU can play in the boot process - mainly housing the option ROM functionality.

An option ROM allows you to use basic peripherals prior to your device loading an operating system (as an example display and Input support when configuring your BIOS). Like a Master Boot Record, an Option Rom also has a unique signature noted by the first two bytes accessed on the devices onboard storage. For our simulation the GPU houses the option ROM and the option ROM’s function is merely to be validated during POST (nothing more at this stage). This means I can come back later and expand on this idea should I decide to expand on the BIOS functionality or work on a GDI service/expand the GPU’s role and functions.

const.ts

src/firmware/gpu/const.ts
export const optionRomBootSignatureOne: number = 0x55;
export const optionRomBootSignatureTwo: number = 0xaa;
export const pointerOptionRomBootSignatureOne = 0;
export const pointerOptionRomBootSignatureTwo = 1;

option-rom.class.ts

src/firmware/gpu/option-rom.class.ts
import {
  pointerOptionRomBootSignatureOne,
  pointerOptionRomBootSignatureTwo,
  optionRomBootSignatureOne,
  optionRomBootSignatureTwo,
} from './const';
export class OptionRom {
  public optionRomData: Uint8Array;
  constructor() {
    this.optionRomData = new Uint8Array(2).fill(0x00);
    this.optionRomData[pointerOptionRomBootSignatureOne] =
      optionRomBootSignatureOne;
    this.optionRomData[pointerOptionRomBootSignatureTwo] =
      optionRomBootSignatureTwo;
  }
  public validateOptionRom(): boolean {
    if (!this.optionRomData) return false;
    return (
      this.optionRomData.length === 2 &&
      this.optionRomData[pointerOptionRomBootSignatureOne] ===
        optionRomBootSignatureOne &&
      this.optionRomData[pointerOptionRomBootSignatureTwo] ===
        optionRomBootSignatureTwo
    );
  }
}

Now we have our building blocks to build a PC ready to be inspected by a BIOS function. Below is what all the parts together look like. First I created a type interface which allocated compatibility for each slot available in a fully constructed PC

types.ts

src/simulated-hardware/pc-builds/types.ts
import { RAM } from '../ram/ram.class';
import { StorageDevice } from '../storage/storage-device.class';
import { PCIeDevice } from '../motherboard/types';
export interface FullSystem {
  dimSlotOne: RAM | null;
  dimSlotTwo: RAM | null;
  dimSlotThree: RAM | null;
  dimSlotFour: RAM | null;
  ideOne: StorageDevice | null;
  ideTwo: StorageDevice | null;
  sataOne: StorageDevice | null;
  pcieOne: PCIeDevice | null;
  pcieTwo: PCIeDevice | null;
}

Following this a helper function was created to return a default system spec - for now this is functional. I will move this to a class at a later stage with the ability to choose multiple builds for testing.

src/simulated-hardware/pc-builds/pc-builds.ts
import { GPU } from '../gpu/gpu.class';
import { RAM } from '../ram/ram.class';
import { StorageDevice } from '../storage/storage-device.class';
import { MotherBoard } from '../motherboard/motherboard.class';
import { windowsMasterBootRecord } from '../../firmware/storage/windows-master-boot-record';
import { OptionRom } from '../../firmware/gpu/option-rom.class';
import type { FullSystem } from './types';
export function defaultSystemSpecs(): FullSystem {
  const ram1 = new RAM(64);
  const gpu = new GPU(64);
  const ideone = new StorageDevice('IDE_HDD_ONE', 64);
  const windowsMBR = windowsMasterBootRecord;
  const gpuOptionRom = new OptionRom();
  if (windowsMBR.validateMasterBootRecord()) {
    // represent kb as bytes for memory allocation
    ideone.allocateMemory(windowsMBR.masterBootRecord.length / 1024);
    ideone.writeMemory(0, windowsMBR.masterBootRecord);
  }
  if (gpuOptionRom.validateOptionRom()) {
    // represent kb as bytes for memory allocation
    gpu.allocateMemory(gpuOptionRom.optionRomData.length / 1024);
    gpu.writeMemory(0, gpuOptionRom.optionRomData);
  }
  const pc = new MotherBoard({
    dimSlotOne: ram1,
    pcieOne: { type: 'gpu', profile: gpu },
    ideOne: { type: 'storage', profile: ideone },
  });
  return pc.currentConfig();
}

As you can see the RAM, GPU, Storage, Option ROM and Windows MBR classes are instantiated. A validation of the option ROM and MBR then allocate and write memory to storage and GPU. Resulting in the return of a constructed PC via the MotherBoard class.

I mentioned earlier following a test driven development model for each of these components iterating on functionality by building to pass tests. This methodology has not only been useful in accounting for edge cases early but it has also been an invaluable tool when I refactored code or separated concerns helping me rapidly find variables/changes that may have causes chain reactions across my project where there were joint dependencies. Below I have included some simulated hardware tests as an example. I have made it a a point to go back and ensure these tests are updated to reduce any burdens caused by refactoring at a later stage.

gpu.test.ts

src/simulated-hardware/__tests__/gpu.test.ts
import { GPU } from '../gpu/gpu.class';
/// <reference types="jest" />
describe('GPU', () => {
  let simulatedGPU: GPU;
  beforeEach(() => {
    simulatedGPU = new GPU(64);
  });
  it(`Should return the hardware specifications as per it's constructor correctly`, () => {
    expect(simulatedGPU.totalMemoryKB).toBe(64);
    expect(simulatedGPU.availableMemoryKB).toBe(64);
    expect(simulatedGPU.memoryContent.length).toBe(64 * 1024);
  });
  it('Should allocate and return the first "byte address" being used', () => {
    expect(simulatedGPU.allocateMemory(10)).toBe(0);
    expect(simulatedGPU.allocateMemory(20)).toBe(10240);
    expect(simulatedGPU.allocateMemory(30)).toBe(30720);
  });
});

motherboard.test.ts

src/simulated-hardware/__tests__/MotherBoard.test.ts
/// <reference types="jest" />
import { MotherBoard } from '../motherboard/motherboard.class';
import { GPU } from '../gpu/gpu.class';
import { RAM } from '../ram/ram.class';
import { StorageDevice } from '../storage/storage-device.class';
describe('PC', () => {
  let simulatedPC: MotherBoard;
  beforeEach(() => {});
  it('should construct a valid PC build with minimum requirements', () => {
    const ram1 = new RAM(64);
    const gpu = new GPU(64);
    const ideone = new StorageDevice('IDE_HDD', 64);
    const pc = new MotherBoard({
      dimSlotOne: ram1,
      pcieOne: { type: 'gpu', profile: gpu },
      ideOne: { type: 'storage', profile: ideone },
    });
    expect(pc.currentConfig()).toStrictEqual({
      dimSlotOne: ram1,
      dimSlotTwo: null,
      dimSlotThree: null,
      dimSlotFour: null,
      ideOne: ideone,
      ideTwo: null,
      sataOne: null,
      pcieOne: gpu,
      pcieTwo: null,
    });
  });
  it('should throw an error if no RAM is provided', () => {
    const pcieGPU = new GPU(64);
    const bootDriveIDE = new StorageDevice('IDE_HDD', 64);
    expect(() => {
      new MotherBoard({
        pcieOne: { type: 'gpu', profile: pcieGPU },
        ideOne: { type: 'storage', profile: bootDriveIDE },
      });
    }).toThrow('PC: Missing RAM');
  });
  it('should throw an error if no GPU is provided in PCIe slots', () => {
    const slotOneRam = new RAM(64);
    const bootDriveIDE = new StorageDevice('IDE_HDD', 64);
  expect(() => {
      new MotherBoard({
        dimSlotOne: slotOneRam,
        ideOne: { type: 'storage', profile: bootDriveIDE },
      });
    }).toThrow('PC: Missing GPU');
  });
  it('should throw an error if no storage device is provided', () => {
    const slotOneRam = new RAM(64);
    const pcieGPU = new GPU(64);
    expect(() => {
      new MotherBoard({
        dimSlotOne: slotOneRam,
        pcieOne: { type: 'gpu', profile: pcieGPU },
      });
    }).toThrow('PC: Missing Storage');
  });
  it('should allow multiple RAM and storage devices', () => {
    const ram1 = new RAM(16);
    const ram2 = new RAM(16);
    const sataDrive = new StorageDevice('SATA_SSD', 128);
    const gpu = new GPU(128);
    const pc = new MotherBoard({
      dimSlotOne: ram1,
      dimSlotTwo: ram2,
      sataOne: { type: 'storage', profile: sataDrive },
      pcieOne: { type: 'gpu', profile: gpu },
    });
    expect(pc.currentConfig()).toStrictEqual({
      dimSlotOne: ram1,
      dimSlotTwo: ram2,
      dimSlotThree: null,
      dimSlotFour: null,
      ideOne: null,
      ideTwo: null,
      sataOne: sataDrive,
      pcieOne: gpu,
      pcieTwo: null,
    });
  });
});

At this stage I felt ready to start tackling the BIOS - at a high level my plan was to:

  1. Take an assembled PC in its constructor
  2. Detect components in PC at a BIOS level.
  3. Conduct a Power on Self Test (POST) to ensure the PC would boot
  4. Check and validate a boot signature and option ROM signature
  5. Invoke an interrupt (19h) to boot into Kernel

The BIOS class would mirror the motherboard when it comes to slot properties alongside a few tracking variables.

  public eventLog: Array<string> = [];
  public postResults: boolean = false;
private dimSlotOne: RAM | null = null;
  private dimSlotTwo: RAM | null = null;
  private dimSlotThree: RAM | null = null;
  private dimSlotFour: RAM | null = null;
  private ideOne: StorageDevice | null = null;
  private ideTwo: StorageDevice | null = null;
  private sataOne: StorageDevice | null = null;
  private pcieOne: PCIeDevice | null = null;
  private pcieTwo: PCIeDevice | null = null;
  private bootTable: Array<StorageDevice> = [];
  private totalMemory: number = 0;
  private totalStorage: number = 0;
  private totalGpuMemory: number = 0;

The two public properties would be the eventLog and postResults with the rest being reserved for the internal state of the class.

The constructor takes an assembled PC, runs a detectDevices function followed by a postResults (Power on Self Test), if the POST fails it returns an error, if it passes it invokes Interrupt 19h (further explained below).

  constructor(assembledPC: FullSystem) {
    if (!assembledPC) {
      this.updateEventLog(`PC: Assembled PC Not Found`, true);
      return;
    }
    this.detectDevices(assembledPC);
    this.postResults = this.powerOnSelfTest();
    if (!this.postResults) {
      this.updateEventLog(
        'BIOS: POST FAILED During boot-up. System cannot proceed.',
        true,
      );
    }
    this.interrupt('19h');
  }

Not much commentary required for the detectDevice Function; it simply translates the slots that are occupied from our assembledPC to the private BIOS variables.

  private detectDevices(assembledPC: FullSystem) {
    if (assembledPC.dimSlotOne) this.dimSlotOne = assembledPC.dimSlotOne;
    if (assembledPC.dimSlotTwo) this.dimSlotTwo = assembledPC.dimSlotTwo;
    if (assembledPC.dimSlotThree) this.dimSlotThree = assembledPC.dimSlotThree;
    if (assembledPC.dimSlotFour) this.dimSlotFour = assembledPC.dimSlotFour;
    if (assembledPC.ideOne) this.ideOne = assembledPC.ideOne;
    if (assembledPC.ideTwo) this.ideTwo = assembledPC.ideTwo;
    if (assembledPC.sataOne) this.sataOne = assembledPC.sataOne;
    if (assembledPC.pcieOne) this.pcieOne = assembledPC.pcieOne;
    if (assembledPC.pcieTwo) this.pcieTwo = assembledPC.pcieTwo;
  }

I included a utility function to track BIOS level events. The intent is to take an event object to lift up to the Kernel (and eventually Event Manager). For now the function takes the arguments of a message and isError. Pushing to our public Array<string> typed eventLog a message entry.

When the function is called a new Date object with the current system time is broken into a string representation of hours, minutes and seconds, padding the start with two zeros to create a time stamp. We then concatenate our timestamp with the event message to update our event log entry. The isError is used to display our message in the DOM as either a console.error or console.log.

  public updateEventLog(message: string, isError?: boolean): Array<string> {
    const date = new Date();
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    const seconds = String(date.getSeconds()).padStart(2, '0');
    const timeStamp = `[${hours}:${minutes}:${seconds}]`;
    const concatEventMessage = `${timeStamp} ${message}`;
    this.eventLog.push(concatEventMessage);
    if (isError) {
      console.error(concatEventMessage);
    } else {
      console.log(concatEventMessage);
    }
    return this.eventLog;
  }

The Power on Self Test is responsible for verifying if the hardware is “functioning”. The function is private to the class and starts with an updateEventLog function call.

    this.updateEventLog('BIOS: Starting Power-On Self-Test (POST)...');
    let overallPostPassed = true;
    let atLeastOneGPUFound = false;
    let atLeastOneBootDriveFound = false;

For the simulation the POST begins with a overallPostPassed boolean set to true, I also created a variable for atLeastOneGPUFound and atLeastOneBootDriveFound. Whilst technically both of these flags are not required for an actual POST function. I went with integrating these two “checks” into the POST to simulate a verifying an option ROM and bootloader. Should I decide to research bootloaders in the future I can easily create a class that sits between our BIOS and Kernel that can act as an additional interface.

Setting some constants helped maintain modularity when testing various slot without having to repeat code. Using the structure of an object entry inside an array I set up storageSupporingSlots, gpuSupportingSlots and ramSupportingSlots.

I also accounted for scenarios where a GPU or a StorageDevice could be using a PCIE slot by using typescripts instanceof type check to determine, and return, the matching class ensuring edge cases were covered.

    const storageSupportingSlots = [
      { name: 'IDE ONE:', device: this.ideOne },
      { name: 'IDE TWO:', device: this.ideTwo },
      { name: 'SATA ONE:', device: this.sataOne },
      {
        name: 'PCIE ONE:',
        device: this.pcieOne instanceof StorageDevice ? this.pcieOne : null,
      },
      {
        name: 'PCIE TWO:',
        device: this.pcieTwo instanceof StorageDevice ? this.pcieTwo : null,
      },
    ];
    const gpuSupportingSlots = [
      {
        name: 'PCIE ONE:',
        device: this.pcieOne instanceof GPU ? this.pcieOne : null,
      },
      {
        name: 'PCIE TWO:',
        device: this.pcieTwo instanceof GPU ? this.pcieTwo : null,
      },
    ];
    const ramSupportingSlots = [
      { name: 'DIM SLOT ONE:', device: this.dimSlotOne },
      { name: 'DIM SLOT TWO:', device: this.dimSlotTwo },
      { name: 'DIM SLOT THREE:', device: this.dimSlotThree },
      { name: 'DIM SLOT FOUR:', device: this.dimSlotFour },
    ];

I was now able to construct a for loop that would take the array and run the right battery of tests based on “slot type” (this pattern is repeated for GPU and Storage). As the class slot properties are constructed with either null or a valid object the property would be mirrored for slotInfo.device reducing operations and only testing on slots that are “occupied”.

For RAM a postTestMemory is first called returning a boolean value followed by adding to the totalMemory of the bios class alongside an update to the overallPostPassed based on a combination of the existing boolean and ramTestPassed.

    for (const slotInfo of ramSupportingSlots) {
      if (slotInfo.device) {
        const ramTestPassed = this.postTestMemory(
          slotInfo.name,
          slotInfo.device,
        );
        this.totalMemory += slotInfo.device.availableMemoryKB;
        overallPostPassed = overallPostPassed && ramTestPassed;
      }
    }

The postTestMemory function is used in both for loops for ramSupportingSlots and storageSupporingSlots. It initially logs an event message followed by a isFunctioning check. Once complete it ensures the memory modules available memory is above 0 before returning true for the test being passed.

  private postTestMemory(slot: string, detectedMemoryModule: RAM): boolean {
    this.updateEventLog(`POST: ${slot} Testing Memory.`);
    if (!this.isFunctioning(detectedMemoryModule, slot)) {
      return false;
    }
    if (detectedMemoryModule.totalMemoryKB <= 0) {
      this.updateEventLog(
        `POST: ${slot} ${detectedMemoryModule.constructor.name} Zero Memory Detected.`,
        true,
      );
      return false;
    }
    this.updateEventLog(
      `POST: ${slot} ${detectedMemoryModule.constructor.name}: ${detectedMemoryModule.constructor.name} Initialised.`,
    );
    return true;
  }

isFunctioning is purely decorative - we don’t really need this in typescript. We are just simulating that the slot is powered and returning a 1 or a 0 basically.

  private isFunctioning(
    testModule: RAM | StorageDevice | GPU | PCIeDevice | null,
    slotName: string,
  ): boolean {
    if (!testModule) {
      this.updateEventLog(`POST: ${slotName} Hardware Detection issue.`, true);
      return false;
    }
    this.updateEventLog(
      `POST: ${slotName} ${testModule.constructor.name} detected.`,
    );
    return true;
  }

Again the for loop cycles through gpuSupporingSlots this time calling the postTestGPU function.

    for (const slotInfo of gpuSupportingSlots) {
      if (slotInfo.device) {
        const gpuTestPassed = this.postTestGPU(slotInfo.name, slotInfo.device);
        overallPostPassed = overallPostPassed && gpuTestPassed;
        this.totalGpuMemory += slotInfo.device.availableMemoryKB;
        if (gpuTestPassed) {
          atLeastOneGPUFound = true;
        }
      }
    }

As relayed earlier to reduce complexity around deploying a full bootloader the POST relies on a GPU not only being loaded but also including a valid option rom signature within its first two bytes.

  private postTestGPU(slot: string, gpuModule: GPU): boolean {
    this.updateEventLog(`POST: ${slot} Testing GPU.`);
    if (!this.isFunctioning(gpuModule, slot)) {
      return false;
    }
    const gpuSignature = gpuModule.readMemory(0, 2);
    if (
      gpuSignature[0] !== optionRomSignatureOne ||
      gpuSignature[1] !== optionRomSignatureTwo
    ) {
      this.updateEventLog(
        `POST ERROR: ${slot} GPU Option ROM Signature Not Found.`,
        true,
      );
      return false;
    } else {
      this.updateEventLog(`POST: ${slot} GPU: Option Rom Signature Found.`);
    }
    this.updateEventLog(`POST: ${slot} GPU: GPU Initialised.`);
    return true;
  }

The final for loop cycle continues with the same tests on storage with an additional check to add devices to a bootTable if the check returns true on a bootSignature

    for (const slotInfo of storageSupportingSlots) {
      if (slotInfo.device) {
        const storageTestPassed = this.postTestStorage(
          slotInfo.name,
          slotInfo.device,
        );
        if (slotInfo.device.availableMemoryKB) {
          this.totalStorage += slotInfo.device.availableMemoryKB;
        }
        overallPostPassed = overallPostPassed && storageTestPassed;
        if (storageTestPassed) {
          const isBootable = this.checkBootSignature(
            slotInfo.name,
            slotInfo.device,
          );
          if (isBootable) {
            this.bootTable.push(slotInfo.device);
            atLeastOneBootDriveFound = true;
          }
        }
      }
    }

and the checkbootSignature function similar to our GPU check returns true if the MBR sectors last two bytes return a boot signature (0x55, 0xaa).

  private checkBootSignature(
    slot: string,
    detectedStorageModule: StorageDevice,
  ): boolean {
    this.updateEventLog(
      `POST: ${slot} ${detectedStorageModule.hardwareProfile} Drive: Searching for Boot Signature in Storage Device.`,
    );
    const bootSignature = detectedStorageModule.readMemory(0, 512);
    if (
      bootSignature[510] !== bootSignatureOne ||
      bootSignature[511] !== bootSignatureTwo
    ) {
      this.updateEventLog(
        `POST: ${slot} ${detectedStorageModule.hardwareProfile} Drive: Boot Signature not found.`,
      );
      return false;
    }
    this.updateEventLog(
      `POST: ${slot} ${detectedStorageModule.constructor.name}: Boot Signature Found.`,
    );
    return true;
  }
}

The last step in POST is a boolean return with a final update to our event log assuming all the tests have passed. This marks the conclusion of the POST function with our Interrupt19h being called next in the constructor should POST be successful.

    overallPostPassed =
      overallPostPassed && atLeastOneBootDriveFound && atLeastOneGPUFound;
    this.updateEventLog('BIOS: POST Completed successfully...');
    return overallPostPassed;

Currently the only Interrupt I have implemented in 19h. Interrupt calls are invoked by operating systems and application programs as an interface to firmware. For our example we are using the 19h call to load our “bootloader”. As mentioned earlier as we are going straight from BIOS to Kernel our 19h call in this example will instantiate a new Kernel object. Using a switch case I have set the foundation for future interrupts with relevant event logging and comments as a reminder of the functionality of each call.

  private interrupt(
    interuptVector: string,
    device?: StorageDevice | GPU | RAM | null,
  ): void {
    switch (interuptVector) {
      // TODO: Expand Interupts if i ever decide to simulate CPU functionality - for now 19h is enough.
      // Proposed Interrupts for CPU Branch
      case '12h':
        this.updateEventLog('Interrupt 12h invoked - Returning Memory Size...');
        break;
      case '13h01h':
        this.updateEventLog('Interrupt 13h01h invoked - Check Drive Status...');
        break;
      case '13h01h':
        this.updateEventLog('Interrupt 13h01h invoked - Check Drive Status...');
        break;
      case '13h02h':
        this.updateEventLog('Interrupt 13h02h invoked - 02h   Read Sectors...');
        break;
      case '13h03h':
        this.updateEventLog('Interrupt 13h03h invoked - 02h   Write Sectors...');
        break;
      // Implemented Interrupt to find bootloader.
      case '19h':
        this.updateEventLog(
          'Interrupt 19h: Invoked - Searching for Boot Table...',
        );
        if (this.bootTable.length > 0) {
          this.updateEventLog(
            'Interrupt 19h: Boot Table Found - Searching for OS...',
          );
          this.interrupt19h();
        }
        break;
      default:
        break;
    }
  }

Finally the interrupt19h function will check the OS Signature and return a new kernel object accordingly (The return has not been implemented yet). This structure allows for me to create support for other operating systems in the future.

  private interrupt19h() {
    for (const bootDrive of this.bootTable) {
      this.updateEventLog(
        `Interrupt 19h: Checking OS Signature on ${bootDrive.constructor.name}`,
      );
      const osSignatureAddress = bootDrive.readMemory(328, 2);
      if (
        osSignatureAddress[0] === osSignatureOneBindows &&
        osSignatureAddress[1] === osSignatureTwoBindows
      ) {
        this.updateEventLog(
          `Interrupt 19h: OS Signature on ${bootDrive.constructor.name} matches BindowsXD`,
        );
      }
    }
  }

I hope you enjoyed part one of my journey so far. This project has helped me really think outside the box, it has driven me to change and adapt things to match a paradigm and constraints. At this stage I am working on a NT Architecture kernel splitting objects and functionality between User and Executive mode with various services which will make up the core of the operating system being orchestrated accordingly. In part two (once I finish most of the kernel level structure and code) I will provide a lot more insight into the NT Architecture and the challenges I faced implementing my simulated approach.