From: Nikolay Sivov nsivov@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com --- dlls/evr/evr.c | 42 ++++++++++++++++++++++++++++++++++++++++++ dlls/evr/tests/evr.c | 14 +++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/dlls/evr/evr.c b/dlls/evr/evr.c index 2251cb5522e..97cc2e27915 100644 --- a/dlls/evr/evr.c +++ b/dlls/evr/evr.c @@ -37,6 +37,7 @@ struct evr IAMFilterMiscFlags IAMFilterMiscFlags_iface; IMFGetService IMFGetService_iface; IMFVideoRenderer IMFVideoRenderer_iface; + IMediaEventSink IMediaEventSink_iface;
IMFTransform *mixer; IMFVideoPresenter *presenter; @@ -111,6 +112,8 @@ static HRESULT evr_query_interface(struct strmbase_renderer *iface, REFIID iid, *out = &filter->IMFGetService_iface; else if (IsEqualGUID(iid, &IID_IMFVideoRenderer)) *out = &filter->IMFVideoRenderer_iface; + else if (IsEqualGUID(iid, &IID_IMediaEventSink)) + *out = &filter->IMediaEventSink_iface; else return E_NOINTERFACE;
@@ -348,6 +351,44 @@ static const IMFVideoRendererVtbl filter_video_renderer_vtbl = filter_video_renderer_InitializeRenderer, };
+static struct evr *impl_from_IMediaEventSink(IMediaEventSink *iface) +{ + return CONTAINING_RECORD(iface, struct evr, IMediaEventSink_iface); +} + +static HRESULT WINAPI filter_media_event_sink_QueryInterface(IMediaEventSink *iface, REFIID riid, void **obj) +{ + struct evr *filter = impl_from_IMediaEventSink(iface); + return IUnknown_QueryInterface(filter->renderer.filter.outer_unk, riid, obj); +} + +static ULONG WINAPI filter_media_event_sink_AddRef(IMediaEventSink *iface) +{ + struct evr *filter = impl_from_IMediaEventSink(iface); + return IUnknown_AddRef(filter->renderer.filter.outer_unk); +} + +static ULONG WINAPI filter_media_event_sink_Release(IMediaEventSink *iface) +{ + struct evr *filter = impl_from_IMediaEventSink(iface); + return IUnknown_Release(filter->renderer.filter.outer_unk); +} + +static HRESULT WINAPI filter_media_event_sink_Notify(IMediaEventSink *iface, LONG event, LONG_PTR param1, LONG_PTR param2) +{ + FIXME("iface %p, event %ld, param1 %Id, param2 %Id.\n", iface, event, param1, param2); + + return E_NOTIMPL; +} + +static const IMediaEventSinkVtbl filter_media_event_sink_vtbl = +{ + filter_media_event_sink_QueryInterface, + filter_media_event_sink_AddRef, + filter_media_event_sink_Release, + filter_media_event_sink_Notify, +}; + HRESULT evr_filter_create(IUnknown *outer, void **out) { struct evr *object; @@ -361,6 +402,7 @@ HRESULT evr_filter_create(IUnknown *outer, void **out) object->IAMFilterMiscFlags_iface.lpVtbl = &filter_misc_flags_vtbl; object->IMFGetService_iface.lpVtbl = &filter_get_service_vtbl; object->IMFVideoRenderer_iface.lpVtbl = &filter_video_renderer_vtbl; + object->IMediaEventSink_iface.lpVtbl = &filter_media_event_sink_vtbl;
TRACE("Created EVR %p.\n", object); *out = &object->renderer.filter.IUnknown_inner; diff --git a/dlls/evr/tests/evr.c b/dlls/evr/tests/evr.c index 28a472a8a7f..30f7ba81ee9 100644 --- a/dlls/evr/tests/evr.c +++ b/dlls/evr/tests/evr.c @@ -273,7 +273,9 @@ static void check_service_interface_(unsigned int line, void *iface_ptr, REFGUID
static void test_interfaces(void) { - IBaseFilter *filter = create_evr(); + IBaseFilter *filter = create_evr(), *filter2; + IUnknown *unk; + HRESULT hr; ULONG ref;
check_interface(filter, &IID_IAMFilterMiscFlags, TRUE); @@ -284,6 +286,7 @@ static void test_interfaces(void) check_interface(filter, &IID_IMediaFilter, TRUE); check_interface(filter, &IID_IMediaPosition, TRUE); check_interface(filter, &IID_IMediaSeeking, TRUE); + check_interface(filter, &IID_IMediaEventSink, TRUE); check_interface(filter, &IID_IPersist, TRUE); check_interface(filter, &IID_IUnknown, TRUE);
@@ -296,6 +299,15 @@ static void test_interfaces(void) check_interface(filter, &IID_IReferenceClock, FALSE); check_interface(filter, &IID_IVideoWindow, FALSE);
+ /* The scope of IMediaEventSink */ + hr = IBaseFilter_QueryInterface(filter, &IID_IMediaEventSink, (void **)&unk); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IUnknown_QueryInterface(unk, &IID_IBaseFilter, (void **)&filter2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(filter == filter2, "Unexpected pointer.\n"); + IBaseFilter_Release(filter2); + IUnknown_Release(unk); + ref = IBaseFilter_Release(filter); ok(!ref, "Got unexpected refcount %ld.\n", ref); }
From: Nikolay Sivov nsivov@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com --- dlls/evr/evr.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ dlls/evr/tests/evr.c | 30 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+)
diff --git a/dlls/evr/evr.c b/dlls/evr/evr.c index 97cc2e27915..734f9621555 100644 --- a/dlls/evr/evr.c +++ b/dlls/evr/evr.c @@ -24,6 +24,7 @@
#include "evr_private.h" #include "d3d9.h" +#include "mferror.h"
#include "initguid.h" #include "dxva2api.h" @@ -38,6 +39,7 @@ struct evr IMFGetService IMFGetService_iface; IMFVideoRenderer IMFVideoRenderer_iface; IMediaEventSink IMediaEventSink_iface; + IMFTopologyServiceLookup IMFTopologyServiceLookup_iface;
IMFTransform *mixer; IMFVideoPresenter *presenter; @@ -114,6 +116,8 @@ static HRESULT evr_query_interface(struct strmbase_renderer *iface, REFIID iid, *out = &filter->IMFVideoRenderer_iface; else if (IsEqualGUID(iid, &IID_IMediaEventSink)) *out = &filter->IMediaEventSink_iface; + else if (IsEqualGUID(iid, &IID_IMFTopologyServiceLookup)) + *out = &filter->IMFTopologyServiceLookup_iface; else return E_NOINTERFACE;
@@ -389,6 +393,46 @@ static const IMediaEventSinkVtbl filter_media_event_sink_vtbl = filter_media_event_sink_Notify, };
+static struct evr *impl_from_IMFTopologyServiceLookup(IMFTopologyServiceLookup *iface) +{ + return CONTAINING_RECORD(iface, struct evr, IMFTopologyServiceLookup_iface); +} + +static HRESULT WINAPI filter_service_lookup_QueryInterface(IMFTopologyServiceLookup *iface, REFIID riid, void **obj) +{ + struct evr *filter = impl_from_IMFTopologyServiceLookup(iface); + return IUnknown_QueryInterface(filter->renderer.filter.outer_unk, riid, obj); +} + +static ULONG WINAPI filter_service_lookup_AddRef(IMFTopologyServiceLookup *iface) +{ + struct evr *filter = impl_from_IMFTopologyServiceLookup(iface); + return IUnknown_AddRef(filter->renderer.filter.outer_unk); +} + +static ULONG WINAPI filter_service_lookup_Release(IMFTopologyServiceLookup *iface) +{ + struct evr *filter = impl_from_IMFTopologyServiceLookup(iface); + return IUnknown_Release(filter->renderer.filter.outer_unk); +} + +static HRESULT WINAPI filter_service_lookup_LookupService(IMFTopologyServiceLookup *iface, MF_SERVICE_LOOKUP_TYPE lookup_type, + DWORD index, REFGUID service, REFIID riid, void **objects, DWORD *num_objects) +{ + FIXME("iface %p, lookup_type %d, index %lu, service %s, riid %s, objects %p, num_objects %p.\n", + iface, lookup_type, index, debugstr_guid(service), debugstr_guid(riid), objects, num_objects); + + return MF_E_NOTACCEPTING; +} + +static const IMFTopologyServiceLookupVtbl filter_service_lookup_vtbl = +{ + filter_service_lookup_QueryInterface, + filter_service_lookup_AddRef, + filter_service_lookup_Release, + filter_service_lookup_LookupService, +}; + HRESULT evr_filter_create(IUnknown *outer, void **out) { struct evr *object; @@ -403,6 +447,7 @@ HRESULT evr_filter_create(IUnknown *outer, void **out) object->IMFGetService_iface.lpVtbl = &filter_get_service_vtbl; object->IMFVideoRenderer_iface.lpVtbl = &filter_video_renderer_vtbl; object->IMediaEventSink_iface.lpVtbl = &filter_media_event_sink_vtbl; + object->IMFTopologyServiceLookup_iface.lpVtbl = &filter_service_lookup_vtbl;
TRACE("Created EVR %p.\n", object); *out = &object->renderer.filter.IUnknown_inner; diff --git a/dlls/evr/tests/evr.c b/dlls/evr/tests/evr.c index 30f7ba81ee9..7413b3fe239 100644 --- a/dlls/evr/tests/evr.c +++ b/dlls/evr/tests/evr.c @@ -283,6 +283,7 @@ static void test_interfaces(void) check_interface(filter, &IID_IEVRFilterConfig, TRUE); check_interface(filter, &IID_IMFGetService, TRUE); check_interface(filter, &IID_IMFVideoRenderer, TRUE); + check_interface(filter, &IID_IMFTopologyServiceLookup, TRUE); check_interface(filter, &IID_IMediaFilter, TRUE); check_interface(filter, &IID_IMediaPosition, TRUE); check_interface(filter, &IID_IMediaSeeking, TRUE); @@ -603,6 +604,34 @@ static void test_display_control(void) ok(!ref, "Got outstanding refcount %ld.\n", ref); }
+static void test_service_lookup(void) +{ + IBaseFilter *filter = create_evr(); + IMFTopologyServiceLookup *service_lookup; + IUnknown *unk; + DWORD count; + HRESULT hr; + ULONG ref; + + hr = IBaseFilter_QueryInterface(filter, &IID_IMFTopologyServiceLookup, (void **)&service_lookup); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFTopologyServiceLookup_QueryInterface(service_lookup, &IID_IBaseFilter, (void **)&unk); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(unk == (IUnknown *)filter, "Unexpected pointer.\n"); + IUnknown_Release(unk); + + count = 1; + hr = IMFTopologyServiceLookup_LookupService(service_lookup, MF_SERVICE_LOOKUP_GLOBAL, 0, + &MR_VIDEO_RENDER_SERVICE, &IID_IMediaEventSink, (void **)&unk, &count); + ok(hr == MF_E_NOTACCEPTING, "Unexpected hr %#lx.\n", hr); + + IMFTopologyServiceLookup_Release(service_lookup); + + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + static IMFMediaType * create_video_type(const GUID *subtype) { IMFMediaType *video_type; @@ -3276,6 +3305,7 @@ START_TEST(evr) test_unconnected_eos(); test_misc_flags(); test_display_control(); + test_service_lookup();
test_default_mixer(); test_default_mixer_type_negotiation();
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=122160
Your paranoid android.
=== w7u_2qxl (32 bit report) ===
evr: evr.c:286: Test failed: Got hr 0x80004002, expected 0. evr.c:617: Test failed: Unexpected hr 0x80004002.
=== w7u_adm (32 bit report) ===
evr: evr.c:286: Test failed: Got hr 0x80004002, expected 0. evr.c:617: Test failed: Unexpected hr 0x80004002.
=== w7u_el (32 bit report) ===
evr: evr.c:286: Test failed: Got hr 0x80004002, expected 0. evr.c:617: Test failed: Unexpected hr 0x80004002.
From: Nikolay Sivov nsivov@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com --- dlls/evr/evr.c | 163 +++++++++++++++++++++++++++++++++++++++++-- dlls/evr/tests/evr.c | 54 ++++++++++++++ 2 files changed, 213 insertions(+), 4 deletions(-)
diff --git a/dlls/evr/evr.c b/dlls/evr/evr.c index 734f9621555..2f0eeb6a22d 100644 --- a/dlls/evr/evr.c +++ b/dlls/evr/evr.c @@ -25,12 +25,20 @@ #include "evr_private.h" #include "d3d9.h" #include "mferror.h" +#include "mfapi.h"
#include "initguid.h" #include "dxva2api.h"
WINE_DEFAULT_DEBUG_CHANNEL(evr);
+enum evr_flags +{ + EVR_INIT_SERVICES = 0x1, /* Currently in InitServices() call. */ + EVR_MIXER_INITED_SERVICES = 0x2, + EVR_PRESENTER_INITED_SERVICES = 0x4, +}; + struct evr { struct strmbase_renderer renderer; @@ -43,6 +51,7 @@ struct evr
IMFTransform *mixer; IMFVideoPresenter *presenter; + unsigned int flags; };
static void evr_uninitialize(struct evr *filter) @@ -125,6 +134,96 @@ static HRESULT evr_query_interface(struct strmbase_renderer *iface, REFIID iid, return S_OK; }
+static BOOL evr_is_mixer_d3d_aware(const struct evr *filter) +{ + IMFAttributes *attributes; + unsigned int value = 0; + BOOL ret; + + if (FAILED(IMFTransform_QueryInterface(filter->mixer, &IID_IMFAttributes, (void **)&attributes))) + return FALSE; + + ret = SUCCEEDED(IMFAttributes_GetUINT32(attributes, &MF_SA_D3D_AWARE, &value)) && value; + IMFAttributes_Release(attributes); + return ret; +} + +static HRESULT evr_init_services(struct evr *filter) +{ + IMFTopologyServiceLookupClient *lookup_client; + HRESULT hr; + + if (SUCCEEDED(hr = IMFTransform_QueryInterface(filter->mixer, &IID_IMFTopologyServiceLookupClient, + (void **)&lookup_client))) + { + filter->flags |= EVR_INIT_SERVICES; + if (SUCCEEDED(hr = IMFTopologyServiceLookupClient_InitServicePointers(lookup_client, + &filter->IMFTopologyServiceLookup_iface))) + { + filter->flags |= EVR_MIXER_INITED_SERVICES; + } + filter->flags &= ~EVR_INIT_SERVICES; + IMFTopologyServiceLookupClient_Release(lookup_client); + } + + if (FAILED(hr)) return hr; + + /* Set device manager that presenter should have created. */ + if (evr_is_mixer_d3d_aware(filter)) + { + IUnknown *device_manager; + IMFGetService *gs; + + if (SUCCEEDED(IUnknown_QueryInterface(filter->presenter, &IID_IMFGetService, (void **)&gs))) + { + if (SUCCEEDED(IMFGetService_GetService(gs, &MR_VIDEO_RENDER_SERVICE, &IID_IDirect3DDeviceManager9, + (void **)&device_manager))) + { + IMFTransform_ProcessMessage(filter->mixer, MFT_MESSAGE_SET_D3D_MANAGER, (ULONG_PTR)device_manager); + IUnknown_Release(device_manager); + } + + IMFGetService_Release(gs); + } + } + + if (SUCCEEDED(hr = IMFVideoPresenter_QueryInterface(filter->presenter, &IID_IMFTopologyServiceLookupClient, + (void **)&lookup_client))) + { + filter->flags |= EVR_INIT_SERVICES; + if (SUCCEEDED(hr = IMFTopologyServiceLookupClient_InitServicePointers(lookup_client, + &filter->IMFTopologyServiceLookup_iface))) + { + filter->flags |= EVR_PRESENTER_INITED_SERVICES; + } + filter->flags &= ~EVR_INIT_SERVICES; + IMFTopologyServiceLookupClient_Release(lookup_client); + } + + return hr; +} + +static void evr_release_services(struct evr *filter) +{ + IMFTopologyServiceLookupClient *lookup_client; + + if (filter->flags & EVR_MIXER_INITED_SERVICES && SUCCEEDED(IMFTransform_QueryInterface(filter->mixer, + &IID_IMFTopologyServiceLookupClient, (void **)&lookup_client))) + { + IMFTopologyServiceLookupClient_ReleaseServicePointers(lookup_client); + IMFTopologyServiceLookupClient_Release(lookup_client); + filter->flags &= ~EVR_MIXER_INITED_SERVICES; + } + + if (filter->flags & EVR_PRESENTER_INITED_SERVICES && SUCCEEDED(IMFVideoPresenter_QueryInterface(filter->presenter, + &IID_IMFTopologyServiceLookupClient, (void **)&lookup_client))) + { + IMFTopologyServiceLookupClient_ReleaseServicePointers(lookup_client); + IMFTopologyServiceLookupClient_Release(lookup_client); + filter->flags &= ~EVR_PRESENTER_INITED_SERVICES; + } +} + static void evr_destroy(struct strmbase_renderer *iface) { struct evr *filter = impl_from_strmbase_renderer(iface); @@ -142,8 +241,37 @@ static HRESULT evr_render(struct strmbase_renderer *iface, IMediaSample *sample)
static HRESULT evr_query_accept(struct strmbase_renderer *iface, const AM_MEDIA_TYPE *mt) { - FIXME("Not implemented.\n"); - return E_NOTIMPL; + struct evr *filter = impl_from_strmbase_renderer(iface); + IMFMediaType *media_type; + HRESULT hr = S_OK; + + EnterCriticalSection(&filter->renderer.filter.filter_cs); + + if (!filter->presenter) + hr = evr_initialize(filter, NULL, NULL); + + if (SUCCEEDED(hr)) + hr = evr_init_services(filter); + + if (SUCCEEDED(hr)) + hr = MFCreateMediaType(&media_type); + + if (SUCCEEDED(hr)) + { + if (SUCCEEDED(hr = MFInitMediaTypeFromAMMediaType(media_type, mt))) + { + /* TODO: some pin -> mixer input mapping is necessary to test the substreams. */ + hr = IMFTransform_SetInputType(filter->mixer, 0, media_type, MFT_SET_TYPE_TEST_ONLY); + } + + IMFMediaType_Release(media_type); + + evr_release_services(filter); + } + + LeaveCriticalSection(&filter->renderer.filter.filter_cs); + + return hr; }
static const struct strmbase_renderer_ops renderer_ops = @@ -419,10 +547,37 @@ static ULONG WINAPI filter_service_lookup_Release(IMFTopologyServiceLookup *ifac static HRESULT WINAPI filter_service_lookup_LookupService(IMFTopologyServiceLookup *iface, MF_SERVICE_LOOKUP_TYPE lookup_type, DWORD index, REFGUID service, REFIID riid, void **objects, DWORD *num_objects) { - FIXME("iface %p, lookup_type %d, index %lu, service %s, riid %s, objects %p, num_objects %p.\n", + struct evr *filter = impl_from_IMFTopologyServiceLookup(iface); + HRESULT hr = S_OK; + + TRACE("iface %p, lookup_type %d, index %lu, service %s, riid %s, objects %p, num_objects %p.\n", iface, lookup_type, index, debugstr_guid(service), debugstr_guid(riid), objects, num_objects);
- return MF_E_NOTACCEPTING; + EnterCriticalSection(&filter->renderer.filter.filter_cs); + + if (!(filter->flags & EVR_INIT_SERVICES)) + hr = MF_E_NOTACCEPTING; + else if (IsEqualGUID(service, &MR_VIDEO_RENDER_SERVICE)) + { + if (IsEqualIID(riid, &IID_IMediaEventSink)) + { + *objects = &filter->IMediaEventSink_iface; + IUnknown_AddRef((IUnknown *)*objects); + } + } + else if (IsEqualGUID(service, &MR_VIDEO_MIXER_SERVICE)) + { + hr = IMFTransform_QueryInterface(filter->mixer, riid, objects); + } + else + { + WARN("Unsupported service %s.\n", debugstr_guid(service)); + hr = MF_E_UNSUPPORTED_SERVICE; + } + + LeaveCriticalSection(&filter->renderer.filter.filter_cs); + + return hr; }
static const IMFTopologyServiceLookupVtbl filter_service_lookup_vtbl = diff --git a/dlls/evr/tests/evr.c b/dlls/evr/tests/evr.c index 7413b3fe239..555e2f00ffb 100644 --- a/dlls/evr/tests/evr.c +++ b/dlls/evr/tests/evr.c @@ -632,6 +632,59 @@ static void test_service_lookup(void) ok(!ref, "Got outstanding refcount %ld.\n", ref); }
+static void test_query_accept(void) +{ + IBaseFilter *filter = create_evr(); + AM_MEDIA_TYPE req_mt = {{0}}; + VIDEOINFOHEADER vih = + { + {0}, {0}, 0, 0, 0, + {sizeof(BITMAPINFOHEADER), 32, 24, 1, 0, 0xdeadbeef} + }; + unsigned int i; + HRESULT hr; + ULONG ref; + IPin *pin; + + static const GUID *subtype_tests[] = + { + &MEDIASUBTYPE_RGB32, + &MEDIASUBTYPE_YUY2, + }; + + static const GUID *unsupported_subtype_tests[] = + { + &MEDIASUBTYPE_RGB8, + }; + + IBaseFilter_FindPin(filter, L"EVR Input0", &pin); + + req_mt.majortype = MEDIATYPE_Video; + req_mt.formattype = FORMAT_VideoInfo; + req_mt.cbFormat = sizeof(VIDEOINFOHEADER); + req_mt.pbFormat = (BYTE *)&vih; + + for (i = 0; i < ARRAY_SIZE(subtype_tests); ++i) + { + memcpy(&req_mt.subtype, subtype_tests[i], sizeof(GUID)); + hr = IPin_QueryAccept(pin, &req_mt); + todo_wine_if(i) + ok(hr == S_OK, "Got hr %#lx.\n", hr); + } + + for (i = 0; i < ARRAY_SIZE(unsupported_subtype_tests); ++i) + { + memcpy(&req_mt.subtype, unsupported_subtype_tests[i], sizeof(GUID)); + hr = IPin_QueryAccept(pin, &req_mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + } + + IPin_Release(pin); + + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + static IMFMediaType * create_video_type(const GUID *subtype) { IMFMediaType *video_type; @@ -3306,6 +3359,7 @@ START_TEST(evr) test_misc_flags(); test_display_control(); test_service_lookup(); + test_query_accept();
test_default_mixer(); test_default_mixer_type_negotiation();
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=122161
Your paranoid android.
=== w7u_2qxl (32 bit report) ===
evr: evr.c:286: Test failed: Got hr 0x80004002, expected 0. evr.c:617: Test failed: Unexpected hr 0x80004002.
=== w7u_adm (32 bit report) ===
evr: evr.c:286: Test failed: Got hr 0x80004002, expected 0. evr.c:617: Test failed: Unexpected hr 0x80004002.
=== w7u_el (32 bit report) ===
evr: evr.c:286: Test failed: Got hr 0x80004002, expected 0. evr.c:617: Test failed: Unexpected hr 0x80004002.