//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Clean dxsupport.csv by sorting and merging existing records // Can also parse survey php and merge new data into dxsupport.csv // //=============================================================================== #include "tier0/platform.h" #include "tier0/progressbar.h" #include "tier2/tier2.h" #include "tier0/memdbgon.h" #include "filesystem.h" #include "icommandline.h" #include "tier1/utlstringmap.h" #include "tier1/strtools.h" #include "tier1/utlmap.h" #include "tier2/fileutils.h" #include "stdlib.h" #include "tier0/dbg.h" #define MAX_RECORDS 128 struct DeviceRecord_t { char szName[256]; char szData[MAX_RECORDS][64]; // No more than MAX_RECORDS columns of data in dxsupport.cfg }; int g_nVendorIDIndex; int g_nDeviceMinIDIndex; int g_nDeviceMaxIDIndex; int g_nNumDXSupportColumns = 0; bool g_bMergeConflict = false; static void GrabSurveyData( char const *pInputFilename, char const *pOutputFilename ) { CRequiredInputTextFile f( pInputFilename ); COutputTextFile o( pOutputFilename ); char linebuffer[4096], szDummy[32], szVendorID[32], szDeviceID[32], szPartName[256], goodLine[] = "$vend_dev_to_name[ MakeLookup("; while ( f.ReadLine( linebuffer, sizeof( linebuffer ) ) ) { if ( !strncmp( linebuffer, goodLine, strlen( goodLine ) ) ) // specific pattern on the line we want { V_strncpy( szDummy, strtok( linebuffer, ",()\"" ), sizeof( szDummy ) ); V_strncpy( szVendorID, strtok( NULL, ",()\"" ), sizeof( szVendorID ) ); V_strncpy( szDeviceID, strtok( NULL, ",()\"" ), sizeof( szDeviceID ) ); V_strncpy( szDummy, strtok( NULL, ",()\"" ), sizeof( szDummy ) ); V_strncpy( szPartName, strtok( NULL, ",()\"" ), sizeof( szPartName ) ); // Convert to hex from decimal sprintf( szVendorID, "%04x", atoi( szVendorID ) ); sprintf( szDeviceID, "%04x", atoi( szDeviceID ) ); // Example string: // ATI Radeon X600,,,,,,,0x1002,0x3150,0x3150,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, o.Write( szPartName, strlen( szPartName ) ); o.Write( ",,,,,,,", 7 ); o.Write( "0x", 2 ); o.Write( szVendorID, strlen( szVendorID ) ); o.Write( ",", 1 ); o.Write( "0x", 2 ); o.Write( szDeviceID, strlen( szDeviceID ) ); o.Write( ",", 1 ); o.Write( "0x", 2 ); o.Write( szDeviceID, strlen( szDeviceID ) ); o.Write( "\n", 1 ); } } o.Close(); } char *CopyTokenUntilComma( char *pOut, char *pIn ) { if ( !pIn || !pOut ) { Assert( 0 ); return NULL; } while ( ( *pIn != ',' ) && ( *pIn != '\0' ) && ( *pIn != '\n' ) ) { *pOut++ = *pIn++; } *pOut = '\0'; return ++pIn; // gobble the comma } void ParseDeviceRecord( char *pStr, DeviceRecord_t *deviceRecord ) { pStr = CopyTokenUntilComma( deviceRecord->szName, pStr ); for ( int i=0; iszData[i], "", 64 ); } int i = 0; // Populate the strings while ( *pStr != '\0' ) { pStr = CopyTokenUntilComma( deviceRecord->szData[i], pStr ); i++; } } // Attempt to merge record nRecord+1 into nRecord static bool DidMerge( CUtlVector &deviceRecords, int nRecord ) { // If we have the same name and vendor, we are a merge candidate if ( !V_strnicmp( deviceRecords[nRecord].szName, deviceRecords[nRecord+1].szName, 256 ) && !V_strnicmp( deviceRecords[nRecord].szData[g_nVendorIDIndex], deviceRecords[nRecord+1].szData[g_nVendorIDIndex], 64 ) ) { // Convert hex device ID strings into integers for comparisons int nMinIDCur, nMaxIDCur, nMinIDNext, nMaxIDNext; sscanf( deviceRecords[nRecord].szData[g_nDeviceMinIDIndex], "%x", &nMinIDCur ); sscanf( deviceRecords[nRecord].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDCur ); sscanf( deviceRecords[nRecord+1].szData[g_nDeviceMinIDIndex], "%x", &nMinIDNext ); sscanf( deviceRecords[nRecord+1].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDNext ); // If the ranges overlap or are adjacent, merge the data if ( ( nMinIDNext >= nMinIDCur ) && ( nMinIDNext <= (nMaxIDCur+1) ) ) { // Set current max to max of the two ranges, merge other elements and flag next record for deletion V_snprintf( deviceRecords[nRecord].szData[g_nDeviceMaxIDIndex], 64, "0x%04x", MAX( nMaxIDNext, nMaxIDCur ) ); for( int i=g_nDeviceMaxIDIndex+1; i < g_nNumDXSupportColumns; i++ ) // Run through elements after maxID { if ( strlen( deviceRecords[nRecord+1].szData[i] ) != 0 ) // If the next record has any data in this element { if ( V_strnicmp( deviceRecords[nRecord].szData[i], deviceRecords[nRecord+1].szData[i], 64 ) ) // If it doesn't match the current record's element { if ( strlen( deviceRecords[nRecord].szData[i] ) == 0 ) // If the current record has no data for this entry { V_strncpy( deviceRecords[nRecord].szData[i], deviceRecords[nRecord+1].szData[i], 64 ); // Just copy the merged record's data into current record } else // This is the interesting case, where we have a bona fide conflict { V_strncat( deviceRecords[nRecord].szData[i], " FIXME ", 64 ); // Mark the element with *** V_strncat( deviceRecords[nRecord].szData[i], deviceRecords[nRecord+1].szData[i], 64 ); // Concat the merged element in g_bMergeConflict = true; } } } } return true; } } return false; } // Look for duplicate adjacent entries (same vendorID and min/max, ignoring device name string) static bool FoundDuplicate( CUtlVector &deviceRecords, int nRecord ) { // Spew cases with same vendor ID and device min/maxes...these are likely the same device if ( !V_strnicmp( deviceRecords[nRecord].szData[g_nVendorIDIndex], deviceRecords[nRecord + 1].szData[g_nVendorIDIndex], 64 ) ) { // Convert hex device ID strings into integers for comparisons int nMinIDCur, nMaxIDCur, nMinIDNext, nMaxIDNext; sscanf( deviceRecords[nRecord].szData[g_nDeviceMinIDIndex], "%x", &nMinIDCur ); sscanf( deviceRecords[nRecord].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDCur ); sscanf( deviceRecords[nRecord + 1].szData[g_nDeviceMinIDIndex], "%x", &nMinIDNext ); sscanf( deviceRecords[nRecord + 1].szData[g_nDeviceMaxIDIndex], "%x", &nMaxIDNext ); if ( ( nMinIDCur == nMinIDNext ) && ( nMaxIDCur == nMaxIDNext ) ) { printf( "Duplicate: %s %s 0x%04x 0x%04x\n", deviceRecords[nRecord].szName, deviceRecords[nRecord].szData[g_nVendorIDIndex], nMinIDCur, nMaxIDCur ); return true; } } return false; } void ParseColumnHeadings( char *pHeadings ) { char *pToken = strtok( pHeadings, "," ); int i = 0; while ( pToken ) { if ( !V_strnicmp( pToken, "VendorID", 64 ) ) { g_nVendorIDIndex = i-1; } else if ( !V_strnicmp( pToken, "MinDeviceID", 64 ) ) { g_nDeviceMinIDIndex = i-1; } else if ( !V_strnicmp( pToken, "MaxDeviceID", 64 ) ) { g_nDeviceMaxIDIndex = i-1; } pToken = strtok( NULL, "," ); i++; } g_nNumDXSupportColumns = i-1; Assert( g_nNumDXSupportColumns <= MAX_RECORDS ); } static void CleanCSVFile( char const *pInputFilename, char const *pOutputFilename ) { CRequiredInputTextFile f( pInputFilename ); COutputTextFile o( pOutputFilename ); char lineBuffer[4096]; CUtlVector deviceRecords; f.ReadLine( lineBuffer, sizeof( lineBuffer ) ); // Pass header line through o.Write( lineBuffer, strlen( lineBuffer ) ); ParseColumnHeadings( lineBuffer ); while ( f.ReadLine( lineBuffer, sizeof( lineBuffer ) ) ) { if ( V_strlen( lineBuffer ) > 12 ) // Just make sure we have some data { DeviceRecord_t deviceRecord; ParseDeviceRecord( lineBuffer, &deviceRecord ); // If we have a line with a real device, add it to the vector if ( ( strlen( deviceRecord.szData[g_nVendorIDIndex] ) != 0 ) && ( strlen( deviceRecord.szData[g_nDeviceMinIDIndex] ) != 0 ) && ( strlen( deviceRecord.szData[g_nDeviceMaxIDIndex] ) != 0 ) ) { deviceRecords.AddToTail( deviceRecord ); } else { o.Write( lineBuffer, strlen( lineBuffer ) ); } } else { o.Write( lineBuffer, strlen( lineBuffer ) ); // pass short lines through } } // At this point, the output file has the header block from the file and we have records for all of the real device lines /////////////////////////////////////////////////////////////////////////////////////////////// // // Merge "adjacent" instances of the same device // int i = 0; while( i < deviceRecords.Count() - 1 ) // March through devices, merging { if ( DidMerge( deviceRecords, i ) ) // Did i+1 get merged into i? { deviceRecords.Remove( i+1 ); // Remove i+1 and check with the same i again continue; // don't increment } i++; // Move to next record } /////////////////////////////////////////////////////////////////////////////////////////////// // // Remove duplicate devices (based on deviceID, not name) // i = 0; while ( i < deviceRecords.Count() - 1 ) // March through devices, looking for duplicates { if ( FoundDuplicate( deviceRecords, i ) ) // Does i+1 look like a duplicate of i? { deviceRecords.Remove( i + 1 ); // Remove i+1 and check with the same i again continue; // don't increment } i++; // Move to next record } // Clean up the hex device ID formatting (pad to 0x1234 with leading zeroes) for ( int i = 0; i< deviceRecords.Count(); i++ ) { int nDummy; sscanf( deviceRecords[i].szData[g_nDeviceMinIDIndex], "%x", &nDummy ); V_snprintf( deviceRecords[i].szData[g_nDeviceMinIDIndex], 64, "0x%04x", nDummy ); sscanf( deviceRecords[i].szData[g_nDeviceMaxIDIndex], "%x", &nDummy ); V_snprintf( deviceRecords[i].szData[g_nDeviceMaxIDIndex], 64, "0x%04x", nDummy ); } // Just spray the devices out for ( int i = 0; i< deviceRecords.Count(); i++ ) { o.Write( deviceRecords[i].szName, strlen( deviceRecords[i].szName ) ); for ( int j=0; j -o \n" ); printf( " If the input file is not csv, dxsupportclean expects hardware\n" ); printf( " survey php syntax. If the input file is csv, dxsupportclean expects\n" ); printf( " the first line to contain specific header columns.\n" ); printf( " If you want to provide some other type of input or if you modify the\n" ); printf( " header columns in dxsupport, you'll have to modify dxsupportclean.\n" ); return; } const char *pInputFile = CommandLine()->ParmValue( "-i" ); const char *pOutputFile = CommandLine()->ParmValue( "-o" ); // Can't input and output the same file if ( !V_strnicmp( pInputFile, pOutputFile, MAX_PATH ) ) { printf( "Input and out files must not be the same file! No output produced.\n" ); return; } if ( !stricmp( "csv", Q_GetFileExtension( pInputFile ) ) ) { CleanCSVFile( pInputFile, pOutputFile ); if ( g_bMergeConflict ) { printf( "\n*************************************************************************\n" ); printf( " Merge Conflict detected. Search output for FIXME and resolve manually!\n" ); printf( "*************************************************************************\n\n" ); } } else { GrabSurveyData( pInputFile, pOutputFile ); } }