Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

680 lines
22 KiB

  1. /****************************************************************************
  2. *
  3. * File: showinfo.cpp
  4. * Project: DxDiag (DirectX Diagnostic Tool)
  5. * Author: Mike Anderson (manders@microsoft.com)
  6. * Purpose: Gather information about DirectShow on this machine
  7. *
  8. * (C) Copyright 2001 Microsoft Corp. All rights reserved.
  9. *
  10. ****************************************************************************/
  11. #include <windows.h>
  12. #include <stdio.h>
  13. #include <strmif.h> // Generated IDL header file for streams interfaces
  14. #include <uuids.h> // declaration of type GUIDs and well-known clsids
  15. #include <assert.h>
  16. #include <tchar.h>
  17. #include "sysinfo.h"
  18. #include "fileinfo.h" // for GetFileVersion
  19. #include "showinfo.h"
  20. /****************************************************************************
  21. *
  22. * Helper IAMFilterData - cut and paste from dshow\h\fil_data.c
  23. *
  24. ****************************************************************************/
  25. /* verify that the <rpcndr.h> version is high enough to compile this file*/
  26. #ifndef __REQUIRED_RPCNDR_H_VERSION__
  27. #define __REQUIRED_RPCNDR_H_VERSION__ 440
  28. #endif
  29. #include "rpc.h"
  30. #include "rpcndr.h"
  31. #ifndef __RPCNDR_H_VERSION__
  32. #error this stub requires an updated version of <rpcndr.h>
  33. #endif // __RPCNDR_H_VERSION__
  34. #ifndef COM_NO_WINDOWS_H
  35. #include "windows.h"
  36. #include "ole2.h"
  37. #endif /*COM_NO_WINDOWS_H*/
  38. #ifndef __fil_data_h__
  39. #define __fil_data_h__
  40. #ifdef __cplusplus
  41. extern "C"{
  42. #endif
  43. /* Forward Declarations */
  44. #ifndef __IAMFilterData_FWD_DEFINED__
  45. #define __IAMFilterData_FWD_DEFINED__
  46. typedef interface IAMFilterData IAMFilterData;
  47. #endif /* __IAMFilterData_FWD_DEFINED__ */
  48. /* header files for imported files */
  49. #include "unknwn.h"
  50. #include "strmif.h"
  51. void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
  52. void __RPC_USER MIDL_user_free( void __RPC_FAR * );
  53. /* interface __MIDL_itf_fil_data_0000 */
  54. /* [local] */
  55. extern RPC_IF_HANDLE __MIDL_itf_fil_data_0000_v0_0_c_ifspec;
  56. extern RPC_IF_HANDLE __MIDL_itf_fil_data_0000_v0_0_s_ifspec;
  57. #ifndef __IAMFilterData_INTERFACE_DEFINED__
  58. #define __IAMFilterData_INTERFACE_DEFINED__
  59. /* interface IAMFilterData */
  60. /* [unique][uuid][object] */
  61. EXTERN_C const IID IID_IAMFilterData;
  62. #if defined(__cplusplus) && !defined(CINTERFACE)
  63. MIDL_INTERFACE("97f7c4d4-547b-4a5f-8332-536430ad2e4d")
  64. IAMFilterData : public IUnknown
  65. {
  66. public:
  67. virtual HRESULT STDMETHODCALLTYPE ParseFilterData(
  68. /* [size_is][in] */ BYTE __RPC_FAR *rgbFilterData,
  69. /* [in] */ ULONG cb,
  70. /* [out] */ BYTE __RPC_FAR *__RPC_FAR *prgbRegFilter2) = 0;
  71. virtual HRESULT STDMETHODCALLTYPE CreateFilterData(
  72. /* [in] */ REGFILTER2 __RPC_FAR *prf2,
  73. /* [out] */ BYTE __RPC_FAR *__RPC_FAR *prgbFilterData,
  74. /* [out] */ ULONG __RPC_FAR *pcb) = 0;
  75. };
  76. #else /* C style interface */
  77. typedef struct IAMFilterDataVtbl
  78. {
  79. BEGIN_INTERFACE
  80. HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
  81. IAMFilterData __RPC_FAR * This,
  82. /* [in] */ REFIID riid,
  83. /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);
  84. ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
  85. IAMFilterData __RPC_FAR * This);
  86. ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
  87. IAMFilterData __RPC_FAR * This);
  88. HRESULT ( STDMETHODCALLTYPE __RPC_FAR *ParseFilterData )(
  89. IAMFilterData __RPC_FAR * This,
  90. /* [size_is][in] */ BYTE __RPC_FAR *rgbFilterData,
  91. /* [in] */ ULONG cb,
  92. /* [out] */ BYTE __RPC_FAR *__RPC_FAR *prgbRegFilter2);
  93. HRESULT ( STDMETHODCALLTYPE __RPC_FAR *CreateFilterData )(
  94. IAMFilterData __RPC_FAR * This,
  95. /* [in] */ REGFILTER2 __RPC_FAR *prf2,
  96. /* [out] */ BYTE __RPC_FAR *__RPC_FAR *prgbFilterData,
  97. /* [out] */ ULONG __RPC_FAR *pcb);
  98. END_INTERFACE
  99. } IAMFilterDataVtbl;
  100. interface IAMFilterData
  101. {
  102. CONST_VTBL struct IAMFilterDataVtbl __RPC_FAR *lpVtbl;
  103. };
  104. #ifdef COBJMACROS
  105. #define IAMFilterData_QueryInterface(This,riid,ppvObject) \
  106. (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)
  107. #define IAMFilterData_AddRef(This) \
  108. (This)->lpVtbl -> AddRef(This)
  109. #define IAMFilterData_Release(This) \
  110. (This)->lpVtbl -> Release(This)
  111. #define IAMFilterData_ParseFilterData(This,rgbFilterData,cb,prgbRegFilter2) \
  112. (This)->lpVtbl -> ParseFilterData(This,rgbFilterData,cb,prgbRegFilter2)
  113. #define IAMFilterData_CreateFilterData(This,prf2,prgbFilterData,pcb) \
  114. (This)->lpVtbl -> CreateFilterData(This,prf2,prgbFilterData,pcb)
  115. #endif /* COBJMACROS */
  116. #endif /* C style interface */
  117. HRESULT STDMETHODCALLTYPE IAMFilterData_ParseFilterData_Proxy(
  118. IAMFilterData __RPC_FAR * This,
  119. /* [size_is][in] */ BYTE __RPC_FAR *rgbFilterData,
  120. /* [in] */ ULONG cb,
  121. /* [out] */ BYTE __RPC_FAR *__RPC_FAR *prgbRegFilter2);
  122. void __RPC_STUB IAMFilterData_ParseFilterData_Stub(
  123. IRpcStubBuffer *This,
  124. IRpcChannelBuffer *_pRpcChannelBuffer,
  125. PRPC_MESSAGE _pRpcMessage,
  126. DWORD *_pdwStubPhase);
  127. HRESULT STDMETHODCALLTYPE IAMFilterData_CreateFilterData_Proxy(
  128. IAMFilterData __RPC_FAR * This,
  129. /* [in] */ REGFILTER2 __RPC_FAR *prf2,
  130. /* [out] */ BYTE __RPC_FAR *__RPC_FAR *prgbFilterData,
  131. /* [out] */ ULONG __RPC_FAR *pcb);
  132. void __RPC_STUB IAMFilterData_CreateFilterData_Stub(
  133. IRpcStubBuffer *This,
  134. IRpcChannelBuffer *_pRpcChannelBuffer,
  135. PRPC_MESSAGE _pRpcMessage,
  136. DWORD *_pdwStubPhase);
  137. #endif /* __IAMFilterData_INTERFACE_DEFINED__ */
  138. /* Additional Prototypes for ALL interfaces */
  139. /* end of Additional Prototypes */
  140. #ifdef __cplusplus
  141. }
  142. #endif
  143. #endif
  144. /****************************************************************************
  145. *
  146. * Helper IAMFilterData - cut and paste from dshow\h\fil_data_i.c
  147. *
  148. ****************************************************************************/
  149. #ifdef __cplusplus
  150. extern "C"{
  151. #endif
  152. #ifndef __IID_DEFINED__
  153. #define __IID_DEFINED__
  154. typedef struct _IID
  155. {
  156. unsigned long x;
  157. unsigned short s1;
  158. unsigned short s2;
  159. unsigned char c[8];
  160. } IID;
  161. #endif // __IID_DEFINED__
  162. #ifndef CLSID_DEFINED
  163. #define CLSID_DEFINED
  164. typedef IID CLSID;
  165. #endif // CLSID_DEFINED
  166. const IID IID_IAMFilterData = {0x97f7c4d4,0x547b,0x4a5f,{0x83,0x32,0x53,0x64,0x30,0xad,0x2e,0x4d}};
  167. #ifdef __cplusplus
  168. }
  169. #endif
  170. /****************************************************************************
  171. *
  172. * Forward declaration
  173. *
  174. ****************************************************************************/
  175. HRESULT GenerateFilterList(ShowInfo* pShowInfo);
  176. HRESULT EnumerateFilterPerCategory(ShowInfo* pShowInfo, CLSID* clsid, WCHAR* wszCatName);
  177. HRESULT GetFilterInfo(IMoniker* pMon, IAMFilterData* pFD, FilterInfo* pFilterInfo);
  178. /****************************************************************************
  179. *
  180. * GetBasicShowInfo - Get minimal info on DirectShow
  181. *
  182. ****************************************************************************/
  183. HRESULT GetBasicShowInfo(ShowInfo** ppShowInfo)
  184. {
  185. ShowInfo* pShowInfoNew;
  186. pShowInfoNew = new ShowInfo;
  187. if (pShowInfoNew == NULL)
  188. return E_OUTOFMEMORY;
  189. ZeroMemory(pShowInfoNew, sizeof(ShowInfo));
  190. *ppShowInfo = pShowInfoNew;
  191. return GenerateFilterList(pShowInfoNew);
  192. }
  193. /****************************************************************************
  194. *
  195. * DestroyShowInfo
  196. *
  197. ****************************************************************************/
  198. VOID DestroyShowInfo(ShowInfo* pShowInfo)
  199. {
  200. if (!pShowInfo) return;
  201. if (pShowInfo->m_dwFilters)
  202. {
  203. FilterInfo* pFilterInfo;
  204. FilterInfo* pFilterInfoNext;
  205. pFilterInfo = pShowInfo->m_pFilters;
  206. while(pFilterInfo)
  207. {
  208. pFilterInfoNext = pFilterInfo->m_pFilterInfoNext;
  209. delete pFilterInfo;
  210. pFilterInfo = pFilterInfoNext;
  211. }
  212. }
  213. delete pShowInfo;
  214. }
  215. HRESULT GenerateFilterList(ShowInfo* pShowInfo)
  216. {
  217. HRESULT hr;
  218. ICreateDevEnum* pSysDevEnum = NULL;
  219. IEnumMoniker* pMonEnum = NULL;
  220. IMoniker* pMon = NULL;
  221. ULONG cFetched;
  222. pShowInfo->m_dwFilters = 0;
  223. hr = CoCreateInstance(CLSID_SystemDeviceEnum,
  224. NULL,
  225. CLSCTX_INPROC,
  226. IID_ICreateDevEnum,
  227. (void **)&pSysDevEnum);
  228. if FAILED(hr)
  229. {
  230. return hr;
  231. }
  232. // Use the meta-category that contains a list of all categories.
  233. // This emulates the behavior of Graphedit.
  234. hr = pSysDevEnum->CreateClassEnumerator(CLSID_ActiveMovieCategories, &pMonEnum, 0);
  235. pSysDevEnum->Release();
  236. if FAILED(hr)
  237. {
  238. return hr;
  239. }
  240. // Enumerate over every category
  241. while (hr = pMonEnum->Next(1, &pMon, &cFetched), hr == S_OK)
  242. {
  243. IPropertyBag *pPropBag;
  244. // Associate moniker with a file
  245. hr = pMon->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
  246. if (SUCCEEDED(hr))
  247. {
  248. WCHAR wszCatName[1024] = L"";
  249. CLSID clsidCategory;
  250. VARIANT var;
  251. var.vt = VT_BSTR;
  252. // Get friendly name
  253. hr = pPropBag->Read(L"FriendlyName", &var, 0);
  254. if(SUCCEEDED(hr))
  255. {
  256. wcsncpy(wszCatName, var.bstrVal, 1024);
  257. wszCatName[1023]=0;
  258. SysFreeString(var.bstrVal);
  259. }
  260. // Get CLSID string from property bag
  261. hr = pPropBag->Read(L"CLSID", &var, 0);
  262. if (SUCCEEDED(hr))
  263. {
  264. if (CLSIDFromString(var.bstrVal, &clsidCategory) == S_OK)
  265. {
  266. if (TEXT('\0') == wszCatName[0])
  267. {
  268. wcsncpy(wszCatName, var.bstrVal, 1024);
  269. wszCatName[1023]=0;
  270. }
  271. }
  272. SysFreeString(var.bstrVal);
  273. }
  274. pPropBag->Release();
  275. // Start to enumerate the filters for this one category
  276. hr = EnumerateFilterPerCategory(pShowInfo, &clsidCategory, wszCatName);
  277. }
  278. pMon->Release();
  279. }
  280. pMonEnum->Release();
  281. return hr;
  282. }
  283. HRESULT EnumerateFilterPerCategory(ShowInfo* pShowInfo, CLSID* clsid, WCHAR* wszCatName)
  284. {
  285. HRESULT hr;
  286. ICreateDevEnum* pSysDevEnum = NULL;
  287. IEnumMoniker *pMonEnum = NULL;
  288. IMoniker *pMon = NULL;
  289. ULONG cFetched;
  290. #ifdef RUNNING_VC
  291. // WMP bug 29936: Voxware codec corrupt: MSMS001 : corrupted heap
  292. // This causes this call int3 when inside a debugger so skip
  293. const CLSID clsidACMClassManager = {0x33d9a761,0x90c8,0x11d0,{0xbd,0x43,0x00,0xa0,0xc9,0x11,0xce,0x86}};
  294. if( *clsid == clsidACMClassManager )
  295. return S_OK;
  296. #endif
  297. hr = CoCreateInstance(CLSID_SystemDeviceEnum,
  298. NULL,
  299. CLSCTX_INPROC,
  300. IID_ICreateDevEnum,
  301. (void **)&pSysDevEnum);
  302. if FAILED(hr)
  303. {
  304. return hr;
  305. }
  306. hr = pSysDevEnum->CreateClassEnumerator(*clsid, &pMonEnum, 0);
  307. pSysDevEnum->Release();
  308. if FAILED(hr)
  309. {
  310. return hr;
  311. }
  312. // If there are no filters of a requested category, don't do anything.
  313. if(NULL == pMonEnum)
  314. {
  315. // could added a string to denote an empty category
  316. return S_FALSE;
  317. }
  318. FilterInfo** ppFilterInfo;
  319. FilterInfo* pFilterInfoNew;
  320. ppFilterInfo = &(pShowInfo->m_pFilters);
  321. while (NULL != *ppFilterInfo)
  322. ppFilterInfo = &((*ppFilterInfo)->m_pFilterInfoNext);
  323. // Enumerate all items associated with the moniker
  324. while(pMonEnum->Next(1, &pMon, &cFetched) == S_OK)
  325. {
  326. // get a new record for FilterInfo
  327. pFilterInfoNew = new FilterInfo;
  328. if (pFilterInfoNew == NULL)
  329. {
  330. hr = E_OUTOFMEMORY;
  331. break;
  332. }
  333. ZeroMemory(pFilterInfoNew, sizeof(FilterInfo));
  334. *ppFilterInfo = pFilterInfoNew;
  335. ppFilterInfo = &(pFilterInfoNew->m_pFilterInfoNext);
  336. pShowInfo->m_dwFilters++;
  337. // set category clsid and friendly name
  338. pFilterInfoNew->m_ClsidCat = *clsid;
  339. #ifdef _UNICODE
  340. wcsncpy(pFilterInfoNew->m_szCatName, wszCatName, 1024);
  341. pFilterInfoNew->m_szCatName[1023]=0;
  342. #else
  343. WideCharToMultiByte(CP_ACP,
  344. 0,
  345. wszCatName,
  346. -1,
  347. pFilterInfoNew->m_szCatName,
  348. sizeof(pFilterInfoNew->m_szCatName),
  349. 0,
  350. 0);
  351. wszCatName[1023]=0;
  352. #endif
  353. IPropertyBag *pPropBag;
  354. // associate moniker with a file
  355. hr = pMon->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
  356. if (SUCCEEDED(hr))
  357. {
  358. VARIANT var;
  359. var.vt = VT_BSTR;
  360. // get filter's friendly name
  361. hr = pPropBag->Read(L"FriendlyName", &var, 0);
  362. if (SUCCEEDED(hr))
  363. {
  364. #ifdef _UNICODE
  365. wcsncpy(pFilterInfoNew->m_szName, var.bstrVal, 1024);
  366. pFilterInfoNew->m_szName[1023]=0;
  367. #else
  368. WideCharToMultiByte(CP_ACP,
  369. 0,
  370. var.bstrVal,
  371. -1,
  372. pFilterInfoNew->m_szName,
  373. sizeof(pFilterInfoNew->m_szName),
  374. 0,
  375. 0);
  376. pFilterInfoNew->m_szName[1023]=0;
  377. #endif
  378. SysFreeString(var.bstrVal);
  379. }
  380. // get filter's CLSID
  381. hr = pPropBag->Read(L"CLSID", &var, 0);
  382. if(SUCCEEDED(hr))
  383. {
  384. if(CLSIDFromString(var.bstrVal, &(pFilterInfoNew->m_ClsidFilter)) == S_OK)
  385. {
  386. // use the guid if we can't get the friendly name
  387. if (TEXT('\0') == pFilterInfoNew->m_szName[0])
  388. {
  389. #ifdef _UNICODE
  390. wcsncpy(pFilterInfoNew->m_szName, var.bstrVal, 1024);
  391. pFilterInfoNew->m_szName[1023]=0;
  392. #else
  393. WideCharToMultiByte(CP_ACP,
  394. 0,
  395. var.bstrVal,
  396. -1,
  397. pFilterInfoNew->m_szName,
  398. sizeof(pFilterInfoNew->m_szName),
  399. 0,
  400. 0);
  401. pFilterInfoNew->m_szName[1023]=0;
  402. #endif
  403. }
  404. }
  405. SysFreeString(var.bstrVal);
  406. }
  407. pPropBag->Release();
  408. }
  409. // start grabbing filter info
  410. IAMFilterData *pFD;
  411. hr = CoCreateInstance(CLSID_FilterMapper,
  412. NULL,
  413. CLSCTX_INPROC_SERVER,
  414. IID_IAMFilterData,
  415. (void **)&pFD);
  416. if(SUCCEEDED(hr))
  417. {
  418. hr = GetFilterInfo(pMon, pFD, pFilterInfoNew);
  419. pFD->Release();
  420. }
  421. else
  422. {
  423. // Must not be on DX8 or above...
  424. }
  425. pMon->Release();
  426. }
  427. pMonEnum->Release();
  428. return hr;
  429. }
  430. HRESULT GetFilterInfo(IMoniker* pMon, IAMFilterData* pFD, FilterInfo* pFilterInfo)
  431. {
  432. HRESULT hr;
  433. IPropertyBag *pPropBag;
  434. hr = pMon->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPropBag);
  435. if(SUCCEEDED(hr))
  436. {
  437. VARIANT varFilData;
  438. varFilData.vt = VT_UI1 | VT_ARRAY;
  439. varFilData.parray = 0; // docs say zero this
  440. BYTE *pbFilterData = NULL;
  441. DWORD dwcbFilterDAta = 0; // 0 if not read
  442. hr = pPropBag->Read(L"FilterData", &varFilData, 0);
  443. if(SUCCEEDED(hr))
  444. {
  445. if( varFilData.vt == (VT_UI1 | VT_ARRAY) )
  446. {
  447. dwcbFilterDAta = varFilData.parray->rgsabound[0].cElements;
  448. if( SUCCEEDED( SafeArrayAccessData(varFilData.parray, (void **)&pbFilterData) ) )
  449. {
  450. BYTE *pb = NULL;
  451. hr = pFD->ParseFilterData(pbFilterData, dwcbFilterDAta, &pb);
  452. if(SUCCEEDED(hr))
  453. {
  454. REGFILTER2** ppRegFilter = (REGFILTER2**)pb;
  455. REGFILTER2* pFil = NULL;
  456. pFil = *ppRegFilter;
  457. if( pFil != NULL && pFil->dwVersion == 2 )
  458. {
  459. pFilterInfo->m_dwMerit = pFil->dwMerit; // set merit
  460. wsprintf(pFilterInfo->m_szVersion, TEXT("v%d"), pFil->dwVersion); // set version
  461. //
  462. // Display the filter's filename
  463. //
  464. // Read filter's CLSID from property bag. This CLSID string will be
  465. // used to find the filter's filename in the registry.
  466. VARIANT varFilterClsid;
  467. varFilterClsid.vt = VT_BSTR;
  468. hr = pPropBag->Read(L"CLSID", &varFilterClsid, 0);
  469. if(SUCCEEDED(hr))
  470. {
  471. TCHAR szKey[2048];
  472. // Convert BSTR to string
  473. WCHAR *wszFilterClsid;
  474. TCHAR szFilterClsid[1024];
  475. wszFilterClsid = varFilterClsid.bstrVal;
  476. #ifdef _UNICODE
  477. wcsncpy(szFilterClsid, wszFilterClsid, 1024);
  478. szFilterClsid[1023]=0;
  479. #else
  480. WideCharToMultiByte(CP_ACP,
  481. 0,
  482. wszFilterClsid,
  483. -1,
  484. szFilterClsid,
  485. sizeof(szFilterClsid),
  486. 0,
  487. 0);
  488. szFilterClsid[1023]=0;
  489. #endif
  490. // Create key name for reading filename registry
  491. _sntprintf(szKey, 2048, TEXT("Software\\Classes\\CLSID\\%s\\InprocServer32\0"),
  492. szFilterClsid);
  493. szKey[2047]=0;
  494. // Variables needed for registry query
  495. HKEY hkeyFilter=0;
  496. DWORD dwSize=MAX_PATH;
  497. TCHAR szFilename[MAX_PATH];
  498. int rc=0;
  499. // Open the CLSID key that contains information about the filter
  500. rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_READ, &hkeyFilter);
  501. if (rc == ERROR_SUCCESS)
  502. {
  503. rc = RegQueryValueEx(hkeyFilter, NULL, // Read (Default) value
  504. NULL, NULL, (BYTE*)szFilename, &dwSize);
  505. if (rc == ERROR_SUCCESS)
  506. {
  507. _tcsncpy( pFilterInfo->m_szFileName, szFilename, MAX_PATH ); // set file name & version
  508. pFilterInfo->m_szFileName[MAX_PATH-1]=0;
  509. GetFileVersion(pFilterInfo->m_szFileName, pFilterInfo->m_szFileVersion, NULL, NULL, NULL, NULL);
  510. }
  511. rc = RegCloseKey(hkeyFilter);
  512. }
  513. SysFreeString(varFilterClsid.bstrVal);
  514. }
  515. int iPinsInput = 0;
  516. int iPinsOutput = 0;
  517. for(UINT iPin = 0; iPin < pFil->cPins; iPin++)
  518. {
  519. if(pFil->rgPins2[iPin].dwFlags & REG_PINFLAG_B_OUTPUT)
  520. {
  521. iPinsOutput++;
  522. }
  523. else
  524. {
  525. iPinsInput++;
  526. }
  527. }
  528. pFilterInfo->m_dwInputs = iPinsInput; // set input
  529. pFilterInfo->m_dwOutputs = iPinsOutput; // set output
  530. }
  531. CoTaskMemFree( (BYTE*) pFil );
  532. }
  533. SafeArrayUnaccessData(varFilData.parray);
  534. }
  535. }
  536. VariantClear(&varFilData);
  537. }
  538. pPropBag->Release();
  539. }
  540. return hr;
  541. }