xref: /reactos/win32ss/user/user32/misc/dllmain.c (revision 84ccccab)
1 #include <user32.h>
2 
3 #include <ndk/cmfuncs.h>
4 
5 #include <wine/debug.h>
6 WINE_DEFAULT_DEBUG_CHANNEL(user32);
7 
8 #define KEY_LENGTH 1024
9 
10 static ULONG User32TlsIndex;
11 HINSTANCE User32Instance;
12 
13 PPROCESSINFO g_ppi = NULL;
14 PUSER_HANDLE_TABLE gHandleTable = NULL;
15 PUSER_HANDLE_ENTRY gHandleEntries = NULL;
16 PSERVERINFO gpsi = NULL;
17 SHAREDINFO gSharedInfo = {0};
18 ULONG_PTR g_ulSharedDelta;
19 BOOLEAN gfLogonProcess  = FALSE;
20 BOOLEAN gfServerProcess = FALSE;
21 BOOLEAN gfFirstThread   = TRUE;
22 HICON hIconSmWindows = NULL, hIconWindows = NULL;
23 
24 WCHAR szAppInit[KEY_LENGTH];
25 
26 BOOL
27 GetDllList(VOID)
28 {
29     NTSTATUS Status;
30     OBJECT_ATTRIBUTES Attributes;
31     BOOL bRet = FALSE;
32     BOOL bLoad;
33     HANDLE hKey = NULL;
34     DWORD dwSize;
35     PKEY_VALUE_PARTIAL_INFORMATION kvpInfo = NULL;
36 
37     UNICODE_STRING szKeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows");
38     UNICODE_STRING szLoadName = RTL_CONSTANT_STRING(L"LoadAppInit_DLLs");
39     UNICODE_STRING szDllsName = RTL_CONSTANT_STRING(L"AppInit_DLLs");
40 
41     InitializeObjectAttributes(&Attributes, &szKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL);
42     Status = NtOpenKey(&hKey, KEY_READ, &Attributes);
43     if (NT_SUCCESS(Status))
44     {
45         dwSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD);
46         kvpInfo = HeapAlloc(GetProcessHeap(), 0, dwSize);
47         if (!kvpInfo)
48             goto end;
49 
50         Status = NtQueryValueKey(hKey,
51                                  &szLoadName,
52                                  KeyValuePartialInformation,
53                                  kvpInfo,
54                                  dwSize,
55                                  &dwSize);
56         if (!NT_SUCCESS(Status))
57             goto end;
58 
59         RtlMoveMemory(&bLoad,
60                       kvpInfo->Data,
61                       kvpInfo->DataLength);
62 
63         HeapFree(GetProcessHeap(), 0, kvpInfo);
64         kvpInfo = NULL;
65 
66         if (bLoad)
67         {
68             Status = NtQueryValueKey(hKey,
69                                      &szDllsName,
70                                      KeyValuePartialInformation,
71                                      NULL,
72                                      0,
73                                      &dwSize);
74             if (Status != STATUS_BUFFER_TOO_SMALL)
75                 goto end;
76 
77             kvpInfo = HeapAlloc(GetProcessHeap(), 0, dwSize);
78             if (!kvpInfo)
79                 goto end;
80 
81             Status = NtQueryValueKey(hKey,
82                                      &szDllsName,
83                                      KeyValuePartialInformation,
84                                      kvpInfo,
85                                      dwSize,
86                                      &dwSize);
87             if (NT_SUCCESS(Status))
88             {
89                 LPWSTR lpBuffer = (LPWSTR)kvpInfo->Data;
90                 if (*lpBuffer != UNICODE_NULL)
91                 {
92                     INT bytesToCopy, nullPos;
93 
94                     bytesToCopy = min(kvpInfo->DataLength, KEY_LENGTH * sizeof(WCHAR));
95 
96                     if (bytesToCopy != 0)
97                     {
98                         RtlMoveMemory(szAppInit,
99                                       kvpInfo->Data,
100                                       bytesToCopy);
101 
102                         nullPos = (bytesToCopy / sizeof(WCHAR)) - 1;
103 
104                         /* ensure string is terminated */
105                         szAppInit[nullPos] = UNICODE_NULL;
106 
107                         bRet = TRUE;
108                     }
109                 }
110             }
111         }
112     }
113 
114 end:
115     if (hKey)
116         NtClose(hKey);
117 
118     if (kvpInfo)
119         HeapFree(GetProcessHeap(), 0, kvpInfo);
120 
121     return bRet;
122 }
123 
124 
125 VOID
126 LoadAppInitDlls(VOID)
127 {
128     szAppInit[0] = UNICODE_NULL;
129 
130     if (GetDllList())
131     {
132         WCHAR buffer[KEY_LENGTH];
133         LPWSTR ptr;
134 		size_t i;
135 
136         RtlCopyMemory(buffer, szAppInit, KEY_LENGTH * sizeof(WCHAR) );
137 
138 		for (i = 0; i < KEY_LENGTH; ++ i)
139 		{
140 			if(buffer[i] == L' ' || buffer[i] == L',')
141 				buffer[i] = 0;
142 		}
143 
144 		for (i = 0; i < KEY_LENGTH; )
145 		{
146 			if(buffer[i] == 0)
147 				++ i;
148 			else
149 			{
150 				ptr = buffer + i;
151 				i += wcslen(ptr);
152 				LoadLibraryW(ptr);
153 			}
154 		}
155     }
156 }
157 
158 VOID
159 UnloadAppInitDlls(VOID)
160 {
161     if (szAppInit[0] != UNICODE_NULL)
162     {
163         WCHAR buffer[KEY_LENGTH];
164         HMODULE hModule;
165         LPWSTR ptr;
166 		size_t i;
167 
168         RtlCopyMemory(buffer, szAppInit, KEY_LENGTH * sizeof(WCHAR));
169 
170 		for (i = 0; i < KEY_LENGTH; ++ i)
171 		{
172 			if(buffer[i] == L' ' || buffer[i] == L',')
173 				buffer[i] = 0;
174 		}
175 
176 		for (i = 0; i < KEY_LENGTH; )
177 		{
178 			if(buffer[i] == 0)
179 				++ i;
180 			else
181 			{
182 				ptr = buffer + i;
183 				i += wcslen(ptr);
184 				hModule = GetModuleHandleW(ptr);
185 				FreeLibrary(hModule);
186 			}
187 		}
188     }
189 }
190 
191 PVOID apfnDispatch[USER32_CALLBACK_MAXIMUM + 1] =
192 {
193     User32CallWindowProcFromKernel,
194     User32CallSendAsyncProcForKernel,
195     User32LoadSysMenuTemplateForKernel,
196     User32SetupDefaultCursors,
197     User32CallHookProcFromKernel,
198     User32CallEventProcFromKernel,
199     User32CallLoadMenuFromKernel,
200     User32CallClientThreadSetupFromKernel,
201     User32CallClientLoadLibraryFromKernel,
202     User32CallGetCharsetInfo,
203     User32CallCopyImageFromKernel,
204     User32CallSetWndIconsFromKernel,
205     User32DeliverUserAPC,
206     User32CallDDEPostFromKernel,
207     User32CallDDEGetFromKernel,
208     User32CallOBMFromKernel,
209 };
210 
211 
212 
213 VOID
214 WINAPI
215 GdiProcessSetup(VOID);
216 
217 BOOL
218 WINAPI
219 ClientThreadSetupHelper(BOOL IsCallback)
220 {
221     /*
222      * Normally we are called by win32k so the win32 thread pointers
223      * should be valid as they are set in win32k::InitThreadCallback.
224      */
225     PCLIENTINFO ClientInfo = GetWin32ClientInfo();
226     BOOLEAN IsFirstThread = _InterlockedExchange8((PCHAR)&gfFirstThread, FALSE);
227 
228     TRACE("In ClientThreadSetup(IsCallback == %s, gfServerProcess = %s, IsFirstThread = %s)\n",
229           IsCallback ? "TRUE" : "FALSE", gfServerProcess ? "TRUE" : "FALSE", IsFirstThread ? "TRUE" : "FALSE");
230 
231     if (IsFirstThread)
232         GdiProcessSetup();
233 
234     /* Check for already initialized thread, and bail out if so */
235     if (ClientInfo->CI_flags & CI_INITTHREAD)
236     {
237         ERR("ClientThreadSetup: Thread already initialized.\n");
238         return FALSE;
239     }
240 
241     /*
242      * CSRSS couldn't use user32::DllMain CSR server-to-server call to connect
243      * to win32k. So it is delayed to a manually-call to ClientThreadSetup.
244      * Also this needs to be done only for the first thread (since the connection
245      * is per-process).
246      */
247     if (gfServerProcess && IsFirstThread)
248     {
249         NTSTATUS Status;
250         USERCONNECT UserCon;
251 
252         RtlZeroMemory(&UserCon, sizeof(UserCon));
253 
254         /* Minimal setup of the connect info structure */
255         UserCon.ulVersion = USER_VERSION;
256 
257         /* Connect to win32k */
258         Status = NtUserProcessConnect(NtCurrentProcess(),
259                                       &UserCon,
260                                       sizeof(UserCon));
261         if (!NT_SUCCESS(Status)) return FALSE;
262 
263         /* Retrieve data */
264         g_ppi = ClientInfo->ppi; // Snapshot PI, used as pointer only!
265         g_ulSharedDelta = UserCon.siClient.ulSharedDelta;
266         gpsi = SharedPtrToUser(UserCon.siClient.psi);
267         gHandleTable = SharedPtrToUser(UserCon.siClient.aheList);
268         gHandleEntries = SharedPtrToUser(gHandleTable->handles);
269         gSharedInfo = UserCon.siClient;
270 
271         // ERR("1 SI 0x%x : HT 0x%x : D 0x%x\n", UserCon.siClient.psi, UserCon.siClient.aheList,  g_ulSharedDelta);
272     }
273 
274     TRACE("Checkpoint (register PFN)\n");
275     if (!RegisterClientPFN())
276     {
277         ERR("RegisterClientPFN failed\n");
278         return FALSE;
279     }
280 
281     /* Mark this thread as initialized */
282     ClientInfo->CI_flags |= CI_INITTHREAD;
283 
284     /* Initialization that should be done once per process */
285     if (IsFirstThread)
286     {
287         TRACE("Checkpoint (Allocating TLS)\n");
288 
289         /* Allocate an index for user32 thread local data */
290         User32TlsIndex = TlsAlloc();
291         if (User32TlsIndex == TLS_OUT_OF_INDEXES)
292             return FALSE;
293 
294         // HAAAAAAAAAACK!!!!!!
295         // ASSERT(gpsi);
296         if (!gpsi) ERR("AAAAAAAAAAAHHHHHHHHHHHHHH!!!!!!!! gpsi == NULL !!!!\n");
297         if (gpsi)
298         {
299         TRACE("Checkpoint (MessageInit)\n");
300 
301         if (MessageInit())
302         {
303             TRACE("Checkpoint (MenuInit)\n");
304             if (MenuInit())
305             {
306                 TRACE("Checkpoint initialization done OK\n");
307                 InitializeCriticalSection(&U32AccelCacheLock);
308                 LoadAppInitDlls();
309                 return TRUE;
310             }
311             MessageCleanup();
312         }
313 
314         TlsFree(User32TlsIndex);
315         return FALSE;
316         }
317     }
318 
319     return TRUE;
320 }
321 
322 /*
323  * @implemented
324  */
325 BOOL
326 WINAPI
327 ClientThreadSetup(VOID)
328 {
329     //
330     // This routine, in Windows, does a lot of what Init does, but in a radically
331     // different way.
332     //
333     // In Windows, because CSRSS's threads have TIF_CSRSSTHREAD set (we have this
334     // flag in ROS but not sure if we use it), the xxxClientThreadSetup callback
335     // isn't made when CSRSS first loads WINSRV.DLL (which loads USER32.DLL).
336     //
337     // However, all the other calls are made as normal, and WINSRV.DLL loads
338     // USER32.dll, the DllMain runs, and eventually the first NtUser system call is
339     // made which initializes Win32k (and initializes the thread, but as mentioned
340     // above, the thread is marked as TIF_CSRSSTHREAD).
341     //
342     // In the DllMain of User32, there is also a CsrClientConnectToServer call to
343     // server 2 (winsrv). When this is done from CSRSS, the "InServer" flag is set,
344     // so user32 will remember that it's running inside of CSRSS. Also, another
345     // flag, called "FirstThread" is manually set by DllMain.
346     //
347     // Then, WINSRV finishes loading, and CSRSRV starts the API thread/loop. This
348     // code then calls CsrConnectToUser, which calls... ClientThreadStartup. Now
349     // this routine detects that it's in the server process, which means it's CSRSS
350     // and that the callback never happened. It does some first-time-Win32k connection
351     // initialization and caches a bunch of things -- if it's the first thread. It also
352     // acquires a critical section to initialize GDI -- and then resets the first thread
353     // flag.
354     //
355     // For now, we'll do none of this, but to support Windows' CSRSRV.DLL which calls
356     // CsrConnectToUser, we'll pretend we "did something" here. Then the rest will
357     // continue as normal.
358     //
359 
360     // FIXME: Disabling this call is a HACK!! See also User32CallClientThreadSetupFromKernel...
361     // return ClientThreadSetupHelper(FALSE);
362     TRACE("ClientThreadSetup is not implemented\n");
363     return TRUE;
364 }
365 
366 BOOL
367 Init(PUSERCONNECT UserCon /*PUSERSRV_API_CONNECTINFO*/)
368 {
369     NTSTATUS Status = STATUS_SUCCESS;
370 
371     TRACE("user32::Init(0x%p) -->\n", UserCon);
372 
373     RtlInitializeCriticalSection(&gcsUserApiHook);
374 
375     /* Initialize callback table in PEB data */
376     NtCurrentPeb()->KernelCallbackTable = apfnDispatch;
377     NtCurrentPeb()->PostProcessInitRoutine = NULL;
378 
379     // This is a HACK!! //
380     gfServerProcess = FALSE;
381     gfFirstThread   = TRUE;
382     //// End of HACK!! ///
383 
384     /*
385      * Retrieve data from the connect info structure if the initializing
386      * process is not CSRSS. In case it is, this will be done from inside
387      * ClientThreadSetup.
388      */
389     if (!gfServerProcess)
390     {
391         // FIXME: HACK!! We should fixup for the NtUserProcessConnect fixups
392         // because it was made in the context of CSRSS process and not ours!!
393         // So... as long as we don't fix that, we need to redo again a call
394         // to NtUserProcessConnect... How perverse is that?!
395         //
396         // HACK(2): This call is necessary since we disabled
397         // the CSR call in DllMain...
398         {
399             RtlZeroMemory(UserCon, sizeof(*UserCon));
400 
401             /* Minimal setup of the connect info structure */
402             UserCon->ulVersion = USER_VERSION;
403 
404             TRACE("HACK: Hackish NtUserProcessConnect call!!\n");
405             /* Connect to win32k */
406             Status = NtUserProcessConnect(NtCurrentProcess(),
407                                           UserCon,
408                                           sizeof(*UserCon));
409             if (!NT_SUCCESS(Status)) return FALSE;
410         }
411 
412         //
413         // We continue as we should do normally...
414         //
415 
416         /* Retrieve data */
417         g_ppi = GetWin32ClientInfo()->ppi; // Snapshot PI, used as pointer only!
418         g_ulSharedDelta = UserCon->siClient.ulSharedDelta;
419         gpsi = SharedPtrToUser(UserCon->siClient.psi);
420         gHandleTable = SharedPtrToUser(UserCon->siClient.aheList);
421         gHandleEntries = SharedPtrToUser(gHandleTable->handles);
422         gSharedInfo = UserCon->siClient;
423     }
424 
425     // FIXME: Yet another hack... This call should normally not be done here, but
426     // instead in ClientThreadSetup, and in User32CallClientThreadSetupFromKernel as well.
427     TRACE("HACK: Using Init-ClientThreadSetupHelper hack!!\n");
428     if (!ClientThreadSetupHelper(FALSE))
429     {
430         TRACE("Init-ClientThreadSetupHelper hack failed!\n");
431         return FALSE;
432     }
433 
434     TRACE("<-- user32::Init()\n");
435 
436     return NT_SUCCESS(Status);
437 }
438 
439 VOID
440 Cleanup(VOID)
441 {
442     UnloadAppInitDlls();
443     DeleteCriticalSection(&U32AccelCacheLock);
444     MenuCleanup();
445     MessageCleanup();
446     TlsFree(User32TlsIndex);
447     DeleteFrameBrushes();
448 }
449 
450 INT WINAPI
451 DllMain(
452    IN PVOID hInstanceDll,
453    IN ULONG dwReason,
454    IN PVOID reserved)
455 {
456     switch (dwReason)
457     {
458         case DLL_PROCESS_ATTACH:
459         {
460 
461 #define WIN_OBJ_DIR L"\\Windows"
462 #define SESSION_DIR L"\\Sessions"
463 
464             USERSRV_API_CONNECTINFO ConnectInfo; // USERCONNECT
465 
466 #if 0 // Disabling this code is a BIG HACK!!
467 
468             NTSTATUS Status;
469             ULONG ConnectInfoSize = sizeof(ConnectInfo);
470             WCHAR SessionDir[256];
471 
472             /* Cache the PEB and Session ID */
473             PPEB Peb = NtCurrentPeb();
474             ULONG SessionId = Peb->SessionId; // gSessionId
475 
476             TRACE("user32::DllMain\n");
477 
478             /* Don't bother us for each thread */
479             DisableThreadLibraryCalls(hInstanceDll);
480 
481             RtlZeroMemory(&ConnectInfo, sizeof(ConnectInfo));
482 
483             /* Minimal setup of the connect info structure */
484             ConnectInfo.ulVersion = USER_VERSION;
485 
486             /* Setup the Object Directory path */
487             if (!SessionId)
488             {
489                 /* Use the raw path */
490                 wcscpy(SessionDir, WIN_OBJ_DIR);
491             }
492             else
493             {
494                 /* Use the session path */
495                 swprintf(SessionDir,
496                          L"%ws\\%ld%ws",
497                          SESSION_DIR,
498                          SessionId,
499                          WIN_OBJ_DIR);
500             }
501 
502             TRACE("Checkpoint (call CSR)\n");
503 
504             /* Connect to the USER Server */
505             Status = CsrClientConnectToServer(SessionDir,
506                                               USERSRV_SERVERDLL_INDEX,
507                                               &ConnectInfo,
508                                               &ConnectInfoSize,
509                                               &gfServerProcess);
510             if (!NT_SUCCESS(Status))
511             {
512                 ERR("Failed to connect to CSR (Status %lx)\n", Status);
513                 return FALSE;
514             }
515 
516             TRACE("Checkpoint (CSR called)\n");
517 
518 #endif
519 
520             User32Instance = hInstanceDll;
521 
522             /* Finish initialization */
523             TRACE("Checkpoint (call Init)\n");
524             if (!Init(&ConnectInfo))
525                 return FALSE;
526 
527             if (!gfServerProcess)
528             {
529 #if WIN32K_ISNT_BROKEN
530                InitializeImmEntryTable();
531 #else
532                /* imm32 takes a refcount and prevents us from unloading */
533                LoadLibraryW(L"user32");
534 #endif
535                //
536                // Wine is stub and throws an exception so save this for real Imm32.dll testing!!!!
537                //
538                //gImmApiEntries.pImmRegisterClient(&gSharedInfo, ghImm32);
539             }
540 
541             break;
542         }
543 
544         case DLL_PROCESS_DETACH:
545         {
546             if (ghImm32)
547                 FreeLibrary(ghImm32);
548 
549             Cleanup();
550             break;
551         }
552     }
553 
554     /* Finally, initialize GDI */
555     return GdiDllInitialize(hInstanceDll, dwReason, reserved);
556 }
557 
558 NTSTATUS
559 WINAPI
560 User32CallClientThreadSetupFromKernel(PVOID Arguments, ULONG ArgumentLength)
561 {
562   TRACE("User32CallClientThreadSetupFromKernel -->\n");
563   // FIXME: Disabling this call is a HACK!! See also ClientThreadSetup...
564   // ClientThreadSetupHelper(TRUE);
565   TRACE("<-- User32CallClientThreadSetupFromKernel\n");
566   return ZwCallbackReturn(NULL, 0, STATUS_SUCCESS);
567 }
568 
569 NTSTATUS
570 WINAPI
571 User32CallGetCharsetInfo(PVOID Arguments, ULONG ArgumentLength)
572 {
573   BOOL Ret;
574   PGET_CHARSET_INFO pgci = (PGET_CHARSET_INFO)Arguments;
575 
576   TRACE("GetCharsetInfo\n");
577 
578   Ret = TranslateCharsetInfo((DWORD *)pgci->Locale, &pgci->Cs, TCI_SRCLOCALE);
579 
580   return ZwCallbackReturn(Arguments, ArgumentLength, Ret ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL);
581 }
582 
583 NTSTATUS
584 WINAPI
585 User32CallSetWndIconsFromKernel(PVOID Arguments, ULONG ArgumentLength)
586 {
587   PSETWNDICONS_CALLBACK_ARGUMENTS Common = Arguments;
588 
589   if (!gpsi->hIconSmWindows)
590   {
591       Common->hIconSample    = LoadImageW(0, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
592       Common->hIconHand      = LoadImageW(0, IDI_HAND,        IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
593       Common->hIconQuestion  = LoadImageW(0, IDI_QUESTION,    IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
594       Common->hIconBang      = LoadImageW(0, IDI_EXCLAMATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
595       Common->hIconNote      = LoadImageW(0, IDI_ASTERISK,    IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
596       Common->hIconWindows   = LoadImageW(0, IDI_WINLOGO,     IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
597       Common->hIconSmWindows = LoadImageW(0, IDI_WINLOGO, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);
598       hIconWindows   = Common->hIconWindows;
599       hIconSmWindows = Common->hIconSmWindows;
600   }
601   ERR("hIconSmWindows %p hIconWindows %p \n",hIconSmWindows,hIconWindows);
602   return ZwCallbackReturn(Arguments, ArgumentLength, STATUS_SUCCESS);
603 }
604 
605 NTSTATUS
606 WINAPI
607 User32DeliverUserAPC(PVOID Arguments, ULONG ArgumentLength)
608 {
609   return ZwCallbackReturn(0, 0, STATUS_SUCCESS);
610 }
611 
612 NTSTATUS
613 WINAPI
614 User32CallOBMFromKernel(PVOID Arguments, ULONG ArgumentLength)
615 {
616   BITMAP bmp;
617   PSETOBM_CALLBACK_ARGUMENTS Common = Arguments;
618 
619   GetObjectW(LoadBitmapW(0, MAKEINTRESOURCEW(OBM_CLOSE)), sizeof(bmp), &bmp);
620   Common->oembmi[OBI_CLOSE].cx = bmp.bmWidth;
621   Common->oembmi[OBI_CLOSE].cy = bmp.bmHeight;
622 
623   GetObjectW(LoadBitmapW(0, MAKEINTRESOURCEW(OBM_MNARROW)), sizeof(bmp), &bmp);
624   Common->oembmi[OBI_MNARROW].cx = bmp.bmWidth;
625   Common->oembmi[OBI_MNARROW].cy = bmp.bmHeight;
626 
627   GetObjectW(LoadBitmapW(0, MAKEINTRESOURCEW(OBM_DNARROW)), sizeof(bmp), &bmp);
628   Common->oembmi[OBI_DNARROW].cx = bmp.bmWidth;
629   Common->oembmi[OBI_DNARROW].cy = bmp.bmHeight;
630 
631   GetObjectW(LoadBitmapW(0, MAKEINTRESOURCEW(OBM_DNARROWI)), sizeof(bmp), &bmp);
632   Common->oembmi[OBI_DNARROWI].cx = bmp.bmWidth;
633   Common->oembmi[OBI_DNARROWI].cy = bmp.bmHeight;
634 
635   GetObjectW(LoadBitmapW(0, MAKEINTRESOURCEW(OBM_UPARROW)), sizeof(bmp), &bmp);
636   Common->oembmi[OBI_UPARROW].cx = bmp.bmWidth;
637   Common->oembmi[OBI_UPARROW].cy = bmp.bmHeight;
638 
639   GetObjectW(LoadBitmapW(0, MAKEINTRESOURCEW(OBM_UPARROWI)), sizeof(bmp), &bmp);
640   Common->oembmi[OBI_UPARROWI].cx = bmp.bmWidth;
641   Common->oembmi[OBI_UPARROWI].cy = bmp.bmHeight;
642 
643   return ZwCallbackReturn(Arguments, ArgumentLength, STATUS_SUCCESS);
644 }
645