[NTVDM]
[reactos.git] / subsystems / ntvdm / bios.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: bios.c
5 * PURPOSE: VDM BIOS
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "bios.h"
12 #include "emulator.h"
13 #include "pic.h"
14 #include "ps2.h"
15 #include "timer.h"
16
17 /* PRIVATE VARIABLES **********************************************************/
18
19 static PBIOS_DATA_AREA Bda;
20 static BYTE BiosKeyboardMap[256];
21 static HANDLE BiosConsoleInput = INVALID_HANDLE_VALUE;
22 static HANDLE BiosConsoleOutput = INVALID_HANDLE_VALUE;
23 static HANDLE BiosGraphicsOutput = NULL;
24 static LPVOID ConsoleFramebuffer = NULL;
25 static HANDLE ConsoleMutex = NULL;
26 static BYTE CurrentVideoMode, CurrentVideoPage;
27 static BOOLEAN VideoNeedsUpdate = TRUE;
28 static SMALL_RECT UpdateRectangle = { 0, 0, 0, 0 };
29 static CONSOLE_SCREEN_BUFFER_INFO BiosSavedBufferInfo;
30
31 static VIDEO_MODE VideoModes[] =
32 {
33 /* Width | Height | Text | Bpp | Gray | Pages | Segment */
34 { 40, 25, TRUE, 16, TRUE, 8, 0xB800}, /* Mode 00h */
35 { 40, 25, TRUE, 16, FALSE, 8, 0xB800}, /* Mode 01h */
36 { 80, 25, TRUE, 16, TRUE, 8, 0xB800}, /* Mode 02h */
37 { 80, 25, TRUE, 16, FALSE, 8, 0xB800}, /* Mode 03h */
38 { 320, 200, FALSE, 2, FALSE, 1, 0xB800}, /* Mode 04h */
39 { 320, 200, FALSE, 2, TRUE, 1, 0xB800}, /* Mode 05h */
40 { 640, 200, FALSE, 1, FALSE, 1, 0xB800}, /* Mode 06h */
41 { 80, 25, TRUE, 8, FALSE, 1, 0xB000}, /* Mode 07h */
42 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 08h - not used */
43 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 09h - not used */
44 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 0Ah - not used */
45 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 0Bh - not used */
46 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 0Ch - not used */
47 { 320, 200, FALSE, 4, FALSE, 1, 0xA000}, /* Mode 0Dh */
48 { 640, 200, FALSE, 4, FALSE, 1, 0xA000}, /* Mode 0Eh */
49 { 640, 350, FALSE, 1, FALSE, 1, 0xA000}, /* Mode 0Fh */
50 { 640, 350, FALSE, 4, FALSE, 1, 0xA000}, /* Mode 10h */
51 { 640, 480, FALSE, 1, FALSE, 1, 0xA000}, /* Mode 11h */
52 { 640, 480, FALSE, 4, FALSE, 1, 0xA000}, /* Mode 12h */
53 { 320, 200, FALSE, 8, FALSE, 1, 0xA000} /* Mode 13h */
54 };
55
56 /* PRIVATE FUNCTIONS **********************************************************/
57
58 static DWORD BiosGetVideoPageSize()
59 {
60 INT i;
61 DWORD BufferSize = VideoModes[CurrentVideoMode].Width
62 * VideoModes[CurrentVideoMode].Height
63 * VideoModes[CurrentVideoMode].Bpp
64 / 8;
65
66 for (i = 0; i < 32; i++) if ((1 << i) >= BufferSize) break;
67
68 return 1 << i;
69 }
70
71 static BYTE BiosVideoAddressToPage(ULONG Address)
72 {
73 return (Address - BiosGetVideoMemoryStart())
74 / BiosGetVideoPageSize();
75 }
76
77 static COORD BiosVideoAddressToCoord(ULONG Address)
78 {
79 COORD Result = {0, 0};
80 DWORD PageStart = BiosVideoAddressToPage(Address) * BiosGetVideoPageSize();
81 DWORD Offset = Address - BiosGetVideoMemoryStart() - PageStart;
82
83 if (VideoModes[CurrentVideoMode].Text)
84 {
85 Result.X = (Offset / sizeof(WORD)) % VideoModes[CurrentVideoMode].Width;
86 Result.Y = (Offset / sizeof(WORD)) / VideoModes[CurrentVideoMode].Width;
87 }
88 else
89 {
90 Result.X = ((Offset * 8) / VideoModes[CurrentVideoMode].Bpp)
91 % VideoModes[CurrentVideoMode].Width;
92 Result.Y = ((Offset * 8) / VideoModes[CurrentVideoMode].Bpp)
93 / VideoModes[CurrentVideoMode].Width;
94 }
95
96 return Result;
97 }
98
99 static BOOLEAN BiosKbdBufferPush(WORD Data)
100 {
101 /* Get the location of the element after the head */
102 WORD NextElement = Bda->KeybdBufferHead + 2;
103
104 /* Wrap it around if it's at or beyond the end */
105 if (NextElement >= Bda->KeybdBufferEnd) NextElement = Bda->KeybdBufferStart;
106
107 /* If it's full, fail */
108 if (NextElement == Bda->KeybdBufferTail) return FALSE;
109
110 /* Put the value in the queue */
111 *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferTail)) = Data;
112 Bda->KeybdBufferTail += sizeof(WORD);
113
114 /* Check if we are at, or have passed, the end of the buffer */
115 if (Bda->KeybdBufferTail >= Bda->KeybdBufferEnd)
116 {
117 /* Return it to the beginning */
118 Bda->KeybdBufferTail = Bda->KeybdBufferStart;
119 }
120
121 /* Return success */
122 return TRUE;
123 }
124
125 static BOOLEAN BiosKbdBufferTop(LPWORD Data)
126 {
127 /* If it's empty, fail */
128 if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
129
130 /* Otherwise, get the value and return success */
131 *Data = *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferHead));
132
133 return TRUE;
134 }
135
136 static BOOLEAN BiosKbdBufferPop()
137 {
138 /* If it's empty, fail */
139 if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
140
141 /* Remove the value from the queue */
142 Bda->KeybdBufferHead += sizeof(WORD);
143
144 /* Check if we are at, or have passed, the end of the buffer */
145 if (Bda->KeybdBufferHead >= Bda->KeybdBufferEnd)
146 {
147 /* Return it to the beginning */
148 Bda->KeybdBufferHead = Bda->KeybdBufferStart;
149 }
150
151 /* Return success */
152 return TRUE;
153 }
154
155 static BOOLEAN BiosCreateGraphicsBuffer(BYTE ModeNumber)
156 {
157 INT i;
158 CONSOLE_GRAPHICS_BUFFER_INFO GraphicsBufferInfo;
159 LPBITMAPINFO BitmapInfo;
160 LPWORD PaletteIndex;
161
162 /* Allocate a bitmap info structure */
163 BitmapInfo = (LPBITMAPINFO)HeapAlloc(GetProcessHeap(),
164 HEAP_ZERO_MEMORY,
165 sizeof(BITMAPINFOHEADER)
166 + (1 << VideoModes[ModeNumber].Bpp)
167 * sizeof(WORD));
168 if (BitmapInfo == NULL) return FALSE;
169
170 /* Fill the bitmap info header */
171 ZeroMemory(&BitmapInfo->bmiHeader, sizeof(BITMAPINFOHEADER));
172 BitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
173 BitmapInfo->bmiHeader.biWidth = VideoModes[ModeNumber].Width;
174 BitmapInfo->bmiHeader.biHeight = VideoModes[ModeNumber].Height;
175 BitmapInfo->bmiHeader.biPlanes = 1;
176 BitmapInfo->bmiHeader.biCompression = BI_RGB;
177 BitmapInfo->bmiHeader.biBitCount = VideoModes[ModeNumber].Bpp;
178
179 /* Calculate the image size */
180 BitmapInfo->bmiHeader.biSizeImage = BitmapInfo->bmiHeader.biWidth
181 * BitmapInfo->bmiHeader.biHeight
182 * (BitmapInfo->bmiHeader.biBitCount >> 3);
183
184 /* Fill the palette data */
185 PaletteIndex = (LPWORD)((ULONG_PTR)BitmapInfo + sizeof(BITMAPINFOHEADER));
186 for (i = 0; i < (1 << VideoModes[ModeNumber].Bpp); i++)
187 {
188 PaletteIndex[i] = i;
189 }
190
191 /* Fill the console graphics buffer info */
192 GraphicsBufferInfo.dwBitMapInfoLength = sizeof(BITMAPINFOHEADER)
193 + (1 << VideoModes[ModeNumber].Bpp)
194 * sizeof(WORD);
195 GraphicsBufferInfo.lpBitMapInfo = BitmapInfo;
196 GraphicsBufferInfo.dwUsage = DIB_PAL_COLORS;
197
198 /* Create the buffer */
199 BiosGraphicsOutput = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
200 FILE_SHARE_READ | FILE_SHARE_WRITE,
201 NULL,
202 CONSOLE_GRAPHICS_BUFFER,
203 &GraphicsBufferInfo);
204
205 /* Save the framebuffer address and mutex */
206 ConsoleFramebuffer = GraphicsBufferInfo.lpBitMap;
207 ConsoleMutex = GraphicsBufferInfo.hMutex;
208
209 /* Free the bitmap information */
210 HeapFree(GetProcessHeap(), 0, BitmapInfo);
211
212 return TRUE;
213 }
214
215 static VOID BiosDestroyGraphicsBuffer()
216 {
217 CloseHandle(ConsoleMutex);
218 CloseHandle(BiosGraphicsOutput);
219 }
220
221 /* PUBLIC FUNCTIONS ***********************************************************/
222
223 BYTE BiosGetVideoMode()
224 {
225 return CurrentVideoMode;
226 }
227
228 BOOLEAN BiosSetVideoMode(BYTE ModeNumber)
229 {
230 COORD Coord;
231
232 /* Make sure this is a valid video mode */
233 if (ModeNumber > BIOS_MAX_VIDEO_MODE) return FALSE;
234 if (VideoModes[ModeNumber].Pages == 0) return FALSE;
235
236 /* Set the new video mode size */
237 Coord.X = VideoModes[ModeNumber].Width;
238 Coord.Y = VideoModes[ModeNumber].Height;
239
240 if (VideoModes[ModeNumber].Text && VideoModes[CurrentVideoMode].Text)
241 {
242 /* Switching from text mode to another text mode */
243
244 /* Resize the text buffer */
245 SetConsoleScreenBufferSize(BiosConsoleOutput, Coord);
246 }
247 else if (VideoModes[ModeNumber].Text && !VideoModes[CurrentVideoMode].Text)
248 {
249 /* Switching from graphics mode to text mode */
250
251 /* Resize the text buffer */
252 SetConsoleScreenBufferSize(BiosConsoleOutput, Coord);
253
254 /* Change the active screen buffer to the text buffer */
255 SetConsoleActiveScreenBuffer(BiosConsoleOutput);
256
257 /* Cleanup the graphics buffer */
258 BiosDestroyGraphicsBuffer();
259 }
260 else if (!VideoModes[ModeNumber].Text && VideoModes[CurrentVideoMode].Text)
261 {
262 /* Switching from text mode to graphics mode */
263 if (!BiosCreateGraphicsBuffer(ModeNumber)) return FALSE;
264
265 SetConsoleActiveScreenBuffer(BiosGraphicsOutput);
266 }
267 else if (!VideoModes[ModeNumber].Text && !VideoModes[CurrentVideoMode].Text)
268 {
269 /* Switching from graphics mode to another graphics mode */
270
271 /* Temporarily switch to the text mode buffer */
272 SetConsoleActiveScreenBuffer(BiosConsoleOutput);
273
274 /* Cleanup the current graphics mode buffer */
275 BiosDestroyGraphicsBuffer();
276
277 /* Create a new graphics mode buffer */
278 if (!BiosCreateGraphicsBuffer(ModeNumber)) return FALSE;
279
280 /* Switch to it */
281 SetConsoleActiveScreenBuffer(BiosGraphicsOutput);
282 }
283
284 /* Change the mode number */
285 CurrentVideoMode = ModeNumber;
286 CurrentVideoPage = 0;
287
288 /* Update the BDA */
289 Bda->VideoMode = CurrentVideoMode;
290 Bda->VideoPage = CurrentVideoPage;
291 Bda->VideoPageSize = BiosGetVideoPageSize();
292 Bda->VideoPageOffset = 0;
293 Bda->ScreenColumns = VideoModes[ModeNumber].Width;
294
295 return TRUE;
296 }
297
298 BOOLEAN BiosSetVideoPage(BYTE PageNumber)
299 {
300 ULONG PageStart;
301 COORD Coordinates;
302 CONSOLE_SCREEN_BUFFER_INFO BufferInfo;
303
304 /* Make sure this is a valid page number */
305 if (PageNumber >= VideoModes[CurrentVideoMode].Pages) return FALSE;
306
307 /* Save the current console buffer in the video memory */
308 PageStart = BiosGetVideoMemoryStart() + CurrentVideoPage * BiosGetVideoPageSize();
309 BiosUpdateVideoMemory(PageStart, PageStart + BiosGetVideoPageSize());
310
311 /* Save the cursor */
312 if (!GetConsoleScreenBufferInfo(BiosConsoleOutput, &BufferInfo)) return FALSE;
313 Bda->CursorPosition[CurrentVideoPage] = MAKEWORD(BufferInfo.dwCursorPosition.X,
314 BufferInfo.dwCursorPosition.Y);
315
316 /* Set the page */
317 CurrentVideoPage = PageNumber;
318
319 /* Update the BDA */
320 Bda->VideoPage = CurrentVideoPage;
321 Bda->VideoPageSize = BiosGetVideoPageSize();
322 Bda->VideoPageOffset = CurrentVideoPage * Bda->VideoPageSize;
323
324 /* Update the console */
325 PageStart = BiosGetVideoMemoryStart() + Bda->VideoPage * BiosGetVideoPageSize();
326 BiosUpdateConsole(PageStart, PageStart + BiosGetVideoPageSize());
327
328 /* Set the cursor */
329 Coordinates.X = LOBYTE(Bda->CursorPosition[Bda->VideoPage]);
330 Coordinates.Y = HIBYTE(Bda->CursorPosition[Bda->VideoPage]);
331 SetConsoleCursorPosition(BiosConsoleOutput, Coordinates);
332
333 return TRUE;
334 }
335
336 inline DWORD BiosGetVideoMemoryStart()
337 {
338 return (VideoModes[CurrentVideoMode].Segment << 4);
339 }
340
341 inline VOID BiosVerticalRefresh()
342 {
343 /* Ignore if we're in text mode */
344 if (VideoModes[CurrentVideoMode].Text) return;
345
346 /* Ignore if there's nothing to update */
347 if (!VideoNeedsUpdate) return;
348
349 /* Redraw the screen */
350 InvalidateConsoleDIBits(BiosGraphicsOutput, &UpdateRectangle);
351
352 /* Clear the update flag */
353 VideoNeedsUpdate = FALSE;
354 }
355
356 BOOLEAN BiosInitialize()
357 {
358 INT i;
359 WORD Offset = 0;
360 LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress);
361 LPBYTE BiosCode = (LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(BIOS_SEGMENT, 0));
362
363 /* Initialize the BDA */
364 Bda = (PBIOS_DATA_AREA)((ULONG_PTR)BaseAddress + TO_LINEAR(BDA_SEGMENT, 0));
365 Bda->EquipmentList = BIOS_EQUIPMENT_LIST;
366 Bda->KeybdBufferStart = FIELD_OFFSET(BIOS_DATA_AREA, KeybdBuffer);
367 Bda->KeybdBufferEnd = Bda->KeybdBufferStart + BIOS_KBD_BUFFER_SIZE * sizeof(WORD);
368
369 /* Generate ISR stubs and fill the IVT */
370 for (i = 0; i < 256; i++)
371 {
372 IntVecTable[i * 2] = Offset;
373 IntVecTable[i * 2 + 1] = BIOS_SEGMENT;
374
375 if (i != SPECIAL_INT_NUM)
376 {
377 BiosCode[Offset++] = 0xFA; // cli
378
379 BiosCode[Offset++] = 0x6A; // push i
380 BiosCode[Offset++] = (BYTE)i;
381
382 BiosCode[Offset++] = 0xCD; // int SPECIAL_INT_NUM
383 BiosCode[Offset++] = SPECIAL_INT_NUM;
384
385 BiosCode[Offset++] = 0x83; // add sp, 2
386 BiosCode[Offset++] = 0xC4;
387 BiosCode[Offset++] = 0x02;
388 }
389
390 BiosCode[Offset++] = 0xCF; // iret
391 }
392
393 /* Get the input and output handles to the real console */
394 BiosConsoleInput = CreateFile(TEXT("CONIN$"),
395 GENERIC_READ | GENERIC_WRITE,
396 FILE_SHARE_READ | FILE_SHARE_WRITE,
397 NULL,
398 OPEN_EXISTING,
399 0,
400 NULL);
401
402 BiosConsoleOutput = CreateFile(TEXT("CONOUT$"),
403 GENERIC_READ | GENERIC_WRITE,
404 FILE_SHARE_READ | FILE_SHARE_WRITE,
405 NULL,
406 OPEN_EXISTING,
407 0,
408 NULL);
409
410 /* Make sure it was successful */
411 if ((BiosConsoleInput == INVALID_HANDLE_VALUE)
412 || (BiosConsoleOutput == INVALID_HANDLE_VALUE))
413 {
414 return FALSE;
415 }
416
417 /* Save the console screen buffer information */
418 if (!GetConsoleScreenBufferInfo(BiosConsoleOutput, &BiosSavedBufferInfo))
419 {
420 return FALSE;
421 }
422
423 /* Store the cursor position */
424 Bda->CursorPosition[0] = MAKEWORD(BiosSavedBufferInfo.dwCursorPosition.X,
425 BiosSavedBufferInfo.dwCursorPosition.Y);
426
427 /* Set the default video mode */
428 BiosSetVideoMode(BIOS_DEFAULT_VIDEO_MODE);
429
430 /* Set the console input mode */
431 SetConsoleMode(BiosConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
432
433 /* Initialize the PIC */
434 PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
435 PicWriteCommand(PIC_SLAVE_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
436
437 /* Set the interrupt offsets */
438 PicWriteData(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT);
439 PicWriteData(PIC_SLAVE_DATA, BIOS_PIC_SLAVE_INT);
440
441 /* Tell the master PIC there is a slave at IRQ 2 */
442 PicWriteData(PIC_MASTER_DATA, 1 << 2);
443 PicWriteData(PIC_SLAVE_DATA, 2);
444
445 /* Make sure the PIC is in 8086 mode */
446 PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086);
447 PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086);
448
449 /* Clear the masks for both PICs */
450 PicWriteData(PIC_MASTER_DATA, 0x00);
451 PicWriteData(PIC_SLAVE_DATA, 0x00);
452
453 PitWriteCommand(0x34);
454 PitWriteData(0, 0x00);
455 PitWriteData(0, 0x00);
456
457 return TRUE;
458 }
459
460 VOID BiosCleanup()
461 {
462 /* Restore the old screen buffer */
463 SetConsoleActiveScreenBuffer(BiosConsoleOutput);
464
465 /* Restore the screen buffer size */
466 SetConsoleScreenBufferSize(BiosConsoleOutput, BiosSavedBufferInfo.dwSize);
467
468 /* Free the graphics buffer */
469 if (!VideoModes[CurrentVideoMode].Text) BiosDestroyGraphicsBuffer();
470
471 /* Close the console handles */
472 if (BiosConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleInput);
473 if (BiosConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleOutput);
474 }
475
476 VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
477 {
478 ULONG i;
479 COORD Coordinates;
480 COORD Origin = { 0, 0 };
481 COORD UnitSize = { 1, 1 };
482 CHAR_INFO Character;
483 SMALL_RECT Rect;
484
485 /* Start from the character address */
486 StartAddress &= ~1;
487
488 if (VideoModes[CurrentVideoMode].Text)
489 {
490 /* Loop through all the addresses */
491 for (i = StartAddress; i < EndAddress; i += 2)
492 {
493 /* Get the coordinates */
494 Coordinates = BiosVideoAddressToCoord(i);
495
496 /* Make sure this is the current page */
497 if (BiosVideoAddressToPage(i) != CurrentVideoPage) continue;
498
499 /* Fill the rectangle structure */
500 Rect.Left = Coordinates.X;
501 Rect.Top = Coordinates.Y;
502 Rect.Right = Rect.Left;
503 Rect.Bottom = Rect.Top;
504
505 /* Fill the character data */
506 Character.Char.AsciiChar = *((PCHAR)((ULONG_PTR)BaseAddress + i));
507 Character.Attributes = *((PBYTE)((ULONG_PTR)BaseAddress + i + 1));
508
509 /* Write the character */
510 WriteConsoleOutputA(BiosConsoleOutput,
511 &Character,
512 UnitSize,
513 Origin,
514 &Rect);
515 }
516 }
517 else
518 {
519 /* Wait for the mutex object */
520 WaitForSingleObject(ConsoleMutex, INFINITE);
521
522 /* Copy the data to the framebuffer */
523 RtlCopyMemory((LPVOID)((ULONG_PTR)ConsoleFramebuffer
524 + StartAddress - BiosGetVideoMemoryStart()),
525 (LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
526 EndAddress - StartAddress);
527
528 /* Release the mutex */
529 ReleaseMutex(ConsoleMutex);
530
531 /* Check if this is the first time the rectangle is updated */
532 if (!VideoNeedsUpdate)
533 {
534 UpdateRectangle.Left = UpdateRectangle.Top = (SHORT)0x7FFF;
535 UpdateRectangle.Right = UpdateRectangle.Bottom = (SHORT)0x8000;
536 }
537
538 /* Expand the update rectangle */
539 for (i = StartAddress; i < EndAddress; i++)
540 {
541 /* Get the coordinates */
542 Coordinates = BiosVideoAddressToCoord(i);
543
544 /* Expand the rectangle to include the point */
545 UpdateRectangle.Left = min(UpdateRectangle.Left, Coordinates.X);
546 UpdateRectangle.Right = max(UpdateRectangle.Right, Coordinates.X);
547 UpdateRectangle.Top = min(UpdateRectangle.Top, Coordinates.Y);
548 UpdateRectangle.Bottom = max(UpdateRectangle.Bottom, Coordinates.Y);
549 }
550
551 /* Set the update flag */
552 VideoNeedsUpdate = TRUE;
553 }
554 }
555
556 VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
557 {
558 ULONG i;
559 COORD Coordinates;
560 WORD Attribute;
561 DWORD CharsWritten;
562
563 if (VideoModes[CurrentVideoMode].Text)
564 {
565 /* Loop through all the addresses */
566 for (i = StartAddress; i < EndAddress; i++)
567 {
568 /* Get the coordinates */
569 Coordinates = BiosVideoAddressToCoord(i);
570
571 /* Make sure this is the current page */
572 if (BiosVideoAddressToPage(i) != CurrentVideoPage) continue;
573
574 /* Check if this is a character byte or an attribute byte */
575 if ((i - BiosGetVideoMemoryStart()) % 2 == 0)
576 {
577 /* This is a regular character */
578 ReadConsoleOutputCharacterA(BiosConsoleOutput,
579 (LPSTR)((ULONG_PTR)BaseAddress + i),
580 sizeof(CHAR),
581 Coordinates,
582 &CharsWritten);
583 }
584 else
585 {
586 /* This is an attribute */
587 ReadConsoleOutputAttribute(BiosConsoleOutput,
588 &Attribute,
589 sizeof(CHAR),
590 Coordinates,
591 &CharsWritten);
592
593 *(PCHAR)((ULONG_PTR)BaseAddress + i) = LOBYTE(Attribute);
594 }
595 }
596 }
597 else
598 {
599 /* Wait for the mutex object */
600 WaitForSingleObject(ConsoleMutex, INFINITE);
601
602 /* Copy the data to the emulator memory */
603 RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
604 (LPVOID)((ULONG_PTR)ConsoleFramebuffer
605 + StartAddress - BiosGetVideoMemoryStart()),
606 EndAddress - StartAddress);
607
608 /* Release the mutex */
609 ReleaseMutex(ConsoleMutex);
610 }
611 }
612
613 WORD BiosPeekCharacter()
614 {
615 WORD CharacterData;
616
617 /* Check if there is a key available */
618 if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return 0xFFFF;
619
620 /* Get the key from the queue, but don't remove it */
621 BiosKbdBufferTop(&CharacterData);
622
623 return CharacterData;
624 }
625
626 WORD BiosGetCharacter()
627 {
628 WORD CharacterData;
629 INPUT_RECORD InputRecord;
630 DWORD Count;
631
632 /* Check if there is a key available */
633 if (Bda->KeybdBufferHead != Bda->KeybdBufferTail)
634 {
635 /* Get the key from the queue, and remove it */
636 BiosKbdBufferTop(&CharacterData);
637 BiosKbdBufferPop();
638 }
639 else
640 {
641 while (TRUE)
642 {
643 /* Wait for a console event */
644 WaitForSingleObject(BiosConsoleInput, INFINITE);
645
646 /* Read the event, and make sure it's a keypress */
647 if (!ReadConsoleInput(BiosConsoleInput, &InputRecord, 1, &Count)) continue;
648 if (InputRecord.EventType != KEY_EVENT) continue;
649 if (!InputRecord.Event.KeyEvent.bKeyDown) continue;
650
651 /* Save the scan code and end the loop */
652 CharacterData = (InputRecord.Event.KeyEvent.wVirtualScanCode << 8)
653 | InputRecord.Event.KeyEvent.uChar.AsciiChar;
654
655 break;
656 }
657 }
658
659 return CharacterData;
660 }
661
662 VOID BiosVideoService()
663 {
664 INT CursorHeight;
665 BOOLEAN Invisible = FALSE;
666 COORD Position;
667 CONSOLE_CURSOR_INFO CursorInfo;
668 CONSOLE_SCREEN_BUFFER_INFO ScreenBufferInfo;
669 CHAR_INFO Character;
670 SMALL_RECT Rect;
671 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
672 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
673 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
674 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
675
676 // TODO: Add support for multiple pages!
677 switch (HIBYTE(Eax))
678 {
679 /* Set Video Mode */
680 case 0x00:
681 {
682 BiosSetVideoMode(LOBYTE(Eax));
683 break;
684 }
685
686 /* Set Text-Mode Cursor Shape */
687 case 0x01:
688 {
689 /* Retrieve and validate the input */
690 Invisible = ((HIBYTE(Ecx) >> 5) & 0x03) ? TRUE : FALSE;
691 CursorHeight = (HIBYTE(Ecx) & 0x1F) - (LOBYTE(Ecx) & 0x1F);
692 if (CursorHeight < 1) CursorHeight = 1;
693 if (CursorHeight > 100) CursorHeight = 100;
694
695 /* Set the cursor */
696 CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
697 CursorInfo.bVisible = !Invisible;
698 SetConsoleCursorInfo(BiosConsoleOutput, &CursorInfo);
699
700 break;
701 }
702
703 /* Set Cursor Position */
704 case 0x02:
705 {
706 /* Make sure the selected video page exists */
707 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
708
709 Bda->CursorPosition[HIBYTE(Ebx)] = LOWORD(Edx);
710
711 /* Check if this is the current video page */
712 if (HIBYTE(Ebx) == CurrentVideoPage)
713 {
714 /* Yes, change the actual cursor */
715 Position.X = LOBYTE(Edx);
716 Position.Y = HIBYTE(Edx);
717 SetConsoleCursorPosition(BiosConsoleOutput, Position);
718 }
719
720 break;
721 }
722
723 /* Get Cursor Position */
724 case 0x03:
725 {
726 INT StartLine;
727
728 /* Make sure the selected video page exists */
729 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
730
731 /* Retrieve the data */
732 GetConsoleCursorInfo(BiosConsoleOutput, &CursorInfo);
733
734 /* Find the first line */
735 StartLine = 32 - ((CursorInfo.dwSize * 32) / 100);
736
737 /* Return the result */
738 EmulatorSetRegister(EMULATOR_REG_AX, 0);
739 EmulatorSetRegister(EMULATOR_REG_CX, (StartLine << 8) | 0x1F);
740 EmulatorSetRegister(EMULATOR_REG_DX, Bda->CursorPosition[HIBYTE(Ebx)]);
741
742 break;
743 }
744
745 /* Select Active Display Page */
746 case 0x05:
747 {
748 /* Check if the page exists */
749 if (LOBYTE(Eax) >= VideoModes[CurrentVideoMode].Pages) break;
750
751 /* Check if this is the same page */
752 if (LOBYTE(Eax) == CurrentVideoPage) break;
753
754 /* Change the video page */
755 BiosSetVideoPage(LOBYTE(Eax));
756
757 break;
758 }
759
760 /* Scroll Up/Down Window */
761 case 0x06:
762 case 0x07:
763 {
764 Rect.Top = HIBYTE(Ecx);
765 Rect.Left = LOBYTE(Ecx);
766 Rect.Bottom = HIBYTE(Edx);
767 Rect.Right = LOBYTE(Edx);
768 Character.Char.UnicodeChar = L' ';
769 Character.Attributes = HIBYTE(Ebx);
770 Position.X = Rect.Left;
771
772 if (HIBYTE(Eax) == 0x06) Position.Y = Rect.Top - LOBYTE(Eax);
773 else Position.Y = Rect.Top + LOBYTE(Eax);
774
775 ScrollConsoleScreenBuffer(BiosConsoleOutput,
776 &Rect,
777 &Rect,
778 Position,
779 &Character);
780 break;
781 }
782
783 /* Read Character And Attribute At Cursor Position */
784 case 0x08:
785 {
786 COORD BufferSize = { 1, 1 }, Origin = { 0, 0 };
787
788 /* Make sure the selected video page exists */
789 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
790
791 if (HIBYTE(Ebx) == CurrentVideoPage)
792 {
793 /* Get the cursor position */
794 GetConsoleScreenBufferInfo(BiosConsoleOutput,
795 &ScreenBufferInfo);
796
797 /* Read at cursor position */
798 Rect.Left = ScreenBufferInfo.dwCursorPosition.X;
799 Rect.Top = ScreenBufferInfo.dwCursorPosition.Y;
800
801 /* Read the console output */
802 ReadConsoleOutput(BiosConsoleOutput,
803 &Character,
804 BufferSize,
805 Origin,
806 &Rect);
807
808 /* Return the result */
809 EmulatorSetRegister(EMULATOR_REG_AX,
810 (LOBYTE(Character.Attributes) << 8)
811 | Character.Char.AsciiChar);
812 }
813 else
814 {
815 // TODO: NOT IMPLEMENTED
816 }
817
818 break;
819 }
820
821 /* Write Character And Attribute At Cursor Position */
822 case 0x09:
823 {
824 DWORD CharsWritten;
825
826 /* Make sure the selected video page exists */
827 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
828
829 if (HIBYTE(Ebx) == CurrentVideoPage)
830 {
831 /* Get the cursor position */
832 GetConsoleScreenBufferInfo(BiosConsoleOutput,
833 &ScreenBufferInfo);
834
835 /* Write the attribute to the output */
836 FillConsoleOutputAttribute(BiosConsoleOutput,
837 LOBYTE(Ebx),
838 LOWORD(Ecx),
839 ScreenBufferInfo.dwCursorPosition,
840 &CharsWritten);
841
842 /* Write the character to the output */
843 FillConsoleOutputCharacterA(BiosConsoleOutput,
844 LOBYTE(Eax),
845 LOWORD(Ecx),
846 ScreenBufferInfo.dwCursorPosition,
847 &CharsWritten);
848 }
849 else
850 {
851 // TODO: NOT IMPLEMENTED
852 }
853
854 break;
855 }
856
857 /* Write Character Only At Cursor Position */
858 case 0x0A:
859 {
860 DWORD CharsWritten;
861
862 /* Make sure the selected video page exists */
863 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
864
865 if (HIBYTE(Ebx) == CurrentVideoPage)
866 {
867 /* Get the cursor position */
868 GetConsoleScreenBufferInfo(BiosConsoleOutput, &ScreenBufferInfo);
869
870 /* Write the character to the output */
871 FillConsoleOutputCharacterA(BiosConsoleOutput,
872 LOBYTE(Eax),
873 LOWORD(Ecx),
874 ScreenBufferInfo.dwCursorPosition,
875 &CharsWritten);
876 }
877 else
878 {
879 // TODO: NOT IMPLEMENTED
880 }
881
882 break;
883 }
884
885 /* Teletype Output */
886 case 0x0E:
887 {
888 CHAR Character = LOBYTE(Eax);
889 DWORD NumWritten;
890
891 /* Make sure the page exists */
892 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
893
894 /* Set the attribute */
895 SetConsoleTextAttribute(BiosConsoleOutput, LOBYTE(Ebx));
896
897 /* Write the character */
898 WriteConsoleA(BiosConsoleOutput,
899 &Character,
900 sizeof(CHAR),
901 &NumWritten,
902 NULL);
903
904 break;
905 }
906
907 /* Get Current Video Mode */
908 case 0x0F:
909 {
910 EmulatorSetRegister(EMULATOR_REG_AX,
911 MAKEWORD(Bda->VideoMode, Bda->ScreenColumns));
912 EmulatorSetRegister(EMULATOR_REG_BX,
913 MAKEWORD(LOBYTE(Ebx), Bda->VideoPage));
914
915 break;
916 }
917
918 default:
919 {
920 DPRINT1("BIOS Function INT 10h, AH = 0x%02X NOT IMPLEMENTED\n",
921 HIBYTE(Eax));
922 }
923 }
924 }
925
926 VOID BiosKeyboardService()
927 {
928 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
929
930 switch (HIBYTE(Eax))
931 {
932 case 0x00:
933 {
934 /* Read the character (and wait if necessary) */
935 EmulatorSetRegister(EMULATOR_REG_AX, BiosGetCharacter());
936
937 break;
938 }
939
940 case 0x01:
941 {
942 WORD Data = BiosPeekCharacter();
943
944 if (Data != 0xFFFF)
945 {
946 /* There is a character, clear ZF and return it */
947 EmulatorSetRegister(EMULATOR_REG_AX, Data);
948 EmulatorClearFlag(EMULATOR_FLAG_ZF);
949 }
950 else
951 {
952 /* No character, set ZF */
953 EmulatorSetFlag(EMULATOR_FLAG_ZF);
954 }
955
956 break;
957 }
958
959 default:
960 {
961 DPRINT1("BIOS Function INT 16h, AH = 0x%02X NOT IMPLEMENTED\n",
962 HIBYTE(Eax));
963 }
964 }
965 }
966
967 VOID BiosTimeService()
968 {
969 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
970 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
971 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
972
973 switch (HIBYTE(Eax))
974 {
975 case 0x00:
976 {
977 /* Set AL to 1 if midnight had passed, 0 otherwise */
978 Eax &= 0xFFFFFF00;
979 if (Bda->MidnightPassed) Eax |= 1;
980
981 /* Return the tick count in CX:DX */
982 EmulatorSetRegister(EMULATOR_REG_AX, Eax);
983 EmulatorSetRegister(EMULATOR_REG_CX, HIWORD(Bda->TickCounter));
984 EmulatorSetRegister(EMULATOR_REG_DX, LOWORD(Bda->TickCounter));
985
986 /* Reset the midnight flag */
987 Bda->MidnightPassed = FALSE;
988
989 break;
990 }
991
992 case 0x01:
993 {
994 /* Set the tick count to CX:DX */
995 Bda->TickCounter = MAKELONG(LOWORD(Edx), LOWORD(Ecx));
996
997 /* Reset the midnight flag */
998 Bda->MidnightPassed = FALSE;
999
1000 break;
1001 }
1002
1003 default:
1004 {
1005 DPRINT1("BIOS Function INT 1Ah, AH = 0x%02X NOT IMPLEMENTED\n",
1006 HIBYTE(Eax));
1007 }
1008 }
1009 }
1010
1011 VOID BiosSystemTimerInterrupt()
1012 {
1013 /* Increase the system tick count */
1014 Bda->TickCounter++;
1015 }
1016
1017 VOID BiosEquipmentService()
1018 {
1019 /* Return the equipment list */
1020 EmulatorSetRegister(EMULATOR_REG_AX, Bda->EquipmentList);
1021 }
1022
1023 VOID BiosHandleIrq(BYTE IrqNumber)
1024 {
1025 switch (IrqNumber)
1026 {
1027 /* PIT IRQ */
1028 case 0:
1029 {
1030 /* Perform the system timer interrupt */
1031 EmulatorInterrupt(BIOS_SYS_TIMER_INTERRUPT);
1032
1033 break;
1034 }
1035
1036 /* Keyboard IRQ */
1037 case 1:
1038 {
1039 BYTE ScanCode, VirtualKey;
1040 WORD Character;
1041
1042 /* Check if there is a scancode available */
1043 if (!(KeyboardReadStatus() & 1)) break;
1044
1045 /* Get the scan code and virtual key code */
1046 ScanCode = KeyboardReadData();
1047 VirtualKey = MapVirtualKey(ScanCode & 0x7F, MAPVK_VSC_TO_VK);
1048
1049 /* Check if this is a key press or release */
1050 if (!(ScanCode & (1 << 7)))
1051 {
1052 /* Key press */
1053 if (VirtualKey == VK_NUMLOCK
1054 || VirtualKey == VK_CAPITAL
1055 || VirtualKey == VK_SCROLL)
1056 {
1057 /* For toggle keys, toggle the lowest bit in the keyboard map */
1058 BiosKeyboardMap[VirtualKey] ^= ~(1 << 0);
1059 }
1060
1061 /* Set the highest bit */
1062 BiosKeyboardMap[VirtualKey] |= (1 << 7);
1063
1064 /* Find out which character this is */
1065 if (ToAscii(VirtualKey, ScanCode, BiosKeyboardMap, &Character, 0) > 0)
1066 {
1067 /* Push it onto the BIOS keyboard queue */
1068 BiosKbdBufferPush((ScanCode << 8) | (Character & 0xFF));
1069 }
1070 }
1071 else
1072 {
1073 /* Key release, unset the highest bit */
1074 BiosKeyboardMap[VirtualKey] &= ~(1 << 7);
1075 }
1076
1077 break;
1078 }
1079 }
1080
1081 /* Send End-of-Interrupt to the PIC */
1082 if (IrqNumber > 8) PicWriteCommand(PIC_SLAVE_CMD, PIC_OCW2_EOI);
1083 PicWriteCommand(PIC_MASTER_CMD, PIC_OCW2_EOI);
1084 }
1085
1086 /* EOF */