Utils.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /****************************************************************************
  19. *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
  20. ****************************************************************************
  21. * *
  22. * Project Name : Setup *
  23. * *
  24. * File Name : UTILS.C *
  25. * *
  26. * Programmers: Maria del Mar McCready Legg *
  27. * *
  28. * Start Date : December 12, 1992 *
  29. * *
  30. * Last Update : March 16, 1998 [MML] *
  31. * *
  32. *--------------------------------------------------------------------------*
  33. * Functions: *
  34. * *
  35. * Clip_Line_To_Rect -- Clips a line (two points) against a *
  36. * rectangle, using CS algorithm. *
  37. * Compute_Code -- computes line clipping bit code for *
  38. * point & rectangle. *
  39. * Copy_File -- Copies a file from one dir to another. *
  40. * Convert_Hex_To_Version -- Converts a hex num obtained from the *
  41. * Registry, into a string *
  42. * representation of a version *
  43. * number ( XX.XX ). *
  44. * Convert_Version_To_Hex -- Converts a string to an unsigned long. *
  45. * Convert_To_Version_Format -- Converts version string's "," to "."s *
  46. * Dialog_Box -- draws a dialog background box *
  47. * Draw_Box -- Displays a highlighted box. *
  48. * Fatal -- General purpose fatal error handler. *
  49. * Get_Version -- Retrieves a version string from a file. *
  50. * Get_String -- Returns a pointer to the undipped text. *
  51. * Is_File_Available -- Use both FindFirst to check that CD is *
  52. * in drive & if File_Exists() to *
  53. * determine if file is really there. *
  54. * Pad_With_Zeros -- Adds zeros to the beginning of string. *
  55. * String_Width -- Calculate with of the string. *
  56. * Strip_Newlines -- Remove '\r' from string passed in. *
  57. * TextPtr -- Returns a pointer to the undipped text. *
  58. * Path_Name_Valid -- Validate that the path has the correct *
  59. * number of chars between '\' in the *
  60. * path. *
  61. * Path_Get_Next_Directory -- Return the next dir path from string. *
  62. * Path_Add_Back_Slash -- Add a "\\" to the end of the string. *
  63. * Path_Remove_Back_Slash -- Remove a '\\' at the end of the string. *
  64. * Path_Trim_Blanks -- Trim lead/trail white spaces off string *
  65. * Pad_With_Zeros -- Adds zeros to the beginning of string. *
  66. * Remove_Ending_Spaces -- Remove any blank spaces at end of string*
  67. * Remove_Spaces -- Remove spaces from string passed in. *
  68. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
  69. #include <io.h>
  70. #include "args.h"
  71. #include "assert.h"
  72. #include "locale_api.h"
  73. #include "resource.h"
  74. #include "utils.h"
  75. #include "winfix.h"
  76. #include "wnd_file.h"
  77. #include <winver.h>
  78. #include <shlwapi.h>
  79. //#include "resources.h"
  80. //----------------------------------------------------------------------------
  81. //
  82. // Function: Fix_Single_Ampersands()
  83. //
  84. // Purpose: To replace each "&" with "&&" for display in a dialog.
  85. // Some dialogs mistake a single "&" for an accelerator key.
  86. //
  87. // Input: pszString - any NULL terminated string.
  88. //
  89. // Returns: VOID (returns nothing)
  90. //
  91. // Comments: Modifies the characters in pszString.
  92. //
  93. //---------------------------------------------------------------------------
  94. void Fix_Single_Ampersands ( LPSTR pszString, bool upper_case )
  95. {
  96. char pszTemp[ MAX_PATH ]; // variable to hold the string passed
  97. char pszOld[ MAX_PATH ]; // variable to hold the string passed
  98. char * letter;
  99. int i = 0;
  100. lstrcpy((LPSTR)pszOld, (LPSTR)pszString );
  101. letter = pszOld;
  102. memset ( pszTemp, '\0', MAX_PATH );
  103. //----------------------------------------------------------------------
  104. // While our ptr has not passed the end of the string...
  105. //----------------------------------------------------------------------
  106. while (*letter != '\0') {
  107. if (*letter == '&') {
  108. pszTemp[i++] = '&';
  109. pszTemp[i++] = '&';
  110. letter++;
  111. } else {
  112. if ( upper_case ) {
  113. pszTemp[i++] = (char) toupper( *( letter++ ));
  114. } else {
  115. pszTemp[i++] = *(letter++);
  116. }
  117. }
  118. }
  119. strcpy((LPSTR)pszString, (LPSTR)pszTemp );
  120. }
  121. void Fix_Single_Ampersands ( wchar_t *pszString, bool upper_case )
  122. {
  123. wchar_t pszTemp[ MAX_PATH ]; // variable to hold the string passed
  124. wchar_t pszOld[ MAX_PATH ]; // variable to hold the string passed
  125. wchar_t *letter;
  126. int i = 0;
  127. wcscpy( pszOld, pszString );
  128. letter = pszOld;
  129. memset ( pszTemp, '\0', MAX_PATH );
  130. //----------------------------------------------------------------------
  131. // While our ptr has not passed the end of the string...
  132. //----------------------------------------------------------------------
  133. while (*letter != '\0') {
  134. if (*letter == '&') {
  135. pszTemp[i++] = '&';
  136. pszTemp[i++] = '&';
  137. letter++;
  138. } else {
  139. if ( upper_case ) {
  140. pszTemp[i++] = (char) toupper( *( letter++ ));
  141. } else {
  142. pszTemp[i++] = *(letter++);
  143. }
  144. }
  145. }
  146. wcscpy( pszString, pszTemp );
  147. }
  148. ////////////////UnicodeString Fix_Single_Ampersands( UnicodeString string, bool upper_case)
  149. ////////////////{
  150. //////////////// UnicodeString retval;
  151. ////////////////
  152. //////////////// Int i = 0;
  153. //////////////// while (i < string.getLength()) {
  154. //////////////// if (upper_case) {
  155. //////////////// retval.concat(toupper(string.getCharAt(i)));
  156. //////////////// } else {
  157. //////////////// retval.concat(string.getCharAt(i));
  158. //////////////// }
  159. //////////////// if (string.getCharAt(i) == L'&') {
  160. //////////////// retval.concat(string.getCharAt(i));
  161. //////////////// }
  162. //////////////// ++i;
  163. //////////////// }
  164. ////////////////
  165. //////////////// return retval;
  166. ////////////////}
  167. //----------------------------------------------------------------------------
  168. //
  169. // Function: Fix_Double_Ampersands()
  170. //
  171. // Purpose: To replace each "&&" with "&" for display in a dialog.
  172. // Some dialogs mistake a single "&" for an accelerator key.
  173. //
  174. // Input: pszString - any NULL terminated string.
  175. //
  176. // Returns: VOID (returns nothing)
  177. //
  178. // Comments: Modifies the characters in pszString.
  179. //
  180. //---------------------------------------------------------------------------
  181. void Fix_Double_Ampersands ( LPSTR pszString, bool upper_case )
  182. {
  183. char pszTemp[ MAX_PATH ]; // variable to hold the string passed
  184. char pszOld[ MAX_PATH ]; // variable to hold the string passed
  185. char *letter;
  186. int i = 0;
  187. lstrcpy( (LPSTR)pszOld, (LPSTR)pszString );
  188. letter = pszOld;
  189. memset ( pszTemp, '\0', MAX_PATH );
  190. //----------------------------------------------------------------------
  191. // While our ptr has not passed the end of the string...
  192. //----------------------------------------------------------------------
  193. while (*letter != '\0') {
  194. if ((*letter == '&') && (*( letter+1 ) == '&')) {
  195. pszTemp[i++] = '&';
  196. letter = letter + 2;
  197. } else {
  198. if ( upper_case ) {
  199. pszTemp[i++] = (char) toupper( *( letter++ ));
  200. } else {
  201. pszTemp[i++] = *(letter++);
  202. }
  203. }
  204. }
  205. strcpy((LPSTR)pszString, (LPSTR)pszTemp );
  206. }
  207. /******************************************************************************
  208. * Load_Alloc_Data -- Allocates a buffer and loads the file into it. *
  209. * *
  210. * This is the C++ replacement for the Load_Alloc_Data function. It will *
  211. * allocate the memory big enough to hold the file & read the file into it. *
  212. * *
  213. * INPUT: file -- The file to read. *
  214. * mem -- The memory system to use for allocation. *
  215. * *
  216. * OUTPUT: Returns with a pointer to the allocated and filled memory block. *
  217. * *
  218. * WARNINGS: none *
  219. * *
  220. * HISTORY: *
  221. * 10/17/1994 JLB : Created. *
  222. *============================================================================*/
  223. void * Load_Alloc_Data( char *filename, long *filesize )
  224. {
  225. int size, bytes_read;
  226. void *ptr = NULL;
  227. StandardFileClass file;
  228. //-------------------------------------------------------------------------
  229. // Open file in READ ONLY mode. If fails, exit.
  230. //-------------------------------------------------------------------------
  231. file.Open( filename, MODE_READ_ONLY );
  232. if ( !file.Query_Open()) {
  233. return( NULL );
  234. }
  235. //-------------------------------------------------------------------------
  236. // Get filesize and create a buffer.
  237. //-------------------------------------------------------------------------
  238. size = file.Query_Size();
  239. ptr = (void*)malloc(size + 1);
  240. if ( !ptr ) {
  241. return( NULL );
  242. }
  243. //-------------------------------------------------------------------------
  244. // Read data into the buffer, close the file.
  245. //-------------------------------------------------------------------------
  246. memset( ptr, '\0', size + 1 );
  247. bytes_read = file.Read( ptr, size );
  248. file.Close();
  249. //-------------------------------------------------------------------------
  250. // Check return bytes. It should match the file size.
  251. //-------------------------------------------------------------------------
  252. assert( bytes_read == size );
  253. if ( bytes_read != size ) {
  254. free(ptr);
  255. return( NULL );
  256. }
  257. if ( filesize != NULL ) {
  258. *filesize = (long)size;
  259. }
  260. return( ptr );
  261. }
  262. /****************************************************************************
  263. * MIXFILECLASS::LOAD_FILE -- Returns a buffer loaded with file desired. *
  264. * *
  265. * INPUT: none. * *
  266. * *
  267. * OUTPUT: none. *
  268. * *
  269. * WARNINGS: Searches MixFile first, then local directory. *
  270. * Use free() to release buffer. * *
  271. * *
  272. * HISTORY: *
  273. * 04/13/1998 ML/MG : Created. *
  274. *==========================================================================*/
  275. void *Load_File ( char *filename, long *filesize )
  276. {
  277. void *ptr = NULL;
  278. if ( filename == NULL || filename[0] == '\0' ) {
  279. return( NULL );
  280. }
  281. //-------------------------------------------------------------------------
  282. // Try loading from local directory.
  283. //-------------------------------------------------------------------------
  284. ptr = Load_Alloc_Data( filename, filesize );
  285. return( ptr );
  286. }
  287. /****************************************************************************
  288. * MAKE_CURRENT_PATH_TO -- Returns a buffer to path desired. *
  289. * *
  290. * INPUT: none. * *
  291. * *
  292. * OUTPUT: none. *
  293. * *
  294. * WARNINGS: * *
  295. * *
  296. * HISTORY: *
  297. * 10/08/2001 MML : Created. *
  298. *==========================================================================*/
  299. char *Make_Current_Path_To ( char *filename, char *path )
  300. {
  301. char szPath [ _MAX_PATH ];
  302. char drive [ _MAX_DRIVE];
  303. char dir [ _MAX_DIR ];
  304. strcpy( szPath, Args->Get_argv(0));
  305. _splitpath( szPath, drive, dir, NULL, NULL );
  306. _makepath( szPath, drive, dir, NULL, NULL );
  307. Path_Add_Back_Slash( szPath );
  308. strcat( szPath, filename );
  309. if( path != NULL ) {
  310. strcpy( path, szPath );
  311. }
  312. return( path );
  313. }
  314. wchar_t *Make_Current_Path_To ( wchar_t *filename, wchar_t *path )
  315. {
  316. wchar_t szPath [ _MAX_PATH ];
  317. wchar_t drive [ _MAX_DRIVE];
  318. wchar_t dir [ _MAX_DIR ];
  319. wcscpy( szPath, (wchar_t *)Args->Get_argv(0));
  320. _wsplitpath( szPath, drive, dir, NULL, NULL );
  321. _wmakepath( szPath, drive, dir, NULL, NULL );
  322. Path_Add_Back_Slash( szPath );
  323. wcscat( szPath, filename );
  324. if( path != NULL ) {
  325. wcscpy( path, szPath );
  326. }
  327. return( path );
  328. }
  329. /******************************************************************************
  330. * Path_Add_Back_Slash -- Add a '\\' to the end of the path.
  331. *
  332. * INPUT: char * path -- Pointer to the string to be modified.
  333. *
  334. * OUTPUT: char * path
  335. *
  336. * WARNINGS: none
  337. *
  338. * HISTORY:
  339. * 08/14/1998 MML : Created.
  340. *============================================================================*/
  341. char *Path_Add_Back_Slash ( char *path )
  342. {
  343. if ( path != NULL && *path != '\0' ) {
  344. if ( path[ strlen( path )-1 ] != '\\' ) {
  345. strcat( path, "\\" );
  346. }
  347. }
  348. return( path );
  349. }
  350. wchar_t *Path_Add_Back_Slash ( wchar_t *path )
  351. {
  352. if ( path != NULL && *path != '\0' ) {
  353. if ( path[ wcslen( path )-1 ] != '\\' ) {
  354. wcscat( path, L"\\" );
  355. }
  356. }
  357. return( path );
  358. }
  359. /******************************************************************************
  360. * Path_Remove_Back_Slash -- Remove a '\\' from the end of the path.
  361. *
  362. * INPUT: char * path -- Pointer to the string to be modified.
  363. *
  364. * OUTPUT: char * path
  365. *
  366. * WARNINGS: none
  367. *
  368. * HISTORY:
  369. * 08/14/1998 MML : Created.
  370. *============================================================================*/
  371. char *Path_Remove_Back_Slash ( char *path )
  372. {
  373. if ( path != NULL && *path != '\0' ) {
  374. if ( path[ strlen( path )-1 ] == '\\' ) {
  375. path[ strlen( path )-1 ] = '\0';
  376. }
  377. }
  378. return( path );
  379. }
  380. wchar_t *Path_Remove_Back_Slash ( wchar_t *path )
  381. {
  382. if ( path != NULL && *path != '\0' ) {
  383. if ( path[ wcslen( path )-1 ] == L'\\' ) {
  384. path[ wcslen( path )-1 ] = L'\0';
  385. }
  386. }
  387. return( path );
  388. }
  389. /*--------------------------------------------------------------------------*/
  390. /* Function: PlugInProductName */
  391. /* */
  392. /* Descrip: The function plugs the product name defined in */
  393. /* SdProductName() into %P found in the static message. */
  394. /* It will search for the first nMax controls only. */
  395. /* Misc: */
  396. /* */
  397. /*--------------------------------------------------------------------------*/
  398. void PlugInProductName ( char *szString, char *szName )
  399. {
  400. int nCount, nMsgLength;
  401. char szTextBuf[ MAX_PATH ];
  402. char szOut[ MAX_PATH ];
  403. char szProduct[ MAX_PATH ];
  404. char * temp = NULL;
  405. char * next = NULL;
  406. if ( szName == NULL || szName[0] == '\0' ) {
  407. return;
  408. }
  409. //--------------------------------------------------------------------------
  410. // Find the first appearance of "%P".
  411. //--------------------------------------------------------------------------
  412. strcpy( szProduct, szName );
  413. strcpy( szTextBuf, szString );
  414. nMsgLength = strlen( szTextBuf );
  415. nCount = 0;
  416. temp = strstr( szTextBuf, "%s" );
  417. //-------------------------------------------------------------
  418. // Substitute each "%P" with "%s". nStrReturn is the index
  419. // into the buffer where "%P" was found.
  420. //-------------------------------------------------------------
  421. while ( temp != NULL && nCount < 6) {
  422. next = temp+1;
  423. nCount = nCount + 1;
  424. temp = strstr( next, "%s" );
  425. }
  426. //-------------------------------------------------------------
  427. // Only support up to 5 product name per message.
  428. // Do the substitution of the product name and store in szOut.
  429. //-------------------------------------------------------------
  430. switch( nCount ) {
  431. case 1:
  432. sprintf( szOut, szTextBuf, szProduct );
  433. break;
  434. case 2:
  435. sprintf( szOut, szTextBuf, szProduct, szProduct );
  436. break;
  437. case 3:
  438. sprintf( szOut, szTextBuf, szProduct, szProduct, szProduct );
  439. break;
  440. case 4:
  441. sprintf( szOut, szTextBuf, szProduct, szProduct, szProduct, szProduct );
  442. break;
  443. case 5:
  444. sprintf( szOut, szTextBuf, szProduct, szProduct, szProduct, szProduct, szProduct, szProduct );
  445. break;
  446. }
  447. //-------------------------------------------------------------
  448. // Replace szTextBuf with szOut.
  449. //-------------------------------------------------------------
  450. if ( nCount >= 1 ) {
  451. strcpy( szString, szOut );
  452. }
  453. }
  454. /*--------------------------------------------------------------------------*/
  455. /* Function: PlugInProductName */
  456. /* */
  457. /* Descrip: The function plugs the product name defined in */
  458. /* SdProductName() into %P found in the static message. */
  459. /* It will search for the first nMax controls only. */
  460. /* Misc: */
  461. /* */
  462. /*--------------------------------------------------------------------------*/
  463. void PlugInProductName( char *szString, int nName )
  464. {
  465. /*
  466. int nCount, nMsgLength;
  467. char szTextBuf[ MAX_PATH ];
  468. char szOut[ MAX_PATH ];
  469. char szProduct[ MAX_PATH ];
  470. char * temp = NULL;
  471. char * next = NULL;
  472. if ( nName <= STRNONE ) {
  473. nName = STRNONE;
  474. }
  475. //--------------------------------------------------------------------------
  476. // Find the first appearance of "%P".
  477. //-------------------------------------------------------------
  478. // LoadString( Main::hInstance, nName, szProduct, MAX_PATH );
  479. Locale_GetString( nName, szProduct );
  480. strcpy( szTextBuf, szString );
  481. nMsgLength = strlen( szTextBuf );
  482. nCount = 0;
  483. temp = strstr( szTextBuf, "%s" );
  484. //-------------------------------------------------------------
  485. // Substitute each "%P" with "%s". nStrReturn is the index
  486. // into the buffer where "%P" was found.
  487. //-------------------------------------------------------------
  488. while ( temp != NULL && nCount < 6) {
  489. next = temp+1;
  490. nCount = nCount + 1;
  491. temp = strstr( next, "%s" );
  492. }
  493. //-------------------------------------------------------------
  494. // Only support up to 5 product name per message.
  495. // Do the substitution of the product name and store in szOut.
  496. //-------------------------------------------------------------
  497. switch( nCount ) {
  498. case 1:
  499. sprintf( szOut, szTextBuf, szProduct );
  500. break;
  501. case 2:
  502. sprintf( szOut, szTextBuf, szProduct, szProduct );
  503. break;
  504. case 3:
  505. sprintf( szOut, szTextBuf, szProduct, szProduct, szProduct );
  506. break;
  507. case 4:
  508. sprintf( szOut, szTextBuf, szProduct, szProduct, szProduct, szProduct );
  509. break;
  510. case 5:
  511. sprintf( szOut, szTextBuf, szProduct, szProduct, szProduct, szProduct, szProduct, szProduct );
  512. break;
  513. }
  514. //-------------------------------------------------------------
  515. // Replace szTextBuf with szOut.
  516. //-------------------------------------------------------------
  517. if ( nCount >= 1 ) {
  518. strcpy( szString, szOut );
  519. }
  520. */
  521. }
  522. /*--------------------------------------------------------------------------*/
  523. /* Function: PlugInProductName */
  524. /* */
  525. /* Descrip: The function plugs the product name defined in */
  526. /* SdProductName() into %P found in the static message. */
  527. /* It will search for the first nMax controls only. */
  528. /* Misc: */
  529. /* */
  530. /*--------------------------------------------------------------------------*/
  531. void PlugInProductName ( wchar_t *szString, const wchar_t *szName )
  532. {
  533. int nCount, nMsgLength;
  534. wchar_t szTextBuf[ MAX_PATH ];
  535. wchar_t szOut[ MAX_PATH ];
  536. wchar_t szProduct[ MAX_PATH ];
  537. wchar_t *temp = NULL;
  538. wchar_t *next = NULL;
  539. if ( szName == NULL || szName[0] == '\0' ) {
  540. return;
  541. }
  542. //--------------------------------------------------------------------------
  543. // Find the first appearance of "%P".
  544. //--------------------------------------------------------------------------
  545. wcscpy( szProduct, szName );
  546. wcscpy( szTextBuf, szString );
  547. nMsgLength = wcslen( szTextBuf );
  548. nCount = 0;
  549. temp = wcsstr( szTextBuf, L"%s" );
  550. //-------------------------------------------------------------
  551. // Substitute each "%P" with "%s". nStrReturn is the index
  552. // into the buffer where "%P" was found.
  553. //-------------------------------------------------------------
  554. while ( temp != NULL && nCount < 6) {
  555. next = temp+1;
  556. nCount = nCount + 1;
  557. temp = wcsstr( next, L"%s" );
  558. }
  559. //-------------------------------------------------------------
  560. // Only support up to 5 product name per message.
  561. // Do the substitution of the product name and store in szOut.
  562. //-------------------------------------------------------------
  563. switch( nCount ) {
  564. case 1:
  565. swprintf( szOut, szTextBuf, szProduct );
  566. break;
  567. case 2:
  568. swprintf( szOut, szTextBuf, szProduct, szProduct );
  569. break;
  570. case 3:
  571. swprintf( szOut, szTextBuf, szProduct, szProduct, szProduct );
  572. break;
  573. case 4:
  574. swprintf( szOut, szTextBuf, szProduct, szProduct, szProduct, szProduct );
  575. break;
  576. case 5:
  577. swprintf( szOut, szTextBuf, szProduct, szProduct, szProduct, szProduct, szProduct, szProduct );
  578. break;
  579. }
  580. //-------------------------------------------------------------
  581. // Replace szTextBuf with szOut.
  582. //-------------------------------------------------------------
  583. if ( nCount >= 1 ) {
  584. wcscpy( szString, szOut );
  585. }
  586. }