PageRenderTime 40ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/indra/llwindow/lldxhardware.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 670 lines | 357 code | 69 blank | 244 comment | 47 complexity | ec97bde63430225a6a0f2b60ffa26f6d MD5 | raw file
Possible License(s): LGPL-2.1
  1. /**
  2. * @file lldxhardware.cpp
  3. * @brief LLDXHardware implementation
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  6. * Second Life Viewer Source Code
  7. * Copyright (C) 2010, Linden Research, Inc.
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU Lesser General Public
  11. * License as published by the Free Software Foundation;
  12. * version 2.1 of the License only.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  22. *
  23. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  24. * $/LicenseInfo$
  25. */
  26. #ifdef LL_WINDOWS
  27. // Culled from some Microsoft sample code
  28. #include "linden_common.h"
  29. #define INITGUID
  30. #include <dxdiag.h>
  31. #undef INITGUID
  32. #include <boost/tokenizer.hpp>
  33. #include "lldxhardware.h"
  34. #include "llerror.h"
  35. #include "llstring.h"
  36. #include "llstl.h"
  37. #include "lltimer.h"
  38. void (*gWriteDebug)(const char* msg) = NULL;
  39. LLDXHardware gDXHardware;
  40. //-----------------------------------------------------------------------------
  41. // Defines, and constants
  42. //-----------------------------------------------------------------------------
  43. #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } }
  44. #define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } }
  45. #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
  46. std::string get_string(IDxDiagContainer *containerp, WCHAR *wszPropName)
  47. {
  48. HRESULT hr;
  49. VARIANT var;
  50. WCHAR wszPropValue[256];
  51. VariantInit( &var );
  52. hr = containerp->GetProp(wszPropName, &var );
  53. if( SUCCEEDED(hr) )
  54. {
  55. // Switch off the type. There's 4 different types:
  56. switch( var.vt )
  57. {
  58. case VT_UI4:
  59. swprintf( wszPropValue, L"%d", var.ulVal ); /* Flawfinder: ignore */
  60. break;
  61. case VT_I4:
  62. swprintf( wszPropValue, L"%d", var.lVal ); /* Flawfinder: ignore */
  63. break;
  64. case VT_BOOL:
  65. wcscpy( wszPropValue, (var.boolVal) ? L"true" : L"false" ); /* Flawfinder: ignore */
  66. break;
  67. case VT_BSTR:
  68. wcsncpy( wszPropValue, var.bstrVal, 255 ); /* Flawfinder: ignore */
  69. wszPropValue[255] = 0;
  70. break;
  71. }
  72. }
  73. // Clear the variant (this is needed to free BSTR memory)
  74. VariantClear( &var );
  75. return utf16str_to_utf8str(wszPropValue);
  76. }
  77. LLVersion::LLVersion()
  78. {
  79. mValid = FALSE;
  80. S32 i;
  81. for (i = 0; i < 4; i++)
  82. {
  83. mFields[i] = 0;
  84. }
  85. }
  86. BOOL LLVersion::set(const std::string &version_string)
  87. {
  88. S32 i;
  89. for (i = 0; i < 4; i++)
  90. {
  91. mFields[i] = 0;
  92. }
  93. // Split the version string.
  94. std::string str(version_string);
  95. typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
  96. boost::char_separator<char> sep(".", "", boost::keep_empty_tokens);
  97. tokenizer tokens(str, sep);
  98. tokenizer::iterator iter = tokens.begin();
  99. S32 count = 0;
  100. for (;(iter != tokens.end()) && (count < 4);++iter)
  101. {
  102. mFields[count] = atoi(iter->c_str());
  103. count++;
  104. }
  105. if (count < 4)
  106. {
  107. //llwarns << "Potentially bogus version string!" << version_string << llendl;
  108. for (i = 0; i < 4; i++)
  109. {
  110. mFields[i] = 0;
  111. }
  112. mValid = FALSE;
  113. }
  114. else
  115. {
  116. mValid = TRUE;
  117. }
  118. return mValid;
  119. }
  120. S32 LLVersion::getField(const S32 field_num)
  121. {
  122. if (!mValid)
  123. {
  124. return -1;
  125. }
  126. else
  127. {
  128. return mFields[field_num];
  129. }
  130. }
  131. std::string LLDXDriverFile::dump()
  132. {
  133. if (gWriteDebug)
  134. {
  135. gWriteDebug("Filename:");
  136. gWriteDebug(mName.c_str());
  137. gWriteDebug("\n");
  138. gWriteDebug("Ver:");
  139. gWriteDebug(mVersionString.c_str());
  140. gWriteDebug("\n");
  141. gWriteDebug("Date:");
  142. gWriteDebug(mDateString.c_str());
  143. gWriteDebug("\n");
  144. }
  145. llinfos << mFilepath << llendl;
  146. llinfos << mName << llendl;
  147. llinfos << mVersionString << llendl;
  148. llinfos << mDateString << llendl;
  149. return "";
  150. }
  151. LLDXDevice::~LLDXDevice()
  152. {
  153. for_each(mDriverFiles.begin(), mDriverFiles.end(), DeletePairedPointer());
  154. }
  155. std::string LLDXDevice::dump()
  156. {
  157. if (gWriteDebug)
  158. {
  159. gWriteDebug("StartDevice\n");
  160. gWriteDebug("DeviceName:");
  161. gWriteDebug(mName.c_str());
  162. gWriteDebug("\n");
  163. gWriteDebug("PCIString:");
  164. gWriteDebug(mPCIString.c_str());
  165. gWriteDebug("\n");
  166. }
  167. llinfos << llendl;
  168. llinfos << "DeviceName:" << mName << llendl;
  169. llinfos << "PCIString:" << mPCIString << llendl;
  170. llinfos << "Drivers" << llendl;
  171. llinfos << "-------" << llendl;
  172. for (driver_file_map_t::iterator iter = mDriverFiles.begin(),
  173. end = mDriverFiles.end();
  174. iter != end; iter++)
  175. {
  176. LLDXDriverFile *filep = iter->second;
  177. filep->dump();
  178. }
  179. if (gWriteDebug)
  180. {
  181. gWriteDebug("EndDevice\n");
  182. }
  183. return "";
  184. }
  185. LLDXDriverFile *LLDXDevice::findDriver(const std::string &driver)
  186. {
  187. for (driver_file_map_t::iterator iter = mDriverFiles.begin(),
  188. end = mDriverFiles.end();
  189. iter != end; iter++)
  190. {
  191. LLDXDriverFile *filep = iter->second;
  192. if (!utf8str_compare_insensitive(filep->mName,driver))
  193. {
  194. return filep;
  195. }
  196. }
  197. return NULL;
  198. }
  199. LLDXHardware::LLDXHardware()
  200. {
  201. mVRAM = 0;
  202. gWriteDebug = NULL;
  203. }
  204. void LLDXHardware::cleanup()
  205. {
  206. // for_each(mDevices.begin(), mDevices.end(), DeletePairedPointer());
  207. }
  208. /*
  209. std::string LLDXHardware::dumpDevices()
  210. {
  211. if (gWriteDebug)
  212. {
  213. gWriteDebug("\n");
  214. gWriteDebug("StartAllDevices\n");
  215. }
  216. for (device_map_t::iterator iter = mDevices.begin(),
  217. end = mDevices.end();
  218. iter != end; iter++)
  219. {
  220. LLDXDevice *devicep = iter->second;
  221. devicep->dump();
  222. }
  223. if (gWriteDebug)
  224. {
  225. gWriteDebug("EndAllDevices\n\n");
  226. }
  227. return "";
  228. }
  229. LLDXDevice *LLDXHardware::findDevice(const std::string &vendor, const std::string &devices)
  230. {
  231. // Iterate through different devices tokenized in devices string
  232. std::string str(devices);
  233. typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
  234. boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
  235. tokenizer tokens(str, sep);
  236. tokenizer::iterator iter = tokens.begin();
  237. for (;iter != tokens.end();++iter)
  238. {
  239. std::string dev_str = *iter;
  240. for (device_map_t::iterator iter = mDevices.begin(),
  241. end = mDevices.end();
  242. iter != end; iter++)
  243. {
  244. LLDXDevice *devicep = iter->second;
  245. if ((devicep->mVendorID == vendor)
  246. && (devicep->mDeviceID == dev_str))
  247. {
  248. return devicep;
  249. }
  250. }
  251. }
  252. return NULL;
  253. }
  254. */
  255. BOOL LLDXHardware::getInfo(BOOL vram_only)
  256. {
  257. LLTimer hw_timer;
  258. BOOL ok = FALSE;
  259. HRESULT hr;
  260. CoInitialize(NULL);
  261. IDxDiagProvider *dx_diag_providerp = NULL;
  262. IDxDiagContainer *dx_diag_rootp = NULL;
  263. IDxDiagContainer *devices_containerp = NULL;
  264. // IDxDiagContainer *system_device_containerp= NULL;
  265. IDxDiagContainer *device_containerp = NULL;
  266. IDxDiagContainer *file_containerp = NULL;
  267. IDxDiagContainer *driver_containerp = NULL;
  268. // CoCreate a IDxDiagProvider*
  269. LL_DEBUGS("AppInit") << "CoCreateInstance IID_IDxDiagProvider" << LL_ENDL;
  270. hr = CoCreateInstance(CLSID_DxDiagProvider,
  271. NULL,
  272. CLSCTX_INPROC_SERVER,
  273. IID_IDxDiagProvider,
  274. (LPVOID*) &dx_diag_providerp);
  275. if (FAILED(hr))
  276. {
  277. LL_WARNS("AppInit") << "No DXDiag provider found! DirectX 9 not installed!" << LL_ENDL;
  278. gWriteDebug("No DXDiag provider found! DirectX 9 not installed!\n");
  279. goto LCleanup;
  280. }
  281. if (SUCCEEDED(hr)) // if FAILED(hr) then dx9 is not installed
  282. {
  283. // Fill out a DXDIAG_INIT_PARAMS struct and pass it to IDxDiagContainer::Initialize
  284. // Passing in TRUE for bAllowWHQLChecks, allows dxdiag to check if drivers are
  285. // digital signed as logo'd by WHQL which may connect via internet to update
  286. // WHQL certificates.
  287. DXDIAG_INIT_PARAMS dx_diag_init_params;
  288. ZeroMemory(&dx_diag_init_params, sizeof(DXDIAG_INIT_PARAMS));
  289. dx_diag_init_params.dwSize = sizeof(DXDIAG_INIT_PARAMS);
  290. dx_diag_init_params.dwDxDiagHeaderVersion = DXDIAG_DX9_SDK_VERSION;
  291. dx_diag_init_params.bAllowWHQLChecks = TRUE;
  292. dx_diag_init_params.pReserved = NULL;
  293. LL_DEBUGS("AppInit") << "dx_diag_providerp->Initialize" << LL_ENDL;
  294. hr = dx_diag_providerp->Initialize(&dx_diag_init_params);
  295. if(FAILED(hr))
  296. {
  297. goto LCleanup;
  298. }
  299. LL_DEBUGS("AppInit") << "dx_diag_providerp->GetRootContainer" << LL_ENDL;
  300. hr = dx_diag_providerp->GetRootContainer( &dx_diag_rootp );
  301. if(FAILED(hr) || !dx_diag_rootp)
  302. {
  303. goto LCleanup;
  304. }
  305. HRESULT hr;
  306. // Get display driver information
  307. LL_DEBUGS("AppInit") << "dx_diag_rootp->GetChildContainer" << LL_ENDL;
  308. hr = dx_diag_rootp->GetChildContainer(L"DxDiag_DisplayDevices", &devices_containerp);
  309. if(FAILED(hr) || !devices_containerp)
  310. {
  311. goto LCleanup;
  312. }
  313. // Get device 0
  314. LL_DEBUGS("AppInit") << "devices_containerp->GetChildContainer" << LL_ENDL;
  315. hr = devices_containerp->GetChildContainer(L"0", &device_containerp);
  316. if(FAILED(hr) || !device_containerp)
  317. {
  318. goto LCleanup;
  319. }
  320. // Get the English VRAM string
  321. {
  322. std::string ram_str = get_string(device_containerp, L"szDisplayMemoryEnglish");
  323. // We don't need the device any more
  324. SAFE_RELEASE(device_containerp);
  325. // Dump the string as an int into the structure
  326. char *stopstring;
  327. mVRAM = strtol(ram_str.c_str(), &stopstring, 10);
  328. LL_INFOS("AppInit") << "VRAM Detected: " << mVRAM << " DX9 string: " << ram_str << LL_ENDL;
  329. }
  330. if (vram_only)
  331. {
  332. ok = TRUE;
  333. goto LCleanup;
  334. }
  335. /* for now, we ONLY do vram_only the rest of this
  336. is commented out, to ensure no-one is tempted
  337. to use it
  338. // Now let's get device and driver information
  339. // Get the IDxDiagContainer object called "DxDiag_SystemDevices".
  340. // This call may take some time while dxdiag gathers the info.
  341. DWORD num_devices = 0;
  342. WCHAR wszContainer[256];
  343. LL_DEBUGS("AppInit") << "dx_diag_rootp->GetChildContainer DxDiag_SystemDevices" << LL_ENDL;
  344. hr = dx_diag_rootp->GetChildContainer(L"DxDiag_SystemDevices", &system_device_containerp);
  345. if (FAILED(hr))
  346. {
  347. goto LCleanup;
  348. }
  349. hr = system_device_containerp->GetNumberOfChildContainers(&num_devices);
  350. if (FAILED(hr))
  351. {
  352. goto LCleanup;
  353. }
  354. LL_DEBUGS("AppInit") << "DX9 iterating over devices" << LL_ENDL;
  355. S32 device_num = 0;
  356. for (device_num = 0; device_num < (S32)num_devices; device_num++)
  357. {
  358. hr = system_device_containerp->EnumChildContainerNames(device_num, wszContainer, 256);
  359. if (FAILED(hr))
  360. {
  361. goto LCleanup;
  362. }
  363. hr = system_device_containerp->GetChildContainer(wszContainer, &device_containerp);
  364. if (FAILED(hr) || device_containerp == NULL)
  365. {
  366. goto LCleanup;
  367. }
  368. std::string device_name = get_string(device_containerp, L"szDescription");
  369. std::string device_id = get_string(device_containerp, L"szDeviceID");
  370. LLDXDevice *dxdevicep = new LLDXDevice;
  371. dxdevicep->mName = device_name;
  372. dxdevicep->mPCIString = device_id;
  373. mDevices[dxdevicep->mPCIString] = dxdevicep;
  374. // Split the PCI string based on vendor, device, subsys, rev.
  375. std::string str(device_id);
  376. typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
  377. boost::char_separator<char> sep("&\\", "", boost::keep_empty_tokens);
  378. tokenizer tokens(str, sep);
  379. tokenizer::iterator iter = tokens.begin();
  380. S32 count = 0;
  381. BOOL valid = TRUE;
  382. for (;(iter != tokens.end()) && (count < 3);++iter)
  383. {
  384. switch (count)
  385. {
  386. case 0:
  387. if (strcmp(iter->c_str(), "PCI"))
  388. {
  389. valid = FALSE;
  390. }
  391. break;
  392. case 1:
  393. dxdevicep->mVendorID = iter->c_str();
  394. break;
  395. case 2:
  396. dxdevicep->mDeviceID = iter->c_str();
  397. break;
  398. default:
  399. // Ignore it
  400. break;
  401. }
  402. count++;
  403. }
  404. // Now, iterate through the related drivers
  405. hr = device_containerp->GetChildContainer(L"Drivers", &driver_containerp);
  406. if (FAILED(hr) || !driver_containerp)
  407. {
  408. goto LCleanup;
  409. }
  410. DWORD num_files = 0;
  411. hr = driver_containerp->GetNumberOfChildContainers(&num_files);
  412. if (FAILED(hr))
  413. {
  414. goto LCleanup;
  415. }
  416. S32 file_num = 0;
  417. for (file_num = 0; file_num < (S32)num_files; file_num++ )
  418. {
  419. hr = driver_containerp->EnumChildContainerNames(file_num, wszContainer, 256);
  420. if (FAILED(hr))
  421. {
  422. goto LCleanup;
  423. }
  424. hr = driver_containerp->GetChildContainer(wszContainer, &file_containerp);
  425. if (FAILED(hr) || file_containerp == NULL)
  426. {
  427. goto LCleanup;
  428. }
  429. std::string driver_path = get_string(file_containerp, L"szPath");
  430. std::string driver_name = get_string(file_containerp, L"szName");
  431. std::string driver_version = get_string(file_containerp, L"szVersion");
  432. std::string driver_date = get_string(file_containerp, L"szDatestampEnglish");
  433. LLDXDriverFile *dxdriverfilep = new LLDXDriverFile;
  434. dxdriverfilep->mName = driver_name;
  435. dxdriverfilep->mFilepath= driver_path;
  436. dxdriverfilep->mVersionString = driver_version;
  437. dxdriverfilep->mVersion.set(driver_version);
  438. dxdriverfilep->mDateString = driver_date;
  439. dxdevicep->mDriverFiles[driver_name] = dxdriverfilep;
  440. SAFE_RELEASE(file_containerp);
  441. }
  442. SAFE_RELEASE(device_containerp);
  443. }
  444. */
  445. }
  446. // dumpDevices();
  447. ok = TRUE;
  448. LCleanup:
  449. if (!ok)
  450. {
  451. LL_WARNS("AppInit") << "DX9 probe failed" << LL_ENDL;
  452. gWriteDebug("DX9 probe failed\n");
  453. }
  454. SAFE_RELEASE(file_containerp);
  455. SAFE_RELEASE(driver_containerp);
  456. SAFE_RELEASE(device_containerp);
  457. SAFE_RELEASE(devices_containerp);
  458. SAFE_RELEASE(dx_diag_rootp);
  459. SAFE_RELEASE(dx_diag_providerp);
  460. CoUninitialize();
  461. return ok;
  462. }
  463. LLSD LLDXHardware::getDisplayInfo()
  464. {
  465. LLTimer hw_timer;
  466. HRESULT hr;
  467. LLSD ret;
  468. CoInitialize(NULL);
  469. IDxDiagProvider *dx_diag_providerp = NULL;
  470. IDxDiagContainer *dx_diag_rootp = NULL;
  471. IDxDiagContainer *devices_containerp = NULL;
  472. IDxDiagContainer *device_containerp = NULL;
  473. IDxDiagContainer *file_containerp = NULL;
  474. IDxDiagContainer *driver_containerp = NULL;
  475. // CoCreate a IDxDiagProvider*
  476. llinfos << "CoCreateInstance IID_IDxDiagProvider" << llendl;
  477. hr = CoCreateInstance(CLSID_DxDiagProvider,
  478. NULL,
  479. CLSCTX_INPROC_SERVER,
  480. IID_IDxDiagProvider,
  481. (LPVOID*) &dx_diag_providerp);
  482. if (FAILED(hr))
  483. {
  484. llwarns << "No DXDiag provider found! DirectX 9 not installed!" << llendl;
  485. gWriteDebug("No DXDiag provider found! DirectX 9 not installed!\n");
  486. goto LCleanup;
  487. }
  488. if (SUCCEEDED(hr)) // if FAILED(hr) then dx9 is not installed
  489. {
  490. // Fill out a DXDIAG_INIT_PARAMS struct and pass it to IDxDiagContainer::Initialize
  491. // Passing in TRUE for bAllowWHQLChecks, allows dxdiag to check if drivers are
  492. // digital signed as logo'd by WHQL which may connect via internet to update
  493. // WHQL certificates.
  494. DXDIAG_INIT_PARAMS dx_diag_init_params;
  495. ZeroMemory(&dx_diag_init_params, sizeof(DXDIAG_INIT_PARAMS));
  496. dx_diag_init_params.dwSize = sizeof(DXDIAG_INIT_PARAMS);
  497. dx_diag_init_params.dwDxDiagHeaderVersion = DXDIAG_DX9_SDK_VERSION;
  498. dx_diag_init_params.bAllowWHQLChecks = TRUE;
  499. dx_diag_init_params.pReserved = NULL;
  500. llinfos << "dx_diag_providerp->Initialize" << llendl;
  501. hr = dx_diag_providerp->Initialize(&dx_diag_init_params);
  502. if(FAILED(hr))
  503. {
  504. goto LCleanup;
  505. }
  506. llinfos << "dx_diag_providerp->GetRootContainer" << llendl;
  507. hr = dx_diag_providerp->GetRootContainer( &dx_diag_rootp );
  508. if(FAILED(hr) || !dx_diag_rootp)
  509. {
  510. goto LCleanup;
  511. }
  512. HRESULT hr;
  513. // Get display driver information
  514. llinfos << "dx_diag_rootp->GetChildContainer" << llendl;
  515. hr = dx_diag_rootp->GetChildContainer(L"DxDiag_DisplayDevices", &devices_containerp);
  516. if(FAILED(hr) || !devices_containerp)
  517. {
  518. goto LCleanup;
  519. }
  520. // Get device 0
  521. llinfos << "devices_containerp->GetChildContainer" << llendl;
  522. hr = devices_containerp->GetChildContainer(L"0", &device_containerp);
  523. if(FAILED(hr) || !device_containerp)
  524. {
  525. goto LCleanup;
  526. }
  527. // Get the English VRAM string
  528. std::string ram_str = get_string(device_containerp, L"szDisplayMemoryEnglish");
  529. // Dump the string as an int into the structure
  530. char *stopstring;
  531. ret["VRAM"] = strtol(ram_str.c_str(), &stopstring, 10);
  532. std::string device_name = get_string(device_containerp, L"szDescription");
  533. ret["DeviceName"] = device_name;
  534. std::string device_driver= get_string(device_containerp, L"szDriverVersion");
  535. ret["DriverVersion"] = device_driver;
  536. // ATI has a slightly different version string
  537. if(device_name.length() >= 4 && device_name.substr(0,4) == "ATI ")
  538. {
  539. // get the key
  540. HKEY hKey;
  541. const DWORD RV_SIZE = 100;
  542. WCHAR release_version[RV_SIZE];
  543. // Hard coded registry entry. Using this since it's simpler for now.
  544. // And using EnumDisplayDevices to get a registry key also requires
  545. // a hard coded Query value.
  546. if(ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\ATI Technologies\\CBT"), &hKey))
  547. {
  548. // get the value
  549. DWORD dwType = REG_SZ;
  550. DWORD dwSize = sizeof(WCHAR) * RV_SIZE;
  551. if(ERROR_SUCCESS == RegQueryValueEx(hKey, TEXT("ReleaseVersion"),
  552. NULL, &dwType, (LPBYTE)release_version, &dwSize))
  553. {
  554. // print the value
  555. // windows doesn't guarantee to be null terminated
  556. release_version[RV_SIZE - 1] = NULL;
  557. ret["DriverVersion"] = utf16str_to_utf8str(release_version);
  558. }
  559. RegCloseKey(hKey);
  560. }
  561. }
  562. }
  563. LCleanup:
  564. SAFE_RELEASE(file_containerp);
  565. SAFE_RELEASE(driver_containerp);
  566. SAFE_RELEASE(device_containerp);
  567. SAFE_RELEASE(devices_containerp);
  568. SAFE_RELEASE(dx_diag_rootp);
  569. SAFE_RELEASE(dx_diag_providerp);
  570. CoUninitialize();
  571. return ret;
  572. }
  573. void LLDXHardware::setWriteDebugFunc(void (*func)(const char*))
  574. {
  575. gWriteDebug = func;
  576. }
  577. #endif