xref: /reactos/win32ss/gdi/eng/ldevobj.c (revision ffb20d33)
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
InitLDEVImpl(VOID)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
LDEVOBJ_AllocLDEV(_In_ LDEVTYPE ldevtype)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
LDEVOBJ_vFreeLDEV(_In_ _Post_ptr_invalid_ PLDEVOBJ pldev)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
LDEVOBJ_bLoadImage(_Inout_ PLDEVOBJ pldev,_In_ PUNICODE_STRING pustrPathName)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
LDEVOBJ_bEnableDriver(_Inout_ PLDEVOBJ pldev,_In_ PFN_DrvEnableDriver pfnEnableDriver)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
LDEVOBJ_vDisableDriver(_Inout_ PLDEVOBJ pldev)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
LDEVOBJ_pvFindImageProcAddress(_In_ PLDEVOBJ pldev,_In_z_ LPSTR pszProcName)223 LDEVOBJ_pvFindImageProcAddress(
224     _In_ PLDEVOBJ pldev,
225     _In_z_ LPSTR pszProcName)
226 {
227     PVOID pvImageBase;
228     PIMAGE_EXPORT_DIRECTORY pExportDir;
229     PVOID pvProcAddress = 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             pvProcAddress = RVA_TO_ADDR(pvImageBase, pAddresses[pOrdinals[i]]);
259             break;
260         }
261     }
262 
263     /* Return the address */
264     return pvProcAddress;
265 }
266 
267 static
268 BOOL
LDEVOBJ_bUnloadImage(_Inout_ PLDEVOBJ pldev)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
LDEVOBJ_pLoadInternal(_In_ PFN_DrvEnableDriver pfnEnableDriver,_In_ ULONG ldevtype)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
LDEVOBJ_pLoadDriver(_In_z_ LPWSTR pwszDriverName,_In_ ULONG ldevtype)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 insensitive) */
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             if (LDEVOBJ_bUnloadImage(pldev))
446                 LDEVOBJ_vFreeLDEV(pldev);
447             else
448                 ERR("Could not unload driver. Leaking memory\n");
449             pldev = NULL;
450             goto leave;
451         }
452 
453         /* Insert the LDEV into the global list */
454         InsertHeadList(&gleLdevListHead, &pldev->leLink);
455     }
456 
457     /* Increase ref count */
458     pldev->cRefs++;
459 
460 leave:
461     /* Unlock loader */
462     EngReleaseSemaphore(ghsemLDEVList);
463 
464     TRACE("LDEVOBJ_pLoadDriver returning %p\n", pldev);
465     return pldev;
466 }
467 
468 static
469 VOID
LDEVOBJ_vDereference(_Inout_ PLDEVOBJ pldev)470 LDEVOBJ_vDereference(
471     _Inout_ PLDEVOBJ pldev)
472 {
473     /* Lock loader */
474     EngAcquireSemaphore(ghsemLDEVList);
475 
476     /* Decrement reference count */
477     ASSERT(pldev->cRefs > 0);
478     pldev->cRefs--;
479 
480     /* More references left? */
481     if (pldev->cRefs > 0)
482     {
483         EngReleaseSemaphore(ghsemLDEVList);
484         return;
485     }
486 
487     LDEVOBJ_vDisableDriver(pldev);
488 
489     if (LDEVOBJ_bUnloadImage(pldev))
490     {
491         /* Remove ldev from the list */
492         RemoveEntryList(&pldev->leLink);
493 
494         /* Free the driver info structure */
495         LDEVOBJ_vFreeLDEV(pldev);
496     }
497     else
498     {
499         WARN("Failed to unload driver '%wZ', trying to re-enable it.\n", &pldev->pGdiDriverInfo->DriverName);
500         LDEVOBJ_bEnableDriver(pldev, pldev->pGdiDriverInfo->EntryPoint);
501 
502         /* Increment again reference count */
503         pldev->cRefs++;
504     }
505 
506     /* Unlock loader */
507     EngReleaseSemaphore(ghsemLDEVList);
508 }
509 
510 ULONG
LDEVOBJ_ulGetDriverModes(_In_ LPWSTR pwszDriverName,_In_ HANDLE hDriver,_Out_ PDEVMODEW * ppdm)511 LDEVOBJ_ulGetDriverModes(
512     _In_ LPWSTR pwszDriverName,
513     _In_ HANDLE hDriver,
514     _Out_ PDEVMODEW *ppdm)
515 {
516     PLDEVOBJ pldev = NULL;
517     ULONG cbSize = 0;
518     PDEVMODEW pdm = NULL;
519 
520     TRACE("LDEVOBJ_ulGetDriverModes('%ls', %p)\n", pwszDriverName, hDriver);
521 
522     pldev = LDEVOBJ_pLoadDriver(pwszDriverName, LDEV_DEVICE_DISPLAY);
523     if (!pldev)
524         goto cleanup;
525 
526     /* Mirror drivers may omit this function */
527     if (!pldev->pfn.GetModes)
528         goto cleanup;
529 
530     /* Call the driver to get the required size */
531     cbSize = pldev->pfn.GetModes(hDriver, 0, NULL);
532     if (!cbSize)
533     {
534         ERR("DrvGetModes returned 0\n");
535         goto cleanup;
536     }
537 
538     /* Allocate a buffer for the DEVMODE array */
539     pdm = ExAllocatePoolWithTag(PagedPool, cbSize, GDITAG_DEVMODE);
540     if (!pdm)
541     {
542         ERR("Could not allocate devmodeinfo\n");
543         goto cleanup;
544     }
545 
546     /* Call the driver again to fill the buffer */
547     cbSize = pldev->pfn.GetModes(hDriver, cbSize, pdm);
548     if (!cbSize)
549     {
550         /* Could not get modes */
551         ERR("DrvrGetModes returned 0 on second call\n");
552         ExFreePoolWithTag(pdm, GDITAG_DEVMODE);
553         pdm = NULL;
554     }
555 
556 cleanup:
557     if (pldev)
558         LDEVOBJ_vDereference(pldev);
559 
560     *ppdm = pdm;
561     return cbSize;
562 }
563 
564 BOOL
LDEVOBJ_bBuildDevmodeList(_Inout_ PGRAPHICS_DEVICE pGraphicsDevice)565 LDEVOBJ_bBuildDevmodeList(
566     _Inout_ PGRAPHICS_DEVICE pGraphicsDevice)
567 {
568     PWSTR pwsz;
569     PDEVMODEINFO pdminfo;
570     PDEVMODEW pdm, pdmEnd;
571     ULONG i, cModes = 0;
572     ULONG cbSize, cbFull;
573 
574     if (pGraphicsDevice->pdevmodeInfo)
575         return TRUE;
576     ASSERT(pGraphicsDevice->pDevModeList == NULL);
577 
578     pwsz = pGraphicsDevice->pDiplayDrivers;
579 
580     /* Loop through the driver names
581      * This is a REG_MULTI_SZ string */
582     for (; *pwsz; pwsz += wcslen(pwsz) + 1)
583     {
584         /* Get the mode list from the driver */
585         TRACE("Trying driver: %ls\n", pwsz);
586         cbSize = LDEVOBJ_ulGetDriverModes(pwsz, pGraphicsDevice->DeviceObject, &pdm);
587         if (!cbSize)
588         {
589             WARN("Driver %ls returned no valid mode\n", pwsz);
590             continue;
591         }
592 
593         /* Add space for the header */
594         cbFull = cbSize + FIELD_OFFSET(DEVMODEINFO, adevmode);
595 
596         /* Allocate a buffer for the DEVMODE array */
597         pdminfo = ExAllocatePoolWithTag(PagedPool, cbFull, GDITAG_DEVMODE);
598         if (!pdminfo)
599         {
600             ERR("Could not allocate devmodeinfo\n");
601             ExFreePoolWithTag(pdm, GDITAG_DEVMODE);
602             continue;
603         }
604 
605         pdminfo->cbdevmode = cbSize;
606         RtlCopyMemory(pdminfo->adevmode, pdm, cbSize);
607         ExFreePoolWithTag(pdm, GDITAG_DEVMODE);
608 
609         /* Attach the mode info to the device */
610         pdminfo->pdmiNext = pGraphicsDevice->pdevmodeInfo;
611         pGraphicsDevice->pdevmodeInfo = pdminfo;
612 
613         /* Loop all DEVMODEs */
614         pdmEnd = (DEVMODEW*)((PCHAR)pdminfo->adevmode + pdminfo->cbdevmode);
615         for (pdm = pdminfo->adevmode;
616              (pdm + 1 <= pdmEnd) && (pdm->dmSize != 0);
617              pdm = (DEVMODEW*)((PCHAR)pdm + pdm->dmSize + pdm->dmDriverExtra))
618         {
619             /* Count this DEVMODE */
620             cModes++;
621 
622             /* Some drivers like the VBox driver don't fill the dmDeviceName
623                with the name of the display driver. So fix that here. */
624             RtlStringCbCopyW(pdm->dmDeviceName, sizeof(pdm->dmDeviceName), pwsz);
625         }
626     }
627 
628     if (!pGraphicsDevice->pdevmodeInfo || cModes == 0)
629     {
630         ERR("No devmodes\n");
631         return FALSE;
632     }
633 
634     /* Allocate an index buffer */
635     pGraphicsDevice->cDevModes = cModes;
636     pGraphicsDevice->pDevModeList = ExAllocatePoolWithTag(PagedPool,
637                                                           cModes * sizeof(DEVMODEENTRY),
638                                                           GDITAG_GDEVICE);
639     if (!pGraphicsDevice->pDevModeList)
640     {
641         ERR("No devmode list\n");
642         return FALSE;
643     }
644 
645     /* Loop through all DEVMODEINFOs */
646     for (pdminfo = pGraphicsDevice->pdevmodeInfo, i = 0;
647          pdminfo;
648          pdminfo = pdminfo->pdmiNext)
649     {
650         /* Calculate End of the DEVMODEs */
651         pdmEnd = (DEVMODEW*)((PCHAR)pdminfo->adevmode + pdminfo->cbdevmode);
652 
653         /* Loop through the DEVMODEs */
654         for (pdm = pdminfo->adevmode;
655              (pdm + 1 <= pdmEnd) && (pdm->dmSize != 0);
656              pdm = (PDEVMODEW)((PCHAR)pdm + pdm->dmSize + pdm->dmDriverExtra))
657         {
658             TRACE("    %S has mode %lux%lux%lu(%lu Hz)\n",
659                   pdm->dmDeviceName,
660                   pdm->dmPelsWidth,
661                   pdm->dmPelsHeight,
662                   pdm->dmBitsPerPel,
663                   pdm->dmDisplayFrequency);
664 
665             /* Initialize the entry */
666             pGraphicsDevice->pDevModeList[i].dwFlags = 0;
667             pGraphicsDevice->pDevModeList[i].pdm = pdm;
668             i++;
669         }
670     }
671     return TRUE;
672 }
673 
674 /* Search the closest display mode according to some settings.
675  * Note that we don't care about the DM_* flags in dmFields, but check if value != 0 instead */
676 static
677 BOOL
LDEVOBJ_bGetClosestMode(_Inout_ PGRAPHICS_DEVICE pGraphicsDevice,_In_ PDEVMODEW RequestedMode,_Out_ PDEVMODEW * pSelectedMode)678 LDEVOBJ_bGetClosestMode(
679     _Inout_ PGRAPHICS_DEVICE pGraphicsDevice,
680     _In_ PDEVMODEW RequestedMode,
681     _Out_ PDEVMODEW *pSelectedMode)
682 {
683     DEVMODEW dmDiff;
684     PDEVMODEW pdmCurrent, pdmBest = NULL;
685     ULONG i;
686 
687     /* Use a DEVMODE to keep the differences between best mode found and expected mode.
688      * Initialize fields to max value so we can find better modes. */
689     dmDiff.dmPelsWidth = 0xffffffff;
690     dmDiff.dmPelsHeight = 0xffffffff;
691     dmDiff.dmBitsPerPel = 0xffffffff;
692     dmDiff.dmDisplayFrequency = 0xffffffff;
693 
694     /* Search the closest mode */
695 #define DM_DIFF(field) (RequestedMode->field > pdmCurrent->field ? (RequestedMode->field - pdmCurrent->field) : (pdmCurrent->field - RequestedMode->field))
696     for (i = 0; i < pGraphicsDevice->cDevModes; i++)
697     {
698         pdmCurrent = pGraphicsDevice->pDevModeList[i].pdm;
699 
700         /* Skip current mode if it is worse than best mode found */
701         if (RequestedMode->dmPelsWidth != 0 && DM_DIFF(dmPelsWidth) > dmDiff.dmPelsWidth)
702             continue;
703         if (RequestedMode->dmPelsHeight != 0 && DM_DIFF(dmPelsHeight) > dmDiff.dmPelsHeight)
704             continue;
705         if (RequestedMode->dmBitsPerPel != 0 && DM_DIFF(dmBitsPerPel) > dmDiff.dmBitsPerPel)
706             continue;
707         if (RequestedMode->dmDisplayFrequency != 0 && DM_DIFF(dmDisplayFrequency) > dmDiff.dmDisplayFrequency)
708             continue;
709 
710         /* Better (or equivalent) mode found. Update differences */
711         dmDiff.dmPelsWidth = DM_DIFF(dmPelsWidth);
712         dmDiff.dmPelsHeight = DM_DIFF(dmPelsHeight);
713         dmDiff.dmBitsPerPel = DM_DIFF(dmBitsPerPel);
714         dmDiff.dmDisplayFrequency = DM_DIFF(dmDisplayFrequency);
715         pdmBest = pdmCurrent;
716     }
717 #undef DM_DIFF
718 
719     if (pdmBest)
720     {
721         TRACE("Closest display mode to '%dx%dx%d %d Hz' is '%dx%dx%d %d Hz'\n",
722               RequestedMode->dmPelsWidth,
723               RequestedMode->dmPelsHeight,
724               RequestedMode->dmBitsPerPel,
725               RequestedMode->dmDisplayFrequency,
726               pdmBest->dmPelsWidth,
727               pdmBest->dmPelsHeight,
728               pdmBest->dmBitsPerPel,
729               pdmBest->dmDisplayFrequency);
730     }
731 
732     *pSelectedMode = pdmBest;
733     return pdmBest != NULL;
734 }
735 
736 BOOL
LDEVOBJ_bProbeAndCaptureDevmode(_Inout_ PGRAPHICS_DEVICE pGraphicsDevice,_In_ PDEVMODEW RequestedMode,_Out_ PDEVMODEW * pSelectedMode,_In_ BOOL bSearchClosestMode)737 LDEVOBJ_bProbeAndCaptureDevmode(
738     _Inout_ PGRAPHICS_DEVICE pGraphicsDevice,
739     _In_ PDEVMODEW RequestedMode,
740     _Out_ PDEVMODEW *pSelectedMode,
741     _In_ BOOL bSearchClosestMode)
742 {
743     DEVMODEW dmSearch;
744     PDEVMODEW pdmCurrent, pdm, pdmSelected = NULL;
745     ULONG i;
746     ULONG ulVirtualWidth = 0, ulVirtualHeight = 0;
747     BOOL bResult = TRUE;
748     NTSTATUS Status;
749 
750     if (!LDEVOBJ_bBuildDevmodeList(pGraphicsDevice))
751         return FALSE;
752 
753     /* At first, load information from registry */
754     RtlZeroMemory(&dmSearch, sizeof(dmSearch));
755     Status = EngpGetDisplayDriverParameters(pGraphicsDevice, &dmSearch);
756     if (!NT_SUCCESS(Status))
757     {
758         ERR("EngpGetDisplayDriverParameters() failed with status 0x%08x\n", Status);
759         return FALSE;
760     }
761 
762     /* Override values with the new ones provided */
763 
764     _SEH2_TRY
765     {
766         bSearchClosestMode |= RequestedMode->dmFields == 0;
767 
768         /* Copy standard fields (if provided) */
769         if (RequestedMode->dmFields & DM_BITSPERPEL && RequestedMode->dmBitsPerPel != 0)
770             dmSearch.dmBitsPerPel = RequestedMode->dmBitsPerPel;
771         if (RequestedMode->dmFields & DM_PELSWIDTH && RequestedMode->dmPelsWidth != 0)
772             dmSearch.dmPelsWidth = RequestedMode->dmPelsWidth;
773         if (RequestedMode->dmFields & DM_PELSHEIGHT && RequestedMode->dmPelsHeight != 0)
774             dmSearch.dmPelsHeight = RequestedMode->dmPelsHeight;
775         if (RequestedMode->dmFields & DM_DISPLAYFREQUENCY && RequestedMode->dmDisplayFrequency != 0)
776             dmSearch.dmDisplayFrequency = RequestedMode->dmDisplayFrequency;
777 
778         if ((RequestedMode->dmFields & (DM_PANNINGWIDTH | DM_PANNINGHEIGHT)) == (DM_PANNINGWIDTH | DM_PANNINGHEIGHT) &&
779             RequestedMode->dmPanningWidth != 0 && RequestedMode->dmPanningHeight != 0 &&
780             RequestedMode->dmPanningWidth < dmSearch.dmPelsWidth &&
781             RequestedMode->dmPanningHeight < dmSearch.dmPelsHeight)
782         {
783             /* Get new panning values */
784             ulVirtualWidth = RequestedMode->dmPelsWidth;
785             ulVirtualHeight = RequestedMode->dmPelsHeight;
786             dmSearch.dmPelsWidth = RequestedMode->dmPanningWidth;
787             dmSearch.dmPelsHeight = RequestedMode->dmPanningHeight;
788         }
789         else if (dmSearch.dmPanningWidth != 0 && dmSearch.dmPanningHeight != 0 &&
790                  dmSearch.dmPanningWidth < dmSearch.dmPelsWidth &&
791                  dmSearch.dmPanningHeight < dmSearch.dmPelsHeight)
792         {
793             /* Keep existing panning values */
794             ulVirtualWidth = dmSearch.dmPelsWidth;
795             ulVirtualHeight = dmSearch.dmPelsHeight;
796             dmSearch.dmPelsWidth = dmSearch.dmPanningWidth;
797             dmSearch.dmPelsHeight = dmSearch.dmPanningHeight;
798         }
799     }
800     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
801     {
802         bResult = FALSE;
803     }
804     _SEH2_END;
805 
806     if (!bResult)
807         return FALSE;
808 
809     if (LDEVOBJ_bGetClosestMode(pGraphicsDevice, &dmSearch, &pdmSelected))
810     {
811         if (bSearchClosestMode)
812         {
813             /* Ok, found a closest mode. Update search */
814             dmSearch.dmBitsPerPel = pdmSelected->dmBitsPerPel;
815             dmSearch.dmPelsWidth = pdmSelected->dmPelsWidth;
816             dmSearch.dmPelsHeight = pdmSelected->dmPelsHeight;
817             dmSearch.dmDisplayFrequency = pdmSelected->dmDisplayFrequency;
818         }
819         else
820         {
821             /* Only update not provided fields */
822             _SEH2_TRY
823             {
824                 if (!(RequestedMode->dmFields & DM_BITSPERPEL) || RequestedMode->dmBitsPerPel == 0)
825                     dmSearch.dmBitsPerPel = pdmSelected->dmBitsPerPel;
826                 if (!(RequestedMode->dmFields & DM_PELSWIDTH) || RequestedMode->dmPelsWidth == 0)
827                     dmSearch.dmPelsWidth = pdmSelected->dmPelsWidth;
828                 if (!(RequestedMode->dmFields & DM_PELSHEIGHT) || RequestedMode->dmPelsHeight == 0)
829                     dmSearch.dmPelsHeight = pdmSelected->dmPelsHeight;
830                 if (!(RequestedMode->dmFields & DM_DISPLAYFREQUENCY) || RequestedMode->dmDisplayFrequency == 0)
831                     dmSearch.dmDisplayFrequency = pdmSelected->dmDisplayFrequency;
832             }
833             _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
834             {
835                 bResult = FALSE;
836             }
837             _SEH2_END;
838 
839             if (!bResult)
840                 return FALSE;
841         }
842     }
843 
844     /* Now, search the exact mode to return to caller */
845     for (i = 0; i < pGraphicsDevice->cDevModes; i++)
846     {
847         pdmCurrent = pGraphicsDevice->pDevModeList[i].pdm;
848 
849         /* For now, we only need those */
850         if (pdmCurrent->dmBitsPerPel != dmSearch.dmBitsPerPel)
851             continue;
852         if (pdmCurrent->dmPelsWidth != dmSearch.dmPelsWidth)
853             continue;
854         if (pdmCurrent->dmPelsHeight != dmSearch.dmPelsHeight)
855             continue;
856         if (pdmCurrent->dmDisplayFrequency != dmSearch.dmDisplayFrequency)
857             continue;
858 
859         pdmSelected = pdmCurrent;
860         break;
861     }
862 
863     if (!pdmSelected)
864     {
865         ERR("Requested mode not found (%dx%dx%d %d Hz)\n",
866             dmSearch.dmPelsWidth,
867             dmSearch.dmPelsHeight,
868             dmSearch.dmBitsPerPel,
869             dmSearch.dmDisplayFrequency);
870         return FALSE;
871     }
872 
873     /* Allocate memory for output */
874     pdm = ExAllocatePoolZero(PagedPool, pdmSelected->dmSize + pdmSelected->dmDriverExtra, GDITAG_DEVMODE);
875     if (!pdm)
876         return FALSE;
877 
878     /* Copy selected mode */
879     RtlCopyMemory(pdm, pdmSelected, pdmSelected->dmSize);
880     RtlCopyMemory((PVOID)((ULONG_PTR)pdm + pdm->dmSize),
881                   (PVOID)((ULONG_PTR)pdmSelected + pdmSelected->dmSize),
882                   pdmSelected->dmDriverExtra);
883 
884     /* Add back panning */
885     if (ulVirtualWidth != 0 && ulVirtualHeight != 0 &&
886         pdm->dmPelsWidth < ulVirtualWidth &&
887         pdm->dmPelsHeight < ulVirtualHeight)
888     {
889         pdm->dmFields |= DM_PANNINGWIDTH | DM_PANNINGHEIGHT;
890         pdm->dmPanningWidth = pdm->dmPelsWidth;
891         pdm->dmPanningHeight = pdm->dmPelsHeight;
892         pdm->dmPelsWidth = ulVirtualWidth;
893         pdm->dmPelsHeight = ulVirtualHeight;
894     }
895 
896     *pSelectedMode = pdm;
897     return TRUE;
898 }
899 
900 /** Exported functions ********************************************************/
901 
902 HANDLE
903 APIENTRY
EngLoadImage(_In_ LPWSTR pwszDriverName)904 EngLoadImage(
905     _In_ LPWSTR pwszDriverName)
906 {
907     return (HANDLE)LDEVOBJ_pLoadDriver(pwszDriverName, LDEV_IMAGE);
908 }
909 
910 
911 VOID
912 APIENTRY
EngUnloadImage(_In_ HANDLE hModule)913 EngUnloadImage(
914     _In_ HANDLE hModule)
915 {
916     PLDEVOBJ pldev = (PLDEVOBJ)hModule;
917 
918     /* Make sure the LDEV is in the list */
919     ASSERT((pldev->leLink.Flink != NULL) &&  (pldev->leLink.Blink != NULL));
920 
921     LDEVOBJ_vDereference(pldev);
922 }
923 
924 
925 PVOID
926 APIENTRY
EngFindImageProcAddress(_In_ HANDLE hModule,_In_ LPSTR lpProcName)927 EngFindImageProcAddress(
928     _In_ HANDLE hModule,
929     _In_ LPSTR  lpProcName)
930 {
931     PLDEVOBJ pldev = (PLDEVOBJ)hModule;
932 
933     ASSERT(gpldevWin32k != NULL);
934 
935     /* Check if win32k is requested */
936     if (!pldev)
937     {
938         pldev = gpldevWin32k;
939     }
940 
941     /* Check if the drivers entry point is requested */
942     if (_strnicmp(lpProcName, "DrvEnableDriver", 15) == 0)
943     {
944         return pldev->pGdiDriverInfo->EntryPoint;
945     }
946 
947     /* Try to find the address */
948     return LDEVOBJ_pvFindImageProcAddress(pldev, lpProcName);
949 }
950 
951 /* EOF */
952