This has been implemented in a few different ways (see MR !7238 and MR !359), neither of which have associated tests.
This way of doing things sidesteps the need to update existing `VT_BLOB` properties by just storing/retrieving them in the same format we always have. If the registry data doesn't match a set of criteria, we treat it as `VT_BLOB` always.
-- v2: mmdevapi: Add support for storing VT_CLSID properties. mmdevapi: Add support for storing VT_BOOL properties.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/mmdevapi/tests/Makefile.in | 2 +- dlls/mmdevapi/tests/propstore.c | 175 +++++++++++++++++++++++++++++++- 2 files changed, 175 insertions(+), 2 deletions(-)
diff --git a/dlls/mmdevapi/tests/Makefile.in b/dlls/mmdevapi/tests/Makefile.in index d7408be1bbf..dc515833ce1 100644 --- a/dlls/mmdevapi/tests/Makefile.in +++ b/dlls/mmdevapi/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = mmdevapi.dll -IMPORTS = ole32 version user32 advapi32 winmm +IMPORTS = ole32 version user32 advapi32 winmm oleaut32 propsys
SOURCES = \ capture.c \ diff --git a/dlls/mmdevapi/tests/propstore.c b/dlls/mmdevapi/tests/propstore.c index b333a556567..88496375dd8 100644 --- a/dlls/mmdevapi/tests/propstore.c +++ b/dlls/mmdevapi/tests/propstore.c @@ -31,6 +31,7 @@ #include "ks.h" #include "ksmedia.h" #include "mmreg.h" +#include "propvarutil.h"
static BOOL (WINAPI *pIsWow64Process)(HANDLE, BOOL *);
@@ -133,19 +134,150 @@ static void test_getat(IPropertyStore *store) ok(found_desc, "DEVPKEY_Device_DeviceDesc not found\n"); }
+static const VARIANT_BOOL vt_bool_vals[] = { VARIANT_TRUE, VARIANT_FALSE }; +static const GUID vt_clsid_vals[] = { { 1 }, { 2 } }; +static const BYTE vt_blob_val[] = { 1, 2, 3, 4, 5, 6 }; +static const WCHAR vt_bstr_val[] = L"Test1"; + +static ULONG set_propvariant_for_vt(PROPVARIANT *pv, VARTYPE vt, const void *val, DWORD size) +{ + ULONG elems = 1; + + PropVariantInit(pv); + pv->vt = vt; + switch (vt) + { + case VT_BOOL: + pv->boolVal = ((const VARIANT_BOOL *)val)[0]; + break; + + case VT_BOOL | VT_VECTOR: + pv->cabool.cElems = size / sizeof(*pv->cabool.pElems); + pv->cabool.pElems = CoTaskMemAlloc(size); + memcpy(pv->cabool.pElems, val, size); + elems = pv->cabool.cElems; + break; + + case VT_CLSID: + pv->puuid = CoTaskMemAlloc(sizeof(*pv->puuid)); + *pv->puuid = ((const GUID *)val)[0]; + break; + + case VT_CLSID | VT_VECTOR: + pv->cauuid.cElems = size / sizeof(*pv->cauuid.pElems); + pv->cauuid.pElems = CoTaskMemAlloc(size); + memcpy(pv->cauuid.pElems, val, size); + elems = pv->cauuid.cElems; + break; + + case VT_BSTR: + pv->bstrVal = SysAllocString((const WCHAR *)val); + break; + + case VT_BLOB: + pv->blob.cbSize = size; + pv->blob.pBlobData = CoTaskMemAlloc(size); + memcpy(pv->blob.pBlobData, val, size); + break; + } + + return elems; +} + +static BOOL compare_propvariant(PROPVARIANT *pv, PROPVARIANT *pv2) +{ + unsigned int i; + + if (pv->vt != pv2->vt) + return FALSE; + + switch (pv->vt) + { + case VT_BSTR: + case VT_CLSID: + return !PropVariantCompareEx(pv, pv2, 0, 0); + + case VT_BOOL: + return pv->boolVal == pv2->boolVal; + + case VT_BOOL | VT_VECTOR: + if (pv->cabool.cElems != pv2->cabool.cElems) + return FALSE; + + for (i = 0; i < pv->cabool.cElems; i++) + { + if (pv->cabool.pElems[i] != pv2->cabool.pElems[i]) + return FALSE; + } + return TRUE; + + case VT_CLSID | VT_VECTOR: + if (pv->cauuid.cElems != pv2->cauuid.cElems) + return FALSE; + + for (i = 0; i < pv->cauuid.cElems; i++) + { + if (memcmp(&pv->cauuid.pElems[i], &pv2->cauuid.pElems[i], sizeof(*pv->cauuid.pElems))) + return FALSE; + } + return TRUE; + + case VT_BLOB: + if (pv->blob.cbSize != pv2->blob.cbSize) + return FALSE; + + return !memcmp(pv->blob.pBlobData, pv2->blob.pBlobData, pv->blob.cbSize); + } + + return FALSE; +} + +struct reg_serialized { + VARTYPE vt; + WORD unk; /* Seems like mostly uninitialized memory... */ + ULONG elems; + BYTE data[]; +}; + static void test_setvalue_on_wow64(IPropertyStore *store) { - PROPVARIANT pv; + static const struct + { + VARTYPE vt; + const void *data; + DWORD data_size; + + HRESULT expected_hr; + DWORD expected_reg_type; + DWORD expected_size; + BOOL todo_hr; + BOOL todo_data; + } propvar_tests[] = + { + { VT_BOOL, vt_bool_vals, sizeof(vt_bool_vals[0]), S_OK, REG_BINARY, 0xa, TRUE, TRUE }, + { VT_BOOL | VT_VECTOR, vt_bool_vals, sizeof(vt_bool_vals), S_OK, REG_BINARY, 0xc, TRUE, TRUE }, + { VT_CLSID, vt_clsid_vals, sizeof(vt_clsid_vals[0]), S_OK, REG_BINARY, 0x18, TRUE, TRUE }, + { VT_CLSID | VT_VECTOR, vt_clsid_vals, sizeof(vt_clsid_vals), S_OK, REG_BINARY, 0x28, TRUE, TRUE }, + { VT_BSTR, vt_bstr_val, sizeof(vt_bstr_val), S_OK, REG_BINARY, 0x14, TRUE, TRUE }, + { VT_BLOB, vt_blob_val, sizeof(vt_blob_val), S_OK, REG_BINARY, 0xe, .todo_data = TRUE }, + }; + PROPVARIANT pv, pv2; HRESULT hr; LONG ret; WCHAR *guidW; HKEY root, props, devkey; DWORD type, regval, size; + unsigned int i; + BYTE buf[256];
static const PROPERTYKEY PKEY_Bogus = { {0x1da5d803, 0xd492, 0x4edd, {0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x00}}, 0x7f }; + static const PROPERTYKEY PKEY_Bogus2 = { + {0x1da5d803, 0xd492, 0x4edd, {0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x00}}, 0x80 + }; static const WCHAR bogusW[] = L"{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F00},127"; + static const WCHAR bogus2W[] = L"{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F00},128";
PropVariantInit(&pv);
@@ -189,6 +321,47 @@ static void test_setvalue_on_wow64(IPropertyStore *store) ok(type == REG_DWORD, "Got wrong value type: %lu\n", type); ok(regval == 0xAB, "Got wrong value: 0x%lx\n", regval);
+ for (i = 0; i < ARRAY_SIZE(propvar_tests); i++) + { + struct reg_serialized *reg_val; + ULONG expected_elems; + + winetest_push_context("Test %u", i); + + expected_elems = set_propvariant_for_vt(&pv, propvar_tests[i].vt, propvar_tests[i].data, propvar_tests[i].data_size); + hr = IPropertyStore_SetValue(store, &PKEY_Bogus2, &pv); + todo_wine_if(propvar_tests[i].todo_hr) ok(hr == propvar_tests[i].expected_hr, "Unexpected hr %#lx.\n", hr); + if (FAILED(hr)) + { + winetest_pop_context(); + PropVariantClear(&pv); + continue; + } + + ret = RegQueryValueExW(props, bogus2W, NULL, &type, NULL, &size); + ok(ret == ERROR_SUCCESS, "Couldn't get bogus propertykey value: %lu.\n", ret); + ok(type == propvar_tests[i].expected_reg_type, "Unexpected registry value type %lu.\n", type); + todo_wine_if(propvar_tests[i].todo_data) ok(size >= propvar_tests[i].expected_size, "Unexpected registry value size 0x%lx.\n", size); + + ret = RegQueryValueExW(props, bogus2W, NULL, &type, buf, &size); + ok(ret == ERROR_SUCCESS, "Couldn't get bogus propertykey value: %lu.\n", ret); + reg_val = (struct reg_serialized *)buf; + todo_wine_if(propvar_tests[i].todo_data) + ok(reg_val->vt == propvar_tests[i].vt, "Unexpected vt: %#x.\n", reg_val->vt); + todo_wine_if(propvar_tests[i].todo_data) + ok(reg_val->elems == expected_elems, "Unexpected elems: %lu.\n", reg_val->elems); + todo_wine_if(propvar_tests[i].todo_data) + ok(!memcmp(reg_val->data, propvar_tests[i].data, propvar_tests[i].data_size), "Unexpected data.\n"); + + hr = IPropertyStore_GetValue(store, &PKEY_Bogus2, &pv2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(compare_propvariant(&pv, &pv2), "Propvariant mismatch.\n"); + + PropVariantClear(&pv); + PropVariantClear(&pv2); + winetest_pop_context(); + } + RegCloseKey(props); RegCloseKey(devkey); RegCloseKey(root);
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/mmdevapi/devenum.c | 150 ++++++++++++++++++++++++++++++-- dlls/mmdevapi/tests/propstore.c | 4 +- 2 files changed, 147 insertions(+), 7 deletions(-)
diff --git a/dlls/mmdevapi/devenum.c b/dlls/mmdevapi/devenum.c index adce05da954..0f48fe161a6 100644 --- a/dlls/mmdevapi/devenum.c +++ b/dlls/mmdevapi/devenum.c @@ -44,6 +44,14 @@ WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi);
DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0);
+#define WINE_REG_PROP_MAGIC 0xbeef +struct reg_prop_serialized { + VARTYPE vt; + WORD unk; /* Uninitialized memory on native, we store a magic value here. */ + ULONG elems; + BYTE data[]; +}; + static HKEY key_render; static HKEY key_capture;
@@ -260,6 +268,116 @@ static HRESULT MMDevPropStore_OpenPropKey(const GUID *guid, DWORD flow, HKEY *pr return S_OK; }
+static BOOL is_vector_vt(VARTYPE vt) +{ + return !!(vt & VT_VECTOR); +} + +static unsigned int get_vt_elem_size(VARTYPE vt) +{ + switch (vt & VT_TYPEMASK) + { + case VT_BOOL: + return sizeof(VARIANT_BOOL); + + default: + return 0; + } +} + +static BOOL is_valid_serialized_reg_prop(BYTE *data, DWORD data_size) +{ + struct reg_prop_serialized *reg_prop; + unsigned int elem_size; + + if (data_size <= sizeof(*reg_prop)) + return FALSE; + + reg_prop = (struct reg_prop_serialized *)data; + if (reg_prop->unk != WINE_REG_PROP_MAGIC) + return FALSE; + + if (((reg_prop->vt & VT_TYPEMASK) > VT_CLSID)) + return FALSE; + + if (!!(reg_prop->vt & ~VT_TYPEMASK) && !is_vector_vt(reg_prop->vt)) + return FALSE; + + if (!reg_prop->elems || ((reg_prop->elems > 1) && !is_vector_vt(reg_prop->vt))) + return FALSE; + + elem_size = get_vt_elem_size(reg_prop->vt); + if (elem_size && (((elem_size * reg_prop->elems) + sizeof(*reg_prop)) > data_size)) + return FALSE; + + return TRUE; +} + +static HRESULT deserialize_reg_prop(BYTE *data, DWORD data_size, PROPVARIANT *pv) +{ + struct reg_prop_serialized *reg_prop = (struct reg_prop_serialized *)data; + unsigned int elems_size; + HRESULT hr = S_OK; + + switch (reg_prop->vt) + { + case VT_BOOL: + pv->vt = reg_prop->vt; + pv->boolVal = ((VARIANT_BOOL *)reg_prop->data)[0]; + break; + + case VT_BOOL | VT_VECTOR: + pv->vt = reg_prop->vt; + pv->cabool.cElems = reg_prop->elems; + elems_size = sizeof(*pv->cabool.pElems) * reg_prop->elems; + pv->cabool.pElems = CoTaskMemAlloc(elems_size); + memcpy(pv->cabool.pElems, reg_prop->data, elems_size); + break; + + default: + ERR("Tried to deserialize unhandled vt %#x.\n", reg_prop->vt); + hr = E_NOTIMPL; + break; + } + + return hr; +} + +static HRESULT serialize_reg_prop(HKEY reg_key, const WCHAR *prop_id, PROPVARIANT *pv) +{ + const struct reg_prop_serialized reg_prop_init = { pv->vt, WINE_REG_PROP_MAGIC }; + struct reg_prop_serialized *reg_prop = NULL; + unsigned int size = sizeof(reg_prop_init); + unsigned int elems_size, elem_count = 1; + void *elems_val = NULL; + LONG ret; + + switch (pv->vt) + { + case VT_BOOL: + elems_size = sizeof(pv->boolVal); + elems_val = &pv->boolVal; + break; + + case VT_BOOL | VT_VECTOR: + elem_count = pv->cabool.cElems; + elems_size = elem_count * sizeof(*pv->cabool.pElems); + elems_val = pv->cabool.pElems; + break; + } + + size += elems_size; + if (!(reg_prop = malloc(size))) + return E_OUTOFMEMORY; + + *reg_prop = reg_prop_init; + reg_prop->elems = elem_count; + memcpy(reg_prop->data, elems_val, elems_size); + ret = RegSetValueExW(reg_key, prop_id, 0, REG_BINARY, (BYTE *)reg_prop, size); + free(reg_prop); + return !ret ? S_OK : E_FAIL; +} + static HRESULT MMDevice_GetPropValue(const GUID *devguid, DWORD flow, REFPROPERTYKEY key, PROPVARIANT *pv) { WCHAR buffer[80]; @@ -304,13 +422,26 @@ static HRESULT MMDevice_GetPropValue(const GUID *devguid, DWORD flow, REFPROPERT } case REG_BINARY: { - pv->vt = VT_BLOB; - pv->blob.cbSize = size; - pv->blob.pBlobData = CoTaskMemAlloc(size); - if (!pv->blob.pBlobData) + BYTE *data = CoTaskMemAlloc(size); + + if (!data) + { hr = E_OUTOFMEMORY; + break; + } + + RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_BINARY, NULL, data, &size); + if (is_valid_serialized_reg_prop(data, size)) + { + hr = deserialize_reg_prop(data, size, pv); + CoTaskMemFree(data); + } else - RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_BINARY, NULL, (BYTE*)pv->blob.pBlobData, &size); + { + pv->vt = VT_BLOB; + pv->blob.cbSize = size; + pv->blob.pBlobData = data; + } break; } default: @@ -343,6 +474,15 @@ static HRESULT MMDevice_SetPropValue(const GUID *devguid, DWORD flow, REFPROPERT ret = RegSetValueExW(regkey, buffer, 0, REG_DWORD, (const BYTE*)&pv->ulVal, sizeof(DWORD)); break; } + + case VT_BOOL: + case VT_BOOL | VT_VECTOR: + { + hr = serialize_reg_prop(regkey, buffer, (PROPVARIANT *)pv); + ret = 0; + break; + } + case VT_BLOB: { ret = RegSetValueExW(regkey, buffer, 0, REG_BINARY, pv->blob.pBlobData, pv->blob.cbSize); diff --git a/dlls/mmdevapi/tests/propstore.c b/dlls/mmdevapi/tests/propstore.c index 88496375dd8..15d1f5b4866 100644 --- a/dlls/mmdevapi/tests/propstore.c +++ b/dlls/mmdevapi/tests/propstore.c @@ -254,8 +254,8 @@ static void test_setvalue_on_wow64(IPropertyStore *store) BOOL todo_data; } propvar_tests[] = { - { VT_BOOL, vt_bool_vals, sizeof(vt_bool_vals[0]), S_OK, REG_BINARY, 0xa, TRUE, TRUE }, - { VT_BOOL | VT_VECTOR, vt_bool_vals, sizeof(vt_bool_vals), S_OK, REG_BINARY, 0xc, TRUE, TRUE }, + { VT_BOOL, vt_bool_vals, sizeof(vt_bool_vals[0]), S_OK, REG_BINARY, 0xa }, + { VT_BOOL | VT_VECTOR, vt_bool_vals, sizeof(vt_bool_vals), S_OK, REG_BINARY, 0xc }, { VT_CLSID, vt_clsid_vals, sizeof(vt_clsid_vals[0]), S_OK, REG_BINARY, 0x18, TRUE, TRUE }, { VT_CLSID | VT_VECTOR, vt_clsid_vals, sizeof(vt_clsid_vals), S_OK, REG_BINARY, 0x28, TRUE, TRUE }, { VT_BSTR, vt_bstr_val, sizeof(vt_bstr_val), S_OK, REG_BINARY, 0x14, TRUE, TRUE },
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/mmdevapi/devenum.c | 36 ++++++++++++++++++++++++++++++++- dlls/mmdevapi/tests/propstore.c | 4 ++-- 2 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/dlls/mmdevapi/devenum.c b/dlls/mmdevapi/devenum.c index 0f48fe161a6..8cd148b0a3c 100644 --- a/dlls/mmdevapi/devenum.c +++ b/dlls/mmdevapi/devenum.c @@ -280,6 +280,9 @@ static unsigned int get_vt_elem_size(VARTYPE vt) case VT_BOOL: return sizeof(VARIANT_BOOL);
+ case VT_CLSID: + return sizeof(GUID); + default: return 0; } @@ -334,6 +337,20 @@ static HRESULT deserialize_reg_prop(BYTE *data, DWORD data_size, PROPVARIANT *pv memcpy(pv->cabool.pElems, reg_prop->data, elems_size); break;
+ case VT_CLSID: + pv->vt = reg_prop->vt; + pv->puuid = CoTaskMemAlloc(sizeof(*pv->puuid)); + *pv->puuid = ((GUID *)reg_prop->data)[0]; + break; + + case VT_CLSID | VT_VECTOR: + pv->vt = reg_prop->vt; + pv->cauuid.cElems = reg_prop->elems; + elems_size = sizeof(*pv->cauuid.pElems) * reg_prop->elems; + pv->cauuid.pElems = CoTaskMemAlloc(elems_size); + memcpy(pv->cauuid.pElems, reg_prop->data, elems_size); + break; + default: ERR("Tried to deserialize unhandled vt %#x.\n", reg_prop->vt); hr = E_NOTIMPL; @@ -348,7 +365,7 @@ static HRESULT serialize_reg_prop(HKEY reg_key, const WCHAR *prop_id, PROPVARIAN const struct reg_prop_serialized reg_prop_init = { pv->vt, WINE_REG_PROP_MAGIC }; struct reg_prop_serialized *reg_prop = NULL; unsigned int size = sizeof(reg_prop_init); - unsigned int elems_size, elem_count = 1; + unsigned int elems_size = 0, elem_count = 1; void *elems_val = NULL; LONG ret;
@@ -364,6 +381,21 @@ static HRESULT serialize_reg_prop(HKEY reg_key, const WCHAR *prop_id, PROPVARIAN elems_size = elem_count * sizeof(*pv->cabool.pElems); elems_val = pv->cabool.pElems; break; + + case VT_CLSID: + elems_size = sizeof(*pv->puuid); + elems_val = pv->puuid; + break; + + case VT_CLSID | VT_VECTOR: + elem_count = pv->cauuid.cElems; + elems_size = elem_count * sizeof(*pv->cauuid.pElems); + elems_val = pv->cauuid.pElems; + break; + + default: + assert(0); + break; }
size += elems_size; @@ -477,6 +509,8 @@ static HRESULT MMDevice_SetPropValue(const GUID *devguid, DWORD flow, REFPROPERT
case VT_BOOL: case VT_BOOL | VT_VECTOR: + case VT_CLSID: + case VT_CLSID | VT_VECTOR: { hr = serialize_reg_prop(regkey, buffer, (PROPVARIANT *)pv); ret = 0; diff --git a/dlls/mmdevapi/tests/propstore.c b/dlls/mmdevapi/tests/propstore.c index 15d1f5b4866..3c1e6c1512a 100644 --- a/dlls/mmdevapi/tests/propstore.c +++ b/dlls/mmdevapi/tests/propstore.c @@ -256,8 +256,8 @@ static void test_setvalue_on_wow64(IPropertyStore *store) { { VT_BOOL, vt_bool_vals, sizeof(vt_bool_vals[0]), S_OK, REG_BINARY, 0xa }, { VT_BOOL | VT_VECTOR, vt_bool_vals, sizeof(vt_bool_vals), S_OK, REG_BINARY, 0xc }, - { VT_CLSID, vt_clsid_vals, sizeof(vt_clsid_vals[0]), S_OK, REG_BINARY, 0x18, TRUE, TRUE }, - { VT_CLSID | VT_VECTOR, vt_clsid_vals, sizeof(vt_clsid_vals), S_OK, REG_BINARY, 0x28, TRUE, TRUE }, + { VT_CLSID, vt_clsid_vals, sizeof(vt_clsid_vals[0]), S_OK, REG_BINARY, 0x18 }, + { VT_CLSID | VT_VECTOR, vt_clsid_vals, sizeof(vt_clsid_vals), S_OK, REG_BINARY, 0x28 }, { VT_BSTR, vt_bstr_val, sizeof(vt_bstr_val), S_OK, REG_BINARY, 0x14, TRUE, TRUE }, { VT_BLOB, vt_blob_val, sizeof(vt_blob_val), S_OK, REG_BINARY, 0xe, .todo_data = TRUE }, };
Huw Davies (@huw) commented about dlls/mmdevapi/devenum.c:
default:
return 0;
- }
+}
+static BOOL is_valid_serialized_reg_prop(BYTE *data, DWORD data_size) +{
- struct reg_prop_serialized *reg_prop;
- unsigned int elem_size;
- if (data_size <= sizeof(*reg_prop))
return FALSE;
- reg_prop = (struct reg_prop_serialized *)data;
- if (reg_prop->unk != WINE_REG_PROP_MAGIC)
return FALSE;
This seems a bit fragile; if something saved in the existing blob format happens to match here, we'll do the wrong thing.
On Sun Sep 7 17:40:16 2025 +0000, Huw Davies wrote:
This seems a bit fragile; if something saved in the existing blob format happens to match here, we'll do the wrong thing.
Quite a few things would have to go right (or wrong, depending on how you think of it :D) here for something to be saved in the existing blob format and be identified as the new format: - It'd need to have a valid VT. (there aren't too many of those) - It'd need an exact match to our magic value here. - It'd need to have a valid element count. - It'd need to be of the appropriate size.
It seems pretty unlikely to me that _all_ of those conditions could be matched accidentally. Alternatively, we could try to detect data in the old format here using this check and then update it to use the new format?