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.

356 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Clean dxsupport.csv by sorting and merging existing records
  4. // Can also parse survey php and merge new data into dxsupport.csv
  5. //
  6. //===============================================================================
  7. #include "tier0/platform.h"
  8. #include "tier0/progressbar.h"
  9. #include "tier2/tier2.h"
  10. #include "tier0/memdbgon.h"
  11. #include "filesystem.h"
  12. #include "icommandline.h"
  13. #include "tier1/utlstringmap.h"
  14. #include "tier1/strtools.h"
  15. #include "tier1/utlmap.h"
  16. #include "tier2/fileutils.h"
  17. #include "stdlib.h"
  18. #include "tier0/dbg.h"
  19. #define MAX_RECORDS 128
  20. struct DeviceRecord_t {
  21. char szName[256];
  22. char szData[MAX_RECORDS][64]; // No more than MAX_RECORDS columns of data in dxsupport.cfg
  23. };
  24. int g_nVendorIDIndex;
  25. int g_nDeviceMinIDIndex;
  26. int g_nDeviceMaxIDIndex;
  27. int g_nNumDXSupportColumns = 0;
  28. bool g_bMergeConflict = false;
  29. static void GrabSurveyData( char const *pInputFilename, char const *pOutputFilename )
  30. {
  31. CRequiredInputTextFile f( pInputFilename );
  32. COutputTextFile o( pOutputFilename );
  33. char linebuffer[4096], szDummy[32], szVendorID[32], szDeviceID[32], szPartName[256], goodLine[] = "$vend_dev_to_name[ MakeLookup(";
  34. while ( f.ReadLine( linebuffer, sizeof( linebuffer ) ) )
  35. {
  36. if ( !strncmp( linebuffer, goodLine, strlen( goodLine ) ) ) // specific pattern on the line we want
  37. {
  38. V_strncpy( szDummy, strtok( linebuffer, ",()\"" ), sizeof( szDummy ) );
  39. V_strncpy( szVendorID, strtok( NULL, ",()\"" ), sizeof( szVendorID ) );
  40. V_strncpy( szDeviceID, strtok( NULL, ",()\"" ), sizeof( szDeviceID ) );
  41. V_strncpy( szDummy, strtok( NULL, ",()\"" ), sizeof( szDummy ) );
  42. V_strncpy( szPartName, strtok( NULL, ",()\"" ), sizeof( szPartName ) );
  43. // Convert to hex from decimal
  44. sprintf( szVendorID, "%04x", atoi( szVendorID ) );
  45. sprintf( szDeviceID, "%04x", atoi( szDeviceID ) );
  46. // Example string:
  47. // ATI Radeon X600,,,,,,,0x1002,0x3150,0x3150,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
  48. o.Write( szPartName, strlen( szPartName ) );
  49. o.Write( ",,,,,,,", 7 );
  50. o.Write( "0x", 2 );
  51. o.Write( szVendorID, strlen( szVendorID ) );
  52. o.Write( ",", 1 );
  53. o.Write( "0x", 2 );
  54. o.Write( szDeviceID, strlen( szDeviceID ) );
  55. o.Write( ",", 1 );
  56. o.Write( "0x", 2 );
  57. o.Write( szDeviceID, strlen( szDeviceID ) );
  58. o.Write( "\n", 1 );
  59. }
  60. }
  61. o.Close();
  62. }
  63. char *CopyTokenUntilComma( char *pOut, char *pIn )
  64. {
  65. if ( !pIn || !pOut )
  66. {
  67. Assert( 0 );
  68. return NULL;
  69. }
  70. while ( ( *pIn != ',' ) && ( *pIn != '\0' ) && ( *pIn != '\n' ) )
  71. {
  72. *pOut++ = *pIn++;
  73. }
  74. *pOut = '\0';
  75. return ++pIn; // gobble the comma
  76. }
  77. void ParseDeviceRecord( char *pStr, DeviceRecord_t *deviceRecord )
  78. {
  79. pStr = CopyTokenUntilComma( deviceRecord->szName, pStr );
  80. for ( int i=0; i<g_nNumDXSupportColumns; i++ ) // Null out the strings
  81. {
  82. V_strncpy( deviceRecord->szData[i], "", 64 );
  83. }
  84. int i = 0; // Populate the strings
  85. while ( *pStr != '\0' )
  86. {
  87. pStr = CopyTokenUntilComma( deviceRecord->szData[i], pStr );
  88. i++;
  89. }
  90. }
  91. // Attempt to merge record nRecord+1 into nRecord
  92. static bool DidMerge( CUtlVector<DeviceRecord_t> &deviceRecords, int nRecord )
  93. {
  94. // If we have the same name and vendor, we are a merge candidate
  95. if ( !V_strnicmp( deviceRecords[nRecord].szName, deviceRecords[nRecord+1].szName, 256 ) &&
  96. !V_strnicmp( deviceRecords[nRecord].szData[g_nVendorIDIndex], deviceRecords[nRecord+1].szData[g_nVendorIDIndex], 64 ) )
  97. {
  98. // Convert hex device ID strings into integers for comparisons
  99. int nMinIDCur, nMaxIDCur, nMinIDNext, nMaxIDNext;
  100. sscanf( deviceRecords[nRecord].szData[g_nDeviceMinIDIndex], "%x", &nMinIDCur );
  101. sscanf( deviceRecords[nRecord].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDCur );
  102. sscanf( deviceRecords[nRecord+1].szData[g_nDeviceMinIDIndex], "%x", &nMinIDNext );
  103. sscanf( deviceRecords[nRecord+1].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDNext );
  104. // If the ranges overlap or are adjacent, merge the data
  105. if ( ( nMinIDNext >= nMinIDCur ) && ( nMinIDNext <= (nMaxIDCur+1) ) )
  106. {
  107. // Set current max to max of the two ranges, merge other elements and flag next record for deletion
  108. V_snprintf( deviceRecords[nRecord].szData[g_nDeviceMaxIDIndex], 64, "0x%04x", MAX( nMaxIDNext, nMaxIDCur ) );
  109. for( int i=g_nDeviceMaxIDIndex+1; i < g_nNumDXSupportColumns; i++ ) // Run through elements after maxID
  110. {
  111. if ( strlen( deviceRecords[nRecord+1].szData[i] ) != 0 ) // If the next record has any data in this element
  112. {
  113. if ( V_strnicmp( deviceRecords[nRecord].szData[i], deviceRecords[nRecord+1].szData[i], 64 ) ) // If it doesn't match the current record's element
  114. {
  115. if ( strlen( deviceRecords[nRecord].szData[i] ) == 0 ) // If the current record has no data for this entry
  116. {
  117. V_strncpy( deviceRecords[nRecord].szData[i], deviceRecords[nRecord+1].szData[i], 64 ); // Just copy the merged record's data into current record
  118. }
  119. else // This is the interesting case, where we have a bona fide conflict
  120. {
  121. V_strncat( deviceRecords[nRecord].szData[i], " FIXME ", 64 ); // Mark the element with ***
  122. V_strncat( deviceRecords[nRecord].szData[i], deviceRecords[nRecord+1].szData[i], 64 ); // Concat the merged element in
  123. g_bMergeConflict = true;
  124. }
  125. }
  126. }
  127. }
  128. return true;
  129. }
  130. }
  131. return false;
  132. }
  133. // Look for duplicate adjacent entries (same vendorID and min/max, ignoring device name string)
  134. static bool FoundDuplicate( CUtlVector<DeviceRecord_t> &deviceRecords, int nRecord )
  135. {
  136. // Spew cases with same vendor ID and device min/maxes...these are likely the same device
  137. if ( !V_strnicmp( deviceRecords[nRecord].szData[g_nVendorIDIndex], deviceRecords[nRecord + 1].szData[g_nVendorIDIndex], 64 ) )
  138. {
  139. // Convert hex device ID strings into integers for comparisons
  140. int nMinIDCur, nMaxIDCur, nMinIDNext, nMaxIDNext;
  141. sscanf( deviceRecords[nRecord].szData[g_nDeviceMinIDIndex], "%x", &nMinIDCur );
  142. sscanf( deviceRecords[nRecord].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDCur );
  143. sscanf( deviceRecords[nRecord + 1].szData[g_nDeviceMinIDIndex], "%x", &nMinIDNext );
  144. sscanf( deviceRecords[nRecord + 1].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDNext );
  145. if ( ( nMinIDCur == nMinIDNext ) && ( nMaxIDCur == nMaxIDNext ) )
  146. {
  147. printf( "Duplicate: %s %s 0x%04x 0x%04x\n", deviceRecords[nRecord].szName, deviceRecords[nRecord].szData[g_nVendorIDIndex], nMinIDCur, nMaxIDCur );
  148. return true;
  149. }
  150. }
  151. return false;
  152. }
  153. void ParseColumnHeadings( char *pHeadings )
  154. {
  155. char *pToken = strtok( pHeadings, "," );
  156. int i = 0;
  157. while ( pToken )
  158. {
  159. if ( !V_strnicmp( pToken, "VendorID", 64 ) )
  160. {
  161. g_nVendorIDIndex = i-1;
  162. }
  163. else if ( !V_strnicmp( pToken, "MinDeviceID", 64 ) )
  164. {
  165. g_nDeviceMinIDIndex = i-1;
  166. }
  167. else if ( !V_strnicmp( pToken, "MaxDeviceID", 64 ) )
  168. {
  169. g_nDeviceMaxIDIndex = i-1;
  170. }
  171. pToken = strtok( NULL, "," );
  172. i++;
  173. }
  174. g_nNumDXSupportColumns = i-1;
  175. Assert( g_nNumDXSupportColumns <= MAX_RECORDS );
  176. }
  177. static void CleanCSVFile( char const *pInputFilename, char const *pOutputFilename )
  178. {
  179. CRequiredInputTextFile f( pInputFilename );
  180. COutputTextFile o( pOutputFilename );
  181. char lineBuffer[4096];
  182. CUtlVector<DeviceRecord_t> deviceRecords;
  183. f.ReadLine( lineBuffer, sizeof( lineBuffer ) ); // Pass header line through
  184. o.Write( lineBuffer, strlen( lineBuffer ) );
  185. ParseColumnHeadings( lineBuffer );
  186. while ( f.ReadLine( lineBuffer, sizeof( lineBuffer ) ) )
  187. {
  188. if ( V_strlen( lineBuffer ) > 12 ) // Just make sure we have some data
  189. {
  190. DeviceRecord_t deviceRecord;
  191. ParseDeviceRecord( lineBuffer, &deviceRecord );
  192. // If we have a line with a real device, add it to the vector
  193. if ( ( strlen( deviceRecord.szData[g_nVendorIDIndex] ) != 0 ) &&
  194. ( strlen( deviceRecord.szData[g_nDeviceMinIDIndex] ) != 0 ) &&
  195. ( strlen( deviceRecord.szData[g_nDeviceMaxIDIndex] ) != 0 ) )
  196. {
  197. deviceRecords.AddToTail( deviceRecord );
  198. }
  199. else
  200. {
  201. o.Write( lineBuffer, strlen( lineBuffer ) );
  202. }
  203. }
  204. else
  205. {
  206. o.Write( lineBuffer, strlen( lineBuffer ) ); // pass short lines through
  207. }
  208. }
  209. // At this point, the output file has the header block from the file and we have records for all of the real device lines
  210. ///////////////////////////////////////////////////////////////////////////////////////////////
  211. //
  212. // Merge "adjacent" instances of the same device
  213. //
  214. int i = 0;
  215. while( i < deviceRecords.Count() - 1 ) // March through devices, merging
  216. {
  217. if ( DidMerge( deviceRecords, i ) ) // Did i+1 get merged into i?
  218. {
  219. deviceRecords.Remove( i+1 ); // Remove i+1 and check with the same i again
  220. continue; // don't increment
  221. }
  222. i++; // Move to next record
  223. }
  224. ///////////////////////////////////////////////////////////////////////////////////////////////
  225. //
  226. // Remove duplicate devices (based on deviceID, not name)
  227. //
  228. i = 0;
  229. while ( i < deviceRecords.Count() - 1 ) // March through devices, looking for duplicates
  230. {
  231. if ( FoundDuplicate( deviceRecords, i ) ) // Does i+1 look like a duplicate of i?
  232. {
  233. deviceRecords.Remove( i + 1 ); // Remove i+1 and check with the same i again
  234. continue; // don't increment
  235. }
  236. i++; // Move to next record
  237. }
  238. // Clean up the hex device ID formatting (pad to 0x1234 with leading zeroes)
  239. for ( int i = 0; i< deviceRecords.Count(); i++ )
  240. {
  241. int nDummy;
  242. sscanf( deviceRecords[i].szData[g_nDeviceMinIDIndex], "%x", &nDummy );
  243. V_snprintf( deviceRecords[i].szData[g_nDeviceMinIDIndex], 64, "0x%04x", nDummy );
  244. sscanf( deviceRecords[i].szData[g_nDeviceMaxIDIndex], "%x", &nDummy );
  245. V_snprintf( deviceRecords[i].szData[g_nDeviceMaxIDIndex], 64, "0x%04x", nDummy );
  246. }
  247. // Just spray the devices out
  248. for ( int i = 0; i< deviceRecords.Count(); i++ )
  249. {
  250. o.Write( deviceRecords[i].szName, strlen( deviceRecords[i].szName ) );
  251. for ( int j=0; j<g_nNumDXSupportColumns; j++ )
  252. {
  253. o.Write( ",", 1 );
  254. o.Write( deviceRecords[i].szData[j], strlen( deviceRecords[i].szData[j] ) );
  255. }
  256. o.Write( "\n", 1 );
  257. }
  258. }
  259. void main(int argc,char **argv)
  260. {
  261. InitCommandLineProgram( argc, argv );
  262. if ( argc != 5 )
  263. {
  264. printf( "\n" );
  265. printf( " dxsupportclean output depends on the input file type (.csv or not).\n" );
  266. printf( " The output is always a CSV file.\n\n" );
  267. printf( " usage: dxsupportclean -i <inputfile> -o <outputfile.csv>\n" );
  268. printf( " If the input file is not csv, dxsupportclean expects hardware\n" );
  269. printf( " survey php syntax. If the input file is csv, dxsupportclean expects\n" );
  270. printf( " the first line to contain specific header columns.\n" );
  271. printf( " If you want to provide some other type of input or if you modify the\n" );
  272. printf( " header columns in dxsupport, you'll have to modify dxsupportclean.\n" );
  273. return;
  274. }
  275. const char *pInputFile = CommandLine()->ParmValue( "-i" );
  276. const char *pOutputFile = CommandLine()->ParmValue( "-o" );
  277. // Can't input and output the same file
  278. if ( !V_strnicmp( pInputFile, pOutputFile, MAX_PATH ) )
  279. {
  280. printf( "Input and out files must not be the same file! No output produced.\n" );
  281. return;
  282. }
  283. if ( !stricmp( "csv", Q_GetFileExtension( pInputFile ) ) )
  284. {
  285. CleanCSVFile( pInputFile, pOutputFile );
  286. if ( g_bMergeConflict )
  287. {
  288. printf( "\n*************************************************************************\n" );
  289. printf( " Merge Conflict detected. Search output for FIXME and resolve manually!\n" );
  290. printf( "*************************************************************************\n\n" );
  291. }
  292. }
  293. else
  294. {
  295. GrabSurveyData( pInputFile, pOutputFile );
  296. }
  297. }