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