1 /*
2  * PROJECT:     appshim_apitest
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Tests for display mode shims
5  * COPYRIGHT:   Copyright 2016-2018 Mark Jansen (mark.jansen@reactos.org)
6  */
7 
8 #include <ntstatus.h>
9 #define WIN32_NO_STATUS
10 #include <windows.h>
11 #ifdef __REACTOS__
12 #include <ntndk.h>
13 #else
14 #include <winternl.h>
15 #endif
16 #include <stdio.h>
17 #include <strsafe.h>
18 #include "wine/test.h"
19 #include "apitest_iathook.h"
20 #include "appshim_apitest.h"
21 
22 static DWORD g_Version;
23 #define WINVER_ANY     0
24 
25 /* aclayers.dll / acgenral.dll */
26 static tGETHOOKAPIS pGetHookAPIs;
27 static BOOL(WINAPI* pNotifyShims)(DWORD fdwReason, PVOID ptr);
28 
29 
30 DWORD get_module_version(HMODULE mod)
31 {
32     DWORD dwVersion = 0;
33     HRSRC hResInfo = FindResource(mod, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
34     DWORD dwSize = SizeofResource(mod, hResInfo);
35     if (hResInfo && dwSize)
36     {
37         VS_FIXEDFILEINFO *lpFfi;
38         UINT uLen;
39 
40         HGLOBAL hResData = LoadResource(mod, hResInfo);
41         LPVOID pRes = LockResource(hResData);
42         HLOCAL pResCopy = LocalAlloc(LMEM_FIXED, dwSize);
43 
44         CopyMemory(pResCopy, pRes, dwSize);
45         FreeResource(hResData);
46 
47         if (VerQueryValueW(pResCopy, L"\\", (LPVOID*)&lpFfi, &uLen))
48         {
49             dwVersion = (HIWORD(lpFfi->dwProductVersionMS) << 8) | LOWORD(lpFfi->dwProductVersionMS);
50             if (!dwVersion)
51                 dwVersion = (HIWORD(lpFfi->dwFileVersionMS) << 8) | LOWORD(lpFfi->dwFileVersionMS);
52         }
53 
54         LocalFree(pResCopy);
55     }
56 
57     return dwVersion;
58 }
59 
60 static LONG g_ChangeCount;
61 static DEVMODEA g_LastDevmode;
62 static DWORD g_LastFlags;
63 
64 static LONG (WINAPI *pChangeDisplaySettingsA)(_In_opt_ PDEVMODEA lpDevMode, _In_ DWORD dwflags);
65 LONG WINAPI mChangeDisplaySettingsA(_In_opt_ PDEVMODEA lpDevMode, _In_ DWORD dwflags)
66 {
67     g_ChangeCount++;
68     g_LastDevmode = *lpDevMode;
69     g_LastFlags = dwflags;
70 
71     return DISP_CHANGE_FAILED;
72 }
73 
74 static LONG g_EnumCount;
75 static BOOL bFix = TRUE;
76 
77 static BOOL (WINAPI *pEnumDisplaySettingsA)(_In_opt_ LPCSTR lpszDeviceName, _In_ DWORD iModeNum, _Inout_ PDEVMODEA lpDevMode);
78 BOOL WINAPI mEnumDisplaySettingsA(_In_opt_ LPCSTR lpszDeviceName, _In_ DWORD iModeNum, _Inout_ PDEVMODEA lpDevMode)
79 {
80     g_EnumCount++;
81     if (pEnumDisplaySettingsA(lpszDeviceName, iModeNum, lpDevMode))
82     {
83         if (bFix)
84         {
85             if (lpDevMode && lpDevMode->dmBitsPerPel == 8)
86             {
87                 trace("Running at 8bpp, faking 16\n");
88                 lpDevMode->dmBitsPerPel = 16;
89             }
90             if (lpDevMode && lpDevMode->dmPelsWidth == 640 && lpDevMode->dmPelsHeight == 480)
91             {
92                 trace("Running at 640x480, faking 800x600\n");
93                 lpDevMode->dmPelsWidth = 800;
94                 lpDevMode->dmPelsHeight = 600;
95             }
96         }
97         else
98         {
99             if (lpDevMode)
100             {
101                 lpDevMode->dmBitsPerPel = 8;
102                 lpDevMode->dmPelsWidth = 640;
103                 lpDevMode->dmPelsHeight = 480;
104             }
105         }
106         return TRUE;
107     }
108     return FALSE;
109 }
110 
111 
112 
113 static LONG g_ThemeCount;
114 static DWORD g_LastThemeFlags;
115 
116 static void (WINAPI *pSetThemeAppProperties)(DWORD dwFlags);
117 void WINAPI mSetThemeAppProperties(DWORD dwFlags)
118 {
119     g_ThemeCount++;
120     g_LastThemeFlags = dwFlags;
121 }
122 
123 
124 static void pre_8bit(void)
125 {
126     g_ChangeCount = 0;
127     memset(&g_LastDevmode, 0, sizeof(g_LastDevmode));
128     g_LastFlags = 0xffffffff;
129     g_EnumCount = 0;
130 }
131 
132 static void pre_8bit_2(void)
133 {
134     bFix = FALSE;
135 
136     pre_8bit();
137 }
138 
139 static void post_8bit(void)
140 {
141     ok_int(g_ChangeCount, 1);
142     ok_hex(g_LastDevmode.dmFields & DM_BITSPERPEL, DM_BITSPERPEL);
143     ok_int(g_LastDevmode.dmBitsPerPel, 8);
144     ok_hex(g_LastFlags, CDS_FULLSCREEN);
145     ok_int(g_EnumCount, 1);
146 }
147 
148 static void post_8bit_2(void)
149 {
150     ok_int(g_ChangeCount, 0);
151     ok_hex(g_LastFlags, 0xffffffff);
152     ok_int(g_EnumCount, 1);
153 
154     bFix = TRUE;
155 }
156 
157 static void post_8bit_no(void)
158 {
159     if (g_Version == _WIN32_WINNT_WS03)
160     {
161         ok_int(g_ChangeCount, 1);
162         ok_hex(g_LastDevmode.dmFields & DM_BITSPERPEL, DM_BITSPERPEL);
163         ok_int(g_LastDevmode.dmBitsPerPel, 8);
164         ok_hex(g_LastFlags, CDS_FULLSCREEN);
165         ok_int(g_EnumCount, 1);
166     }
167     else
168     {
169         ok_int(g_ChangeCount, 0);
170         ok_hex(g_LastFlags, 0xffffffff);
171         ok_int(g_EnumCount, 0);
172     }
173 
174     bFix = TRUE;
175 }
176 
177 static void post_8bit_2_no(void)
178 {
179     if (g_Version == _WIN32_WINNT_WS03)
180     {
181         ok_int(g_ChangeCount, 0);
182         ok_hex(g_LastFlags, 0xffffffff);
183         ok_int(g_EnumCount, 1);
184     }
185     else
186     {
187         ok_int(g_ChangeCount, 0);
188         ok_hex(g_LastFlags, 0xffffffff);
189         ok_int(g_EnumCount, 0);
190     }
191 
192     bFix = TRUE;
193 }
194 
195 static void pre_640(void)
196 {
197     g_ChangeCount = 0;
198     memset(&g_LastDevmode, 0, sizeof(g_LastDevmode));
199     g_LastFlags = 0xffffffff;
200     g_EnumCount = 0;
201 }
202 
203 static void pre_640_2(void)
204 {
205     bFix = FALSE;
206 
207     pre_640();
208 }
209 
210 static void post_640(void)
211 {
212     ok_int(g_ChangeCount, 1);
213     ok_hex(g_LastDevmode.dmFields & (DM_PELSWIDTH | DM_PELSHEIGHT), (DM_PELSWIDTH | DM_PELSHEIGHT));
214     ok_int(g_LastDevmode.dmPelsWidth, 640);
215     ok_int(g_LastDevmode.dmPelsHeight, 480);
216     ok_hex(g_LastFlags, CDS_FULLSCREEN);
217     ok_int(g_EnumCount, 1);
218 }
219 
220 static void post_640_2(void)
221 {
222     ok_int(g_ChangeCount, 0);
223     ok_hex(g_LastFlags, 0xffffffff);
224     ok_int(g_EnumCount, 1);
225 
226     bFix = TRUE;
227 }
228 
229 static void post_640_no(void)
230 {
231     if (g_Version == _WIN32_WINNT_WS03)
232     {
233         ok_int(g_ChangeCount, 1);
234         ok_hex(g_LastDevmode.dmFields & (DM_PELSWIDTH | DM_PELSHEIGHT), (DM_PELSWIDTH | DM_PELSHEIGHT));
235         ok_int(g_LastDevmode.dmPelsWidth, 640);
236         ok_int(g_LastDevmode.dmPelsHeight, 480);
237         ok_hex(g_LastFlags, CDS_FULLSCREEN);
238         ok_int(g_EnumCount, 1);
239     }
240     else
241     {
242         ok_int(g_ChangeCount, 0);
243         ok_hex(g_LastFlags, 0xffffffff);
244         ok_int(g_EnumCount, 0);
245     }
246 
247     bFix = TRUE;
248 }
249 
250 static void post_640_2_no(void)
251 {
252     if (g_Version == _WIN32_WINNT_WS03)
253     {
254         ok_int(g_ChangeCount, 0);
255         ok_hex(g_LastFlags, 0xffffffff);
256         ok_int(g_EnumCount, 1);
257     }
258     else
259     {
260         ok_int(g_ChangeCount, 0);
261         ok_hex(g_LastFlags, 0xffffffff);
262         ok_int(g_EnumCount, 0);
263     }
264 
265     bFix = TRUE;
266 }
267 
268 static void pre_theme(void)
269 {
270     g_ThemeCount = 0;
271     g_LastThemeFlags = 0xffffffff;
272 }
273 
274 static void post_theme(void)
275 {
276     ok_int(g_ThemeCount, 1);
277     ok_hex(g_LastThemeFlags, 0);
278 }
279 
280 static void post_theme_no(void)
281 {
282     if (g_Version == _WIN32_WINNT_WS03)
283     {
284         ok_int(g_ThemeCount, 1);
285         ok_hex(g_LastThemeFlags, 0);
286     }
287     else
288     {
289         ok_int(g_ThemeCount, 0);
290         ok_hex(g_LastThemeFlags, 0xffffffff);
291     }
292 }
293 
294 
295 static BOOL hook_disp(HMODULE dll)
296 {
297     return RedirectIat(dll, "user32.dll", "ChangeDisplaySettingsA", (ULONG_PTR)mChangeDisplaySettingsA, (ULONG_PTR*)&pChangeDisplaySettingsA) &&
298         RedirectIat(dll, "user32.dll", "EnumDisplaySettingsA", (ULONG_PTR)mEnumDisplaySettingsA, (ULONG_PTR*)&pEnumDisplaySettingsA);
299 }
300 
301 static VOID unhook_disp(HMODULE dll)
302 {
303     RestoreIat(dll, "user32.dll", "ChangeDisplaySettingsA", (ULONG_PTR)pChangeDisplaySettingsA);
304     RestoreIat(dll, "user32.dll", "EnumDisplaySettingsA", (ULONG_PTR)pEnumDisplaySettingsA);
305 }
306 
307 static BOOL hook_theme(HMODULE dll)
308 {
309     return RedirectIat(dll, "uxtheme.dll", "SetThemeAppProperties", (ULONG_PTR)mSetThemeAppProperties, (ULONG_PTR*)&pSetThemeAppProperties);
310 }
311 
312 static VOID unhook_theme(HMODULE dll)
313 {
314     RestoreIat(dll, "uxtheme.dll", "SetThemeAppProperties", (ULONG_PTR)pSetThemeAppProperties);
315 }
316 
317 static void test_one(LPCSTR shim, DWORD dwReason, void(*pre)(), void(*post)(), void(*second)(void))
318 {
319     DWORD num_shims = 0;
320     WCHAR wide_shim[50] = { 0 };
321     PVOID hook;
322     BOOL ret;
323     MultiByteToWideChar(CP_ACP, 0, shim, -1, wide_shim, 50);
324 
325     if (pre)
326         pre();
327 
328     hook = pGetHookAPIs("", wide_shim, &num_shims);
329     if (hook == NULL)
330     {
331         skip("Skipping tests for layers (%s) not present in this os (0x%x)\n", shim, g_Version);
332         return;
333     }
334     ok(hook != NULL, "Expected hook to be a valid pointer for %s\n", shim);
335     ok(num_shims == 0, "Expected not to find any apihooks, got: %u for %s\n", num_shims, shim);
336 
337     ret = pNotifyShims(dwReason, NULL);
338 
339     /* Win7 and Win10 return 1, w2k3 returns a pointer */
340     ok(ret != 0, "Expected pNotifyShims to succeed (%i)\n", ret);
341 
342     if (post)
343         post();
344 
345     /* Invoking it a second time does not call the init functions again! */
346     if (pre && second)
347     {
348         pre();
349 
350         ret = pNotifyShims(dwReason, NULL);
351         ok(ret != 0, "Expected pNotifyShims to succeed (%i)\n", ret);
352 
353         second();
354     }
355 }
356 
357 /* In 2k3 0, 2, 4, 6, 8 are not guarded against re-initializations! */
358 static struct test_info
359 {
360     const char* name;
361     const WCHAR* dll;
362     DWORD winver;
363     DWORD reason;
364     BOOL(*hook)(HMODULE);
365     void(*unhook)(HMODULE);
366     void(*pre)(void);
367     void(*post)(void);
368     void(*second)(void);
369 } tests[] =
370 {
371     /* Success */
372     { "Force8BitColor", L"aclayers.dll", WINVER_ANY, 1, hook_disp, unhook_disp, pre_8bit, post_8bit, post_8bit_no },
373     { "Force8BitColor", L"aclayers.dll", _WIN32_WINNT_VISTA, 100, hook_disp, unhook_disp,pre_8bit, post_8bit, post_8bit_no },
374     { "Force640x480", L"aclayers.dll", WINVER_ANY, 1, hook_disp, unhook_disp, pre_640, post_640, post_640_no },
375     { "Force640x480", L"aclayers.dll", _WIN32_WINNT_VISTA, 100, hook_disp, unhook_disp, pre_640, post_640, post_640_no },
376     { "DisableThemes", L"acgenral.dll", WINVER_ANY, 1, hook_theme, unhook_theme, pre_theme, post_theme, post_theme_no },
377     { "DisableThemes", L"acgenral.dll", _WIN32_WINNT_VISTA, 100, hook_theme, unhook_theme, pre_theme, post_theme, post_theme_no },
378 
379     /* No need to change anything */
380     { "Force8BitColor", L"aclayers.dll", WINVER_ANY, 1, hook_disp, unhook_disp, pre_8bit_2, post_8bit_2, post_8bit_2_no },
381     { "Force8BitColor", L"aclayers.dll", _WIN32_WINNT_VISTA, 100, hook_disp, unhook_disp, pre_8bit_2, post_8bit_2, post_8bit_2_no },
382     { "Force640x480", L"aclayers.dll", WINVER_ANY, 1, hook_disp, unhook_disp, pre_640_2, post_640_2, post_640_2_no },
383     { "Force640x480", L"aclayers.dll", _WIN32_WINNT_VISTA, 100, hook_disp, unhook_disp, pre_640_2, post_640_2, post_640_2_no },
384 };
385 
386 
387 static void run_test(size_t n, BOOL unload)
388 {
389     BOOL ret;
390     HMODULE dll;
391 
392     if (!LoadShimDLL(tests[n].dll, &dll, &pGetHookAPIs))
393         pGetHookAPIs = NULL;
394     pNotifyShims = (void*)GetProcAddress(dll, "NotifyShims");
395 
396     if (!pGetHookAPIs || !pNotifyShims)
397     {
398         skip("%s not loaded, or does not export GetHookAPIs or pNotifyShims (%s, %p, %p)\n",
399              wine_dbgstr_w(tests[n].dll), tests[n].name, pGetHookAPIs, pNotifyShims);
400         return;
401     }
402 
403     g_Version = get_module_version(dll);
404 
405     if (!g_Version)
406     {
407         g_Version = _WIN32_WINNT_WS03;
408         trace("Module %s has no version, faking 2k3\n", wine_dbgstr_w(tests[n].dll));
409     }
410 
411     if (g_Version >= tests[n].winver)
412     {
413         ret = tests[n].hook(dll);
414         if (ret)
415         {
416             test_one(tests[n].name, tests[n].reason, tests[n].pre, tests[n].post, tests[n].second);
417             tests[n].unhook(dll);
418         }
419         else
420         {
421             ok(0, "Unable to redirect functions!\n");
422         }
423     }
424     FreeLibrary(dll);
425     if (unload)
426     {
427         dll = GetModuleHandleW(tests[n].dll);
428         ok(dll == NULL, "Unable to unload %s\n", wine_dbgstr_w(tests[n].dll));
429     }
430 }
431 
432 
433 START_TEST(dispmode)
434 {
435     HMODULE dll = LoadLibraryA("apphelp.dll");
436     size_t n;
437     int argc;
438     char **argv;
439 
440     argc = winetest_get_mainargs(&argv);
441     if (argc < 3)
442     {
443         WCHAR path[MAX_PATH];
444         GetModuleFileNameW(NULL, path, _countof(path));
445         dll = GetModuleHandleW(L"aclayers.dll");
446         if (!dll)
447             dll = GetModuleHandleW(L"acgenral.dll");
448         if (dll != NULL)
449             trace("Loaded under a shim, running each test in it's own process\n");
450 
451         for (n = 0; n < _countof(tests); ++n)
452         {
453             LONG failures = winetest_get_failures();
454 
455             if (dll == NULL)
456             {
457                 run_test(n, TRUE);
458             }
459             else
460             {
461                 WCHAR buf[MAX_PATH+40];
462                 STARTUPINFOW si = { sizeof(si) };
463                 PROCESS_INFORMATION pi;
464                 BOOL created;
465 
466                 StringCchPrintfW(buf, _countof(buf), L"\"%ls\" dispmode %u", path, n);
467                 created = CreateProcessW(NULL, buf, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
468                 ok(created, "Expected CreateProcess to succeed\n");
469                 if (created)
470                 {
471                     winetest_wait_child_process(pi.hProcess);
472                     CloseHandle(pi.hThread);
473                     CloseHandle(pi.hProcess);
474                 }
475             }
476 
477             ok(failures == winetest_get_failures(), "Last %u failures are from %d (%s)\n",
478                 winetest_get_failures() - failures, n, tests[n].name);
479         }
480     }
481     else
482     {
483         n = (size_t)atoi(argv[2]);
484         if (n < _countof(tests))
485         {
486             run_test(n, FALSE);
487         }
488         else
489         {
490             ok(0, "Test out of range: %u\n", n);
491         }
492     }
493 }
494