xref: /reactos/ntoskrnl/inbv/inbv.c (revision 3e1f4074)
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