[NTVDM]
[reactos.git] / subsystems / ntvdm / bios.c
index 6d1de0c..db6dcb3 100644 (file)
 
 /* INCLUDES *******************************************************************/
 
+#define NDEBUG
+
 #include "bios.h"
 #include "emulator.h"
+#include "vga.h"
 #include "pic.h"
 #include "ps2.h"
 #include "timer.h"
 
 /* PRIVATE VARIABLES **********************************************************/
 
+static PBIOS_DATA_AREA Bda;
 static BYTE BiosKeyboardMap[256];
-static WORD BiosKbdBuffer[BIOS_KBD_BUFFER_SIZE];
-static UINT BiosKbdBufferStart = 0, BiosKbdBufferEnd = 0;
-static BOOLEAN BiosKbdBufferEmpty = TRUE;
-static DWORD BiosTickCount = 0;
-static BOOLEAN BiosPassedMidnight = FALSE;
-static HANDLE BiosConsoleInput = INVALID_HANDLE_VALUE;
+static HANDLE BiosConsoleInput  = INVALID_HANDLE_VALUE;
 static HANDLE BiosConsoleOutput = INVALID_HANDLE_VALUE;
-static BYTE CurrentVideoMode = BIOS_DEFAULT_VIDEO_MODE;
-static BYTE CurrentVideoPage = 0;
-static HANDLE ConsoleBuffers[BIOS_MAX_PAGES] = { NULL };
-static LPVOID ConsoleFramebuffers[BIOS_MAX_PAGES] = { NULL };
-static HANDLE ConsoleMutexes[BIOS_MAX_PAGES] = { NULL };
-static BOOLEAN VideoNeedsUpdate = TRUE;
-static SMALL_RECT UpdateRectangle = { 0, 0, 0, 0 };
 static CONSOLE_SCREEN_BUFFER_INFO BiosSavedBufferInfo;
-static VIDEO_MODE VideoModes[] =
-{
-    /* Width | Height | Text | Colors | Gray | Pages | Segment */
-    { 40,       25,     TRUE,   16,     TRUE,   8,      0xB800}, /* Mode 00h */
-    { 40,       25,     TRUE,   16,     FALSE,  8,      0xB800}, /* Mode 01h */
-    { 80,       25,     TRUE,   16,     TRUE,   8,      0xB800}, /* Mode 02h */
-    { 80,       25,     TRUE,   16,     FALSE,  8,      0xB800}, /* Mode 03h */
-    { 320,      200,    FALSE,  4,      FALSE,  4,      0xB800}, /* Mode 04h */
-    { 320,      200,    FALSE,  4,      TRUE,   4,      0xB800}, /* Mode 05h */
-    { 640,      200,    FALSE,  2,      FALSE,  2,      0xB800}, /* Mode 06h */
-    { 80,       25,     TRUE,   3,      FALSE,  1,      0xB000}, /* Mode 07h */
-    { 0,        0,      FALSE,  0,      FALSE,  0,      0x0000}, /* Mode 08h - not used */
-    { 0,        0,      FALSE,  0,      FALSE,  0,      0x0000}, /* Mode 09h - not used */
-    { 0,        0,      FALSE,  0,      FALSE,  0,      0x0000}, /* Mode 0Ah - not used */
-    { 0,        0,      FALSE,  0,      FALSE,  0,      0x0000}, /* Mode 0Bh - not used */
-    { 0,        0,      FALSE,  0,      FALSE,  0,      0x0000}, /* Mode 0Ch - not used */
-    { 320,      200,    FALSE,  16,     FALSE,  8,      0xA000}, /* Mode 0Dh */
-    { 640,      200,    FALSE,  16,     FALSE,  4,      0xA000}, /* Mode 0Eh */
-    { 640,      350,    FALSE,  3,      FALSE,  2,      0xA000}, /* Mode 0Fh */
-    { 640,      350,    FALSE,  4,      FALSE,  2,      0xA000}, /* Mode 10h */
-    { 640,      480,    FALSE,  2,      FALSE,  1,      0xA000}, /* Mode 11h */
-    { 640,      480,    FALSE,  16,     FALSE,  1,      0xA000}, /* Mode 12h */
-    { 640,      480,    FALSE,  256,    FALSE,  1,      0xA000}  /* Mode 13h */
+
+/*
+ * VGA Register Configurations for BIOS Video Modes
+ * The configurations come from DosBox.
+ */
+static BYTE VideoMode_40x25_text[] =
+{
+    /* Miscellaneous Register */
+    0x67,
+
+    /* Sequencer Registers */
+    0x00, 0x08, 0x03, 0x00, 0x07,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x0F, 0xFF,
+
+    /* CRTC Registers */
+    0x2D, 0x27, 0x28, 0x90, 0x2B, 0xA0, 0xBF, 0x1F, 0x00, 0x4F, 0x0D, 0x0E,
+    0x00, 0x00, 0x00, 0x00, 0x9C, 0x8E, 0x8F, 0x14, 0x1F, 0x96, 0xB9, 0xA3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x39, 0x0A, 0x3B,
+    0x0C, 0x3D, 0x0E, 0x3F, 0x10, 0x00, 0x12, 0x08, 0x04
 };
 
-/* PRIVATE FUNCTIONS **********************************************************/
+static BYTE VideoMode_80x25_text[] =
+{
+    /* Miscellaneous Register */
+    0x67,
+
+    /* Sequencer Registers */
+    0x00, 0x00, 0x03, 0x00, 0x07,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x0F, 0xFF,
 
-static INT BiosColorNumberToBits(DWORD Colors)
+    /* CRTC Registers */
+    0x5F, 0x4F, 0x50, 0x82, 0x55, 0x81, 0xBF, 0x1F, 0x00, 0x4F, 0x0D, 0x0E,
+    0x00, 0x00, 0x00, 0x00, 0x9C, 0x8E, 0x8F, 0x28, 0x1F, 0x96, 0xB9, 0xA3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x39, 0x0A, 0x3B,
+    0x0C, 0x3D, 0x0E, 0x3F, 0x10, 0x00, 0x12, 0x08, 0x04
+};
+
+static BYTE VideoMode_320x200_4color[] =
 {
-    INT i;
+    /* Miscellaneous Register */
+    0x63,
 
-    /* Find the index of the highest-order bit */
-    for (i = 31; i >= 0; i--) if (Colors & (1 << i)) break;
+    /* Sequencer Registers */
+    0x00, 0x09, 0x00, 0x00, 0x02,
 
-    /* Special case for zero */
-    if (i == 0) i = 32;
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0F, 0x0F, 0xFF,
 
-    return i;
-}
+    /* CRTC Registers */
+    0x2D, 0x27, 0x28, 0x90, 0x2B, 0x80, 0xBF, 0x1F, 0x00, 0xC1, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x9C, 0x8E, 0x8F, 0x14, 0x00, 0x96, 0xB9, 0xA2,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x13, 0x02, 0x17, 0x04, 0x04, 0x06, 0x07, 0x08, 0x11, 0x0A, 0x13,
+    0x0C, 0x15, 0x0E, 0x17, 0x10, 0x00, 0x12, 0x00, 0x04
+};
 
-static COORD BiosVideoAddressToCoord(ULONG Address)
+static BYTE VideoMode_640x200_2color[] =
 {
-    COORD Result = {0, 0};
-    INT BitsPerPixel;
-    DWORD Offset = Address - (VideoModes[CurrentVideoMode].Segment << 4);
+    /* Miscellaneous Register */
+    0x63,
 
-    if (VideoModes[CurrentVideoMode].Text)
-    {
-        Result.X = (Offset / sizeof(WORD)) % VideoModes[CurrentVideoMode].Width;
-        Result.Y = (Offset / sizeof(WORD)) / VideoModes[CurrentVideoMode].Width;
-    }
-    else
-    {
-        BitsPerPixel = BiosColorNumberToBits(VideoModes[CurrentVideoMode].Colors);
+    /* Sequencer Registers */
+    0x00, 0x09, 0x0F, 0x00, 0x02,
 
-        Result.X = ((Offset * 8) / BitsPerPixel)
-                   % VideoModes[CurrentVideoMode].Width;
-        Result.Y = ((Offset * 8) / BitsPerPixel)
-                   / VideoModes[CurrentVideoMode].Width;
-    }
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0xFF,
 
-    return Result;
-}
+    /* CRTC Registers */
+    0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, 0x00, 0xC1, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x9C, 0x8E, 0x8F, 0x28, 0x00, 0x96, 0xB9, 0xC2,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x17, 0x02, 0x17, 0x04, 0x17, 0x06, 0x17, 0x08, 0x17, 0x0A, 0x17,
+    0x0C, 0x17, 0x0E, 0x17, 0x10, 0x00, 0x12, 0x00, 0x04
+};
+
+static BYTE VideoMode_320x200_16color[] =
+{
+    /* Miscellaneous Register */
+    0x63,
+
+    /* Sequencer Registers */
+    0x00, 0x09, 0x0F, 0x00, 0x02,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0xFF,
+
+    /* CRTC Registers */
+    0x2D, 0x27, 0x28, 0x90, 0x2B, 0x80, 0xBF, 0x1F, 0x00, 0xC0, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x9C, 0x8E, 0x8F, 0x14, 0x00, 0x96, 0xB9, 0xE3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x0A, 0x13,
+    0x0C, 0x15, 0x0E, 0x17, 0x10, 0x00, 0x12, 0x00, 0x04
+};
+
+static BYTE VideoMode_640x200_16color[] =
+{
+    /* Miscellaneous Register */
+    0x63,
+
+    /* Sequencer Registers */
+    0x00, 0x01, 0x0F, 0x00, 0x02,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0xFF,
+
+    /* CRTC Registers */
+    0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, 0x00, 0xC0, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x9C, 0x8E, 0x8F, 0x28, 0x00, 0x96, 0xB9, 0xE3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x0A, 0x13,
+    0x0C, 0x15, 0x0E, 0x17, 0x10, 0x00, 0x12, 0x00, 0x04
+};
+
+static BYTE VideoMode_640x350_16color[] =
+{
+    /* Miscellaneous Register */
+    0xA3,
+
+    /* Sequencer Registers */
+    0x00, 0x01, 0x0F, 0x00, 0x02,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0xFF,
+
+    /* CRTC Registers */
+    0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, 0x00, 0x40, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x83, 0x85, 0x5D, 0x28, 0x0F, 0x63, 0xBA, 0xE3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x39, 0x0A, 0x3B,
+    0x0C, 0x3D, 0x0E, 0x3F, 0x10, 0x00, 0x12, 0x00, 0x04
+};
+
+static BYTE VideoMode_640x480_2color[] =
+{
+    /* Miscellaneous Register */
+    0xE3,
+
+    /* Sequencer Registers */
+    0x00, 0x01, 0x0F, 0x00, 0x02,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0xFF,
+
+    /* CRTC Registers */
+    0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0x0B, 0x3E, 0x00, 0x40, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0xEA, 0x8C, 0xDF, 0x28, 0x00, 0xE7, 0x04, 0xC3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x3F, 0x02, 0x3F, 0x04, 0x3F, 0x06, 0x3F, 0x08, 0x3F, 0x0A, 0x3F,
+    0x0C, 0x3F, 0x0E, 0x3F, 0x10, 0x00, 0x12, 0x00, 0x04
+};
+
+static BYTE VideoMode_640x480_16color[] =
+{
+    /* Miscellaneous Register */
+    0xE3,
+
+    /* Sequencer Registers */
+    0x00, 0x01, 0x0F, 0x00, 0x02,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0xFF,
+
+    /* CRTC Registers */
+    0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0x0B, 0x3E, 0x00, 0x40, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0xEA, 0x8C, 0xDF, 0x28, 0x00, 0xE7, 0x04, 0xE3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x39, 0x0A, 0x3B,
+    0x0C, 0x3D, 0x0E, 0x3F, 0x10, 0x00, 0x12, 0x00, 0x04
+};
+
+static BYTE VideoMode_320x200_256color[] =
+{
+    /* Miscellaneous Register */
+    0x63,
+
+    /* Sequencer Registers */
+    0x00, 0x01, 0x0F, 0x00, 0x0E,
+
+    /* GC Registers */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, 0xFF,
+
+    /* CRTC Registers */
+    0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, 0x00, 0x41, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x9C, 0x8E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3,
+    0xFF,
+
+    /* AC Registers */
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
+    0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x00, 0x12, 0x00, 0x04
+};
+
+static LPBYTE VideoModes[] =
+{
+    VideoMode_40x25_text,       /* Mode 00h */
+    VideoMode_40x25_text,       /* Mode 01h */
+    VideoMode_80x25_text,       /* Mode 02h */
+    VideoMode_80x25_text,       /* Mode 03h */
+    VideoMode_320x200_4color,   /* Mode 04h */
+    VideoMode_320x200_4color,   /* Mode 05h */
+    VideoMode_640x200_2color,   /* Mode 06h */
+    NULL,                       /* Mode 07h */
+    NULL,                       /* Mode 08h */
+    NULL,                       /* Mode 09h */
+    NULL,                       /* Mode 0Ah */
+    NULL,                       /* Mode 0Bh */
+    NULL,                       /* Mode 0Ch */
+    VideoMode_320x200_16color,  /* Mode 0Dh */
+    VideoMode_640x200_16color,  /* Mode 0Eh */
+    NULL,                       /* Mode 0Fh */
+    VideoMode_640x350_16color,  /* Mode 10h */
+    VideoMode_640x480_2color,   /* Mode 11h */
+    VideoMode_640x480_16color,  /* Mode 12h */
+    VideoMode_320x200_256color, /* Mode 13h */
+};
+
+/* PRIVATE FUNCTIONS **********************************************************/
 
 static BOOLEAN BiosKbdBufferPush(WORD Data)
 {
+    /* Get the location of the element after the head */
+    WORD NextElement = Bda->KeybdBufferHead + 2;
+
+    /* Wrap it around if it's at or beyond the end */
+    if (NextElement >= Bda->KeybdBufferEnd) NextElement = Bda->KeybdBufferStart;
+
     /* If it's full, fail */
-    if (!BiosKbdBufferEmpty && (BiosKbdBufferStart == BiosKbdBufferEnd))
+    if (NextElement == Bda->KeybdBufferTail) return FALSE;
+
+    /* Put the value in the queue */
+    *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferTail)) = Data;
+    Bda->KeybdBufferTail += sizeof(WORD);
+
+    /* Check if we are at, or have passed, the end of the buffer */
+    if (Bda->KeybdBufferTail >= Bda->KeybdBufferEnd)
     {
-        return FALSE;
+        /* Return it to the beginning */
+        Bda->KeybdBufferTail = Bda->KeybdBufferStart;
     }
 
-    /* Otherwise, add the value to the queue */
-    BiosKbdBuffer[BiosKbdBufferEnd] = Data;
-    BiosKbdBufferEnd++;
-    BiosKbdBufferEnd %= BIOS_KBD_BUFFER_SIZE;
-    BiosKbdBufferEmpty = FALSE;
-
     /* Return success */
     return TRUE;
 }
@@ -117,258 +294,263 @@ static BOOLEAN BiosKbdBufferPush(WORD Data)
 static BOOLEAN BiosKbdBufferTop(LPWORD Data)
 {
     /* If it's empty, fail */
-    if (BiosKbdBufferEmpty) return FALSE;
+    if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
 
     /* Otherwise, get the value and return success */
-    *Data = BiosKbdBuffer[BiosKbdBufferStart];
+    *Data = *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferHead));
+
     return TRUE;
 }
 
-static BOOLEAN BiosKbdBufferPop()
+static BOOLEAN BiosKbdBufferPop(VOID)
 {
     /* If it's empty, fail */
-    if (BiosKbdBufferEmpty) return FALSE;
+    if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
 
-    /* Otherwise, remove the value and return success */
-    BiosKbdBufferStart++;
-    BiosKbdBufferStart %= BIOS_KBD_BUFFER_SIZE;
-    if (BiosKbdBufferStart == BiosKbdBufferEnd) BiosKbdBufferEmpty = TRUE;
+    /* Remove the value from the queue */
+    Bda->KeybdBufferHead += sizeof(WORD);
 
+    /* Check if we are at, or have passed, the end of the buffer */
+    if (Bda->KeybdBufferHead >= Bda->KeybdBufferEnd)
+    {
+        /* Return it to the beginning */
+        Bda->KeybdBufferHead = Bda->KeybdBufferStart;
+    }
+
+    /* Return success */
     return TRUE;
 }
 
+static VOID BiosReadWindow(LPWORD Buffer, SMALL_RECT Rectangle, BYTE Page)
+{
+    INT i, j;
+    INT Counter = 0;
+    WORD Character;
+    DWORD VideoAddress = TO_LINEAR(TEXT_VIDEO_SEG, Page * Bda->VideoPageSize);
+
+    for (i = Rectangle.Top; i <= Rectangle.Bottom; i++)
+    {
+        for (j = Rectangle.Left; j <= Rectangle.Right; j++)
+        {
+            /* Read from video memory */
+            VgaReadMemory(VideoAddress + (i * Bda->ScreenColumns + j) * sizeof(WORD),
+                          (LPVOID)&Character,
+                          sizeof(WORD));
+
+            /* Write the data to the buffer in row order */
+            Buffer[Counter++] = Character;
+        }
+    }
+}
+
+static VOID BiosWriteWindow(LPWORD Buffer, SMALL_RECT Rectangle, BYTE Page)
+{
+    INT i, j;
+    INT Counter = 0;
+    WORD Character;
+    DWORD VideoAddress = TO_LINEAR(TEXT_VIDEO_SEG, Page * Bda->VideoPageSize);
+
+    for (i = Rectangle.Top; i <= Rectangle.Bottom; i++)
+    {
+        for (j = Rectangle.Left; j <= Rectangle.Right; j++)
+        {
+            Character = Buffer[Counter++];
+
+            /* Read from video memory */
+            VgaWriteMemory(VideoAddress + (i * Bda->ScreenColumns + j) * sizeof(WORD),
+                           (LPVOID)&Character,
+                           sizeof(WORD));
+        }
+    }
+}
+
 /* PUBLIC FUNCTIONS ***********************************************************/
 
-BYTE BiosGetVideoMode()
+BYTE BiosGetVideoMode(VOID)
 {
-    return CurrentVideoMode;
+    return Bda->VideoMode;
 }
 
 BOOLEAN BiosSetVideoMode(BYTE ModeNumber)
 {
     INT i;
-    COORD Coord;
-    CONSOLE_GRAPHICS_BUFFER_INFO GraphicsBufferInfo;
-    LPBITMAPINFO BitmapInfo;
-    LPWORD PaletteIndex;
+    COORD Resolution;
+    LPBYTE Values = VideoModes[ModeNumber];
+
+    if (Values == NULL) return FALSE;
 
-    /* Make sure this is a valid video mode */
-    if (ModeNumber > BIOS_MAX_VIDEO_MODE) return FALSE;
-    if (VideoModes[ModeNumber].Pages == 0) return FALSE;
+    /* Write the misc register */
+    VgaWritePort(VGA_MISC_WRITE, *(Values++));
 
-    /* Free the current buffers */
-    for (i = 0; i < VideoModes[CurrentVideoMode].Pages; i++)
+    /* Write the sequencer registers */
+    for (i = 0; i < VGA_SEQ_MAX_REG; i++)
     {
-        if (ConsoleBuffers[i] != NULL) CloseHandle(ConsoleBuffers[i]);
-        if (!VideoModes[CurrentVideoMode].Text) CloseHandle(ConsoleMutexes[i]);
+        VgaWritePort(VGA_SEQ_INDEX, i);
+        VgaWritePort(VGA_SEQ_DATA, *(Values++));
     }
 
-    if (VideoModes[ModeNumber].Text)
+    /* Write the GC registers */
+    for (i = 0; i < VGA_GC_MAX_REG; i++)
     {
-        /* Page 0 is CONOUT$ */
-        ConsoleBuffers[0] = CreateFile(TEXT("CONOUT$"),
-                                       GENERIC_READ | GENERIC_WRITE,
-                                       FILE_SHARE_READ | FILE_SHARE_WRITE,
-                                       NULL,
-                                       OPEN_EXISTING,
-                                       0,
-                                       NULL);
-
-        /* Set the current page to page 0 */
-        CurrentVideoPage = 0;
-
-        /* Create console buffers for other pages */
-        for (i = 1; i < VideoModes[ModeNumber].Pages; i++)
-        {
-            ConsoleBuffers[i] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
-                                                          FILE_SHARE_READ | FILE_SHARE_WRITE,
-                                                          NULL,
-                                                          CONSOLE_TEXTMODE_BUFFER,
-                                                          NULL);
-        }
-
-        /* Set the size for the buffers */
-        for (i = 0; i < VideoModes[ModeNumber].Pages; i++)
-        {
-            Coord.X = VideoModes[ModeNumber].Width;
-            Coord.Y = VideoModes[ModeNumber].Height;
-
-            SetConsoleScreenBufferSize(ConsoleBuffers[i], Coord);
-        }
+        VgaWritePort(VGA_GC_INDEX, i);
+        VgaWritePort(VGA_GC_DATA, *(Values++));
     }
-    else
-    {
-        /* Allocate a bitmap info structure */
-        BitmapInfo = (LPBITMAPINFO)HeapAlloc(GetProcessHeap(),
-                                             HEAP_ZERO_MEMORY,
-                                             sizeof(BITMAPINFOHEADER)
-                                             + VideoModes[ModeNumber].Colors
-                                             * sizeof(WORD));
-        if (BitmapInfo == NULL) return FALSE;
-
-        /* Fill the bitmap info header */
-        ZeroMemory(&BitmapInfo->bmiHeader, sizeof(BITMAPINFOHEADER));
-        BitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
-        BitmapInfo->bmiHeader.biWidth = VideoModes[ModeNumber].Width;
-        BitmapInfo->bmiHeader.biHeight = VideoModes[ModeNumber].Height;
-        BitmapInfo->bmiHeader.biPlanes = 1;
-        BitmapInfo->bmiHeader.biCompression = BI_RGB;
-        BitmapInfo->bmiHeader.biBitCount = BiosColorNumberToBits(VideoModes[ModeNumber].Colors);
-
-        /* Calculate the image size */
-        BitmapInfo->bmiHeader.biSizeImage = BitmapInfo->bmiHeader.biWidth
-                                            * BitmapInfo->bmiHeader.biHeight
-                                            * (BitmapInfo->bmiHeader.biBitCount >> 3);
-
-        /* Fill the palette data */
-        PaletteIndex = (LPWORD)((ULONG_PTR)BitmapInfo + sizeof(BITMAPINFOHEADER));
-        for (i = 0; i < VideoModes[ModeNumber].Colors; i++)
-        {
-            PaletteIndex[i] = i;
-        }
 
-        /* Create a console buffer for each page */
-        for (i = 0; i < VideoModes[ModeNumber].Pages; i++)
-        {
-            /* Fill the console graphics buffer info */
-            GraphicsBufferInfo.dwBitMapInfoLength = sizeof(BITMAPINFOHEADER)
-                                                    + VideoModes[ModeNumber].Colors
-                                                    * sizeof(WORD);
-            GraphicsBufferInfo.lpBitMapInfo = BitmapInfo;
-            GraphicsBufferInfo.dwUsage = DIB_PAL_COLORS;
-
-            /* Create the buffer */
-            ConsoleBuffers[i] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
-                                                          FILE_SHARE_READ | FILE_SHARE_WRITE,
-                                                          NULL,
-                                                          CONSOLE_GRAPHICS_BUFFER,
-                                                          &GraphicsBufferInfo);
-
-            /* Save the framebuffer address and mutex */
-            ConsoleFramebuffers[i] = GraphicsBufferInfo.lpBitMap;
-            ConsoleMutexes[i] = GraphicsBufferInfo.hMutex;
-        }
+    /* Write the CRTC registers */
+    for (i = 0; i < VGA_CRTC_MAX_REG; i++)
+    {
+        VgaWritePort(VGA_CRTC_INDEX, i);
+        VgaWritePort(VGA_CRTC_DATA, *(Values++));
+    }
 
-        /* Free the bitmap information */
-        HeapFree(GetProcessHeap(), 0, BitmapInfo);
+    /* Write the AC registers */
+    for (i = 0; i < VGA_AC_MAX_REG; i++)
+    {
+        VgaWritePort(VGA_AC_INDEX, i);
+        VgaWritePort(VGA_AC_WRITE, *(Values++));
     }
 
-    /* Set the active page console buffer */
-    SetConsoleActiveScreenBuffer(ConsoleBuffers[CurrentVideoPage]);
+    /* Update the values in the BDA */
+    Bda->VideoMode = ModeNumber;
+    Bda->VideoPage = 0;
+    Bda->VideoPageSize = BIOS_PAGE_SIZE;
+    Bda->VideoPageOffset = 0;
 
-    /* Update the mode number */
-    CurrentVideoMode = ModeNumber;
+    /* Get the character height */
+    VgaWritePort(VGA_CRTC_INDEX, VGA_CRTC_MAX_SCAN_LINE_REG);
+    Bda->CharacterHeight = 1 + (VgaReadPort(VGA_CRTC_DATA) & 0x1F);
 
-    return TRUE;
-}
+    Resolution = VgaGetDisplayResolution();
+    Bda->ScreenColumns = Resolution.X;
+    Bda->ScreenRows = Resolution.Y - 1;
 
-inline DWORD BiosGetVideoMemoryStart()
-{
-    return (VideoModes[CurrentVideoMode].Segment << 4);
+    return TRUE;
 }
 
-inline VOID BiosVerticalRefresh()
+BOOLEAN BiosSetVideoPage(BYTE PageNumber)
 {
-    /* Ignore if we're in text mode */
-    if (VideoModes[CurrentVideoMode].Text) return;
+    if (PageNumber >= BIOS_MAX_PAGES) return FALSE;
 
-    /* Ignore if there's nothing to update */
-    if (!VideoNeedsUpdate) return;
+    /* Set the values in the BDA */
+    Bda->VideoPage = PageNumber;
+    Bda->VideoPageSize = BIOS_PAGE_SIZE;
+    Bda->VideoPageOffset = PageNumber * BIOS_PAGE_SIZE;
 
-    /* Redraw the screen */
-    InvalidateConsoleDIBits(ConsoleBuffers[CurrentVideoPage],
-                            &UpdateRectangle);
+    /* Set the start address in the CRTC */
+    VgaWritePort(VGA_CRTC_INDEX, VGA_CRTC_CURSOR_LOC_LOW_REG);
+    VgaWritePort(VGA_CRTC_DATA , LOBYTE(Bda->VideoPageOffset));
+    VgaWritePort(VGA_CRTC_INDEX, VGA_CRTC_CURSOR_LOC_HIGH_REG);
+    VgaWritePort(VGA_CRTC_DATA , HIBYTE(Bda->VideoPageOffset));
 
-    /* Clear the update flag */
-    VideoNeedsUpdate = FALSE;
+    return TRUE;
 }
 
-BOOLEAN BiosInitialize()
+BOOLEAN BiosInitialize(VOID)
 {
     INT i;
     WORD Offset = 0;
     LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress);
     LPBYTE BiosCode = (LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(BIOS_SEGMENT, 0));
 
+    /* Initialize the BDA */
+    Bda = (PBIOS_DATA_AREA)((ULONG_PTR)BaseAddress + TO_LINEAR(BDA_SEGMENT, 0));
+    Bda->EquipmentList = BIOS_EQUIPMENT_LIST;
+    Bda->KeybdBufferStart = FIELD_OFFSET(BIOS_DATA_AREA, KeybdBuffer);
+    Bda->KeybdBufferEnd = Bda->KeybdBufferStart + BIOS_KBD_BUFFER_SIZE * sizeof(WORD);
+
     /* Generate ISR stubs and fill the IVT */
     for (i = 0; i < 256; i++)
     {
         IntVecTable[i * 2] = Offset;
         IntVecTable[i * 2 + 1] = BIOS_SEGMENT;
 
-        if (i != SPECIAL_INT_NUM)
-        {
-            BiosCode[Offset++] = 0xFA; // cli
+        BiosCode[Offset++] = 0xFA; // cli
 
-            BiosCode[Offset++] = 0x6A; // push i
-            BiosCode[Offset++] = (BYTE)i;
+        BiosCode[Offset++] = 0x6A; // push i
+        BiosCode[Offset++] = (BYTE)i;
 
-            BiosCode[Offset++] = 0xCD; // int SPECIAL_INT_NUM
-            BiosCode[Offset++] = SPECIAL_INT_NUM;
+        BiosCode[Offset++] = LOBYTE(EMULATOR_BOP); // BOP sequence
+        BiosCode[Offset++] = HIBYTE(EMULATOR_BOP);
+        BiosCode[Offset++] = LOBYTE(EMULATOR_INT_BOP);
+        BiosCode[Offset++] = HIBYTE(EMULATOR_INT_BOP);
 
-            BiosCode[Offset++] = 0x83; // add sp, 2
-            BiosCode[Offset++] = 0xC4;
-            BiosCode[Offset++] = 0x02;
-        }
+        BiosCode[Offset++] = 0x83; // add sp, 2
+        BiosCode[Offset++] = 0xC4;
+        BiosCode[Offset++] = 0x02;
 
         BiosCode[Offset++] = 0xCF; // iret
     }
 
-    /* Get the input and output handles to the real console */
-    BiosConsoleInput = CreateFile(TEXT("CONIN$"),
-                                  GENERIC_READ | GENERIC_WRITE,
-                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
-                                  NULL,
-                                  OPEN_EXISTING,
-                                  0,
-                                  NULL);
-
-    BiosConsoleOutput = CreateFile(TEXT("CONOUT$"),
+    /* Get the input handle to the real console, and check for success */
+    BiosConsoleInput = CreateFileW(L"CONIN$",
                                    GENERIC_READ | GENERIC_WRITE,
                                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                                    NULL,
                                    OPEN_EXISTING,
                                    0,
                                    NULL);
+    if (BiosConsoleInput == INVALID_HANDLE_VALUE)
+    {
+        return FALSE;
+    }
 
-    /* Make sure it was successful */
-    if ((BiosConsoleInput == INVALID_HANDLE_VALUE)
-        || (BiosConsoleOutput == INVALID_HANDLE_VALUE))
+    /* Get the output handle to the real console, and check for success */
+    BiosConsoleOutput = CreateFileW(L"CONOUT$",
+                                    GENERIC_READ | GENERIC_WRITE,
+                                    FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                    NULL,
+                                    OPEN_EXISTING,
+                                    0,
+                                    NULL);
+    if (BiosConsoleOutput == INVALID_HANDLE_VALUE)
     {
+        CloseHandle(BiosConsoleInput);
         return FALSE;
     }
 
     /* Save the console screen buffer information */
     if (!GetConsoleScreenBufferInfo(BiosConsoleOutput, &BiosSavedBufferInfo))
     {
+        CloseHandle(BiosConsoleOutput);
+        CloseHandle(BiosConsoleInput);
         return FALSE;
     }
-    
-    /* Set the default video mode */
-    BiosSetVideoMode(BIOS_DEFAULT_VIDEO_MODE);
+
+    /* Initialize VGA */
+    if (!VgaInitialize(BiosConsoleOutput))
+    {
+        CloseHandle(BiosConsoleOutput);
+        CloseHandle(BiosConsoleInput);
+        return FALSE;
+    }
+
+    /* Update the cursor position */
+    BiosSetCursorPosition(BiosSavedBufferInfo.dwCursorPosition.Y,
+                          BiosSavedBufferInfo.dwCursorPosition.X,
+                          0);
 
     /* Set the console input mode */
     SetConsoleMode(BiosConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
 
     /* Initialize the PIC */
     PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
-    PicWriteCommand(PIC_SLAVE_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
+    PicWriteCommand(PIC_SLAVE_CMD , PIC_ICW1 | PIC_ICW1_ICW4);
 
     /* Set the interrupt offsets */
     PicWriteData(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT);
-    PicWriteData(PIC_SLAVE_DATA, BIOS_PIC_SLAVE_INT);
+    PicWriteData(PIC_SLAVE_DATA , BIOS_PIC_SLAVE_INT);
 
     /* Tell the master PIC there is a slave at IRQ 2 */
     PicWriteData(PIC_MASTER_DATA, 1 << 2);
-    PicWriteData(PIC_SLAVE_DATA, 2);
+    PicWriteData(PIC_SLAVE_DATA , 2);
 
     /* Make sure the PIC is in 8086 mode */
     PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086);
-    PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086);
+    PicWriteData(PIC_SLAVE_DATA , PIC_ICW4_8086);
 
     /* Clear the masks for both PICs */
     PicWriteData(PIC_MASTER_DATA, 0x00);
-    PicWriteData(PIC_SLAVE_DATA, 0x00);
+    PicWriteData(PIC_SLAVE_DATA , 0x00);
 
     PitWriteCommand(0x34);
     PitWriteData(0, 0x00);
@@ -377,165 +559,25 @@ BOOLEAN BiosInitialize()
     return TRUE;
 }
 
-VOID BiosCleanup()
+VOID BiosCleanup(VOID)
 {
-    INT i;
-
     /* Restore the old screen buffer */
     SetConsoleActiveScreenBuffer(BiosConsoleOutput);
 
     /* Restore the screen buffer size */
     SetConsoleScreenBufferSize(BiosConsoleOutput, BiosSavedBufferInfo.dwSize);
 
-    /* Free the buffers */
-    for (i = 0; i < VideoModes[CurrentVideoMode].Pages; i++)
-    {
-        if (ConsoleBuffers[i] != NULL) CloseHandle(ConsoleBuffers[i]);
-        if (!VideoModes[CurrentVideoMode].Text) CloseHandle(ConsoleMutexes[i]);
-    }
-
     /* Close the console handles */
-    if (BiosConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleInput);
     if (BiosConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleOutput);
+    if (BiosConsoleInput  != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleInput);
 }
 
-VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
-{
-    ULONG i;
-    COORD Coordinates;
-    COORD Origin = { 0, 0 };
-    COORD UnitSize = { 1, 1 };
-    CHAR_INFO Character;
-    SMALL_RECT Rect;
-
-    /* Start from the character address */
-    StartAddress &= ~1;
-
-    if (VideoModes[CurrentVideoMode].Text)
-    {
-        /* Loop through all the addresses */
-        for (i = StartAddress; i < EndAddress; i += 2)
-        {
-            /* Get the coordinates */
-            Coordinates = BiosVideoAddressToCoord(i);
-
-            /* Fill the rectangle structure */
-            Rect.Left = Coordinates.X;
-            Rect.Top = Coordinates.Y;
-            Rect.Right = Rect.Left;
-            Rect.Bottom = Rect.Top;
-
-            /* Fill the character data */
-            Character.Char.AsciiChar = *((PCHAR)((ULONG_PTR)BaseAddress + i));
-            Character.Attributes = *((PBYTE)((ULONG_PTR)BaseAddress + i + 1));
-
-            /* Write the character */
-            WriteConsoleOutputA(BiosConsoleOutput,
-                                &Character,
-                                UnitSize,
-                                Origin,
-                                &Rect);
-        }
-    }
-    else
-    {
-        /* Wait for the mutex object */
-        WaitForSingleObject(ConsoleMutexes[CurrentVideoPage], INFINITE);
-
-        /* Copy the data to the framebuffer */
-        RtlCopyMemory((LPVOID)((ULONG_PTR)ConsoleFramebuffers[CurrentVideoPage]
-                      + StartAddress - BiosGetVideoMemoryStart()),
-                      (LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
-                      EndAddress - StartAddress);
-
-        /* Release the mutex */
-        ReleaseMutex(ConsoleMutexes[CurrentVideoPage]);
-
-        /* Check if this is the first time the rectangle is updated */
-        if (!VideoNeedsUpdate)
-        {
-            UpdateRectangle.Left = UpdateRectangle.Top = (SHORT)0x7FFF;
-            UpdateRectangle.Right = UpdateRectangle.Bottom = (SHORT)0x8000;
-        }
-
-        /* Expand the update rectangle */
-        for (i = StartAddress; i < EndAddress; i++)
-        {
-            /* Get the coordinates */
-            Coordinates = BiosVideoAddressToCoord(i);
-
-            /* Expand the rectangle to include the point */
-            UpdateRectangle.Left = min(UpdateRectangle.Left, Coordinates.X);
-            UpdateRectangle.Right = max(UpdateRectangle.Right, Coordinates.X);
-            UpdateRectangle.Top = min(UpdateRectangle.Top, Coordinates.Y);
-            UpdateRectangle.Bottom = max(UpdateRectangle.Bottom, Coordinates.Y);
-        }
-
-        /* Set the update flag */
-        VideoNeedsUpdate = TRUE;
-    }
-}
-
-VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
-{
-    ULONG i;
-    COORD Coordinates;
-    WORD Attribute;
-    DWORD CharsWritten;
-
-    if (VideoModes[CurrentVideoMode].Text)
-    {
-        /* Loop through all the addresses */
-        for (i = StartAddress; i < EndAddress; i++)
-        {
-            /* Get the coordinates */
-            Coordinates = BiosVideoAddressToCoord(i);
-
-            /* Check if this is a character byte or an attribute byte */
-            if ((i - (VideoModes[CurrentVideoMode].Segment << 4)) % 2 == 0)
-            {
-                /* This is a regular character */
-                ReadConsoleOutputCharacterA(BiosConsoleOutput,
-                                            (LPSTR)((ULONG_PTR)BaseAddress + i),
-                                            sizeof(CHAR),
-                                            Coordinates,
-                                            &CharsWritten);
-            }
-            else
-            {
-                /*  This is an attribute */
-                ReadConsoleOutputAttribute(BiosConsoleOutput,
-                                           &Attribute,
-                                           sizeof(CHAR),
-                                           Coordinates,
-                                           &CharsWritten);
-
-                *(PCHAR)((ULONG_PTR)BaseAddress + i) = LOBYTE(Attribute);
-            }
-        }
-    }
-    else
-    {
-        /* Wait for the mutex object */
-        WaitForSingleObject(ConsoleMutexes[CurrentVideoPage], INFINITE);
-
-        /* Copy the data to the emulator memory */
-        RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
-                      (LPVOID)((ULONG_PTR)ConsoleFramebuffers[CurrentVideoPage]
-                      + StartAddress - BiosGetVideoMemoryStart()),
-                      EndAddress - StartAddress);
-
-        /* Release the mutex */
-        ReleaseMutex(ConsoleMutexes[CurrentVideoPage]);
-    }
-}
-
-WORD BiosPeekCharacter()
+WORD BiosPeekCharacter(VOID)
 {
     WORD CharacterData;
     
     /* Check if there is a key available */
-    if (BiosKbdBufferEmpty) return 0xFFFF;
+    if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return 0xFFFF;
 
     /* Get the key from the queue, but don't remove it */
     BiosKbdBufferTop(&CharacterData);
@@ -543,14 +585,14 @@ WORD BiosPeekCharacter()
     return CharacterData;
 }
 
-WORD BiosGetCharacter()
+WORD BiosGetCharacter(VOID)
 {
     WORD CharacterData;
     INPUT_RECORD InputRecord;
     DWORD Count;
 
     /* Check if there is a key available */
-    if (!BiosKbdBufferEmpty)
+    if (Bda->KeybdBufferHead != Bda->KeybdBufferTail)
     {
         /* Get the key from the queue, and remove it */
         BiosKbdBufferTop(&CharacterData);
@@ -579,15 +621,117 @@ WORD BiosGetCharacter()
     return CharacterData;
 }
 
-VOID BiosVideoService()
+VOID BiosSetCursorPosition(BYTE Row, BYTE Column, BYTE Page)
+{
+    /* Make sure the selected video page is valid */
+    if (Page >= BIOS_MAX_PAGES) return;
+
+    /* Update the position in the BDA */
+    Bda->CursorPosition[Page] = (Row << 8) | Column;
+
+    /* Check if this is the current video page */
+    if (Page == Bda->VideoPage)
+    {
+        WORD Offset = Row * Bda->ScreenColumns + Column;
+
+        /* Modify the CRTC registers */
+        VgaWritePort(VGA_CRTC_INDEX, VGA_CRTC_CURSOR_LOC_LOW_REG);
+        VgaWritePort(VGA_CRTC_DATA , LOBYTE(Offset));
+        VgaWritePort(VGA_CRTC_INDEX, VGA_CRTC_CURSOR_LOC_HIGH_REG);
+        VgaWritePort(VGA_CRTC_DATA , HIBYTE(Offset));
+    }
+}
+
+BOOLEAN BiosScrollWindow(INT Direction,
+                         DWORD Amount,
+                         SMALL_RECT Rectangle,
+                         BYTE Page,
+                         BYTE FillAttribute)
+{
+    DWORD i;
+    LPWORD WindowData;
+    DWORD WindowSize = (Rectangle.Bottom - Rectangle.Top + 1)
+                       * (Rectangle.Right - Rectangle.Left + 1);
+
+    /* Allocate a buffer for the window */
+    WindowData = (LPWORD)HeapAlloc(GetProcessHeap(),
+                                   HEAP_ZERO_MEMORY,
+                                   WindowSize * sizeof(WORD));
+    if (WindowData == NULL) return FALSE;
+
+    /* Read the window data */
+    BiosReadWindow(WindowData, Rectangle, Page);
+
+    if (Amount == 0)
+    {
+        /* Fill the window */
+        for (i = 0; i < WindowSize; i++)
+        {
+            WindowData[i] = ' ' | (FillAttribute << 8);
+        }
+
+        goto Done;
+    }
+
+    // TODO: Scroll the window!
+
+Done:
+    /* Write back the window data */
+    BiosWriteWindow(WindowData, Rectangle, Page);
+
+    /* Free the window buffer */
+    HeapFree(GetProcessHeap(), 0, WindowData);
+
+    return TRUE;
+}
+
+VOID BiosPrintCharacter(CHAR Character, BYTE Attribute, BYTE Page)
+{
+    WORD CharData = (Attribute << 8) | Character;
+    BYTE Row, Column;
+
+    /* Make sure the page exists */
+    if (Page >= BIOS_MAX_PAGES) return;
+
+    /* Get the cursor location */
+    Row = HIBYTE(Bda->CursorPosition[Page]);
+    Column = LOBYTE(Bda->CursorPosition[Page]);
+
+    /* Write the character */
+    VgaWriteMemory(TO_LINEAR(TEXT_VIDEO_SEG,
+                   (Row * Bda->ScreenColumns + Column) * sizeof(WORD)),
+                   (LPVOID)&CharData,
+                   sizeof(WORD));
+
+    /* Advance the cursor */
+    Column++;
+
+    /* Check if it passed the end of the row */
+    if (Column == Bda->ScreenColumns)
+    {
+        /* Return to the first column */
+        Column = 0;
+
+        if (Row == Bda->ScreenRows)
+        {
+            /* The screen must be scrolled */
+            SMALL_RECT Rectangle = { 0, 0, Bda->ScreenColumns - 1, Bda->ScreenRows };
+
+            BiosScrollWindow(SCROLL_DIRECTION_UP,
+                             1,
+                             Rectangle,
+                             Page,
+                             DEFAULT_ATTRIBUTE);
+        }
+        else Row++;
+    }
+
+    /* Set the cursor position */
+    BiosSetCursorPosition(Row, Column, Page);
+}
+
+VOID BiosVideoService(LPWORD Stack)
 {
-    INT CursorHeight;
-    BOOLEAN Invisible = FALSE;
-    COORD Position;
-    CONSOLE_CURSOR_INFO CursorInfo;
-    CONSOLE_SCREEN_BUFFER_INFO ScreenBufferInfo;
-    CHAR_INFO Character;
-    SMALL_RECT Rect;
     DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
     DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
     DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
@@ -599,22 +743,23 @@ VOID BiosVideoService()
         case 0x00:
         {
             BiosSetVideoMode(LOBYTE(Eax));
+            VgaClearMemory();
+
             break;
         }
 
         /* Set Text-Mode Cursor Shape */
         case 0x01:
         {
-            /* Retrieve and validate the input */
-            Invisible = ((HIBYTE(Ecx) >> 5) & 0x03) ? TRUE : FALSE;
-            CursorHeight = (HIBYTE(Ecx) & 0x1F) - (LOBYTE(Ecx) & 0x1F);
-            if (CursorHeight < 1) CursorHeight = 1;
-            if (CursorHeight > 100) CursorHeight = 100;
+            /* Update the BDA */
+            Bda->CursorStartLine = HIBYTE(Ecx);
+            Bda->CursorEndLine = LOBYTE(Ecx);
 
-            /* Set the cursor */
-            CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
-            CursorInfo.bVisible = !Invisible;
-            SetConsoleCursorInfo(BiosConsoleOutput, &CursorInfo);
+            /* Modify the CRTC registers */
+            VgaWritePort(VGA_CRTC_INDEX, VGA_CRTC_CURSOR_START_REG);
+            VgaWritePort(VGA_CRTC_DATA , Bda->CursorStartLine);
+            VgaWritePort(VGA_CRTC_INDEX, VGA_CRTC_CURSOR_END_REG);
+            VgaWritePort(VGA_CRTC_DATA , Bda->CursorEndLine);
 
             break;
         }
@@ -622,119 +767,116 @@ VOID BiosVideoService()
         /* Set Cursor Position */
         case 0x02:
         {
-            Position.X = LOBYTE(Edx);
-            Position.Y = HIBYTE(Edx);
-
-            SetConsoleCursorPosition(BiosConsoleOutput, Position);
+            BiosSetCursorPosition(HIBYTE(Edx), LOBYTE(Edx), HIBYTE(Ebx));
             break;
         }
 
         /* Get Cursor Position */
         case 0x03:
         {
-            INT StartLine;
-
-            /* Retrieve the data */
-            GetConsoleCursorInfo(BiosConsoleOutput, &CursorInfo);
-            GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
-
-            /* Find the first line */
-            StartLine = 32 - ((CursorInfo.dwSize * 32) / 100);
+            /* Make sure the selected video page exists */
+            if (HIBYTE(Ebx) >= BIOS_MAX_PAGES) break;
 
             /* Return the result */
             EmulatorSetRegister(EMULATOR_REG_AX, 0);
-            EmulatorSetRegister(EMULATOR_REG_CX, (StartLine << 8) | 0x1F);
-            EmulatorSetRegister(EMULATOR_REG_DX,
-                                LOWORD(ScreenBufferInfo.dwCursorPosition.Y) << 8
-                                || LOWORD(ScreenBufferInfo.dwCursorPosition.X));
+            EmulatorSetRegister(EMULATOR_REG_CX,
+                                (Bda->CursorStartLine << 8) | Bda->CursorEndLine);
+            EmulatorSetRegister(EMULATOR_REG_DX, Bda->CursorPosition[HIBYTE(Ebx)]);
+
             break;
         }
 
-        /* Scroll Up/Down Window */
-        case 0x06:
-        case 0x07:
+        /* Query Light Pen */
+        case 0x04:
         {
-            Rect.Top = HIBYTE(Ecx);
-            Rect.Left = LOBYTE(Ecx);
-            Rect.Bottom = HIBYTE(Edx);
-            Rect.Right = LOBYTE(Edx);
-            Character.Char.UnicodeChar = L' ';
-            Character.Attributes = HIBYTE(Ebx);
-            Position.X = Rect.Left;
-            if (HIBYTE(Eax) == 0x06) Position.Y = Rect.Top - LOBYTE(Eax);
-            else Position.Y = Rect.Top + LOBYTE(Eax);
-
-            ScrollConsoleScreenBuffer(BiosConsoleOutput,
-                                      &Rect,
-                                      &Rect,
-                                      Position,
-                                      &Character);
+            /*
+             * On modern BIOSes, this function returns 0
+             * so that we can ignore the other registers.
+             */
+            EmulatorSetRegister(EMULATOR_REG_AX, 0);
             break;
         }
 
-        /* Read Character And Attribute At Cursor Position */
-        case 0x08:
+        /* Select Active Display Page */
+        case 0x05:
         {
-            COORD BufferSize = { 1, 1 }, Origin = { 0, 0 };
+            /* Check if the page exists */
+            if (LOBYTE(Eax) >= BIOS_MAX_PAGES) break;
 
-            /* Get the cursor position */
-            GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
+            /* Check if this is the same page */
+            if (LOBYTE(Eax) == Bda->VideoPage) break;
 
-            /* Read at cursor position */
-            Rect.Left = ScreenBufferInfo.dwCursorPosition.X;
-            Rect.Top = ScreenBufferInfo.dwCursorPosition.Y;
-            
-            /* Read the console output */
-            ReadConsoleOutput(BiosConsoleOutput, &Character, BufferSize, Origin, &Rect);
+            /* Change the video page */
+            BiosSetVideoPage(LOBYTE(Eax));
 
-            /* Return the result */
-            EmulatorSetRegister(EMULATOR_REG_AX,
-                                (LOBYTE(Character.Attributes) << 8)
-                                | Character.Char.AsciiChar);
+            break;
+        }
+
+        /* Scroll Window Up/Down */
+        case 0x06:
+        case 0x07:
+        {
+            SMALL_RECT Rectangle =
+            {
+                LOBYTE(Ecx),
+                HIBYTE(Ecx),
+                LOBYTE(Edx),
+                HIBYTE(Edx)
+            };
+
+            /* Call the internal function */
+            BiosScrollWindow((HIBYTE(Eax) == 0x06)
+                             ? SCROLL_DIRECTION_UP : SCROLL_DIRECTION_DOWN,
+                             LOBYTE(Eax),
+                             Rectangle,
+                             Bda->VideoPage,
+                             HIBYTE(Ebx));
 
             break;
         }
 
-        /* Write Character And Attribute At Cursor Position */
+        /* Read/Write Character From Cursor Position */
+        case 0x08:
         case 0x09:
+        case 0x0A:
         {
-            DWORD CharsWritten;
+            WORD CharacterData = MAKEWORD(LOBYTE(Eax), LOBYTE(Ebx));
+            BYTE Page = HIBYTE(Ebx);
+            DWORD Offset;
+
+            /* Check if the page exists */
+            if (Page >= BIOS_MAX_PAGES) break;
 
-            /* Get the cursor position */
-            GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
+            /* Find the offset of the character */
+            Offset = Page * Bda->VideoPageSize
+                     + (HIBYTE(Bda->CursorPosition[Page]) * Bda->ScreenColumns
+                     + LOBYTE(Bda->CursorPosition[Page])) * 2;
 
-            /* Write the attribute to the output */
-            FillConsoleOutputAttribute(BiosConsoleOutput,
-                                       LOBYTE(Ebx),
-                                       LOWORD(Ecx),
-                                       ScreenBufferInfo.dwCursorPosition,
-                                       &CharsWritten);
+            if (HIBYTE(Eax) == 0x08)
+            {
+                /* Read from the video memory */
+                VgaReadMemory(TO_LINEAR(TEXT_VIDEO_SEG, Offset),
+                              (LPVOID)&CharacterData,
+                              sizeof(WORD));
 
-            /* Write the character to the output */
-            FillConsoleOutputCharacterA(BiosConsoleOutput,
-                                        LOBYTE(Eax),
-                                        LOWORD(Ecx),
-                                        ScreenBufferInfo.dwCursorPosition,
-                                        &CharsWritten);
+                /* Return the character in AX */
+                EmulatorSetRegister(EMULATOR_REG_AX, CharacterData);
+            }
+            else
+            {
+                /* Write to video memory */
+                VgaWriteMemory(TO_LINEAR(TEXT_VIDEO_SEG, Offset),
+                               (LPVOID)&CharacterData,
+                               (HIBYTE(Ebx) == 0x09) ? sizeof(WORD) : sizeof(BYTE));
+            }
 
             break;
         }
 
-        /* Write Character Only At Cursor Position */
-        case 0x0A:
+        /* Teletype Output */
+        case 0x0E:
         {
-            DWORD CharsWritten;
-
-            /* Get the cursor position */
-            GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
-
-            /* Write the character to the output */
-            FillConsoleOutputCharacterA(BiosConsoleOutput,
-                                        LOBYTE(Eax),
-                                        LOWORD(Ecx),
-                                        ScreenBufferInfo.dwCursorPosition,
-                                        &CharsWritten);
-
+            BiosPrintCharacter(LOBYTE(Eax), LOBYTE(Ebx), HIBYTE(Ebx));
             break;
         }
 
@@ -742,10 +884,30 @@ VOID BiosVideoService()
         case 0x0F:
         {
             EmulatorSetRegister(EMULATOR_REG_AX,
-                                (VideoModes[CurrentVideoMode].Width << 8)
-                                | CurrentVideoMode);
+                                MAKEWORD(Bda->VideoMode, Bda->ScreenColumns));
             EmulatorSetRegister(EMULATOR_REG_BX,
-                                (CurrentVideoPage << 8) | LOBYTE(Ebx));
+                                MAKEWORD(LOBYTE(Ebx), Bda->VideoPage));
+
+            break;
+        }
+
+        /* Scroll Window */
+        case 0x12:
+        {
+            SMALL_RECT Rectangle =
+            {
+                LOBYTE(Ecx),
+                HIBYTE(Ecx),
+                LOBYTE(Edx),
+                HIBYTE(Edx)
+            };
+
+            /* Call the internal function */
+            BiosScrollWindow(LOBYTE(Ebx),
+                             LOBYTE(Eax),
+                             Rectangle,
+                             Bda->VideoPage,
+                             DEFAULT_ATTRIBUTE);
 
             break;
         }
@@ -758,7 +920,7 @@ VOID BiosVideoService()
     }
 }
 
-VOID BiosKeyboardService()
+VOID BiosKeyboardService(LPWORD Stack)
 {
     DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
 
@@ -780,12 +942,12 @@ VOID BiosKeyboardService()
             {
                 /* There is a character, clear ZF and return it */
                 EmulatorSetRegister(EMULATOR_REG_AX, Data);
-                EmulatorClearFlag(EMULATOR_FLAG_ZF);
+                Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
             }
             else
             {
                 /* No character, set ZF */
-                EmulatorSetFlag(EMULATOR_FLAG_ZF);
+                Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
             }
 
             break;
@@ -799,7 +961,7 @@ VOID BiosKeyboardService()
     }
 }
 
-VOID BiosTimeService()
+VOID BiosTimeService(LPWORD Stack)
 {
     DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
     DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
@@ -811,15 +973,15 @@ VOID BiosTimeService()
         {
             /* Set AL to 1 if midnight had passed, 0 otherwise */
             Eax &= 0xFFFFFF00;
-            if (BiosPassedMidnight) Eax |= 1;
+            if (Bda->MidnightPassed) Eax |= 1;
 
             /* Return the tick count in CX:DX */
             EmulatorSetRegister(EMULATOR_REG_AX, Eax);
-            EmulatorSetRegister(EMULATOR_REG_CX, HIWORD(BiosTickCount));
-            EmulatorSetRegister(EMULATOR_REG_DX, LOWORD(BiosTickCount));
+            EmulatorSetRegister(EMULATOR_REG_CX, HIWORD(Bda->TickCounter));
+            EmulatorSetRegister(EMULATOR_REG_DX, LOWORD(Bda->TickCounter));
 
             /* Reset the midnight flag */
-            BiosPassedMidnight = FALSE;
+            Bda->MidnightPassed = FALSE;
 
             break;
         }
@@ -827,10 +989,10 @@ VOID BiosTimeService()
         case 0x01:
         {
             /* Set the tick count to CX:DX */
-            BiosTickCount = MAKELONG(LOWORD(Edx), LOWORD(Ecx));
+            Bda->TickCounter = MAKELONG(LOWORD(Edx), LOWORD(Ecx));
 
             /* Reset the midnight flag */
-            BiosPassedMidnight = FALSE;
+            Bda->MidnightPassed = FALSE;
 
             break;
         }
@@ -843,19 +1005,19 @@ VOID BiosTimeService()
     }
 }
 
-VOID BiosSystemTimerInterrupt()
+VOID BiosSystemTimerInterrupt(LPWORD Stack)
 {
     /* Increase the system tick count */
-    BiosTickCount++;
+    Bda->TickCounter++;
 }
 
-VOID BiosEquipmentService()
+VOID BiosEquipmentService(LPWORD Stack)
 {
     /* Return the equipment list */
-    EmulatorSetRegister(EMULATOR_REG_AX, BIOS_EQUIPMENT_LIST);
+    EmulatorSetRegister(EMULATOR_REG_AX, Bda->EquipmentList);
 }
 
-VOID BiosHandleIrq(BYTE IrqNumber)
+VOID BiosHandleIrq(BYTE IrqNumber, LPWORD Stack)
 {
     switch (IrqNumber)
     {
@@ -879,7 +1041,7 @@ VOID BiosHandleIrq(BYTE IrqNumber)
 
             /* Get the scan code and virtual key code */
             ScanCode = KeyboardReadData();
-            VirtualKey = MapVirtualKey(ScanCode, MAPVK_VSC_TO_VK);
+            VirtualKey = MapVirtualKey(ScanCode & 0x7F, MAPVK_VSC_TO_VK);
 
             /* Check if this is a key press or release */
             if (!(ScanCode & (1 << 7)))
@@ -897,10 +1059,11 @@ VOID BiosHandleIrq(BYTE IrqNumber)
                 BiosKeyboardMap[VirtualKey] |= (1 << 7);
 
                 /* Find out which character this is */
-                ToAscii(ScanCode, VirtualKey, BiosKeyboardMap, &Character, 0);
-
-                /* Push it onto the BIOS keyboard queue */
-                BiosKbdBufferPush((ScanCode << 8) | (Character & 0xFF));
+                if (ToAscii(VirtualKey, ScanCode, BiosKeyboardMap, &Character, 0) > 0)
+                {
+                    /* Push it onto the BIOS keyboard queue */
+                    BiosKbdBufferPush((ScanCode << 8) | (Character & 0xFF));
+                }
             }
             else
             {