128 lines
6.7 KiB
C
128 lines
6.7 KiB
C
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
#define COBJMACROS
|
|
#include "bootstrapper_hook.h"
|
|
#include "menu_bridge.h"
|
|
#include <initguid.h>
|
|
#include <uiautomationcore.h>
|
|
#include <uiautomationcoreapi.h>
|
|
#include <uiautomationclient.h>
|
|
#include <string.h>
|
|
|
|
typedef LRESULT (WINAPI *ReturnProvider)(HWND, WPARAM, LPARAM, IRawElementProviderSimple *);
|
|
static ReturnProvider originalReturn;
|
|
static HWND queryWindow, queryBridge;
|
|
static LONG querying;
|
|
|
|
static void read_toggle(IRawElementProviderSimple *root) {
|
|
IRawElementProviderFragmentRoot *fragmentRoot = NULL;
|
|
IRawElementProviderFragment *focus = NULL;
|
|
IRawElementProviderSimple *provider = NULL;
|
|
IUnknown *pattern = NULL;
|
|
IToggleProvider *toggle = NULL;
|
|
VARIANT type = {0}, name = {0}, enabled = {0}, password = {0};
|
|
enum ToggleState state;
|
|
if (FAILED(IRawElementProviderSimple_QueryInterface(root, &IID_IRawElementProviderFragmentRoot,
|
|
(void **)&fragmentRoot)) || !fragmentRoot) goto done;
|
|
if (FAILED(IRawElementProviderFragmentRoot_GetFocus(fragmentRoot, &focus)) || !focus) goto done;
|
|
if (FAILED(IRawElementProviderFragment_QueryInterface(focus, &IID_IRawElementProviderSimple,
|
|
(void **)&provider)) || !provider) goto done;
|
|
if (FAILED(IRawElementProviderSimple_GetPropertyValue(provider, UIA_ControlTypePropertyId, &type)) ||
|
|
type.vt != VT_I4 || type.lVal != UIA_CheckBoxControlTypeId) goto done;
|
|
if (FAILED(IRawElementProviderSimple_GetPatternProvider(provider, UIA_TogglePatternId, &pattern)) ||
|
|
!pattern || FAILED(IUnknown_QueryInterface(pattern, &IID_IToggleProvider, (void **)&toggle)) ||
|
|
!toggle || FAILED(IToggleProvider_get_ToggleState(toggle, &state)) ||
|
|
(state != ToggleState_Off && state != ToggleState_On && state != ToggleState_Indeterminate)) goto done;
|
|
if (FAILED(IRawElementProviderSimple_GetPropertyValue(provider, UIA_NamePropertyId, &name)) ||
|
|
name.vt != VT_BSTR || !name.bstrVal || GetForegroundWindow() != queryWindow) goto done;
|
|
WineAccessBootstrapperState event = {0};
|
|
event.owner = (ULONGLONG)(UINT_PTR)queryWindow;
|
|
event.state = state;
|
|
// Copy all checkbox state on this UI thread. Mixing a newer Toggle value
|
|
// with flags from the asynchronous UIA worker can announce a false change.
|
|
if (SUCCEEDED(IRawElementProviderSimple_GetPropertyValue(provider, UIA_IsEnabledPropertyId, &enabled)) &&
|
|
enabled.vt == VT_BOOL && enabled.boolVal == VARIANT_FALSE)
|
|
event.flags |= WINE_ACCESS_BOOTSTRAPPER_DISABLED;
|
|
if (SUCCEEDED(IRawElementProviderSimple_GetPropertyValue(provider, UIA_IsPasswordPropertyId, &password)) &&
|
|
password.vt == VT_BOOL && password.boolVal == VARIANT_TRUE)
|
|
event.flags |= WINE_ACCESS_BOOTSTRAPPER_PASSWORD;
|
|
event.tick = GetTickCount64();
|
|
UINT length = SysStringLen(name.bstrVal);
|
|
if (length >= ARRAYSIZE(event.name)) goto done;
|
|
memcpy(event.name, name.bstrVal, length * sizeof(WCHAR));
|
|
COPYDATASTRUCT data = {WINE_ACCESS_BOOTSTRAPPER_TAG, sizeof(event), &event};
|
|
DWORD_PTR result;
|
|
SendMessageTimeoutW(queryBridge, WM_COPYDATA, 0, (LPARAM)&data,
|
|
SMTO_ABORTIFHUNG | SMTO_BLOCK, 50, &result);
|
|
done:
|
|
VariantClear(&type); VariantClear(&name);
|
|
VariantClear(&enabled); VariantClear(&password);
|
|
if (toggle) IToggleProvider_Release(toggle);
|
|
if (pattern) IUnknown_Release(pattern);
|
|
if (provider) IRawElementProviderSimple_Release(provider);
|
|
if (focus) IRawElementProviderFragment_Release(focus);
|
|
if (fragmentRoot) IRawElementProviderFragmentRoot_Release(fragmentRoot);
|
|
}
|
|
|
|
static LRESULT WINAPI return_provider(HWND window, WPARAM wp, LPARAM lp,
|
|
IRawElementProviderSimple *provider) {
|
|
// Borrow the provider only during this original call, on its UI thread.
|
|
// Never retain it, toggle a control, or change the original UIA result.
|
|
if (provider && window == queryWindow && queryBridge && (LONG)lp == UiaRootObjectId)
|
|
read_toggle(provider);
|
|
return originalReturn(window, wp, lp, provider);
|
|
}
|
|
|
|
static BOOL watch_import(void) {
|
|
BYTE *base = (BYTE *)GetModuleHandleW(NULL);
|
|
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)base;
|
|
if (!base || dos->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;
|
|
IMAGE_NT_HEADERS *header = (IMAGE_NT_HEADERS *)(base + dos->e_lfanew);
|
|
if (header->Signature != IMAGE_NT_SIGNATURE) return FALSE;
|
|
IMAGE_DATA_DIRECTORY directory = header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
|
if (!directory.VirtualAddress || !directory.Size) return FALSE;
|
|
IMAGE_IMPORT_DESCRIPTOR *imports = (IMAGE_IMPORT_DESCRIPTOR *)(base + directory.VirtualAddress);
|
|
for (; imports->Name; ++imports) {
|
|
if (lstrcmpiA((char *)(base + imports->Name), "uiautomationcore.dll") ||
|
|
!imports->OriginalFirstThunk) continue;
|
|
IMAGE_THUNK_DATA *names = (IMAGE_THUNK_DATA *)(base + imports->OriginalFirstThunk);
|
|
IMAGE_THUNK_DATA *addresses = (IMAGE_THUNK_DATA *)(base + imports->FirstThunk);
|
|
for (; names->u1.AddressOfData; ++names, ++addresses) {
|
|
if (IMAGE_SNAP_BY_ORDINAL(names->u1.Ordinal)) continue;
|
|
IMAGE_IMPORT_BY_NAME *name = (IMAGE_IMPORT_BY_NAME *)(base + names->u1.AddressOfData);
|
|
if (strcmp((char *)name->Name, "UiaReturnRawElementProvider")) continue;
|
|
if (addresses->u1.Function == (ULONG_PTR)return_provider) return TRUE;
|
|
HMODULE uia = GetModuleHandleW(L"uiautomationcore.dll"), pinned = NULL;
|
|
if (!uia) return FALSE;
|
|
FARPROC address = GetProcAddress(uia, "UiaReturnRawElementProvider");
|
|
if (!address || addresses->u1.Function != (ULONG_PTR)address) return FALSE;
|
|
memcpy(&originalReturn, &address, sizeof(originalReturn));
|
|
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
|
|
(LPCWSTR)(ULONG_PTR)return_provider, &pinned)) return FALSE;
|
|
DWORD protection;
|
|
if (!VirtualProtect(&addresses->u1.Function, sizeof(addresses->u1.Function), PAGE_READWRITE,
|
|
&protection)) return FALSE;
|
|
InterlockedExchangePointer((PVOID volatile *)&addresses->u1.Function, (PVOID)(ULONG_PTR)return_provider);
|
|
DWORD ignored;
|
|
VirtualProtect(&addresses->u1.Function, sizeof(addresses->u1.Function), protection, &ignored);
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void wine_access_query_bootstrapper_state(HWND window, HWND bridge) {
|
|
WCHAR className[128];
|
|
DWORD process = 0;
|
|
if (!bridge || window != GetForegroundWindow() ||
|
|
GetWindowThreadProcessId(window, &process) != GetCurrentThreadId() ||
|
|
process != GetCurrentProcessId() || !GetClassNameW(window, className, ARRAYSIZE(className)) ||
|
|
lstrcmpW(className, L"Blizzard Bootstrapper")) return;
|
|
if (InterlockedCompareExchange(&querying, 1, 0)) return;
|
|
if (watch_import()) {
|
|
queryWindow = window; queryBridge = bridge;
|
|
SendMessageW(window, WM_GETOBJECT, 0, UiaRootObjectId);
|
|
queryWindow = queryBridge = NULL;
|
|
}
|
|
InterlockedExchange(&querying, 0);
|
|
}
|