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.

647 lines
20 KiB

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