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