xref: /reactos/win32ss/gdi/eng/ldevobj.c (revision ccef43f3)
1 /*
2  * COPYRIGHT:        See COPYING in the top level directory
3  * PROJECT:          ReactOS Win32k subsystem
4  * PURPOSE:          Support for logical devices
5  * FILE:             win32ss/gdi/eng/ldevobj.c
6  * PROGRAMER:        Timo Kreuzer (timo.kreuzer@reactos.org)
7  */
8 
9 #include <win32k.h>
10 #define NDEBUG
11 #include <debug.h>
12 DBG_DEFAULT_CHANNEL(EngLDev);
13 
14 #ifndef RVA_TO_ADDR
15 #define RVA_TO_ADDR(Base,Rva) ((PVOID)(((ULONG_PTR)(Base)) + (Rva)))
16 #endif
17 
18 /** Globals *******************************************************************/
19 
20 static HSEMAPHORE ghsemLDEVList;
21 static LIST_ENTRY gleLdevListHead;
22 static LDEVOBJ *gpldevWin32k = NULL;
23 
24 
25 /** Private functions *********************************************************/
26 
27 CODE_SEG("INIT")
28 NTSTATUS
29 NTAPI
30 InitLDEVImpl(VOID)
31 {
32     ULONG cbSize;
33 
34     /* Initialize the LDEV list head */
35     InitializeListHead(&gleLdevListHead);
36 
37     /* Initialize the loader lock */
38     ghsemLDEVList = EngCreateSemaphore();
39     if (!ghsemLDEVList)
40     {
41         ERR("Failed to create ghsemLDEVList\n");
42         return STATUS_INSUFFICIENT_RESOURCES;
43     }
44 
45     /* Allocate a LDEVOBJ for win32k */
46     gpldevWin32k = ExAllocatePoolWithTag(PagedPool,
47                                          sizeof(LDEVOBJ) +
48                                          sizeof(SYSTEM_GDI_DRIVER_INFORMATION),
49                                          GDITAG_LDEV);
50     if (!gpldevWin32k)
51     {
52         return STATUS_NO_MEMORY;
53     }
54 
55     /* Initialize the LDEVOBJ for win32k */
56     gpldevWin32k->leLink.Flink = NULL;
57     gpldevWin32k->leLink.Blink = NULL;
58     gpldevWin32k->ldevtype = LDEV_DEVICE_DISPLAY;
59     gpldevWin32k->cRefs = 1;
60     gpldevWin32k->ulDriverVersion = GDI_ENGINE_VERSION;
61     gpldevWin32k->pGdiDriverInfo = (PVOID)(gpldevWin32k + 1);
62     RtlInitUnicodeString(&gpldevWin32k->pGdiDriverInfo->DriverName,
63                          L"\\SystemRoot\\System32\\win32k.sys");
64     gpldevWin32k->pGdiDriverInfo->ImageAddress = &__ImageBase;
65     gpldevWin32k->pGdiDriverInfo->SectionPointer = NULL;
66     gpldevWin32k->pGdiDriverInfo->EntryPoint = (PVOID)DriverEntry;
67     gpldevWin32k->pGdiDriverInfo->ExportSectionPointer =
68         RtlImageDirectoryEntryToData(&__ImageBase,
69                                      TRUE,
70                                      IMAGE_DIRECTORY_ENTRY_EXPORT,
71                                      &cbSize);
72     gpldevWin32k->pGdiDriverInfo->ImageLength = 0; // FIXME
73 
74     return STATUS_SUCCESS;
75 }
76 
77 static
78 PLDEVOBJ
79 LDEVOBJ_AllocLDEV(
80     _In_ LDEVTYPE ldevtype)
81 {
82     PLDEVOBJ pldev;
83 
84     /* Allocate the structure from paged pool */
85     pldev = ExAllocatePoolWithTag(PagedPool, sizeof(LDEVOBJ), GDITAG_LDEV);
86     if (!pldev)
87     {
88         ERR("Failed to allocate LDEVOBJ.\n");
89         return NULL;
90     }
91 
92     /* Zero out the structure */
93     RtlZeroMemory(pldev, sizeof(LDEVOBJ));
94 
95     /* Set the ldevtype */
96     pldev->ldevtype = ldevtype;
97 
98     return pldev;
99 }
100 
101 static
102 VOID
103 LDEVOBJ_vFreeLDEV(
104     _In_ _Post_ptr_invalid_ PLDEVOBJ pldev)
105 {
106     /* Make sure we don't have a driver loaded */
107     ASSERT(pldev && pldev->pGdiDriverInfo == NULL);
108     ASSERT(pldev->cRefs == 0);
109 
110     /* Free the memory */
111     ExFreePoolWithTag(pldev, GDITAG_LDEV);
112 }
113 
114 static
115 BOOL
116 LDEVOBJ_bLoadImage(
117     _Inout_ PLDEVOBJ pldev,
118     _In_ PUNICODE_STRING pustrPathName)
119 {
120     PSYSTEM_GDI_DRIVER_INFORMATION pDriverInfo;
121     NTSTATUS Status;
122     ULONG cbSize;
123 
124     /* Make sure no image is loaded yet */
125     ASSERT(pldev && pldev->pGdiDriverInfo == NULL);
126 
127     /* Allocate a SYSTEM_GDI_DRIVER_INFORMATION structure */
128     cbSize = sizeof(SYSTEM_GDI_DRIVER_INFORMATION) + pustrPathName->Length;
129     pDriverInfo = ExAllocatePoolWithTag(PagedPool, cbSize, GDITAG_LDEV);
130     if (!pDriverInfo)
131     {
132         ERR("Failed to allocate SYSTEM_GDI_DRIVER_INFORMATION\n");
133         return FALSE;
134     }
135 
136     /* Initialize the UNICODE_STRING and copy the driver name */
137     RtlInitEmptyUnicodeString(&pDriverInfo->DriverName,
138                               (PWSTR)(pDriverInfo + 1),
139                               pustrPathName->Length);
140     RtlCopyUnicodeString(&pDriverInfo->DriverName, pustrPathName);
141 
142     /* Try to load the driver */
143     Status = ZwSetSystemInformation(SystemLoadGdiDriverInformation,
144                                     pDriverInfo,
145                                     sizeof(SYSTEM_GDI_DRIVER_INFORMATION));
146     if (!NT_SUCCESS(Status))
147     {
148         ERR("Failed to load a GDI driver: '%wZ', Status = 0x%lx\n",
149             pustrPathName, Status);
150 
151         /* Free the allocated memory */
152         ExFreePoolWithTag(pDriverInfo, GDITAG_LDEV);
153         return FALSE;
154     }
155 
156     /* Set the driver info */
157     pldev->pGdiDriverInfo = pDriverInfo;
158 
159     /* Return success. */
160     return TRUE;
161 }
162 
163 static
164 BOOL
165 LDEVOBJ_bEnableDriver(
166     _Inout_ PLDEVOBJ pldev,
167     _In_ PFN_DrvEnableDriver pfnEnableDriver)
168 {
169     DRVENABLEDATA ded;
170     ULONG i;
171 
172     TRACE("LDEVOBJ_bEnableDriver('%wZ')\n", &pldev->pGdiDriverInfo->DriverName);
173 
174     ASSERT(pldev);
175     ASSERT(pldev->cRefs == 0);
176 
177     if (pldev->ldevtype == LDEV_IMAGE)
178         return TRUE;
179 
180     /* Call the drivers DrvEnableDriver function */
181     RtlZeroMemory(&ded, sizeof(ded));
182     if (!pfnEnableDriver(GDI_ENGINE_VERSION, sizeof(ded), &ded))
183     {
184         ERR("DrvEnableDriver failed\n");
185         return FALSE;
186     }
187 
188     /* Copy the returned driver version */
189     pldev->ulDriverVersion = ded.iDriverVersion;
190 
191     /* Fill the driver function array */
192     for (i = 0; i < ded.c; i++)
193     {
194         pldev->apfn[ded.pdrvfn[i].iFunc] = ded.pdrvfn[i].pfn;
195     }
196 
197     /* Return success. */
198     return TRUE;
199 }
200 
201 static
202 VOID
203 LDEVOBJ_vDisableDriver(
204     _Inout_ PLDEVOBJ pldev)
205 {
206     ASSERT(pldev);
207     ASSERT(pldev->cRefs == 0);
208 
209     TRACE("LDEVOBJ_vDisableDriver('%wZ')\n", &pldev->pGdiDriverInfo->DriverName);
210 
211     if (pldev->ldevtype == LDEV_IMAGE)
212         return;
213 
214     if (pldev->pfn.DisableDriver)
215     {
216         /* Call the unload function */
217         pldev->pfn.DisableDriver();
218     }
219 }
220 
221 static
222 PVOID
223 LDEVOBJ_pvFindImageProcAddress(
224     _In_ PLDEVOBJ pldev,
225     _In_z_ LPSTR pszProcName)
226 {
227     PVOID pvImageBase;
228     PIMAGE_EXPORT_DIRECTORY pExportDir;
229     PVOID pvProcAdress = NULL;
230     PUSHORT pOrdinals;
231     PULONG pNames, pAddresses;
232     ULONG i;
233 
234     /* Make sure we have a driver info */
235     ASSERT(pldev && pldev->pGdiDriverInfo != NULL);
236 
237     /* Get the pointer to the export directory */
238     pvImageBase = pldev->pGdiDriverInfo->ImageAddress;
239     pExportDir = pldev->pGdiDriverInfo->ExportSectionPointer;
240     if (!pExportDir)
241     {
242         ERR("LDEVOBJ_pvFindImageProcAddress: no export section found\n");
243         return NULL;
244     }
245 
246     /* Get pointers to some tables */
247     pNames = RVA_TO_ADDR(pvImageBase, pExportDir->AddressOfNames);
248     pOrdinals = RVA_TO_ADDR(pvImageBase, pExportDir->AddressOfNameOrdinals);
249     pAddresses = RVA_TO_ADDR(pvImageBase, pExportDir->AddressOfFunctions);
250 
251     /* Loop the export table */
252     for (i = 0; i < pExportDir->NumberOfNames; i++)
253     {
254         /* Compare the name */
255         if (_stricmp(pszProcName, RVA_TO_ADDR(pvImageBase, pNames[i])) == 0)
256         {
257             /* Found! Calculate the procedure address */
258             pvProcAdress = RVA_TO_ADDR(pvImageBase, pAddresses[pOrdinals[i]]);
259             break;
260         }
261     }
262 
263     /* Return the address */
264     return pvProcAdress;
265 }
266 
267 static
268 BOOL
269 LDEVOBJ_bUnloadImage(
270     _Inout_ PLDEVOBJ pldev)
271 {
272     NTSTATUS Status;
273 
274     /* Make sure we have a driver info */
275     ASSERT(pldev && pldev->pGdiDriverInfo != NULL);
276     ASSERT(pldev->cRefs == 0);
277 
278     TRACE("LDEVOBJ_bUnloadImage('%wZ')\n", &pldev->pGdiDriverInfo->DriverName);
279 
280     /* Unload the driver */
281 #if 0
282     Status = ZwSetSystemInformation(SystemUnloadGdiDriverInformation,
283                                     &pldev->pGdiDriverInfo->SectionPointer,
284                                     sizeof(HANDLE));
285 #else
286     /* Unfortunately, ntoskrnl allows unloading a driver, but fails loading
287      * it again with STATUS_IMAGE_ALREADY_LOADED. Prevent this problem by
288      * never unloading any driver.
289      */
290     UNIMPLEMENTED;
291     Status = STATUS_NOT_IMPLEMENTED;
292 #endif
293     if (!NT_SUCCESS(Status))
294         return FALSE;
295 
296     ExFreePoolWithTag(pldev->pGdiDriverInfo, GDITAG_LDEV);
297     pldev->pGdiDriverInfo = NULL;
298 
299     return TRUE;
300 }
301 
302 PLDEVOBJ
303 LDEVOBJ_pLoadInternal(
304     _In_ PFN_DrvEnableDriver pfnEnableDriver,
305     _In_ ULONG ldevtype)
306 {
307     PLDEVOBJ pldev;
308 
309     TRACE("LDEVOBJ_pLoadInternal(%lu)\n", ldevtype);
310 
311     /* Lock loader */
312     EngAcquireSemaphore(ghsemLDEVList);
313 
314     /* Allocate a new LDEVOBJ */
315     pldev = LDEVOBJ_AllocLDEV(ldevtype);
316     if (!pldev)
317     {
318         ERR("Could not allocate LDEV\n");
319         goto leave;
320     }
321 
322     /* Load the driver */
323     if (!LDEVOBJ_bEnableDriver(pldev, pfnEnableDriver))
324     {
325         ERR("LDEVOBJ_bEnableDriver failed\n");
326         LDEVOBJ_vFreeLDEV(pldev);
327         pldev = NULL;
328         goto leave;
329     }
330 
331     /* Insert the LDEV into the global list */
332     InsertHeadList(&gleLdevListHead, &pldev->leLink);
333 
334     /* Increase ref count */
335     pldev->cRefs++;
336 
337 leave:
338     /* Unlock loader */
339     EngReleaseSemaphore(ghsemLDEVList);
340 
341     TRACE("LDEVOBJ_pLoadInternal returning %p\n", pldev);
342     return pldev;
343 }
344 
345 PLDEVOBJ
346 NTAPI
347 LDEVOBJ_pLoadDriver(
348     _In_z_ LPWSTR pwszDriverName,
349     _In_ ULONG ldevtype)
350 {
351     WCHAR acwBuffer[MAX_PATH];
352     PLIST_ENTRY pleLink;
353     PLDEVOBJ pldev;
354     UNICODE_STRING strDriverName;
355     SIZE_T cwcLength;
356     LPWSTR pwsz;
357 
358     TRACE("LDEVOBJ_pLoadDriver(%ls, %lu)\n", pwszDriverName, ldevtype);
359     ASSERT(pwszDriverName);
360 
361     /* Initialize buffer for the the driver name */
362     RtlInitEmptyUnicodeString(&strDriverName, acwBuffer, sizeof(acwBuffer));
363 
364     /* Start path with systemroot */
365     RtlAppendUnicodeToString(&strDriverName, L"\\SystemRoot\\System32\\");
366 
367     /* Get Length of given string */
368     cwcLength = wcslen(pwszDriverName);
369 
370     /* Check if we have a system32 path given */
371     pwsz = pwszDriverName + cwcLength;
372     while (pwsz > pwszDriverName)
373     {
374         if ((*pwsz == L'\\') && (_wcsnicmp(pwsz, L"\\system32\\", 10) == 0))
375         {
376             /* Driver name starts after system32 */
377             pwsz += 10;
378             break;
379         }
380         pwsz--;
381     }
382 
383     /* Append the driver name */
384     RtlAppendUnicodeToString(&strDriverName, pwsz);
385 
386     /* MSDN says "The driver must include this suffix in the pwszDriver string."
387        But in fact it's optional. The function can also load .sys files without
388        appending the .dll extension. */
389     if ((cwcLength < 4) ||
390         ((_wcsnicmp(pwszDriverName + cwcLength - 4, L".dll", 4) != 0) &&
391          (_wcsnicmp(pwszDriverName + cwcLength - 4, L".sys", 4) != 0)) )
392     {
393         /* Append the .dll suffix */
394         RtlAppendUnicodeToString(&strDriverName, L".dll");
395     }
396 
397     /* Lock loader */
398     EngAcquireSemaphore(ghsemLDEVList);
399 
400     /* Search the List of LDEVS for the driver name */
401     for (pleLink = gleLdevListHead.Flink;
402          pleLink != &gleLdevListHead;
403          pleLink = pleLink->Flink)
404     {
405         pldev = CONTAINING_RECORD(pleLink, LDEVOBJ, leLink);
406 
407         /* Check if the ldev is associated with a file */
408         if (pldev->pGdiDriverInfo)
409         {
410             /* Check for match (case insensative) */
411             if (RtlEqualUnicodeString(&pldev->pGdiDriverInfo->DriverName, &strDriverName, TRUE))
412             {
413                 /* Image found in LDEV list */
414                 break;
415             }
416         }
417     }
418 
419     /* Did we find one? */
420     if (pleLink == &gleLdevListHead)
421     {
422         /* No, allocate a new LDEVOBJ */
423         pldev = LDEVOBJ_AllocLDEV(ldevtype);
424         if (!pldev)
425         {
426             ERR("Could not allocate LDEV\n");
427             goto leave;
428         }
429 
430         /* Load the image */
431         if (!LDEVOBJ_bLoadImage(pldev, &strDriverName))
432         {
433             LDEVOBJ_vFreeLDEV(pldev);
434             pldev = NULL;
435             ERR("LDEVOBJ_bLoadImage failed\n");
436             goto leave;
437         }
438 
439         /* Load the driver */
440         if (!LDEVOBJ_bEnableDriver(pldev, pldev->pGdiDriverInfo->EntryPoint))
441         {
442             ERR("LDEVOBJ_bEnableDriver failed\n");
443 
444             /* Unload the image. */
445             LDEVOBJ_bUnloadImage(pldev);
446             LDEVOBJ_vFreeLDEV(pldev);
447             pldev = NULL;
448             goto leave;
449         }
450 
451         /* Insert the LDEV into the global list */
452         InsertHeadList(&gleLdevListHead, &pldev->leLink);
453     }
454 
455     /* Increase ref count */
456     pldev->cRefs++;
457 
458 leave:
459     /* Unlock loader */
460     EngReleaseSemaphore(ghsemLDEVList);
461 
462     TRACE("LDEVOBJ_pLoadDriver returning %p\n", pldev);
463     return pldev;
464 }
465 
466 static
467 VOID
468 LDEVOBJ_vDereference(
469     _Inout_ PLDEVOBJ pldev)
470 {
471     /* Lock loader */
472     EngAcquireSemaphore(ghsemLDEVList);
473 
474     /* Decrement reference count */
475     ASSERT(pldev->cRefs > 0);
476     pldev->cRefs--;
477 
478     /* More references left? */
479     if (pldev->cRefs > 0)
480     {
481         EngReleaseSemaphore(ghsemLDEVList);
482         return;
483     }
484 
485     LDEVOBJ_vDisableDriver(pldev);
486 
487     if (LDEVOBJ_bUnloadImage(pldev))
488     {
489         /* Remove ldev from the list */
490         RemoveEntryList(&pldev->leLink);
491 
492         /* Free the driver info structure */
493         LDEVOBJ_vFreeLDEV(pldev);
494     }
495     else
496     {
497         WARN("Failed to unload driver '%wZ', trying to re-enable it.\n", &pldev->pGdiDriverInfo->DriverName);
498         LDEVOBJ_bEnableDriver(pldev, pldev->pGdiDriverInfo->EntryPoint);
499 
500         /* Increment again reference count */
501         pldev->cRefs++;
502     }
503 
504     /* Unlock loader */
505     EngReleaseSemaphore(ghsemLDEVList);
506 }
507 
508 ULONG
509 LDEVOBJ_ulGetDriverModes(
510     _In_ LPWSTR pwszDriverName,
511     _In_ HANDLE hDriver,
512     _Out_ PDEVMODEW *ppdm)
513 {
514     PLDEVOBJ pldev = NULL;
515     ULONG cbSize = 0;
516     PDEVMODEW pdm = NULL;
517 
518     TRACE("LDEVOBJ_ulGetDriverModes('%ls', %p)\n", pwszDriverName, hDriver);
519 
520     pldev = LDEVOBJ_pLoadDriver(pwszDriverName, LDEV_DEVICE_DISPLAY);
521     if (!pldev)
522         goto cleanup;
523 
524     /* Mirror drivers may omit this function */
525     if (!pldev->pfn.GetModes)
526         goto cleanup;
527 
528     /* Call the driver to get the required size */
529     cbSize = pldev->pfn.GetModes(hDriver, 0, NULL);
530     if (!cbSize)
531     {
532         ERR("DrvGetModes returned 0\n");
533         goto cleanup;
534     }
535 
536     /* Allocate a buffer for the DEVMODE array */
537     pdm = ExAllocatePoolWithTag(PagedPool, cbSize, GDITAG_DEVMODE);
538     if (!pdm)
539     {
540         ERR("Could not allocate devmodeinfo\n");
541         goto cleanup;
542     }
543 
544     /* Call the driver again to fill the buffer */
545     cbSize = pldev->pfn.GetModes(hDriver, cbSize, pdm);
546     if (!cbSize)
547     {
548         /* Could not get modes */
549         ERR("DrvrGetModes returned 0 on second call\n");
550         ExFreePoolWithTag(pdm, GDITAG_DEVMODE);
551         pdm = NULL;
552     }
553 
554 cleanup:
555     if (pldev)
556         LDEVOBJ_vDereference(pldev);
557 
558     *ppdm = pdm;
559     return cbSize;
560 }
561 
562 BOOL
563 LDEVOBJ_bBuildDevmodeList(
564     _Inout_ PGRAPHICS_DEVICE pGraphicsDevice)
565 {
566     PWSTR pwsz;
567     PDEVMODEINFO pdminfo;
568     PDEVMODEW pdm, pdmEnd;
569     ULONG i, cModes = 0;
570     ULONG cbSize, cbFull;
571 
572     if (pGraphicsDevice->pdevmodeInfo)
573         return TRUE;
574     ASSERT(pGraphicsDevice->pDevModeList == NULL);
575 
576     pwsz = pGraphicsDevice->pDiplayDrivers;
577 
578     /* Loop through the driver names
579      * This is a REG_MULTI_SZ string */
580     for (; *pwsz; pwsz += wcslen(pwsz) + 1)
581     {
582         /* Get the mode list from the driver */
583         TRACE("Trying driver: %ls\n", pwsz);
584         cbSize = LDEVOBJ_ulGetDriverModes(pwsz, pGraphicsDevice->DeviceObject, &pdm);
585         if (!cbSize)
586         {
587             WARN("Driver %ls returned no valid mode\n", pwsz);
588             continue;
589         }
590 
591         /* Add space for the header */
592         cbFull = cbSize + FIELD_OFFSET(DEVMODEINFO, adevmode);
593 
594         /* Allocate a buffer for the DEVMODE array */
595         pdminfo = ExAllocatePoolWithTag(PagedPool, cbFull, GDITAG_DEVMODE);
596         if (!pdminfo)
597         {
598             ERR("Could not allocate devmodeinfo\n");
599             ExFreePoolWithTag(pdm, GDITAG_DEVMODE);
600             continue;
601         }
602 
603         pdminfo->cbdevmode = cbSize;
604         RtlCopyMemory(pdminfo->adevmode, pdm, cbSize);
605         ExFreePoolWithTag(pdm, GDITAG_DEVMODE);
606 
607         /* Attach the mode info to the device */
608         pdminfo->pdmiNext = pGraphicsDevice->pdevmodeInfo;
609         pGraphicsDevice->pdevmodeInfo = pdminfo;
610 
611         /* Loop all DEVMODEs */
612         pdmEnd = (DEVMODEW*)((PCHAR)pdminfo->adevmode + pdminfo->cbdevmode);
613         for (pdm = pdminfo->adevmode;
614              (pdm + 1 <= pdmEnd) && (pdm->dmSize != 0);
615              pdm = (DEVMODEW*)((PCHAR)pdm + pdm->dmSize + pdm->dmDriverExtra))
616         {
617             /* Count this DEVMODE */
618             cModes++;
619 
620             /* Some drivers like the VBox driver don't fill the dmDeviceName
621                with the name of the display driver. So fix that here. */
622             RtlStringCbCopyW(pdm->dmDeviceName, sizeof(pdm->dmDeviceName), pwsz);
623         }
624     }
625 
626     if (!pGraphicsDevice->pdevmodeInfo || cModes == 0)
627     {
628         ERR("No devmodes\n");
629         return FALSE;
630     }
631 
632     /* Allocate an index buffer */
633     pGraphicsDevice->cDevModes = cModes;
634     pGraphicsDevice->pDevModeList = ExAllocatePoolWithTag(PagedPool,
635                                                           cModes * sizeof(DEVMODEENTRY),
636                                                           GDITAG_GDEVICE);
637     if (!pGraphicsDevice->pDevModeList)
638     {
639         ERR("No devmode list\n");
640         return FALSE;
641     }
642 
643     /* Loop through all DEVMODEINFOs */
644     for (pdminfo = pGraphicsDevice->pdevmodeInfo, i = 0;
645          pdminfo;
646          pdminfo = pdminfo->pdmiNext)
647     {
648         /* Calculate End of the DEVMODEs */
649         pdmEnd = (DEVMODEW*)((PCHAR)pdminfo->adevmode + pdminfo->cbdevmode);
650 
651         /* Loop through the DEVMODEs */
652         for (pdm = pdminfo->adevmode;
653              (pdm + 1 <= pdmEnd) && (pdm->dmSize != 0);
654              pdm = (PDEVMODEW)((PCHAR)pdm + pdm->dmSize + pdm->dmDriverExtra))
655         {
656             TRACE("    %S has mode %lux%lux%lu(%lu Hz)\n",
657                   pdm->dmDeviceName,
658                   pdm->dmPelsWidth,
659                   pdm->dmPelsHeight,
660                   pdm->dmBitsPerPel,
661                   pdm->dmDisplayFrequency);
662 
663             /* Initialize the entry */
664             pGraphicsDevice->pDevModeList[i].dwFlags = 0;
665             pGraphicsDevice->pDevModeList[i].pdm = pdm;
666             i++;
667         }
668     }
669     return TRUE;
670 }
671 
672 /* Search the closest display mode according to some settings.
673  * Note that we don't care about the DM_* flags in dmFields, but check if value != 0 instead */
674 static
675 BOOL
676 LDEVOBJ_bGetClosestMode(
677     _Inout_ PGRAPHICS_DEVICE pGraphicsDevice,
678     _In_ PDEVMODEW RequestedMode,
679     _Out_ PDEVMODEW *pSelectedMode)
680 {
681     DEVMODEW dmDiff;
682     PDEVMODEW pdmCurrent, pdmBest = NULL;
683     ULONG i;
684 
685     /* Use a DEVMODE to keep the differences between best mode found and expected mode.
686      * Initialize fields to max value so we can find better modes. */
687     dmDiff.dmPelsWidth = 0xffffffff;
688     dmDiff.dmPelsHeight = 0xffffffff;
689     dmDiff.dmBitsPerPel = 0xffffffff;
690     dmDiff.dmDisplayFrequency = 0xffffffff;
691 
692     /* Search the closest mode */
693 #define DM_DIFF(field) (RequestedMode->field > pdmCurrent->field ? (RequestedMode->field - pdmCurrent->field) : (pdmCurrent->field - RequestedMode->field))
694     for (i = 0; i < pGraphicsDevice->cDevModes; i++)
695     {
696         pdmCurrent = pGraphicsDevice->pDevModeList[i].pdm;
697 
698         /* Skip current mode if it is worse than best mode found */
699         if (RequestedMode->dmPelsWidth != 0 && DM_DIFF(dmPelsWidth) > dmDiff.dmPelsWidth)
700             continue;
701         if (RequestedMode->dmPelsHeight != 0 && DM_DIFF(dmPelsHeight) > dmDiff.dmPelsHeight)
702             continue;
703         if (RequestedMode->dmBitsPerPel != 0 && DM_DIFF(dmBitsPerPel) > dmDiff.dmBitsPerPel)
704             continue;
705         if (RequestedMode->dmDisplayFrequency != 0 && DM_DIFF(dmDisplayFrequency) > dmDiff.dmDisplayFrequency)
706             continue;
707 
708         /* Better (or equivalent) mode found. Update differences */
709         dmDiff.dmPelsWidth = DM_DIFF(dmPelsWidth);
710         dmDiff.dmPelsHeight = DM_DIFF(dmPelsHeight);
711         dmDiff.dmBitsPerPel = DM_DIFF(dmBitsPerPel);
712         dmDiff.dmDisplayFrequency = DM_DIFF(dmDisplayFrequency);
713         pdmBest = pdmCurrent;
714     }
715 #undef DM_DIFF
716 
717     if (pdmBest)
718     {
719         TRACE("Closest display mode to '%dx%dx%d %d Hz' is '%dx%dx%d %d Hz'\n",
720               RequestedMode->dmPelsWidth,
721               RequestedMode->dmPelsHeight,
722               RequestedMode->dmBitsPerPel,
723               RequestedMode->dmDisplayFrequency,
724               pdmBest->dmPelsWidth,
725               pdmBest->dmPelsHeight,
726               pdmBest->dmBitsPerPel,
727               pdmBest->dmDisplayFrequency);
728     }
729 
730     *pSelectedMode = pdmBest;
731     return pdmBest != NULL;
732 }
733 
734 BOOL
735 LDEVOBJ_bProbeAndCaptureDevmode(
736     _Inout_ PGRAPHICS_DEVICE pGraphicsDevice,
737     _In_ PDEVMODEW RequestedMode,
738     _Out_ PDEVMODEW *pSelectedMode,
739     _In_ BOOL bSearchClosestMode)
740 {
741     DEVMODEW dmSearch;
742     PDEVMODEW pdmCurrent, pdm, pdmSelected = NULL;
743     ULONG i;
744     ULONG ulVirtualWidth = 0, ulVirtualHeight = 0;
745     BOOL bResult = TRUE;
746     NTSTATUS Status;
747 
748     if (!LDEVOBJ_bBuildDevmodeList(pGraphicsDevice))
749         return FALSE;
750 
751     /* At first, load information from registry */
752     RtlZeroMemory(&dmSearch, sizeof(dmSearch));
753     Status = EngpGetDisplayDriverParameters(pGraphicsDevice, &dmSearch);
754     if (!NT_SUCCESS(Status))
755     {
756         ERR("EngpGetDisplayDriverParameters() failed with status 0x%08x\n", Status);
757         return FALSE;
758     }
759 
760     /* Override values with the new ones provided */
761 
762     _SEH2_TRY
763     {
764         bSearchClosestMode |= RequestedMode->dmFields == 0;
765 
766         /* Copy standard fields (if provided) */
767         if (RequestedMode->dmFields & DM_BITSPERPEL && RequestedMode->dmBitsPerPel != 0)
768             dmSearch.dmBitsPerPel = RequestedMode->dmBitsPerPel;
769         if (RequestedMode->dmFields & DM_PELSWIDTH && RequestedMode->dmPelsWidth != 0)
770             dmSearch.dmPelsWidth = RequestedMode->dmPelsWidth;
771         if (RequestedMode->dmFields & DM_PELSHEIGHT && RequestedMode->dmPelsHeight != 0)
772             dmSearch.dmPelsHeight = RequestedMode->dmPelsHeight;
773         if (RequestedMode->dmFields & DM_DISPLAYFREQUENCY && RequestedMode->dmDisplayFrequency != 0)
774             dmSearch.dmDisplayFrequency = RequestedMode->dmDisplayFrequency;
775 
776         if ((RequestedMode->dmFields & (DM_PANNINGWIDTH | DM_PANNINGHEIGHT)) == (DM_PANNINGWIDTH | DM_PANNINGHEIGHT) &&
777             RequestedMode->dmPanningWidth != 0 && RequestedMode->dmPanningHeight != 0 &&
778             RequestedMode->dmPanningWidth < dmSearch.dmPelsWidth &&
779             RequestedMode->dmPanningHeight < dmSearch.dmPelsHeight)
780         {
781             /* Get new panning values */
782             ulVirtualWidth = RequestedMode->dmPelsWidth;
783             ulVirtualHeight = RequestedMode->dmPelsHeight;
784             dmSearch.dmPelsWidth = RequestedMode->dmPanningWidth;
785             dmSearch.dmPelsHeight = RequestedMode->dmPanningHeight;
786         }
787         else if (dmSearch.dmPanningWidth != 0 && dmSearch.dmPanningHeight != 0 &&
788                  dmSearch.dmPanningWidth < dmSearch.dmPelsWidth &&
789                  dmSearch.dmPanningHeight < dmSearch.dmPelsHeight)
790         {
791             /* Keep existing panning values */
792             ulVirtualWidth = dmSearch.dmPelsWidth;
793             ulVirtualHeight = dmSearch.dmPelsHeight;
794             dmSearch.dmPelsWidth = dmSearch.dmPanningWidth;
795             dmSearch.dmPelsHeight = dmSearch.dmPanningHeight;
796         }
797     }
798     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
799     {
800         bResult = FALSE;
801     }
802     _SEH2_END;
803 
804     if (!bResult)
805         return FALSE;
806 
807     if (LDEVOBJ_bGetClosestMode(pGraphicsDevice, &dmSearch, &pdmSelected))
808     {
809         if (bSearchClosestMode)
810         {
811             /* Ok, found a closest mode. Update search */
812             dmSearch.dmBitsPerPel = pdmSelected->dmBitsPerPel;
813             dmSearch.dmPelsWidth = pdmSelected->dmPelsWidth;
814             dmSearch.dmPelsHeight = pdmSelected->dmPelsHeight;
815             dmSearch.dmDisplayFrequency = pdmSelected->dmDisplayFrequency;
816         }
817         else
818         {
819             /* Only update not provided fields */
820             _SEH2_TRY
821             {
822                 if (!(RequestedMode->dmFields & DM_BITSPERPEL) || RequestedMode->dmBitsPerPel == 0)
823                     dmSearch.dmBitsPerPel = pdmSelected->dmBitsPerPel;
824                 if (!(RequestedMode->dmFields & DM_PELSWIDTH) || RequestedMode->dmPelsWidth == 0)
825                     dmSearch.dmPelsWidth = pdmSelected->dmPelsWidth;
826                 if (!(RequestedMode->dmFields & DM_PELSHEIGHT) || RequestedMode->dmPelsHeight == 0)
827                     dmSearch.dmPelsHeight = pdmSelected->dmPelsHeight;
828                 if (!(RequestedMode->dmFields & DM_DISPLAYFREQUENCY) || RequestedMode->dmDisplayFrequency == 0)
829                     dmSearch.dmDisplayFrequency = pdmSelected->dmDisplayFrequency;
830             }
831             _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
832             {
833                 bResult = FALSE;
834             }
835             _SEH2_END;
836 
837             if (!bResult)
838                 return FALSE;
839         }
840     }
841 
842     /* Now, search the exact mode to return to caller */
843     for (i = 0; i < pGraphicsDevice->cDevModes; i++)
844     {
845         pdmCurrent = pGraphicsDevice->pDevModeList[i].pdm;
846 
847         /* For now, we only need those */
848         if (pdmCurrent->dmBitsPerPel != dmSearch.dmBitsPerPel)
849             continue;
850         if (pdmCurrent->dmPelsWidth != dmSearch.dmPelsWidth)
851             continue;
852         if (pdmCurrent->dmPelsHeight != dmSearch.dmPelsHeight)
853             continue;
854         if (pdmCurrent->dmDisplayFrequency != dmSearch.dmDisplayFrequency)
855             continue;
856 
857         pdmSelected = pdmCurrent;
858         break;
859     }
860 
861     if (!pdmSelected)
862     {
863         ERR("Requested mode not found (%dx%dx%d %d Hz)\n",
864             dmSearch.dmPelsWidth,
865             dmSearch.dmPelsHeight,
866             dmSearch.dmBitsPerPel,
867             dmSearch.dmDisplayFrequency);
868         return FALSE;
869     }
870 
871     /* Allocate memory for output */
872     pdm = ExAllocatePoolZero(PagedPool, pdmSelected->dmSize + pdmSelected->dmDriverExtra, GDITAG_DEVMODE);
873     if (!pdm)
874         return FALSE;
875 
876     /* Copy selected mode */
877     RtlCopyMemory(pdm, pdmSelected, pdmSelected->dmSize);
878     RtlCopyMemory((PVOID)((ULONG_PTR)pdm + pdm->dmSize),
879                   (PVOID)((ULONG_PTR)pdmSelected + pdmSelected->dmSize),
880                   pdmSelected->dmDriverExtra);
881 
882     /* Add back panning */
883     if (ulVirtualWidth != 0 && ulVirtualHeight != 0 &&
884         pdm->dmPelsWidth < ulVirtualWidth &&
885         pdm->dmPelsHeight < ulVirtualHeight)
886     {
887         pdm->dmFields |= DM_PANNINGWIDTH | DM_PANNINGHEIGHT;
888         pdm->dmPanningWidth = pdm->dmPelsWidth;
889         pdm->dmPanningHeight = pdm->dmPelsHeight;
890         pdm->dmPelsWidth = ulVirtualWidth;
891         pdm->dmPelsHeight = ulVirtualHeight;
892     }
893 
894     *pSelectedMode = pdm;
895     return TRUE;
896 }
897 
898 /** Exported functions ********************************************************/
899 
900 HANDLE
901 APIENTRY
902 EngLoadImage(
903     _In_ LPWSTR pwszDriverName)
904 {
905     return (HANDLE)LDEVOBJ_pLoadDriver(pwszDriverName, LDEV_IMAGE);
906 }
907 
908 
909 VOID
910 APIENTRY
911 EngUnloadImage(
912     _In_ HANDLE hModule)
913 {
914     PLDEVOBJ pldev = (PLDEVOBJ)hModule;
915 
916     /* Make sure the LDEV is in the list */
917     ASSERT((pldev->leLink.Flink != NULL) &&  (pldev->leLink.Blink != NULL));
918 
919     LDEVOBJ_vDereference(pldev);
920 }
921 
922 
923 PVOID
924 APIENTRY
925 EngFindImageProcAddress(
926     _In_ HANDLE hModule,
927     _In_ LPSTR  lpProcName)
928 {
929     PLDEVOBJ pldev = (PLDEVOBJ)hModule;
930 
931     ASSERT(gpldevWin32k != NULL);
932 
933     /* Check if win32k is requested */
934     if (!pldev)
935     {
936         pldev = gpldevWin32k;
937     }
938 
939     /* Check if the drivers entry point is requested */
940     if (_strnicmp(lpProcName, "DrvEnableDriver", 15) == 0)
941     {
942         return pldev->pGdiDriverInfo->EntryPoint;
943     }
944 
945     /* Try to find the address */
946     return LDEVOBJ_pvFindImageProcAddress(pldev, lpProcName);
947 }
948 
949 /* EOF */
950