Team Fortress 2 Source Code as on 22/4/2020
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.

590 lines
16 KiB

  1. #include "../vpc/vpc.h"
  2. #include "crccheck_shared.h"
  3. #include "tier1/checksum_crc.h"
  4. #include "tier1/strtools.h"
  5. #include <string.h>
  6. #include <stdio.h>
  7. #include <stdarg.h>
  8. #ifdef _WIN32
  9. #include <process.h>
  10. #else
  11. #include <stdlib.h>
  12. #define stricmp strcasecmp
  13. #endif
  14. #pragma warning( disable : 4996 )
  15. #pragma warning( disable : 4127 )
  16. #define MAX_INCLUDE_STACK_DEPTH 10
  17. static bool IsValidPathChar( char token )
  18. {
  19. // does it look like a file? If this ends up too tight, can probably just check that it's not '[' or '{'
  20. // cause conditional blocks are what we really want to avoid.
  21. return isalpha(token) || isdigit(token) || (token == '.') || (token == '\\') || (token == '/');
  22. }
  23. extern const char *g_szArrPlatforms[];
  24. static void BuildReplacements( const char *token, char *szReplacements )
  25. {
  26. // Now go pickup the any files that exist, but were non-matches
  27. *szReplacements = '\0';
  28. for ( int i = 0; g_szArrPlatforms[i] != NULL; i++ )
  29. {
  30. char szPath[MAX_PATH];
  31. char szPathExpanded[MAX_PATH];
  32. V_strncpy( szPath, token, sizeof(szPath) );
  33. Sys_ReplaceString( szPath, "$os", g_szArrPlatforms[i], szPathExpanded, sizeof(szPathExpanded) );
  34. V_FixSlashes( szPathExpanded );
  35. V_RemoveDotSlashes( szPathExpanded );
  36. V_FixDoubleSlashes( szPathExpanded );
  37. // this fopen is probably using a relative path, but that's ok, as everything in
  38. // the crc code is opening relative paths and assuming the cwd is set ok.
  39. FILE *f = fopen( szPathExpanded, "rb" );
  40. if ( f )
  41. {
  42. fclose(f);
  43. // strcat - blech
  44. strcat( szReplacements, g_szArrPlatforms[i] ); // really just need to stick the existing platforms seen in
  45. strcat( szReplacements, ";" );
  46. }
  47. }
  48. }
  49. static const char * GetToken( const char *ln, char *token )
  50. {
  51. *token = '\0';
  52. while ( *ln && isspace(*ln) )
  53. ln++;
  54. if (!ln[0])
  55. return NULL;
  56. if ( ln[0] == '"' )
  57. { // does vpc allow \" inside the filename string - shouldn't matter, but we're going to assume no.
  58. ln++;
  59. while (*ln)
  60. {
  61. if ( ln[0] == '"' )
  62. break;
  63. *token++ = *ln++;
  64. }
  65. *token = '\0';
  66. }
  67. else if ( IsValidPathChar( *ln ) )
  68. {
  69. while (*ln)
  70. {
  71. if ( isspace(*ln) )
  72. break;
  73. *token++ = *ln++;
  74. }
  75. *token = '\0';
  76. }
  77. else
  78. {
  79. token[0] = ln[0];
  80. token[1] = '\0';
  81. }
  82. return ln;
  83. }
  84. static void PerformFileSubstitions( char * line, int linelen )
  85. {
  86. static bool bFindFilePending = false;
  87. const char *ln = line;
  88. if ( !bFindFilePending )
  89. {
  90. ln = V_stristr( ln, "$file " );
  91. if ( ln )
  92. bFindFilePending = true;
  93. }
  94. if ( bFindFilePending )
  95. {
  96. char token[1024];
  97. ln = GetToken( ln, token );
  98. if ( !ln )
  99. return; // no more tokens on line, we should try the next line
  100. bFindFilePending = false;
  101. if ( V_stristr(token, "$os") )
  102. {
  103. if ( !IsValidPathChar( *token ) )
  104. fprintf( stderr, "Warning: can't expand %s for crc calculation. Changes to this file set won't trigger automatic rebuild\n", token );
  105. char szReplacements[2048];
  106. char buffer[4096];
  107. BuildReplacements( token, szReplacements );
  108. Sys_ReplaceString( line, "$os", szReplacements, buffer, sizeof(buffer) );
  109. V_strncpy( line, buffer, linelen );
  110. }
  111. }
  112. static bool bFindFilePatternPending = false;
  113. ln = line;
  114. if ( !bFindFilePatternPending )
  115. {
  116. ln = V_stristr( ln, "$filepattern" );
  117. while ( ln )
  118. {
  119. ln += 13;
  120. if ( isspace( ln[-1] ) )
  121. {
  122. bFindFilePatternPending = true;
  123. break;
  124. }
  125. }
  126. }
  127. if ( bFindFilePatternPending )
  128. {
  129. char token[1024];
  130. ln = GetToken( ln, token );
  131. if ( !ln )
  132. return; // no more tokens on line, we should try the next line
  133. bFindFilePatternPending = false;
  134. char szReplacements[2048]; szReplacements[0] = '\0';
  135. char buffer[4096];
  136. CUtlVector< CUtlString > vecResults;
  137. Sys_ExpandFilePattern( token, vecResults );
  138. if ( vecResults.Count() )
  139. {
  140. for ( int i= 0; i < vecResults.Count(); i++ )
  141. {
  142. V_strncat( szReplacements, CFmtStr( "%s;", vecResults[i].String() ).Access(), V_ARRAYSIZE( szReplacements ) );
  143. }
  144. CRC32_t nCRC = CRC32_ProcessSingleBuffer( szReplacements, V_strlen( szReplacements ) );
  145. Sys_ReplaceString( line, token, CFmtStr( "%s:%u", token, nCRC ).Access(), buffer, sizeof(buffer) );
  146. V_strncpy( line, buffer, linelen );
  147. }
  148. else
  149. {
  150. if ( !IsValidPathChar( *token ) )
  151. fprintf( stderr, "Warning: %s couldn't be expanded during crc calculation. Changes to this file set won't trigger automatic project rebuild\n", token );
  152. }
  153. }
  154. }
  155. //-----------------------------------------------------------------------------
  156. // Sys_Error
  157. //
  158. //-----------------------------------------------------------------------------
  159. void Sys_Error( const char* format, ... )
  160. {
  161. va_list argptr;
  162. va_start( argptr,format );
  163. vfprintf( stderr, format, argptr );
  164. va_end( argptr );
  165. exit( 1 );
  166. }
  167. void SafeSnprintf( char *pOut, int nOutLen, const char *pFormat, ... )
  168. {
  169. va_list marker;
  170. va_start( marker, pFormat );
  171. V_vsnprintf( pOut, nOutLen, pFormat, marker );
  172. va_end( marker );
  173. pOut[nOutLen-1] = 0;
  174. }
  175. // for linked lists of strings
  176. struct StringNode_t
  177. {
  178. StringNode_t *m_pNext;
  179. char m_Text[1]; // the string data
  180. };
  181. static StringNode_t *MakeStrNode( char const *pStr )
  182. {
  183. size_t nLen = strlen( pStr );
  184. StringNode_t *nRet = ( StringNode_t * ) new unsigned char[sizeof( StringNode_t ) + nLen ];
  185. strcpy( nRet->m_Text, pStr );
  186. return nRet;
  187. }
  188. //-----------------------------------------------------------------------------
  189. // Sys_LoadTextFileWithIncludes
  190. //-----------------------------------------------------------------------------
  191. int Sys_LoadTextFileWithIncludes( const char* filename, char** bufferptr, bool bInsertFileMacroExpansion )
  192. {
  193. FILE *pFileStack[MAX_INCLUDE_STACK_DEPTH];
  194. int nSP = MAX_INCLUDE_STACK_DEPTH;
  195. StringNode_t *pFileLines = NULL; // tail ptr for fast adds
  196. size_t nTotalFileBytes = 0;
  197. FILE *handle = fopen( filename, "r" );
  198. if ( !handle )
  199. return -1;
  200. pFileStack[--nSP] = handle; // push
  201. while ( nSP < MAX_INCLUDE_STACK_DEPTH )
  202. {
  203. // read lines
  204. for (;;)
  205. {
  206. char lineBuffer[4096];
  207. char *ln = fgets( lineBuffer, sizeof( lineBuffer ), pFileStack[nSP] );
  208. if ( !ln )
  209. break; // out of text
  210. ln += strspn( ln, "\t " ); // skip white space
  211. // Need to insert actual files to make sure crc changes if disk-matched files match
  212. if ( bInsertFileMacroExpansion )
  213. PerformFileSubstitions( ln, sizeof(lineBuffer) - (ln-lineBuffer) );
  214. if ( memcmp( ln, "#include", 8 ) == 0 )
  215. {
  216. // omg, an include
  217. ln += 8;
  218. ln += strspn( ln, " \t\"<" ); // skip whitespace, ", and <
  219. size_t nPathNameLength = strcspn( ln, " \t\">\n" );
  220. if ( !nPathNameLength )
  221. {
  222. Sys_Error( "bad include %s via %s\n", lineBuffer, filename );
  223. }
  224. ln[nPathNameLength] = 0; // kill everything after end of filename
  225. FILE *inchandle = fopen( ln, "r" );
  226. if ( !inchandle )
  227. {
  228. Sys_Error( "can't open #include of %s\n", ln );
  229. }
  230. if ( !nSP )
  231. {
  232. Sys_Error( "include nesting too deep via %s", filename );
  233. }
  234. pFileStack[--nSP] = inchandle;
  235. }
  236. else
  237. {
  238. size_t nLen = strlen( ln );
  239. nTotalFileBytes += nLen;
  240. StringNode_t *pNewLine = MakeStrNode( ln );
  241. pNewLine->m_pNext = pFileLines;
  242. pFileLines = pNewLine;
  243. }
  244. }
  245. fclose( pFileStack[nSP] );
  246. nSP++; // pop stack
  247. }
  248. // Reverse the pFileLines list so it goes the right way.
  249. StringNode_t *pPrev = NULL;
  250. StringNode_t *pCur;
  251. for( pCur = pFileLines; pCur; )
  252. {
  253. StringNode_t *pNext = pCur->m_pNext;
  254. pCur->m_pNext = pPrev;
  255. pPrev = pCur;
  256. pCur = pNext;
  257. }
  258. pFileLines = pPrev;
  259. // Now dump all the lines out into a single buffer.
  260. char *buffer = new char[nTotalFileBytes + 1]; // and null
  261. *bufferptr = buffer; // tell caller
  262. // copy all strings and null terminate
  263. int nLine = 0;
  264. StringNode_t *pNext;
  265. for( pCur=pFileLines; pCur; pCur=pNext )
  266. {
  267. pNext = pCur->m_pNext;
  268. size_t nLen = strlen( pCur->m_Text );
  269. memcpy( buffer, pCur->m_Text, nLen );
  270. buffer += nLen;
  271. nLine++;
  272. // Cleanup the line..
  273. //delete [] (unsigned char*)pCur;
  274. }
  275. *( buffer++ ) = 0; // null
  276. return (int)nTotalFileBytes;
  277. }
  278. // Just like fgets() but it removes trailing newlines.
  279. char* ChompLineFromFile( char *pOut, int nOutBytes, FILE *fp )
  280. {
  281. char *pReturn = fgets( pOut, nOutBytes, fp );
  282. if ( pReturn )
  283. {
  284. int len = (int)strlen( pReturn );
  285. if ( len > 0 && pReturn[len-1] == '\n' )
  286. {
  287. pReturn[len-1] = 0;
  288. if ( len > 1 && pReturn[len-2] == '\r' )
  289. pReturn[len-2] = 0;
  290. }
  291. }
  292. return pReturn;
  293. }
  294. bool CheckSupplementalString( const char *pSupplementalString, const char *pReferenceSupplementalString )
  295. {
  296. // The supplemental string is only checked while VPC is determining if a project file is stale or not.
  297. // It's not used by the pre-build event's CRC check.
  298. // The supplemental string contains various options that tell how the project was built. It's generated in VPC_GenerateCRCOptionString.
  299. //
  300. // If there's no reference supplemental string (which is the case if we're running vpccrccheck.exe), then we ignore it and continue.
  301. if ( !pReferenceSupplementalString )
  302. return true;
  303. return ( pSupplementalString && pReferenceSupplementalString && stricmp( pSupplementalString, pReferenceSupplementalString ) == 0 );
  304. }
  305. bool CheckVPCExeCRC( char *pVPCCRCCheckString, const char *szFilename, char *pErrorString, int nErrorStringLength )
  306. {
  307. if ( pVPCCRCCheckString == NULL )
  308. {
  309. SafeSnprintf( pErrorString, nErrorStringLength, "Unexpected end-of-file in %s", szFilename );
  310. return false;
  311. }
  312. char *pSpace = strchr( pVPCCRCCheckString, ' ' );
  313. if ( !pSpace )
  314. {
  315. SafeSnprintf( pErrorString, nErrorStringLength, "Invalid line ('%s') in %s", pVPCCRCCheckString, szFilename );
  316. return false;
  317. }
  318. // Null-terminate it so we have the CRC by itself and the filename follows the space.
  319. *pSpace = 0;
  320. const char *pVPCFilename = pSpace + 1;
  321. // Parse the CRC out.
  322. unsigned int nReferenceCRC;
  323. sscanf( pVPCCRCCheckString, "%x", &nReferenceCRC );
  324. char *pBuffer;
  325. int cbVPCExe = Sys_LoadFile( pVPCFilename, (void**)&pBuffer );
  326. if ( !pBuffer )
  327. {
  328. SafeSnprintf( pErrorString, nErrorStringLength, "Unable to load %s for comparison.", pVPCFilename );
  329. return false;
  330. }
  331. if ( cbVPCExe < 0 )
  332. {
  333. SafeSnprintf( pErrorString, nErrorStringLength, "Could not load file '%s' to check CRC", pVPCFilename );
  334. return false;
  335. }
  336. // Calculate the CRC from the contents of the file.
  337. CRC32_t nCRCFromFileContents = CRC32_ProcessSingleBuffer( pBuffer, cbVPCExe );
  338. delete [] pBuffer;
  339. // Compare them.
  340. if ( nCRCFromFileContents != nReferenceCRC )
  341. {
  342. SafeSnprintf( pErrorString, nErrorStringLength, "VPC executable has changed since the project was generated." );
  343. return false;
  344. }
  345. return true;
  346. }
  347. bool VPC_CheckProjectDependencyCRCs( const char *pProjectFilename, const char *pReferenceSupplementalString, char *pErrorString, int nErrorStringLength )
  348. {
  349. // Build the xxxxx.vcproj.vpc_crc filename
  350. char szFilename[512];
  351. SafeSnprintf( szFilename, sizeof( szFilename ), "%s.%s", pProjectFilename, VPCCRCCHECK_FILE_EXTENSION );
  352. // Open it up.
  353. FILE *fp = fopen( szFilename, "rt" );
  354. if ( !fp )
  355. {
  356. SafeSnprintf( pErrorString, nErrorStringLength, "Unable to load %s to check CRC strings", szFilename );
  357. return false;
  358. }
  359. bool bReturnValue = false;
  360. char lineBuffer[2048];
  361. // Check the version of the CRC file.
  362. const char *pVersionString = ChompLineFromFile( lineBuffer, sizeof( lineBuffer ), fp );
  363. if ( pVersionString && stricmp( pVersionString, VPCCRCCHECK_FILE_VERSION_STRING ) == 0 )
  364. {
  365. char *pVPCExeCRCString = ChompLineFromFile( lineBuffer, sizeof( lineBuffer ), fp );
  366. if ( CheckVPCExeCRC( pVPCExeCRCString, szFilename, pErrorString, nErrorStringLength ) )
  367. {
  368. // Check the supplemental CRC string.
  369. const char *pSupplementalString = ChompLineFromFile( lineBuffer, sizeof( lineBuffer ), fp );
  370. if ( CheckSupplementalString( pSupplementalString, pReferenceSupplementalString ) )
  371. {
  372. // Now read each line. Each line has a CRC and a filename on it.
  373. while ( 1 )
  374. {
  375. char *pLine = ChompLineFromFile( lineBuffer, sizeof( lineBuffer ), fp );
  376. if ( !pLine )
  377. {
  378. // We got all the way through the file without a CRC error, so all's well.
  379. bReturnValue = true;
  380. break;
  381. }
  382. char *pSpace = strchr( pLine, ' ' );
  383. if ( !pSpace )
  384. {
  385. SafeSnprintf( pErrorString, nErrorStringLength, "Invalid line ('%s') in %s", pLine, szFilename );
  386. break;
  387. }
  388. // Null-terminate it so we have the CRC by itself and the filename follows the space.
  389. *pSpace = 0;
  390. const char *pVPCFilename = pSpace + 1;
  391. // Parse the CRC out.
  392. unsigned int nReferenceCRC;
  393. sscanf( pLine, "%x", &nReferenceCRC );
  394. // Calculate the CRC from the contents of the file.
  395. char *pBuffer;
  396. int nTotalFileBytes = Sys_LoadTextFileWithIncludes( pVPCFilename, &pBuffer, true );
  397. if ( nTotalFileBytes == -1 )
  398. {
  399. SafeSnprintf( pErrorString, nErrorStringLength, "Unable to load %s for CRC comparison.", pVPCFilename );
  400. break;
  401. }
  402. CRC32_t nCRCFromTextContents = CRC32_ProcessSingleBuffer( pBuffer, nTotalFileBytes );
  403. delete [] pBuffer;
  404. // Compare them.
  405. if ( nCRCFromTextContents != nReferenceCRC )
  406. {
  407. SafeSnprintf( pErrorString, nErrorStringLength, "This VCPROJ is out of sync with its VPC scripts.\n %s mismatches (0x%x vs 0x%x).\n Please use VPC to re-generate!\n \n", pVPCFilename, nReferenceCRC, nCRCFromTextContents );
  408. break;
  409. }
  410. }
  411. }
  412. else
  413. {
  414. SafeSnprintf( pErrorString, nErrorStringLength, "Supplemental string mismatch." );
  415. }
  416. }
  417. }
  418. else
  419. {
  420. SafeSnprintf( pErrorString, nErrorStringLength, "CRC file %s has an invalid version string ('%s')", szFilename, pVersionString ? pVersionString : "[null]" );
  421. }
  422. fclose( fp );
  423. return bReturnValue;
  424. }
  425. int VPC_OldeStyleCRCChecks( int argc, char **argv )
  426. {
  427. for ( int i=1; (i+2) < argc; )
  428. {
  429. const char *pTestArg = argv[i];
  430. if ( stricmp( pTestArg, "-crc" ) != 0 )
  431. {
  432. ++i;
  433. continue;
  434. }
  435. const char *pVPCFilename = argv[i+1];
  436. // Get the CRC value on the command line.
  437. const char *pTestCRC = argv[i+2];
  438. unsigned int nCRCFromCommandLine;
  439. sscanf( pTestCRC, "%x", &nCRCFromCommandLine );
  440. // Calculate the CRC from the contents of the file.
  441. char *pBuffer;
  442. int nTotalFileBytes = Sys_LoadTextFileWithIncludes( pVPCFilename, &pBuffer, true );
  443. if ( nTotalFileBytes == -1 )
  444. {
  445. Sys_Error( "Unable to load %s for CRC comparison.", pVPCFilename );
  446. }
  447. CRC32_t nCRCFromTextContents = CRC32_ProcessSingleBuffer( pBuffer, nTotalFileBytes );
  448. delete [] pBuffer;
  449. // Compare them.
  450. if ( nCRCFromTextContents != nCRCFromCommandLine )
  451. {
  452. Sys_Error( " \n This VCPROJ is out of sync with its VPC scripts.\n %s mismatches (0x%x vs 0x%x).\n Please use VPC to re-generate!\n \n", pVPCFilename, nCRCFromCommandLine, nCRCFromTextContents );
  453. }
  454. i += 2;
  455. }
  456. return 0;
  457. }
  458. int VPC_CommandLineCRCChecks( int argc, char **argv )
  459. {
  460. if ( argc < 2 )
  461. {
  462. fprintf( stderr, "Invalid arguments to " VPCCRCCHECK_EXE_FILENAME ". Format: " VPCCRCCHECK_EXE_FILENAME " [project filename]\n" );
  463. return 1;
  464. }
  465. const char *pFirstCRC = argv[1];
  466. // If the first argument starts with -crc but is not -crc2, then this is an old CRC check command line with all the CRCs and filenames
  467. // directly on the command line. The new format puts all that in a separate file.
  468. if ( pFirstCRC[0] == '-' && pFirstCRC[1] == 'c' && pFirstCRC[2] == 'r' && pFirstCRC[3] == 'c' && pFirstCRC[4] != '2' )
  469. {
  470. return VPC_OldeStyleCRCChecks( argc, argv );
  471. }
  472. if ( stricmp( pFirstCRC, "-crc2" ) != 0 )
  473. {
  474. fprintf( stderr, "Missing -crc2 parameter on vpc CRC check command line." );
  475. return 1;
  476. }
  477. const char *pProjectFilename = argv[2];
  478. char errorString[1024];
  479. bool bCRCsValid = VPC_CheckProjectDependencyCRCs( pProjectFilename, NULL, errorString, sizeof( errorString ) );
  480. if ( bCRCsValid )
  481. {
  482. return 0;
  483. }
  484. else
  485. {
  486. fprintf( stderr, "%s", errorString );
  487. return 1;
  488. }
  489. }