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