[NTVDM]
[reactos.git] / subsystems / ntvdm / bios.c
index 40632dd..3b9c99b 100644 (file)
@@ -8,6 +8,8 @@
 
 /* INCLUDES *******************************************************************/
 
+#define NDEBUG
+
 #include "bios.h"
 #include "emulator.h"
 #include "pic.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, BiosConsoleOutput;
-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 BiosConsoleInput = INVALID_HANDLE_VALUE;
+static HANDLE BiosConsoleOutput = INVALID_HANDLE_VALUE;
+static HANDLE BiosGraphicsOutput = NULL;
+static LPVOID ConsoleFramebuffer = NULL;
+static HANDLE ConsoleMutex = NULL;
+static BYTE CurrentVideoMode, CurrentVideoPage;
+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 */
+    /* Width | Height | Text | Bpp   | 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 */
+    { 320,      200,    FALSE,  2,      FALSE,  1,      0xB800}, /* Mode 04h */
+    { 320,      200,    FALSE,  2,      TRUE,   1,      0xB800}, /* Mode 05h */
+    { 640,      200,    FALSE,  1,      FALSE,  1,      0xB800}, /* Mode 06h */
+    { 80,       25,     TRUE,   8,      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 */
+    { 320,      200,    FALSE,  4,      FALSE,  1,      0xA000}, /* Mode 0Dh */
+    { 640,      200,    FALSE,  4,      FALSE,  1,      0xA000}, /* Mode 0Eh */
+    { 640,      350,    FALSE,  1,      FALSE,  1,      0xA000}, /* Mode 0Fh */
+    { 640,      350,    FALSE,  4,      FALSE,  1,      0xA000}, /* Mode 10h */
+    { 640,      480,    FALSE,  1,      FALSE,  1,      0xA000}, /* Mode 11h */
+    { 640,      480,    FALSE,  4,      FALSE,  1,      0xA000}, /* Mode 12h */
+    { 320,      200,    FALSE,  8,      FALSE,  1,      0xA000}  /* Mode 13h */
 };
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
+static DWORD BiosGetVideoPageSize()
+{
+    INT i;
+    DWORD BufferSize = VideoModes[CurrentVideoMode].Width
+                       * VideoModes[CurrentVideoMode].Height
+                       * VideoModes[CurrentVideoMode].Bpp
+                       / 8;
+    
+    for (i = 0; i < 32; i++) if ((1 << i) >= BufferSize) break;
+
+    return 1 << i;
+}
+
+static BYTE BiosVideoAddressToPage(ULONG Address)
+{
+    return (Address - BiosGetVideoMemoryStart())
+            / BiosGetVideoPageSize();
+}
+
 static COORD BiosVideoAddressToCoord(ULONG Address)
 {
     COORD Result = {0, 0};
+    DWORD PageStart = BiosVideoAddressToPage(Address) * BiosGetVideoPageSize();
+    DWORD Offset = Address - BiosGetVideoMemoryStart() - PageStart;
 
-    Result.X = ((Address - (VideoModes[CurrentVideoMode].Segment << 4)) >> 1)
-               % VideoModes[CurrentVideoMode].Width;
-    Result.Y = ((Address - (VideoModes[CurrentVideoMode].Segment << 4)) >> 1)
-               / VideoModes[CurrentVideoMode].Width;
+    if (VideoModes[CurrentVideoMode].Text)
+    {
+        Result.X = (Offset / sizeof(WORD)) % VideoModes[CurrentVideoMode].Width;
+        Result.Y = (Offset / sizeof(WORD)) / VideoModes[CurrentVideoMode].Width;
+    }
+    else
+    {
+        Result.X = ((Offset * 8) / VideoModes[CurrentVideoMode].Bpp)
+                   % VideoModes[CurrentVideoMode].Width;
+        Result.Y = ((Offset * 8) / VideoModes[CurrentVideoMode].Bpp)
+                   / VideoModes[CurrentVideoMode].Width;
+    }
 
     return Result;
 }
 
 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;
 }
@@ -87,26 +127,99 @@ 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()
 {
     /* If it's empty, fail */
-    if (BiosKbdBufferEmpty) return FALSE;
+    if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
+
+    /* 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 BOOLEAN BiosCreateGraphicsBuffer(BYTE ModeNumber)
+{
+    INT i;
+    CONSOLE_GRAPHICS_BUFFER_INFO GraphicsBufferInfo;
+    LPBITMAPINFO BitmapInfo;
+    LPWORD PaletteIndex;
 
-    /* Otherwise, remove the value and return success */
-    BiosKbdBufferStart++;
-    BiosKbdBufferStart %= BIOS_KBD_BUFFER_SIZE;
-    if (BiosKbdBufferStart == BiosKbdBufferEnd) BiosKbdBufferEmpty = TRUE;
+    /* Allocate a bitmap info structure */
+    BitmapInfo = (LPBITMAPINFO)HeapAlloc(GetProcessHeap(),
+                                         HEAP_ZERO_MEMORY,
+                                         sizeof(BITMAPINFOHEADER)
+                                         + (1 << VideoModes[ModeNumber].Bpp)
+                                         * 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 = VideoModes[ModeNumber].Bpp;
+
+    /* 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 < (1 << VideoModes[ModeNumber].Bpp); i++)
+    {
+        PaletteIndex[i] = i;
+    }
+
+    /* Fill the console graphics buffer info */
+    GraphicsBufferInfo.dwBitMapInfoLength = sizeof(BITMAPINFOHEADER)
+                                            + (1 << VideoModes[ModeNumber].Bpp)
+                                            * sizeof(WORD);
+    GraphicsBufferInfo.lpBitMapInfo = BitmapInfo;
+    GraphicsBufferInfo.dwUsage = DIB_PAL_COLORS;
+
+    /* Create the buffer */
+    BiosGraphicsOutput = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
+                                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                                   NULL,
+                                                   CONSOLE_GRAPHICS_BUFFER,
+                                                   &GraphicsBufferInfo);
+
+    /* Save the framebuffer address and mutex */
+    ConsoleFramebuffer = GraphicsBufferInfo.lpBitMap;
+    ConsoleMutex = GraphicsBufferInfo.hMutex;
+
+    /* Free the bitmap information */
+    HeapFree(GetProcessHeap(), 0, BitmapInfo);
 
     return TRUE;
 }
 
+static VOID BiosDestroyGraphicsBuffer()
+{
+    CloseHandle(ConsoleMutex);
+    CloseHandle(BiosGraphicsOutput);
+}
+
 /* PUBLIC FUNCTIONS ***********************************************************/
 
 BYTE BiosGetVideoMode()
@@ -116,123 +229,108 @@ BYTE BiosGetVideoMode()
 
 BOOLEAN BiosSetVideoMode(BYTE ModeNumber)
 {
-    INT i;
     COORD Coord;
-    CONSOLE_GRAPHICS_BUFFER_INFO GraphicsBufferInfo;
-    LPBITMAPINFO BitmapInfo;
-    LPWORD PaletteIndex;
 
     /* Make sure this is a valid video mode */
     if (ModeNumber > BIOS_MAX_VIDEO_MODE) return FALSE;
     if (VideoModes[ModeNumber].Pages == 0) return FALSE;
 
-    /* Free the current buffers */
-    for (i = 0; i < VideoModes[CurrentVideoMode].Pages; i++)
+    /* Set the new video mode size */
+    Coord.X = VideoModes[ModeNumber].Width;
+    Coord.Y = VideoModes[ModeNumber].Height;
+
+    if (VideoModes[ModeNumber].Text && VideoModes[CurrentVideoMode].Text)
     {
-        if (ConsoleBuffers[i] != NULL) CloseHandle(ConsoleBuffers[i]);
-    }
+        /* Switching from text mode to another text mode */
 
-    if (VideoModes[ModeNumber].Text)
+        /* Resize the text buffer */
+        SetConsoleScreenBufferSize(BiosConsoleOutput, Coord);
+    }
+    else if (VideoModes[ModeNumber].Text && !VideoModes[CurrentVideoMode].Text)
     {
-        /* 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);
-        }
+        /* Switching from graphics mode to text mode */
 
-        /* Set the size for the buffers */
-        for (i = 0; i < VideoModes[ModeNumber].Pages; i++)
-        {
-            Coord.X = VideoModes[ModeNumber].Width;
-            Coord.Y = VideoModes[ModeNumber].Height;
+        /* Resize the text buffer */
+        SetConsoleScreenBufferSize(BiosConsoleOutput, Coord);
 
-            SetConsoleScreenBufferSize(ConsoleBuffers[i], Coord);
-        }
+        /* Change the active screen buffer to the text buffer */
+        SetConsoleActiveScreenBuffer(BiosConsoleOutput);
+
+        /* Cleanup the graphics buffer */
+        BiosDestroyGraphicsBuffer();
     }
-    else
+    else if (!VideoModes[ModeNumber].Text && VideoModes[CurrentVideoMode].Text)
     {
-        /* 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;
-
-        /* Calculate the number of color bits */
-        for (i = 31; i >= 0; i--)
-        {
-            if (VideoModes[ModeNumber].Colors & (1 << i)) break;
-        }
-        if (i == 0) i = 32;
-        BitmapInfo->bmiHeader.biBitCount = i;
+        /* Switching from text mode to graphics mode */
+        if (!BiosCreateGraphicsBuffer(ModeNumber)) return FALSE;
 
-        /* Calculate the image size */
-        BitmapInfo->bmiHeader.biSizeImage = BitmapInfo->bmiHeader.biWidth
-                                            * BitmapInfo->bmiHeader.biHeight
-                                            * (BitmapInfo->bmiHeader.biBitCount >> 3);
+        SetConsoleActiveScreenBuffer(BiosGraphicsOutput);
+    }
+    else if (!VideoModes[ModeNumber].Text && !VideoModes[CurrentVideoMode].Text)
+    {
+        /* Switching from graphics mode to another graphics mode */
+    
+        /* Temporarily switch to the text mode buffer */
+        SetConsoleActiveScreenBuffer(BiosConsoleOutput);
 
-        /* Fill the palette data */
-        PaletteIndex = (LPWORD)((ULONG_PTR)BitmapInfo + sizeof(BITMAPINFOHEADER));
-        for (i = 0; i < VideoModes[ModeNumber].Colors; i++)
-        {
-            PaletteIndex[i] = i;
-        }
+        /* Cleanup the current graphics mode buffer */
+        BiosDestroyGraphicsBuffer();
 
-        /* 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 */
-            ConsoleFramebuffers[i] = GraphicsBufferInfo.lpBitMap;
-        }
+        /* Create a new graphics mode buffer */
+        if (!BiosCreateGraphicsBuffer(ModeNumber)) return FALSE;
 
-        /* Free the bitmap information */
-        HeapFree(GetProcessHeap(), 0, BitmapInfo);
+        /* Switch to it */
+        SetConsoleActiveScreenBuffer(BiosGraphicsOutput);
     }
 
-    /* Set the active page console buffer */
-    SetConsoleActiveScreenBuffer(ConsoleBuffers[CurrentVideoPage]);
-
-    /* Update the mode number */
+    /* Change the mode number */
     CurrentVideoMode = ModeNumber;
+    CurrentVideoPage = 0;
+
+    /* Update the BDA */
+    Bda->VideoMode = CurrentVideoMode;
+    Bda->VideoPage = CurrentVideoPage;
+    Bda->VideoPageSize = BiosGetVideoPageSize();
+    Bda->VideoPageOffset = 0;
+    Bda->ScreenColumns = VideoModes[ModeNumber].Width;
+
+    return TRUE;
+}
+
+BOOLEAN BiosSetVideoPage(BYTE PageNumber)
+{
+    ULONG PageStart;
+    COORD Coordinates;
+    CONSOLE_SCREEN_BUFFER_INFO BufferInfo;
+
+    /* Make sure this is a valid page number */
+    if (PageNumber >= VideoModes[CurrentVideoMode].Pages) return FALSE;
+
+    /* Save the current console buffer in the video memory */
+    PageStart = BiosGetVideoMemoryStart() + CurrentVideoPage * BiosGetVideoPageSize();
+    BiosUpdateVideoMemory(PageStart, PageStart + BiosGetVideoPageSize());
+
+    /* Save the cursor */
+    if (!GetConsoleScreenBufferInfo(BiosConsoleOutput, &BufferInfo)) return FALSE;
+    Bda->CursorPosition[CurrentVideoPage] = MAKEWORD(BufferInfo.dwCursorPosition.X,
+                                                     BufferInfo.dwCursorPosition.Y);
+
+    /* Set the page */
+    CurrentVideoPage = PageNumber;
+
+    /* Update the BDA */
+    Bda->VideoPage = CurrentVideoPage;
+    Bda->VideoPageSize = BiosGetVideoPageSize();
+    Bda->VideoPageOffset = CurrentVideoPage * Bda->VideoPageSize;
+
+    /* Update the console */
+    PageStart = BiosGetVideoMemoryStart() + Bda->VideoPage * BiosGetVideoPageSize();
+    BiosUpdateConsole(PageStart, PageStart + BiosGetVideoPageSize());
+
+    /* Set the cursor */
+    Coordinates.X = LOBYTE(Bda->CursorPosition[Bda->VideoPage]);
+    Coordinates.Y = HIBYTE(Bda->CursorPosition[Bda->VideoPage]);
+    SetConsoleCursorPosition(BiosConsoleOutput, Coordinates);
 
     return TRUE;
 }
@@ -244,61 +342,94 @@ inline DWORD BiosGetVideoMemoryStart()
 
 inline VOID BiosVerticalRefresh()
 {
-    SMALL_RECT Region;
-
     /* Ignore if we're in text mode */
     if (VideoModes[CurrentVideoMode].Text) return;
 
-    /* Fill the rectangle structure */
-    Region.Left = Region.Top = 0;
-    Region.Right = VideoModes[CurrentVideoMode].Width;
-    Region.Bottom = VideoModes[CurrentVideoMode].Height;
+    /* Ignore if there's nothing to update */
+    if (!VideoNeedsUpdate) return;
 
     /* Redraw the screen */
-    InvalidateConsoleDIBits(ConsoleBuffers[CurrentVideoPage],
-                            &Region);
+    InvalidateConsoleDIBits(BiosGraphicsOutput, &UpdateRectangle);
+
+    /* Clear the update flag */
+    VideoNeedsUpdate = FALSE;
 }
 
-BOOLEAN BiosInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
+BOOLEAN BiosInitialize()
 {
     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
     }
 
-    /* Set global console I/O handles */
-    BiosConsoleInput = ConsoleInput;
-    BiosConsoleOutput = ConsoleOutput;
+    /* 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$"),
+                                   GENERIC_READ | GENERIC_WRITE,
+                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                   NULL,
+                                   OPEN_EXISTING,
+                                   0,
+                                   NULL);
+
+    /* Make sure it was successful */
+    if ((BiosConsoleInput == INVALID_HANDLE_VALUE)
+        || (BiosConsoleOutput == INVALID_HANDLE_VALUE))
+    {
+        return FALSE;
+    }
+
+    /* Save the console screen buffer information */
+    if (!GetConsoleScreenBufferInfo(BiosConsoleOutput, &BiosSavedBufferInfo))
+    {
+        return FALSE;
+    }
 
+    /* Store the cursor position */
+    Bda->CursorPosition[0] = MAKEWORD(BiosSavedBufferInfo.dwCursorPosition.X,
+                                      BiosSavedBufferInfo.dwCursorPosition.Y);
+    
     /* Set the default video mode */
     BiosSetVideoMode(BIOS_DEFAULT_VIDEO_MODE);
 
     /* Set the console input mode */
-    SetConsoleMode(ConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
+    SetConsoleMode(BiosConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
 
     /* Initialize the PIC */
     PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
@@ -327,6 +458,22 @@ BOOLEAN BiosInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
     return TRUE;
 }
 
+VOID BiosCleanup()
+{
+    /* Restore the old screen buffer */
+    SetConsoleActiveScreenBuffer(BiosConsoleOutput);
+
+    /* Restore the screen buffer size */
+    SetConsoleScreenBufferSize(BiosConsoleOutput, BiosSavedBufferInfo.dwSize);
+
+    /* Free the graphics buffer */
+    if (!VideoModes[CurrentVideoMode].Text) BiosDestroyGraphicsBuffer();
+
+    /* Close the console handles */
+    if (BiosConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleInput);
+    if (BiosConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleOutput);
+}
+
 VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
 {
     ULONG i;
@@ -347,6 +494,9 @@ VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
             /* Get the coordinates */
             Coordinates = BiosVideoAddressToCoord(i);
 
+            /* Make sure this is the current page */
+            if (BiosVideoAddressToPage(i) != CurrentVideoPage) continue;
+
             /* Fill the rectangle structure */
             Rect.Left = Coordinates.X;
             Rect.Top = Coordinates.Y;
@@ -367,11 +517,40 @@ VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
     }
     else
     {
+        /* Wait for the mutex object */
+        WaitForSingleObject(ConsoleMutex, INFINITE);
+
         /* Copy the data to the framebuffer */
-        RtlCopyMemory((LPVOID)((ULONG_PTR)ConsoleFramebuffers[CurrentVideoPage]
+        RtlCopyMemory((LPVOID)((ULONG_PTR)ConsoleFramebuffer
                       + StartAddress - BiosGetVideoMemoryStart()),
                       (LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
                       EndAddress - StartAddress);
+
+        /* Release the mutex */
+        ReleaseMutex(ConsoleMutex);
+
+        /* 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;
     }
 }
 
@@ -390,8 +569,11 @@ VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
             /* Get the coordinates */
             Coordinates = BiosVideoAddressToCoord(i);
 
+            /* Make sure this is the current page */
+            if (BiosVideoAddressToPage(i) != CurrentVideoPage) continue;
+
             /* Check if this is a character byte or an attribute byte */
-            if ((i - (VideoModes[CurrentVideoMode].Segment << 4)) % 2 == 0)
+            if ((i - BiosGetVideoMemoryStart()) % 2 == 0)
             {
                 /* This is a regular character */
                 ReadConsoleOutputCharacterA(BiosConsoleOutput,
@@ -415,11 +597,17 @@ VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
     }
     else
     {
+        /* Wait for the mutex object */
+        WaitForSingleObject(ConsoleMutex, INFINITE);
+
         /* Copy the data to the emulator memory */
         RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
-                      (LPVOID)((ULONG_PTR)ConsoleFramebuffers[CurrentVideoPage]
+                      (LPVOID)((ULONG_PTR)ConsoleFramebuffer
                       + StartAddress - BiosGetVideoMemoryStart()),
                       EndAddress - StartAddress);
+
+        /* Release the mutex */
+        ReleaseMutex(ConsoleMutex);
     }
 }
 
@@ -428,7 +616,7 @@ WORD BiosPeekCharacter()
     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);
@@ -443,7 +631,7 @@ WORD BiosGetCharacter()
     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);
@@ -474,11 +662,10 @@ WORD BiosGetCharacter()
 
 VOID BiosVideoService()
 {
-    INT CursorHeight;
+    INT i, 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);
@@ -504,6 +691,10 @@ VOID BiosVideoService()
             if (CursorHeight < 1) CursorHeight = 1;
             if (CursorHeight > 100) CursorHeight = 100;
 
+            /* Update the BDA */
+            Bda->CursorStartLine = HIBYTE(Ecx);
+            Bda->CursorEndLine = LOBYTE(Ecx) & 0x1F;
+
             /* Set the cursor */
             CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
             CursorInfo.bVisible = !Invisible;
@@ -515,38 +706,60 @@ VOID BiosVideoService()
         /* Set Cursor Position */
         case 0x02:
         {
-            Position.X = LOBYTE(Edx);
-            Position.Y = HIBYTE(Edx);
+            /* Make sure the selected video page exists */
+            if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
+
+            Bda->CursorPosition[HIBYTE(Ebx)] = LOWORD(Edx);
+
+            /* Check if this is the current video page */
+            if (HIBYTE(Ebx) == CurrentVideoPage)
+            {
+                /* Yes, change the actual cursor */
+                Position.X = LOBYTE(Edx);
+                Position.Y = HIBYTE(Edx);
+                SetConsoleCursorPosition(BiosConsoleOutput, Position);
+            }
 
-            SetConsoleCursorPosition(BiosConsoleOutput, Position);
             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) >= VideoModes[CurrentVideoMode].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;
+        }
+
+        /* Select Active Display Page */
+        case 0x05:
+        {
+            /* Check if the page exists */
+            if (LOBYTE(Eax) >= VideoModes[CurrentVideoMode].Pages) break;
+
+            /* Check if this is the same page */
+            if (LOBYTE(Eax) == CurrentVideoPage) break;
+
+            /* Change the video page */
+            BiosSetVideoPage(LOBYTE(Eax));
+
             break;
         }
 
         /* Scroll Up/Down Window */
+        // TODO: Implement for different pages
         case 0x06:
         case 0x07:
         {
+            BYTE Lines = LOBYTE(Eax);
+
             Rect.Top = HIBYTE(Ecx);
             Rect.Left = LOBYTE(Ecx);
             Rect.Bottom = HIBYTE(Edx);
@@ -554,79 +767,117 @@ VOID BiosVideoService()
             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);
+
+            /* 0 means clear entire window */
+            if (Lines == 0) Lines = Rect.Bottom - Rect.Top;
+
+            if (HIBYTE(Eax) == 0x06) Position.Y = Rect.Top - Lines;
+            else Position.Y = Rect.Top + Lines;
 
             ScrollConsoleScreenBuffer(BiosConsoleOutput,
                                       &Rect,
                                       &Rect,
                                       Position,
                                       &Character);
+
             break;
         }
 
         /* Read Character And Attribute At Cursor Position */
         case 0x08:
         {
-            COORD BufferSize = { 1, 1 }, Origin = { 0, 0 };
-
-            /* Get the cursor position */
-            GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
-
-            /* Read at cursor position */
-            Rect.Left = ScreenBufferInfo.dwCursorPosition.X;
-            Rect.Top = ScreenBufferInfo.dwCursorPosition.Y;
+            DWORD Address;
             
-            /* Read the console output */
-            ReadConsoleOutput(BiosConsoleOutput, &Character, BufferSize, Origin, &Rect);
+            /* Make sure this is text mode */
+            if (!VideoModes[CurrentVideoMode].Text) break;
 
-            /* Return the result */
+            /* Make sure the selected video page exists */
+            if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
+            
+            /* Find the address */
+            Address = BiosGetVideoMemoryStart()
+                      + HIBYTE(Ebx) * BiosGetVideoPageSize()
+                      + (HIBYTE(Bda->CursorPosition[HIBYTE(Ebx)])
+                      * VideoModes[CurrentVideoMode].Height
+                      + LOBYTE(Bda->CursorPosition[HIBYTE(Ebx)]))
+                      * VideoModes[CurrentVideoMode].Bpp / 8;
+
+            /* Update the video memory at that address */
+            BiosUpdateVideoMemory(Address,
+                                  Address + VideoModes[CurrentVideoMode].Bpp / 8);
+
+            /* Return the result in AX */
             EmulatorSetRegister(EMULATOR_REG_AX,
-                                (LOBYTE(Character.Attributes) << 8)
-                                | Character.Char.AsciiChar);
+                                *((LPWORD)((ULONG_PTR)BaseAddress + Address)));
 
             break;
         }
 
         /* Write Character And Attribute At Cursor Position */
         case 0x09:
+        case 0x0A:
         {
-            DWORD CharsWritten;
-
-            /* Get the cursor position */
-            GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
-
-            /* Write the attribute to the output */
-            FillConsoleOutputAttribute(BiosConsoleOutput,
-                                       LOBYTE(Ebx),
-                                       LOWORD(Ecx),
-                                       ScreenBufferInfo.dwCursorPosition,
-                                       &CharsWritten);
+            BYTE PixelSize = VideoModes[CurrentVideoMode].Bpp / 8;
+            WORD Data = (LOBYTE(Ebx) << 8) | LOBYTE(Eax);
+            WORD Repeat = LOWORD(Ecx);
+            DWORD Address = BiosGetVideoMemoryStart()
+                            + CurrentVideoPage * BiosGetVideoPageSize()
+                            + (HIBYTE(Bda->CursorPosition[CurrentVideoPage])
+                            * VideoModes[CurrentVideoMode].Height
+                            + LOBYTE(Bda->CursorPosition[CurrentVideoPage]))
+                            * PixelSize;
+
+            /* Make sure this is text mode */
+            if (!VideoModes[CurrentVideoMode].Text) break;
+
+            /* Make sure the selected video page exists */
+            if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
+
+            /* Make sure we don't write over the end of video memory */
+            Repeat = min(Repeat,
+                        (CONSOLE_VIDEO_MEM_END - Address)
+                        / PixelSize);
+
+            /* Copy the values to the memory */
+            for (i = 0; i < Repeat; i++)
+            {
+                if (PixelSize == sizeof(BYTE) || HIBYTE(Eax) == 0x0A)
+                {
+                    /* Just characters, no attributes */
+                    *((LPBYTE)((ULONG_PTR)BaseAddress + Address) + i * PixelSize) = LOBYTE(Data);
+                }
+                else if (PixelSize == sizeof(WORD))
+                {
+                    /* First byte for characters, second for attributes */
+                    *((LPWORD)((ULONG_PTR)BaseAddress + Address) + i) = Data;
+                }
+            }
 
-            /* Write the character to the output */
-            FillConsoleOutputCharacterA(BiosConsoleOutput,
-                                        LOBYTE(Eax),
-                                        LOWORD(Ecx),
-                                        ScreenBufferInfo.dwCursorPosition,
-                                        &CharsWritten);
+            /* Update the range */
+            BiosUpdateConsole(Address,
+                              Address + Repeat * (VideoModes[CurrentVideoMode].Bpp / 8));
 
             break;
         }
 
-        /* Write Character Only At Cursor Position */
-        case 0x0A:
+        /* Teletype Output */
+        case 0x0E:
         {
-            DWORD CharsWritten;
+            CHAR Character = LOBYTE(Eax);
+            DWORD NumWritten;
 
-            /* Get the cursor position */
-            GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
+            /* Make sure the page exists */
+            if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
 
-            /* Write the character to the output */
-            FillConsoleOutputCharacterA(BiosConsoleOutput,
-                                        LOBYTE(Eax),
-                                        LOWORD(Ecx),
-                                        ScreenBufferInfo.dwCursorPosition,
-                                        &CharsWritten);
+            /* Set the attribute */
+            SetConsoleTextAttribute(BiosConsoleOutput, LOBYTE(Ebx));
+
+            /* Write the character */
+            WriteConsoleA(BiosConsoleOutput,
+                          &Character,
+                          sizeof(CHAR),
+                          &NumWritten,
+                          NULL);
 
             break;
         }
@@ -635,10 +886,35 @@ 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:
+        {
+            Rect.Top = HIBYTE(Ecx);
+            Rect.Left = LOBYTE(Ecx);
+            Rect.Bottom = HIBYTE(Edx);
+            Rect.Right = LOBYTE(Edx);
+            Character.Char.UnicodeChar = L' ';
+            Character.Attributes = 0x07;
+            Position.X = Rect.Left;
+            Position.Y = Rect.Top;
+
+            if (LOBYTE(Ebx) == 0) Position.Y -= LOBYTE(Eax);
+            else if (LOBYTE(Ebx) == 1) Position.Y += LOBYTE(Eax);
+            else if (LOBYTE(Ebx) == 2) Position.X -= LOBYTE(Eax);
+            else if (LOBYTE(Ebx) == 3) Position.X += LOBYTE(Eax);
+
+            ScrollConsoleScreenBuffer(BiosConsoleOutput,
+                                      &Rect,
+                                      &Rect,
+                                      Position,
+                                      &Character);
 
             break;
         }
@@ -704,15 +980,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;
         }
@@ -720,10 +996,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;
         }
@@ -736,10 +1012,16 @@ VOID BiosTimeService()
     }
 }
 
+VOID BiosSystemTimerInterrupt()
+{
+    /* Increase the system tick count */
+    Bda->TickCounter++;
+}
+
 VOID BiosEquipmentService()
 {
     /* Return the equipment list */
-    EmulatorSetRegister(EMULATOR_REG_AX, BIOS_EQUIPMENT_LIST);
+    EmulatorSetRegister(EMULATOR_REG_AX, Bda->EquipmentList);
 }
 
 VOID BiosHandleIrq(BYTE IrqNumber)
@@ -749,11 +1031,8 @@ VOID BiosHandleIrq(BYTE IrqNumber)
         /* PIT IRQ */
         case 0:
         {
-            /* Increase the system tick count */
-            BiosTickCount++;
-
             /* Perform the system timer interrupt */
-            EmulatorInterrupt(0x1C);
+            EmulatorInterrupt(BIOS_SYS_TIMER_INTERRUPT);
 
             break;
         }
@@ -769,7 +1048,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)))
@@ -787,10 +1066,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
             {