Source code of Windows XP (NT5)
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.

645 lines
19 KiB

  1. //---------------------------------------------------------------------------
  2. //
  3. //---------------------------------------------------------------------------
  4. #include "grpconv.h"
  5. #include "util.h"
  6. #include "rcids.h"
  7. //---------------------------------------------------------------------------
  8. // Global to this file only.
  9. const TCHAR g_szDot[] = TEXT(".");
  10. const TCHAR g_szShellOpenCommand[] = TEXT("\\Shell\\Open\\Command");
  11. const TCHAR c_szElipses[] = TEXT("...");
  12. const TCHAR c_szSpace[] = TEXT(" ");
  13. const TCHAR c_szUS[] = TEXT("_");
  14. static BOOL g_fShowProgressDlg = FALSE;
  15. HWND g_hwndProgress = NULL; // Progress dialog.
  16. //---------------------------------------------------------------------------
  17. LRESULT CALLBACK ProgressWndProc(HWND hdlg, UINT msg, WPARAM wparam, LPARAM lparam)
  18. {
  19. switch (msg)
  20. {
  21. case WM_INITDIALOG:
  22. SetDlgItemText(hdlg, IDC_GROUPNAME, (LPTSTR)lparam);
  23. EnableMenuItem(GetSystemMenu(hdlg, FALSE), SC_CLOSE, MF_BYCOMMAND|MF_DISABLED|MF_GRAYED);
  24. return TRUE;
  25. }
  26. return 0;
  27. }
  28. //---------------------------------------------------------------------------
  29. void ShowProgressDlg(void)
  30. {
  31. // Has someone tried to create the dialog but it isn't up yet?
  32. if (g_fShowUI && g_fShowProgressDlg && !g_hwndProgress)
  33. {
  34. // Yep.
  35. // NB We can handle this failing, we just try to carry on without
  36. // the dialog.
  37. g_hwndProgress = CreateDialog(g_hinst, MAKEINTRESOURCE(DLG_PROGRESS), NULL, ProgressWndProc);
  38. }
  39. }
  40. //---------------------------------------------------------------------------
  41. void Group_CreateProgressDlg(void)
  42. {
  43. // NB We just set a flag here, the first guy to try to set the
  44. // current progress actually puts up the dialag.
  45. g_fShowProgressDlg = TRUE;
  46. }
  47. //---------------------------------------------------------------------------
  48. void Group_DestroyProgressDlg(void)
  49. {
  50. if (g_hwndProgress)
  51. {
  52. DestroyWindow(g_hwndProgress);
  53. g_hwndProgress = NULL;
  54. }
  55. g_fShowProgressDlg = FALSE;
  56. }
  57. //---------------------------------------------------------------------------
  58. // If the text is too long, lop off the end and stick on some elipses.
  59. void Text_TruncateAndAddElipses(HWND hwnd, LPTSTR lpszText)
  60. {
  61. RECT rcClient;
  62. SIZE sizeText;
  63. SIZE sizeElipses;
  64. HDC hdc;
  65. UINT cch;
  66. Assert(hwnd);
  67. Assert(lpszText);
  68. hdc = GetDC(hwnd);
  69. if (hdc)
  70. {
  71. GetClientRect(hwnd, &rcClient);
  72. GetTextExtentPoint(hdc, lpszText, lstrlen(lpszText), &sizeText);
  73. // Is the text too long?
  74. if (sizeText.cx > rcClient.right)
  75. {
  76. // Yes, it is, clip it.
  77. GetTextExtentPoint(hdc, c_szElipses, 3, &sizeElipses);
  78. GetTextExtentExPoint(hdc, lpszText, lstrlen(lpszText), rcClient.right - sizeElipses.cx,
  79. &cch, NULL, &sizeText);
  80. lstrcpy(lpszText+cch, c_szElipses);
  81. }
  82. ReleaseDC(hwnd, hdc);
  83. }
  84. }
  85. //---------------------------------------------------------------------------
  86. void Group_SetProgressDesc(UINT nID)
  87. {
  88. TCHAR sz[MAX_PATH];
  89. ShowProgressDlg();
  90. if (g_hwndProgress)
  91. {
  92. LoadString(g_hinst, nID, sz, ARRAYSIZE(sz));
  93. SendDlgItemMessage(g_hwndProgress, IDC_STATIC, WM_SETTEXT, 0, (LPARAM)sz);
  94. }
  95. }
  96. //---------------------------------------------------------------------------
  97. void Group_SetProgressNameAndRange(LPCTSTR lpszGroup, int iMax)
  98. {
  99. TCHAR sz[MAX_PATH];
  100. TCHAR szNew[MAX_PATH];
  101. LPTSTR lpszName;
  102. MSG msg;
  103. static int cGen = 1;
  104. ShowProgressDlg();
  105. if (g_hwndProgress)
  106. {
  107. // DebugMsg(DM_TRACE, "gc.gspnar: Range 0 to %d", iMax);
  108. SendDlgItemMessage(g_hwndProgress, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, iMax));
  109. if (lpszGroup == (LPTSTR)-1)
  110. {
  111. // Use some sensible name - Programs (x)
  112. // where x = 1 to n, incremented each time this is
  113. // called.
  114. LoadString(g_hinst, IDS_GROUP, sz, ARRAYSIZE(sz));
  115. wsprintf(szNew, TEXT("%s (%d)"), sz, cGen++);
  116. SetDlgItemText(g_hwndProgress, IDC_GROUPNAME, szNew);
  117. }
  118. else if (lpszGroup && *lpszGroup)
  119. {
  120. lpszName = PathFindFileName(lpszGroup);
  121. lstrcpy(sz, lpszName);
  122. Text_TruncateAndAddElipses(GetDlgItem(g_hwndProgress, IDC_GROUPNAME), sz);
  123. SetDlgItemText(g_hwndProgress, IDC_GROUPNAME, sz);
  124. }
  125. else
  126. {
  127. // Use some sensible name.
  128. LoadString(g_hinst, IDS_PROGRAMS, sz, ARRAYSIZE(sz));
  129. SetDlgItemText(g_hwndProgress, IDC_GROUPNAME, sz);
  130. }
  131. // Let paints come in.
  132. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  133. {
  134. DispatchMessage(&msg);
  135. }
  136. }
  137. }
  138. //---------------------------------------------------------------------------
  139. void Group_SetProgress(int i)
  140. {
  141. MSG msg;
  142. ShowProgressDlg();
  143. if (g_hwndProgress)
  144. {
  145. // DebugMsg(DM_TRACE, "gc.gsp: Progress %d", i);
  146. // Progman keeps trying to steal the focus...
  147. SetForegroundWindow(g_hwndProgress);
  148. SendDlgItemMessage(g_hwndProgress, IDC_PROGRESS, PBM_SETPOS, i, 0);
  149. }
  150. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  151. {
  152. DispatchMessage(&msg);
  153. }
  154. }
  155. #if 0
  156. //---------------------------------------------------------------------------
  157. BOOL WritePrivateProfileInt(LPCTSTR lpszSection, LPCTSTR lpszValue, int i, LPCTSTR lpszIniFile)
  158. {
  159. TCHAR szBuf[CCHSZSHORT];
  160. wsprintf(szBuf, TEXT("%d"), i);
  161. return WritePrivateProfileString(lpszSection, lpszValue, szBuf, lpszIniFile);
  162. }
  163. #endif
  164. //---------------------------------------------------------------------------
  165. // Register an app as being able to handle a particular extension with the
  166. // given internal type, human readble type and command.
  167. // NB lpszExt doesn't need a dot.
  168. // By default this won't overide something in the registration DB.
  169. // Setting fOveride to TRUE will cause existing entries in the DB
  170. // to be over written.
  171. void ShellRegisterApp(LPCTSTR lpszExt, LPCTSTR lpszTypeKey,
  172. LPCTSTR lpszTypeValue, LPCTSTR lpszCommand, BOOL fOveride)
  173. {
  174. TCHAR szKey[CCHSZNORMAL];
  175. TCHAR szValue[CCHSZSHORT];
  176. LONG lcb;
  177. LONG lStatus;
  178. // Deal with the mapping from extension to TypeKey.
  179. lstrcpy(szKey, g_szDot);
  180. lstrcat(szKey, lpszExt);
  181. lcb = SIZEOF(szValue);
  182. lStatus = RegQueryValue(HKEY_CLASSES_ROOT, szKey, szValue, &lcb);
  183. // Is the extension not registered or do we even care?
  184. if (lStatus != ERROR_SUCCESS || fOveride)
  185. {
  186. // No, so register it.
  187. lstrcpy(szValue, lpszTypeKey);
  188. if (RegSetValue(HKEY_CLASSES_ROOT, szKey, REG_SZ, lpszTypeKey, 0) == ERROR_SUCCESS)
  189. {
  190. // DebugMsg(DM_TRACE, "gc.sra: Extension registered.");
  191. }
  192. else
  193. {
  194. DebugMsg(DM_ERROR, TEXT("gc.sra: Error registering extension."));
  195. }
  196. }
  197. // Deal with the mapping from TypeKey to TypeValue
  198. lcb = SIZEOF(szValue);
  199. lStatus = RegQueryValue(HKEY_CLASSES_ROOT, lpszTypeKey, szValue, &lcb);
  200. // Is the type not registered or do we even care?
  201. if (lStatus != ERROR_SUCCESS || fOveride)
  202. {
  203. // No, so register it.
  204. if (RegSetValue(HKEY_CLASSES_ROOT, lpszTypeKey, REG_SZ, lpszTypeValue, 0) == ERROR_SUCCESS)
  205. {
  206. // DebugMsg(DM_TRACE, "gc.sra: Type registered.");
  207. }
  208. else
  209. {
  210. DebugMsg(DM_ERROR, TEXT("gc.sra: Error registering type."));
  211. }
  212. }
  213. // Deal with adding the open command.
  214. lstrcpy(szKey, lpszTypeKey);
  215. lstrcat(szKey, g_szShellOpenCommand);
  216. lcb = SIZEOF(szValue);
  217. lStatus = RegQueryValue(HKEY_CLASSES_ROOT, szKey, szValue, &lcb);
  218. // Is the command not registered or do we even care?
  219. if (lStatus != ERROR_SUCCESS || fOveride)
  220. {
  221. // No, so register it.
  222. if (RegSetValue(HKEY_CLASSES_ROOT, szKey, REG_SZ, lpszCommand, 0) == ERROR_SUCCESS)
  223. {
  224. // DebugMsg(DM_TRACE, "gc.sra: Command registered.");
  225. }
  226. else
  227. {
  228. DebugMsg(DM_ERROR, TEXT("gc.sra: Error registering command."));
  229. }
  230. }
  231. }
  232. #if 0
  233. //-------------------------------------------------------------------------
  234. // Do a unix(ish) gets(). This assumes bufferd i/o.
  235. // Reads cb-1 characters (the last one will be a NULL) or up to and including
  236. // the first NULL.
  237. LPTSTR fgets(LPTSTR sz, WORD cb, int fh)
  238. {
  239. UINT i;
  240. // Leave room for the NULL.
  241. cb--;
  242. for (i=0; i<cb; i++)
  243. {
  244. _lread(fh, &sz[i], 1);
  245. // Check for a null.
  246. if (sz[i] == TEXT('\0'))
  247. return sz;
  248. }
  249. // Ran out of room.
  250. // NULL Terminate.
  251. sz[cb-1] = TEXT('\0');
  252. return sz;
  253. }
  254. #else
  255. //-------------------------------------------------------------------------
  256. // Do a unix(ish) gets(). This assumes bufferd i/o.
  257. // Reads cb-1 characters (the last one will be a NULL) or up to and including
  258. // the first NULL.
  259. #ifdef UNICODE
  260. LPTSTR fgets(LPTSTR sz, DWORD count, HANDLE fh)
  261. {
  262. DWORD cch;
  263. DWORD dwFilePointer, dwBytesRead;
  264. CHAR *AnsiString = NULL, *AnsiStringPointer, ch;
  265. LPTSTR retval = NULL;
  266. //
  267. // Allocate memory for the reading the ansi string from the stream
  268. //
  269. if ((AnsiString = (CHAR *)LocalAlloc(LPTR, count * SIZEOF(CHAR))) == NULL) {
  270. return(retval);
  271. }
  272. AnsiStringPointer = AnsiString;
  273. // Where are we?
  274. dwFilePointer = SetFilePointer(fh, 0, NULL, FILE_CURRENT);
  275. // Fill the buffer.
  276. ReadFile(fh, AnsiString, count, &dwBytesRead, NULL);
  277. // Always null the buffer.
  278. AnsiString[count-1] = '\0';
  279. // Convert the Ansi String to Unicode
  280. if (MultiByteToWideChar(
  281. CP_ACP,
  282. MB_PRECOMPOSED,
  283. AnsiString,
  284. -1,
  285. sz,
  286. count
  287. ) != 0) {
  288. retval = sz;
  289. }
  290. // If there was an earlied null we need to puke the rest
  291. // back in to the stream?
  292. cch = lstrlenA(AnsiString);
  293. if (cch != count-1)
  294. SetFilePointer(fh, dwFilePointer+cch+1, NULL, FILE_BEGIN);
  295. // Do Cleanup
  296. if (AnsiString != NULL) {
  297. LocalFree(AnsiString);
  298. }
  299. return retval;
  300. }
  301. #else
  302. LPTSTR fgets(LPTSTR sz, WORD cb, int fh)
  303. {
  304. int cch;
  305. LONG lpos;
  306. // Where are we?
  307. lpos = _llseek(fh, 0, 1);
  308. // Fill the buffer.
  309. _lread(fh, sz, cb);
  310. // Always null the buffer.
  311. sz[cb-1] = TEXT('\0');
  312. // If there was an earlied null we need to puke the rest
  313. // back in to the stream?
  314. cch = lstrlen(sz);
  315. if (cch != cb-1)
  316. _llseek(fh, lpos+cch+1, 0);
  317. return sz;
  318. }
  319. #endif
  320. #endif
  321. //---------------------------------------------------------------------------
  322. // Put up a message box wsprintf style.
  323. int MyMessageBox(HWND hwnd, UINT idTitle, UINT idMessage, LPCTSTR lpsz, UINT nStyle)
  324. {
  325. TCHAR szTempField[CCHSZNORMAL];
  326. TCHAR szTitle[CCHSZNORMAL];
  327. TCHAR szMessage[CCHSZNORMAL];
  328. int iMsgResult;
  329. if (LoadString(g_hinst, idTitle, szTitle, ARRAYSIZE(szTitle)))
  330. {
  331. if (LoadString(g_hinst, idMessage, szTempField, ARRAYSIZE(szTempField)))
  332. {
  333. if (lpsz)
  334. wsprintf(szMessage, szTempField, (LPTSTR)lpsz);
  335. else
  336. lstrcpy(szMessage, szTempField);
  337. if (hwnd)
  338. hwnd = GetLastActivePopup(hwnd);
  339. iMsgResult = MessageBox(hwnd, szMessage, szTitle, nStyle);
  340. if (iMsgResult != -1)
  341. return iMsgResult;
  342. }
  343. }
  344. // Out of memory...
  345. DebugMsg(DM_ERROR, TEXT("MMB: Out of memory.\n\r"));
  346. return -1;
  347. }
  348. //-------------------------------------------------------------------------
  349. // Replace hash characters in a string with NULLS.
  350. void ConvertHashesToNulls(LPTSTR p)
  351. {
  352. while (*p)
  353. {
  354. if (*p == TEXT('#'))
  355. {
  356. *p = TEXT('\0');
  357. // You can't do an AnsiNext on a NULL.
  358. // NB - we know this is a single byte.
  359. p++;
  360. }
  361. else
  362. p = CharNext(p);
  363. }
  364. }
  365. //-------------------------------------------------------------------------
  366. // Copy the directory component of a path into the given buffer.
  367. // i.e. everything after the last slash and the slash itself for everything
  368. // but the root.
  369. // lpszDir is assumed to be as big as lpszPath.
  370. void Path_GetDirectory(LPCTSTR lpszPath, LPTSTR lpszDir)
  371. {
  372. LPTSTR lpszFileName;
  373. UINT cb;
  374. // The default is a null.
  375. lpszDir[0] = TEXT('\0');
  376. // Copy over everything but the filename.
  377. lpszFileName = PathFindFileName(lpszPath);
  378. cb = (UINT)(lpszFileName-lpszPath);
  379. if (cb)
  380. {
  381. // REVIEW lstrcpyn seems to have a problem with a cb of 0;
  382. lstrcpyn(lpszDir, lpszPath, cb+1);
  383. // Remove the trailing slash if needed.
  384. if (!PathIsRoot(lpszDir))
  385. lpszDir[cb-1] = TEXT('\0');
  386. }
  387. }
  388. //-------------------------------------------------------------------------
  389. //
  390. // internal CoCreateInstance.
  391. //
  392. // bind straight to shell232 DllGetClassObject()
  393. // this is meant to skip all the CoCreateInstance stuff when we
  394. // know the thing we are looking for is in shell232.dll. this also
  395. // makes things work if the registry is messed up
  396. //
  397. HRESULT ICoCreateInstance(REFCLSID rclsid, REFIID riid, LPVOID FAR* ppv)
  398. {
  399. LPCLASSFACTORY pcf;
  400. HRESULT hres = SHDllGetClassObject(rclsid, &IID_IClassFactory, &pcf);
  401. if (SUCCEEDED(hres))
  402. {
  403. hres = pcf->lpVtbl->CreateInstance(pcf, NULL, riid, ppv);
  404. pcf->lpVtbl->Release(pcf);
  405. }
  406. return hres;
  407. }
  408. //-------------------------------------------------------------------------
  409. LPTSTR _lstrcatn(LPTSTR lpszDest, LPCTSTR lpszSrc, UINT cbDest)
  410. {
  411. UINT i;
  412. i = lstrlen(lpszDest);
  413. lstrcpyn(lpszDest+i, lpszSrc, cbDest-i);
  414. return lpszDest;
  415. }
  416. //-------------------------------------------------------------------------
  417. // Simplified from shelldll. Keep sticking on numbers till the name is unique.
  418. BOOL WINAPI MakeUniqueName(LPTSTR pszNewName, UINT cbNewName, LPCTSTR pszOldName,
  419. UINT nStart, PFNISUNIQUE pfnIsUnique, UINT nUser, BOOL fLFN)
  420. {
  421. TCHAR szAddend[4];
  422. int cbAddend;
  423. int i;
  424. // Is it already unique?
  425. if ((*pfnIsUnique)(pszOldName, nUser))
  426. {
  427. lstrcpyn(pszNewName, pszOldName, cbNewName);
  428. return TRUE;
  429. }
  430. else
  431. {
  432. // NB Max is 100 identically names things but we should never
  433. // hit this as the max number of items in a progman group was 50.
  434. for (i=nStart; i<100; i++)
  435. {
  436. // Generate the addend.
  437. wsprintf(szAddend, TEXT("#%d"), i);
  438. cbAddend = lstrlen(szAddend);
  439. // Lotsa room?
  440. if ((UINT)(lstrlen(pszOldName)+cbAddend+1) > cbNewName)
  441. {
  442. // Nope.
  443. lstrcpyn(pszNewName, pszOldName, cbNewName);
  444. lstrcpy(pszNewName+(cbNewName-cbAddend), szAddend);
  445. }
  446. else
  447. {
  448. // Yep.
  449. lstrcpy(pszNewName, pszOldName);
  450. if (!fLFN)
  451. lstrcat(pszNewName, c_szSpace);
  452. lstrcat(pszNewName, szAddend);
  453. }
  454. // Is it unique?
  455. if ((*pfnIsUnique)(pszNewName, nUser))
  456. {
  457. // Yep.
  458. return TRUE;
  459. }
  460. }
  461. }
  462. // Ooopsie.
  463. lstrcpyn(pszNewName, pszOldName, cbNewName);
  464. DebugMsg(DM_ERROR, TEXT("gp.mun: Unable to generate a unique name for %s."), pszOldName);
  465. return FALSE;
  466. }
  467. //-------------------------------------------------------------------------
  468. // Simplified from shell.dll (For LFN things only).
  469. BOOL WINAPI YetAnotherMakeUniqueName(LPTSTR pszNewName, UINT cbNewName, LPCTSTR pszOldName,
  470. PFNISUNIQUE pfnIsUnique, UINT n, BOOL fLFN)
  471. {
  472. BOOL fRet = FALSE;
  473. TCHAR szTemp[MAX_PATH];
  474. // Is given name already unique?
  475. if ((*pfnIsUnique)(pszOldName, n))
  476. {
  477. // Yep,
  478. lstrcpyn(pszNewName, pszOldName, cbNewName);
  479. }
  480. else
  481. {
  482. if (fLFN)
  483. {
  484. // Try "another".
  485. LoadString(g_hinst, IDS_ANOTHER, szTemp, ARRAYSIZE(szTemp));
  486. _lstrcatn(szTemp, pszOldName, cbNewName);
  487. if (!(*pfnIsUnique)(szTemp, n))
  488. {
  489. // Nope, use the old technique of sticking on numbers.
  490. return MakeUniqueName(pszNewName, cbNewName, pszOldName, 3, pfnIsUnique, n, FALSE);
  491. }
  492. else
  493. {
  494. // Yep.
  495. lstrcpyn(pszNewName, szTemp, cbNewName);
  496. }
  497. }
  498. else
  499. {
  500. // Just stick on numbers.
  501. return MakeUniqueName(pszNewName, cbNewName, pszOldName, 2, pfnIsUnique, n, TRUE);
  502. }
  503. }
  504. // Name is unique.
  505. return TRUE;
  506. }
  507. //----------------------------------------------------------------------------
  508. // Sort of a registry equivalent of the profile API's.
  509. BOOL WINAPI Reg_Get(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, LPVOID pData, DWORD cbData)
  510. {
  511. HKEY hkeyNew;
  512. BOOL fRet = FALSE;
  513. DWORD dwType;
  514. if (!GetSystemMetrics(SM_CLEANBOOT) && (RegOpenKey(hkey, pszSubKey, &hkeyNew) == ERROR_SUCCESS))
  515. {
  516. if (RegQueryValueEx(hkeyNew, (LPVOID)pszValue, 0, &dwType, pData, &cbData) == ERROR_SUCCESS)
  517. {
  518. fRet = TRUE;
  519. }
  520. RegCloseKey(hkeyNew);
  521. }
  522. return fRet;
  523. }
  524. //----------------------------------------------------------------------------
  525. // Sort of a registry equivalent of the profile API's.
  526. BOOL WINAPI Reg_Set(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, DWORD dwType,
  527. LPVOID pData, DWORD cbData)
  528. {
  529. HKEY hkeyNew;
  530. BOOL fRet = FALSE;
  531. if (pszSubKey)
  532. {
  533. if (RegCreateKey(hkey, pszSubKey, &hkeyNew) == ERROR_SUCCESS)
  534. {
  535. if (RegSetValueEx(hkeyNew, pszValue, 0, dwType, pData, cbData) == ERROR_SUCCESS)
  536. {
  537. fRet = TRUE;
  538. }
  539. RegCloseKey(hkeyNew);
  540. }
  541. }
  542. else
  543. {
  544. if (RegSetValueEx(hkey, pszValue, 0, dwType, pData, cbData) == ERROR_SUCCESS)
  545. {
  546. fRet = TRUE;
  547. }
  548. }
  549. return fRet;
  550. }
  551. //----------------------------------------------------------------------------
  552. BOOL WINAPI Reg_SetDWord(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, DWORD dw)
  553. {
  554. return Reg_Set(hkey, pszSubKey, pszValue, REG_DWORD, &dw, SIZEOF(dw));
  555. }
  556. //----------------------------------------------------------------------------
  557. BOOL WINAPI Reg_GetDWord(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, LPDWORD pdw)
  558. {
  559. return Reg_Get(hkey, pszSubKey, pszValue, pdw, SIZEOF(*pdw));
  560. }
  561. //----------------------------------------------------------------------------
  562. void __cdecl _Log(LPCTSTR pszMsg, ...)
  563. {
  564. TCHAR sz[2*MAX_PATH+40]; // Handles 2*largest path + slop for message
  565. va_list vaListMarker;
  566. va_start(vaListMarker, pszMsg);
  567. if (g_hkeyGrpConv)
  568. {
  569. wvsprintf(sz, pszMsg, vaListMarker);
  570. Reg_SetString(g_hkeyGrpConv, NULL, TEXT("Log"), sz);
  571. }
  572. }