1 /* 2 * PROJECT: ReactOS Kernel 3 * LICENSE: BSD - See COPYING.ARM in the top level directory 4 * PURPOSE: Boot Video Driver support 5 * COPYRIGHT: Copyright 2007 Alex Ionescu (alex.ionescu@reactos.org) 6 * Copyright 2010 Aleksey Bragin (aleksey@reactos.org) 7 * Copyright 2015-2022 Hermès Bélusca-Maïto 8 */ 9 10 /* INCLUDES ******************************************************************/ 11 12 #include <ntoskrnl.h> 13 #include "inbv/logo.h" 14 15 /* GLOBALS *******************************************************************/ 16 17 /* 18 * Enable this define if you want Inbv to use coloured headless mode. 19 */ 20 // #define INBV_HEADLESS_COLORS 21 22 typedef struct _INBV_PROGRESS_STATE 23 { 24 ULONG Floor; 25 ULONG Ceiling; 26 ULONG Bias; 27 } INBV_PROGRESS_STATE; 28 29 typedef struct _BT_PROGRESS_INDICATOR 30 { 31 ULONG Count; 32 ULONG Expected; 33 ULONG Percentage; 34 } BT_PROGRESS_INDICATOR, *PBT_PROGRESS_INDICATOR; 35 36 static KSPIN_LOCK BootDriverLock; 37 static KIRQL InbvOldIrql; 38 static INBV_DISPLAY_STATE InbvDisplayState = INBV_DISPLAY_STATE_DISABLED; 39 BOOLEAN InbvBootDriverInstalled = FALSE; 40 static INBV_RESET_DISPLAY_PARAMETERS InbvResetDisplayParameters = NULL; 41 42 static BOOLEAN InbvDisplayDebugStrings = FALSE; 43 static INBV_DISPLAY_STRING_FILTER InbvDisplayFilter = NULL; 44 45 ULONG ProgressBarLeft = 0, ProgressBarTop = 0; 46 BOOLEAN ShowProgressBar = FALSE; 47 static INBV_PROGRESS_STATE InbvProgressState; 48 static BT_PROGRESS_INDICATOR InbvProgressIndicator = {0, 25, 0}; 49 50 static ULONG ResourceCount = 0; 51 static PUCHAR ResourceList[1 + IDB_MAX_RESOURCE]; // First entry == NULL, followed by 'ResourceCount' entries. 52 53 54 /* 55 * Headless terminal text colors 56 */ 57 58 #ifdef INBV_HEADLESS_COLORS 59 60 // Conversion table CGA to ANSI color index 61 static const UCHAR CGA_TO_ANSI_COLOR_TABLE[16] = 62 { 63 0, // Black 64 4, // Blue 65 2, // Green 66 6, // Cyan 67 1, // Red 68 5, // Magenta 69 3, // Brown/Yellow 70 7, // Grey/White 71 72 60, // Bright Black 73 64, // Bright Blue 74 62, // Bright Green 75 66, // Bright Cyan 76 61, // Bright Red 77 65, // Bright Magenta 78 63, // Bright Yellow 79 67 // Bright Grey (White) 80 }; 81 82 #define CGA_TO_ANSI_COLOR(CgaColor) \ 83 CGA_TO_ANSI_COLOR_TABLE[CgaColor & 0x0F] 84 85 #endif 86 87 // Default colors: text in white, background in black 88 static ULONG InbvTerminalTextColor = 37; 89 static ULONG InbvTerminalBkgdColor = 40; 90 91 92 /* FUNCTIONS *****************************************************************/ 93 94 CODE_SEG("INIT") 95 static 96 PVOID 97 FindBitmapResource( 98 _In_ PLOADER_PARAMETER_BLOCK LoaderBlock, 99 _In_ ULONG ResourceId) 100 { 101 UNICODE_STRING UpString = RTL_CONSTANT_STRING(L"ntoskrnl.exe"); 102 UNICODE_STRING MpString = RTL_CONSTANT_STRING(L"ntkrnlmp.exe"); 103 PLIST_ENTRY NextEntry, ListHead; 104 PLDR_DATA_TABLE_ENTRY LdrEntry; 105 PIMAGE_RESOURCE_DATA_ENTRY ResourceDataEntry; 106 LDR_RESOURCE_INFO ResourceInfo; 107 NTSTATUS Status; 108 PVOID Data = NULL; 109 110 /* Loop the driver list */ 111 ListHead = &LoaderBlock->LoadOrderListHead; 112 NextEntry = ListHead->Flink; 113 while (NextEntry != ListHead) 114 { 115 /* Get the entry */ 116 LdrEntry = CONTAINING_RECORD(NextEntry, 117 LDR_DATA_TABLE_ENTRY, 118 InLoadOrderLinks); 119 120 /* Check for a match */ 121 if (RtlEqualUnicodeString(&LdrEntry->BaseDllName, &UpString, TRUE) || 122 RtlEqualUnicodeString(&LdrEntry->BaseDllName, &MpString, TRUE)) 123 { 124 /* Break out */ 125 break; 126 } 127 } 128 129 /* Check if we found it */ 130 if (NextEntry != ListHead) 131 { 132 /* Try to find the resource */ 133 ResourceInfo.Type = 2; // RT_BITMAP; 134 ResourceInfo.Name = ResourceId; 135 ResourceInfo.Language = 0; 136 Status = LdrFindResource_U(LdrEntry->DllBase, 137 &ResourceInfo, 138 RESOURCE_DATA_LEVEL, 139 &ResourceDataEntry); 140 if (NT_SUCCESS(Status)) 141 { 142 /* Access the resource */ 143 ULONG Size = 0; 144 Status = LdrAccessResource(LdrEntry->DllBase, 145 ResourceDataEntry, 146 &Data, 147 &Size); 148 if ((Data) && (ResourceId < 3)) 149 { 150 KiBugCheckData[4] ^= RtlComputeCrc32(0, Data, Size); 151 } 152 if (!NT_SUCCESS(Status)) Data = NULL; 153 } 154 } 155 156 /* Return the pointer */ 157 return Data; 158 } 159 160 PUCHAR 161 NTAPI 162 InbvGetResourceAddress( 163 _In_ ULONG ResourceNumber) 164 { 165 /* Validate the resource number */ 166 if (ResourceNumber > ResourceCount) return NULL; 167 168 /* Return the address */ 169 return ResourceList[ResourceNumber]; 170 } 171 172 CODE_SEG("INIT") 173 BOOLEAN 174 NTAPI 175 InbvDriverInitialize( 176 _In_ PLOADER_PARAMETER_BLOCK LoaderBlock, 177 _In_ ULONG Count) 178 { 179 PCHAR CommandLine; 180 BOOLEAN ResetMode = FALSE; // By default do not reset the video mode 181 ULONG i; 182 183 /* Quit if we're already installed */ 184 if (InbvBootDriverInstalled) return TRUE; 185 186 /* Initialize the lock and check the current display state */ 187 KeInitializeSpinLock(&BootDriverLock); 188 if (InbvDisplayState == INBV_DISPLAY_STATE_OWNED) 189 { 190 /* Reset the video mode in case we do not have a custom boot logo */ 191 CommandLine = (LoaderBlock->LoadOptions ? _strupr(LoaderBlock->LoadOptions) : NULL); 192 ResetMode = (CommandLine == NULL) || (strstr(CommandLine, "BOOTLOGO") == NULL); 193 } 194 195 /* Initialize the video */ 196 InbvBootDriverInstalled = VidInitialize(ResetMode); 197 if (InbvBootDriverInstalled) 198 { 199 /* Find bitmap resources in the kernel */ 200 ResourceCount = min(Count, RTL_NUMBER_OF(ResourceList) - 1); 201 for (i = 1; i <= ResourceCount; i++) 202 { 203 /* Do the lookup */ 204 ResourceList[i] = FindBitmapResource(LoaderBlock, i); 205 } 206 207 /* Set the progress bar ranges */ 208 InbvSetProgressBarSubset(0, 100); 209 210 // BootAnimInitialize(LoaderBlock, Count); 211 } 212 213 /* Return install state */ 214 return InbvBootDriverInstalled; 215 } 216 217 VOID 218 NTAPI 219 InbvAcquireLock(VOID) 220 { 221 KIRQL OldIrql; 222 223 /* Check if we're at dispatch level or lower */ 224 OldIrql = KeGetCurrentIrql(); 225 if (OldIrql <= DISPATCH_LEVEL) 226 { 227 /* Loop until the lock is free */ 228 while (!KeTestSpinLock(&BootDriverLock)); 229 230 /* Raise IRQL to dispatch level */ 231 KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); 232 } 233 234 /* Acquire the lock */ 235 KiAcquireSpinLock(&BootDriverLock); 236 InbvOldIrql = OldIrql; 237 } 238 239 VOID 240 NTAPI 241 InbvReleaseLock(VOID) 242 { 243 KIRQL OldIrql; 244 245 /* Capture the old IRQL */ 246 OldIrql = InbvOldIrql; 247 248 /* Release the driver lock */ 249 KiReleaseSpinLock(&BootDriverLock); 250 251 /* If we were at dispatch level or lower, restore the old IRQL */ 252 if (InbvOldIrql <= DISPATCH_LEVEL) KeLowerIrql(OldIrql); 253 } 254 255 VOID 256 NTAPI 257 InbvEnableBootDriver( 258 _In_ BOOLEAN Enable) 259 { 260 /* Check if we're installed */ 261 if (InbvBootDriverInstalled) 262 { 263 /* Check for lost state */ 264 if (InbvDisplayState >= INBV_DISPLAY_STATE_LOST) return; 265 266 /* Acquire the lock */ 267 InbvAcquireLock(); 268 269 /* Cleanup the screen if we own it */ 270 if (InbvDisplayState == INBV_DISPLAY_STATE_OWNED) VidCleanUp(); 271 272 /* Set the new display state */ 273 InbvDisplayState = Enable ? INBV_DISPLAY_STATE_OWNED : 274 INBV_DISPLAY_STATE_DISABLED; 275 276 /* Release the lock */ 277 InbvReleaseLock(); 278 } 279 else 280 { 281 /* Set the new display state */ 282 InbvDisplayState = Enable ? INBV_DISPLAY_STATE_OWNED : 283 INBV_DISPLAY_STATE_DISABLED; 284 } 285 } 286 287 VOID 288 NTAPI 289 InbvAcquireDisplayOwnership(VOID) 290 { 291 /* Check if we have a callback and we're just acquiring it now */ 292 if ((InbvResetDisplayParameters) && 293 (InbvDisplayState == INBV_DISPLAY_STATE_LOST)) 294 { 295 /* Call the callback */ 296 InbvResetDisplayParameters(80, 50); 297 } 298 299 /* Acquire the display */ 300 InbvDisplayState = INBV_DISPLAY_STATE_OWNED; 301 } 302 303 VOID 304 NTAPI 305 InbvSetDisplayOwnership( 306 _In_ BOOLEAN DisplayOwned) 307 { 308 /* Set the new display state */ 309 InbvDisplayState = DisplayOwned ? INBV_DISPLAY_STATE_OWNED : 310 INBV_DISPLAY_STATE_LOST; 311 } 312 313 BOOLEAN 314 NTAPI 315 InbvCheckDisplayOwnership(VOID) 316 { 317 /* Return if we own it or not */ 318 return InbvDisplayState != INBV_DISPLAY_STATE_LOST; 319 } 320 321 INBV_DISPLAY_STATE 322 NTAPI 323 InbvGetDisplayState(VOID) 324 { 325 /* Return the actual state */ 326 return InbvDisplayState; 327 } 328 329 BOOLEAN 330 NTAPI 331 InbvDisplayString( 332 _In_ PCHAR String) 333 { 334 /* Make sure we own the display */ 335 if (InbvDisplayState == INBV_DISPLAY_STATE_OWNED) 336 { 337 /* If we're not allowed, return success anyway */ 338 if (!InbvDisplayDebugStrings) return TRUE; 339 340 /* Check if a filter is installed */ 341 if (InbvDisplayFilter) InbvDisplayFilter(&String); 342 343 /* Acquire the lock */ 344 InbvAcquireLock(); 345 346 /* Make sure we're installed and display the string */ 347 if (InbvBootDriverInstalled) VidDisplayString((PUCHAR)String); 348 349 /* Print the string on the EMS port */ 350 HeadlessDispatch(HeadlessCmdPutString, 351 String, 352 strlen(String) + sizeof(ANSI_NULL), 353 NULL, 354 NULL); 355 356 /* Release the lock */ 357 InbvReleaseLock(); 358 359 /* All done */ 360 return TRUE; 361 } 362 363 /* We don't own it, fail */ 364 return FALSE; 365 } 366 367 BOOLEAN 368 NTAPI 369 InbvEnableDisplayString( 370 _In_ BOOLEAN Enable) 371 { 372 BOOLEAN OldSetting; 373 374 /* Get the old setting */ 375 OldSetting = InbvDisplayDebugStrings; 376 377 /* Update it */ 378 InbvDisplayDebugStrings = Enable; 379 380 /* Return the old setting */ 381 return OldSetting; 382 } 383 384 VOID 385 NTAPI 386 InbvInstallDisplayStringFilter( 387 _In_ INBV_DISPLAY_STRING_FILTER DisplayFilter) 388 { 389 /* Save the filter */ 390 InbvDisplayFilter = DisplayFilter; 391 } 392 393 BOOLEAN 394 NTAPI 395 InbvIsBootDriverInstalled(VOID) 396 { 397 /* Return driver state */ 398 return InbvBootDriverInstalled; 399 } 400 401 VOID 402 NTAPI 403 InbvNotifyDisplayOwnershipLost( 404 _In_ INBV_RESET_DISPLAY_PARAMETERS Callback) 405 { 406 /* Check if we're installed */ 407 if (InbvBootDriverInstalled) 408 { 409 /* Acquire the lock and cleanup if we own the screen */ 410 InbvAcquireLock(); 411 if (InbvDisplayState != INBV_DISPLAY_STATE_LOST) VidCleanUp(); 412 413 /* Set the reset callback and display state */ 414 InbvResetDisplayParameters = Callback; 415 InbvDisplayState = INBV_DISPLAY_STATE_LOST; 416 417 /* Release the lock */ 418 InbvReleaseLock(); 419 } 420 else 421 { 422 /* Set the reset callback and display state */ 423 InbvResetDisplayParameters = Callback; 424 InbvDisplayState = INBV_DISPLAY_STATE_LOST; 425 } 426 } 427 428 BOOLEAN 429 NTAPI 430 InbvResetDisplay(VOID) 431 { 432 /* Check if we're installed and we own it */ 433 if (InbvBootDriverInstalled && 434 (InbvDisplayState == INBV_DISPLAY_STATE_OWNED)) 435 { 436 /* Do the reset */ 437 VidResetDisplay(TRUE); 438 return TRUE; 439 } 440 441 /* Nothing to reset */ 442 return FALSE; 443 } 444 445 VOID 446 NTAPI 447 InbvSetScrollRegion( 448 _In_ ULONG Left, 449 _In_ ULONG Top, 450 _In_ ULONG Right, 451 _In_ ULONG Bottom) 452 { 453 /* Just call bootvid */ 454 VidSetScrollRegion(Left, Top, Right, Bottom); 455 } 456 457 VOID 458 NTAPI 459 InbvSetTextColor( 460 _In_ ULONG Color) 461 { 462 HEADLESS_CMD_SET_COLOR HeadlessSetColor; 463 464 /* Set color for EMS port */ 465 #ifdef INBV_HEADLESS_COLORS 466 InbvTerminalTextColor = 30 + CGA_TO_ANSI_COLOR(Color); 467 #else 468 InbvTerminalTextColor = 37; 469 #endif 470 HeadlessSetColor.TextColor = InbvTerminalTextColor; 471 HeadlessSetColor.BkgdColor = InbvTerminalBkgdColor; 472 HeadlessDispatch(HeadlessCmdSetColor, 473 &HeadlessSetColor, 474 sizeof(HeadlessSetColor), 475 NULL, 476 NULL); 477 478 /* Update the text color */ 479 VidSetTextColor(Color); 480 } 481 482 VOID 483 NTAPI 484 InbvSolidColorFill( 485 _In_ ULONG Left, 486 _In_ ULONG Top, 487 _In_ ULONG Right, 488 _In_ ULONG Bottom, 489 _In_ ULONG Color) 490 { 491 HEADLESS_CMD_SET_COLOR HeadlessSetColor; 492 493 /* Make sure we own it */ 494 if (InbvDisplayState == INBV_DISPLAY_STATE_OWNED) 495 { 496 /* Acquire the lock */ 497 InbvAcquireLock(); 498 499 /* Check if we're installed */ 500 if (InbvBootDriverInstalled) 501 { 502 /* Call bootvid */ 503 VidSolidColorFill(Left, Top, Right, Bottom, (UCHAR)Color); 504 } 505 506 /* Set color for EMS port and clear display */ 507 #ifdef INBV_HEADLESS_COLORS 508 InbvTerminalBkgdColor = 40 + CGA_TO_ANSI_COLOR(Color); 509 #else 510 InbvTerminalBkgdColor = 40; 511 #endif 512 HeadlessSetColor.TextColor = InbvTerminalTextColor; 513 HeadlessSetColor.BkgdColor = InbvTerminalBkgdColor; 514 HeadlessDispatch(HeadlessCmdSetColor, 515 &HeadlessSetColor, 516 sizeof(HeadlessSetColor), 517 NULL, 518 NULL); 519 HeadlessDispatch(HeadlessCmdClearDisplay, 520 NULL, 0, 521 NULL, NULL); 522 523 /* Release the lock */ 524 InbvReleaseLock(); 525 } 526 } 527 528 VOID 529 NTAPI 530 InbvBitBlt( 531 _In_ PUCHAR Buffer, 532 _In_ ULONG X, 533 _In_ ULONG Y) 534 { 535 /* Check if we're installed and we own it */ 536 if (InbvBootDriverInstalled && 537 (InbvDisplayState == INBV_DISPLAY_STATE_OWNED)) 538 { 539 /* Acquire the lock */ 540 InbvAcquireLock(); 541 542 /* Do the blit */ 543 VidBitBlt(Buffer, X, Y); 544 545 /* Release the lock */ 546 InbvReleaseLock(); 547 } 548 } 549 550 VOID 551 NTAPI 552 InbvBufferToScreenBlt( 553 _In_ PUCHAR Buffer, 554 _In_ ULONG X, 555 _In_ ULONG Y, 556 _In_ ULONG Width, 557 _In_ ULONG Height, 558 _In_ ULONG Delta) 559 { 560 /* Check if we're installed and we own it */ 561 if (InbvBootDriverInstalled && 562 (InbvDisplayState == INBV_DISPLAY_STATE_OWNED)) 563 { 564 /* Do the blit */ 565 VidBufferToScreenBlt(Buffer, X, Y, Width, Height, Delta); 566 } 567 } 568 569 VOID 570 NTAPI 571 InbvScreenToBufferBlt( 572 _Out_ PUCHAR Buffer, 573 _In_ ULONG X, 574 _In_ ULONG Y, 575 _In_ ULONG Width, 576 _In_ ULONG Height, 577 _In_ ULONG Delta) 578 { 579 /* Check if we're installed and we own it */ 580 if (InbvBootDriverInstalled && 581 (InbvDisplayState == INBV_DISPLAY_STATE_OWNED)) 582 { 583 /* Do the blit */ 584 VidScreenToBufferBlt(Buffer, X, Y, Width, Height, Delta); 585 } 586 } 587 588 /** 589 * @brief 590 * Sets the screen coordinates of the loading progress bar and enable it. 591 * 592 * @param[in] Left 593 * @param[in] Top 594 * The left/top coordinates. 595 * 596 * @return None. 597 **/ 598 VOID 599 NTAPI 600 InbvSetProgressBarCoordinates( 601 _In_ ULONG Left, 602 _In_ ULONG Top) 603 { 604 /* Update the coordinates */ 605 ProgressBarLeft = Left; 606 ProgressBarTop = Top; 607 608 /* Enable the progress bar */ 609 ShowProgressBar = TRUE; 610 } 611 612 /** 613 * @brief 614 * Gives some progress feedback, without specifying any explicit number 615 * of progress steps or percentage. 616 * The corresponding percentage is derived from the progress indicator's 617 * current count, capped to the number of expected calls to be made to 618 * this function (default: 25, see @b InbvProgressIndicator.Expected). 619 * 620 * @return None. 621 **/ 622 CODE_SEG("INIT") 623 VOID 624 NTAPI 625 InbvIndicateProgress(VOID) 626 { 627 ULONG Percentage; 628 629 /* Increase progress */ 630 InbvProgressIndicator.Count++; 631 632 /* Compute the new percentage - Don't go over 100% */ 633 Percentage = 100 * InbvProgressIndicator.Count / 634 InbvProgressIndicator.Expected; 635 Percentage = min(Percentage, 99); 636 637 if (Percentage != InbvProgressIndicator.Percentage) 638 { 639 /* Percentage has changed, update the progress bar */ 640 InbvProgressIndicator.Percentage = Percentage; 641 InbvUpdateProgressBar(Percentage); 642 } 643 } 644 645 /** 646 * @brief 647 * Specifies a progress percentage sub-range. 648 * Further calls to InbvIndicateProgress() or InbvUpdateProgressBar() 649 * will update the progress percentage relative to this sub-range. 650 * In particular, the percentage provided to InbvUpdateProgressBar() 651 * is relative to this sub-range. 652 * 653 * @param[in] Floor 654 * The lower bound percentage of the sub-range (default: 0). 655 * 656 * @param[in] Ceiling 657 * The upper bound percentage of the sub-range (default: 100). 658 * 659 * @return None. 660 **/ 661 VOID 662 NTAPI 663 InbvSetProgressBarSubset( 664 _In_ ULONG Floor, 665 _In_ ULONG Ceiling) 666 { 667 /* Sanity checks */ 668 ASSERT(Floor < Ceiling); 669 ASSERT(Ceiling <= 100); 670 671 /* Update the progress bar state */ 672 InbvProgressState.Floor = Floor * 100; 673 InbvProgressState.Ceiling = Ceiling * 100; 674 InbvProgressState.Bias = Ceiling - Floor; 675 } 676 677 /** 678 * @brief 679 * Updates the progress bar percentage, relative to the current 680 * percentage sub-range previously set by InbvSetProgressBarSubset(). 681 * 682 * @param[in] Percentage 683 * The progress percentage, relative to the current sub-range. 684 * 685 * @return None. 686 **/ 687 VOID 688 NTAPI 689 InbvUpdateProgressBar( 690 _In_ ULONG Percentage) 691 { 692 ULONG TotalProgress; 693 694 /* Make sure the progress bar is enabled, that we own and are installed */ 695 if (ShowProgressBar && 696 InbvBootDriverInstalled && 697 (InbvDisplayState == INBV_DISPLAY_STATE_OWNED)) 698 { 699 /* Compute the total progress and tick the progress bar */ 700 TotalProgress = InbvProgressState.Floor + (Percentage * InbvProgressState.Bias); 701 // TotalProgress /= (100 * 100); 702 703 BootAnimTickProgressBar(TotalProgress); 704 } 705 } 706 707 NTSTATUS 708 NTAPI 709 NtDisplayString(IN PUNICODE_STRING DisplayString) 710 { 711 NTSTATUS Status; 712 UNICODE_STRING CapturedString; 713 OEM_STRING OemString; 714 ULONG OemLength; 715 KPROCESSOR_MODE PreviousMode; 716 717 PAGED_CODE(); 718 719 PreviousMode = ExGetPreviousMode(); 720 721 /* We require the TCB privilege */ 722 if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode)) 723 return STATUS_PRIVILEGE_NOT_HELD; 724 725 /* Capture the string */ 726 Status = ProbeAndCaptureUnicodeString(&CapturedString, PreviousMode, DisplayString); 727 if (!NT_SUCCESS(Status)) 728 return Status; 729 730 /* Do not display the string if it is empty */ 731 if (CapturedString.Length == 0 || CapturedString.Buffer == NULL) 732 { 733 Status = STATUS_SUCCESS; 734 goto Quit; 735 } 736 737 /* 738 * Convert the string since INBV understands only ANSI/OEM. Allocate the 739 * string buffer in non-paged pool because INBV passes it down to BOOTVID. 740 * We cannot perform the allocation using RtlUnicodeStringToOemString() 741 * since its allocator uses PagedPool. 742 */ 743 OemLength = RtlUnicodeStringToOemSize(&CapturedString); 744 if (OemLength > MAXUSHORT) 745 { 746 Status = STATUS_BUFFER_OVERFLOW; 747 goto Quit; 748 } 749 RtlInitEmptyAnsiString((PANSI_STRING)&OemString, NULL, (USHORT)OemLength); 750 OemString.Buffer = ExAllocatePoolWithTag(NonPagedPool, OemLength, TAG_OSTR); 751 if (OemString.Buffer == NULL) 752 { 753 Status = STATUS_NO_MEMORY; 754 goto Quit; 755 } 756 Status = RtlUnicodeStringToOemString(&OemString, &CapturedString, FALSE); 757 if (!NT_SUCCESS(Status)) 758 { 759 ExFreePoolWithTag(OemString.Buffer, TAG_OSTR); 760 goto Quit; 761 } 762 763 /* Display the string */ 764 InbvDisplayString(OemString.Buffer); 765 766 /* Free the string buffer */ 767 ExFreePoolWithTag(OemString.Buffer, TAG_OSTR); 768 769 Status = STATUS_SUCCESS; 770 771 Quit: 772 /* Free the captured string */ 773 ReleaseCapturedUnicodeString(&CapturedString, PreviousMode); 774 775 return Status; 776 } 777