//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// //#define PROTECTED_THINGS_DISABLE #undef PROTECT_FILEIO_FUNCTIONS #undef fopen #include "winlite.h" #include #ifdef WIN32 #include #include #else #include #define _stat stat #endif #include #include #include #include "basetypes.h" #include "utlvector.h" #include "utlsymbol.h" #include "utldict.h" #include "utlbuffer.h" #include "utlmap.h" #include "bugreporter/bugreporter.h" #include "filesystem_tools.h" #include "KeyValues.h" #include "vstdlib/random.h" #ifdef WIN32 #define BUGSUB_CONFIG "\\\\bugbait\\bugsub\\config.txt" #else #ifdef OSX #define BUGSUB_CONFIG "/Volumes/bugsub/config.txt" #define BUGSUB_MOUNT "/Volumes/bugsub" #define BUGSUB_MOUNT_COMMAND "mount_smbfs //guest:@bugbait.valvesoftware.com/bugsub " BUGSUB_MOUNT #else // We can't do sudo here, so we rely on /etc/fstab being setup for '/mnt/bugsub'. See comment in CBugReporter::Init(). #define BUGSUB_CONFIG "/mnt/bugsub/config.txt" #define BUGSUB_MOUNT "/mnt/bugsub" #define BUGSUB_MOUNT_COMMAND "mount /mnt/bugsub" #endif #define BUGSUB_UNMOUNT_COMMAND "umount " BUGSUB_MOUNT #endif class CBugReporter *g_bugreporter = NULL; class CBug { public: CBug() { Clear(); } void Clear() { Q_memset( title, 0, sizeof( title ) ); Q_memset( desc, 0, sizeof( desc ) ); Q_memset( submitter, 0, sizeof( submitter ) ); Q_memset( owner, 0, sizeof( owner ) ); Q_memset( severity, 0, sizeof( severity ) ); Q_memset( priority, 0, sizeof( priority ) ); Q_memset( area, 0, sizeof( area ) ); Q_memset( mapnumber, 0, sizeof( mapnumber) ); Q_memset( reporttype, 0, sizeof( reporttype ) ); Q_memset( level, 0, sizeof( level ) ); Q_memset( build, 0, sizeof( build ) ); Q_memset( position, 0, sizeof( position ) ); Q_memset( orientation, 0, sizeof( orientation ) ); Q_memset( screenshot_unc, 0, sizeof( screenshot_unc ) ); Q_memset( savegame_unc, 0, sizeof( savegame_unc ) ); Q_memset( bsp_unc, 0, sizeof( bsp_unc ) ); Q_memset( vmf_unc, 0, sizeof( vmf_unc ) ); Q_memset( driverinfo, 0, sizeof( driverinfo ) ); Q_memset( misc, 0, sizeof( misc ) ); includedfiles.Purge(); } char title[ 256 ]; char desc[ 8192 ]; char owner[ 256 ]; char submitter[ 256 ]; char severity[ 256 ]; char priority[ 256 ]; char area[ 256 ]; char mapnumber[ 256 ]; char reporttype[ 256 ]; char level[ 256 ]; char build[ 256 ]; char position[ 256 ]; char orientation[ 256 ]; char screenshot_unc[ 256 ]; char savegame_unc[ 256 ]; char bsp_unc[ 256 ]; char vmf_unc[ 256 ]; char driverinfo[ 2048 ]; char misc[ 1024 ]; struct incfile { char name[ 256 ]; }; CUtlVector< incfile > includedfiles; }; class CBugReporter : public IBugReporter { public: CBugReporter(); virtual ~CBugReporter(); // Initialize and login with default username/password for this computer (from resource/bugreporter.res) virtual bool Init( CreateInterfaceFn engineFactory ); virtual void Shutdown(); virtual bool IsPublicUI() { return false; } virtual char const *GetUserName(); virtual char const *GetUserName_Display(); virtual int GetNameCount(); virtual char const *GetName( int index ); virtual int GetDisplayNameCount(); virtual char const *GetDisplayName( int index ); virtual char const *GetUserNameForIndex( int index ); virtual char const *GetDisplayNameForUserName( char const *username ); virtual char const *GetUserNameForDisplayName( char const *display ); virtual char const *GetAreaMapForArea( char const * area); virtual int GetSeverityCount(); virtual char const *GetSeverity( int index ); virtual int GetPriorityCount(); virtual char const *GetPriority( int index ); virtual int GetAreaCount(); virtual char const *GetArea( int index ); virtual int GetAreaMapCount(); virtual char const *GetAreaMap( int index ); virtual int GetMapNumberCount(); virtual char const *GetMapNumber( int index ); virtual int GetReportTypeCount(); virtual char const *GetReportType( int index ); virtual char const *GetRepositoryURL( void ); virtual char const *GetSubmissionURL( void ); virtual int GetLevelCount(int area); virtual char const *GetLevel(int area, int index ); // Submission API virtual void StartNewBugReport(); virtual void CancelNewBugReport(); virtual bool CommitBugReport( int& bugSubmissionId ); virtual void SetTitle( char const *title ); virtual void SetDescription( char const *description ); // NULL for current user virtual void SetSubmitter( char const *username = 0 ); virtual void SetOwner( char const *username ); virtual void SetSeverity( char const *severity ); virtual void SetPriority( char const *priority ); virtual void SetArea( char const *area ); virtual void SetMapNumber ( char const *mapnumber ); virtual void SetReportType( char const *reporttype ); virtual void SetLevel( char const *levelnamne ); virtual void SetPosition( char const *position ); virtual void SetOrientation( char const *pitch_yaw_roll ); virtual void SetBuildNumber( char const *build_num ); virtual void SetScreenShot( char const *screenshot_unc_address ); virtual void SetSaveGame( char const *savegame_unc_address ); virtual void SetBSPName( char const *bsp_unc_address ); virtual void SetVMFName( char const *vmf_unc_address ); virtual void AddIncludedFile( char const *filename ); virtual void ResetIncludedFiles(); virtual void SetZipAttachmentName( char const *zipfilename ) {} // only used by public bug reporter virtual void SetDriverInfo( char const *info ); virtual void SetMiscInfo( char const *info ); // These are stubbed here, but are used by the public version... virtual void SetCSERAddress( const struct netadr_s& adr ) {} virtual void SetExeName( char const *exename ) {} virtual void SetGameDirectory( char const *pchGamedir ) {} virtual void SetRAM( int ram ) {} virtual void SetCPU( int cpu ) {} virtual void SetProcessor( char const *processor ) {} virtual void SetDXVersion( unsigned int high, unsigned int low, unsigned int vendor, unsigned int device ) {} virtual void SetOSVersion( char const *osversion ) {} virtual void SetSteamUserID( void *steamid, int idsize ) {}; bool SymbolLessThan(const CUtlSymbol &sym1, const CUtlSymbol &sym2); private: bool PopulateLists(); bool PopulateChoiceList( char const *listname, CUtlVector< CUtlSymbol >& list ); CUtlSymbolTable m_BugStrings; CUtlVector< CUtlSymbol > m_Severity; CUtlVector< CUtlSymbol > m_SortedDisplayNames; CUtlVector< CUtlSymbol > m_SortedUserNames; CUtlVector< CUtlSymbol > m_Priority; CUtlVector< CUtlSymbol > m_Area; CUtlVector< CUtlSymbol > m_AreaMap; CUtlVector< CUtlSymbol > m_MapNumber; CUtlVector< CUtlSymbol > m_ReportType; CUtlMap *> m_LevelMap; CUtlSymbol m_UserName; CBug *m_pBug; char m_BugRootDirectory[512]; KeyValues *m_OptionsFile; int m_CurrentBugID; char m_CurrentBugDirectory[512]; bool m_bMountedBugSub; }; bool CUtlSymbol_LessThan(const CUtlSymbol &sym1, const CUtlSymbol &sym2) { return g_bugreporter->SymbolLessThan(sym1, sym2); } CBugReporter::CBugReporter() { m_pBug = NULL; m_CurrentBugID = 0; m_bMountedBugSub = false; m_LevelMap.SetLessFunc(&CUtlSymbol_LessThan); g_bugreporter = this; } CBugReporter::~CBugReporter() { m_BugStrings.RemoveAll(); m_Severity.Purge(); m_SortedDisplayNames.Purge(); m_Priority.Purge(); m_Area.Purge(); m_MapNumber.Purge(); m_ReportType.Purge(); m_LevelMap.RemoveAll(); delete m_pBug; } //----------------------------------------------------------------------------- // Purpose: Initialize and login with default username/password for this computer (from resource/bugreporter.res) // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBugReporter::Init( CreateInterfaceFn engineFactory ) { // Load our bugreporter_text options file m_OptionsFile = new KeyValues( "OptionsFile" ); #ifdef POSIX // check if we can see the config file struct _stat mount_info; if (_stat(BUGSUB_CONFIG, &mount_info)) { // if not we may need to mount bugbait if ( _stat(BUGSUB_MOUNT, &mount_info ) ) { // make the mount dir if needed mkdir( BUGSUB_MOUNT, S_IRWXU | S_IRWXG | S_IRWXO ); } // now run the smbmount on it system( BUGSUB_MOUNT_COMMAND ); #ifdef LINUX if( _stat( BUGSUB_CONFIG, &mount_info ) ) { Color clr( 255, 100, 50, 255 ); // The mount failed - probably because the /etc/fstab entry is missing? ConColorMsg( clr, "ERROR: failed to mount '" BUGSUB_MOUNT "' with '" BUGSUB_MOUNT_COMMAND "'.\n" ); ConColorMsg( clr, "Bugsub not set up yet? Do 'sudo apt-get install smbfs', 'sudo mkdir /mnt/bugsub', and add this /etc/fstab:\n" ); ConColorMsg( clr, " bugbait.valvesoftware.com/bugsub /mnt/bugsub smbfs rw,user,username=guest,password=,noauto 0 0\n" ); return false; } #endif m_bMountedBugSub = true; } #elif defined(WIN32) #else #error "need to get to \\bugbait somehow" #endif // open file old skool way to avoid steam filesystem restrictions struct _stat cfg_info; if (_stat(BUGSUB_CONFIG, &cfg_info)) { AssertMsg( 0, "Failed to find bugreporter options file." ); return false; } int buf_size = (cfg_info.st_size + 1); char *buf = new char[buf_size]; FILE *fp = fopen(BUGSUB_CONFIG, "rb"); if (!fp) { AssertMsg(0, "failed to open bugreporter options file" ); delete [] buf; return false; } fread(buf, cfg_info.st_size, 1, fp); fclose(fp); buf[cfg_info.st_size] = 0; if ( !m_OptionsFile->LoadFromBuffer(BUGSUB_CONFIG, buf) ) { AssertMsg( 0, "Failed to load bugreporter_text options file." ); delete [] buf; return false; } #ifdef WIN32 V_strncpy( m_BugRootDirectory, m_OptionsFile->GetString( "bug_directory", "." ), sizeof(m_BugRootDirectory) ); #elif defined(OSX) V_strncpy( m_BugRootDirectory, m_OptionsFile->GetString( "bug_directory_osx", BUGSUB_MOUNT ), sizeof(m_BugRootDirectory) ); #elif defined(LINUX) V_strncpy( m_BugRootDirectory, m_OptionsFile->GetString( "bug_directory_linux", BUGSUB_MOUNT ), sizeof(m_BugRootDirectory) ); #else #error #endif PopulateLists(); #ifdef WIN32 m_UserName = m_BugStrings.AddString(getenv( "username" )); #elif POSIX m_UserName = m_BugStrings.AddString(getenv( "USER" )); #else #error #endif delete [] buf; return true; } void CBugReporter::Shutdown() { #ifdef POSIX if ( m_bMountedBugSub ) { system( BUGSUB_UNMOUNT_COMMAND ); } #endif } char const *CBugReporter::GetUserName() { return m_BugStrings.String( m_UserName ); } char const *CBugReporter::GetUserName_Display() { return GetDisplayNameForUserName( GetUserName() ); } int CBugReporter::GetNameCount() { return GetDisplayNameCount(); } char const *CBugReporter::GetName( int index ) { return GetDisplayName(index); } int CBugReporter::GetDisplayNameCount() { return m_SortedDisplayNames.Count(); } char const *CBugReporter::GetDisplayName( int index ) { if ( index < 0 || index >= (int)m_SortedDisplayNames.Count() ) return "<>"; return m_BugStrings.String( m_SortedDisplayNames[ index ] ); } char const *CBugReporter::GetUserNameForIndex( int index ) { if ( index < 0 || index >= (int)m_SortedUserNames.Count() ) return "<>"; return m_BugStrings.String( m_SortedUserNames[ index ] ); } char const *CBugReporter::GetDisplayNameForUserName( char const *username ) { CUtlSymbol username_sym = m_BugStrings.Find(username); FOR_EACH_VEC(m_SortedUserNames, index) { if (m_SortedUserNames[index] == username_sym) { return GetDisplayName(index); } } return username; } char const *CBugReporter::GetUserNameForDisplayName( char const *display ) { CUtlSymbol display_sym = m_BugStrings.Find(display); FOR_EACH_VEC(m_SortedDisplayNames, index) { if (m_SortedDisplayNames[index] == display_sym) { return GetUserNameForIndex(index); } } return display; } char const *CBugReporter::GetAreaMapForArea( char const *area) { CUtlSymbol area_sym = m_BugStrings.Find(area); FOR_EACH_VEC(m_Area, index) { if (m_Area[index] == area_sym && index > 0) { char const *areamap = m_BugStrings.String(m_AreaMap[index-1]); return areamap+1; } } return "<>"; } int CBugReporter::GetSeverityCount() { return m_Severity.Count() ; } char const *CBugReporter::GetSeverity( int index ) { if ( index < 0 || index >= m_Severity.Count() ) return "<>"; return m_BugStrings.String( m_Severity[ index ] ); } int CBugReporter::GetPriorityCount() { return m_Priority.Count(); } char const *CBugReporter::GetPriority( int index ) { if ( index < 0 || index >= m_Priority.Count() ) return "<>"; return m_BugStrings.String( m_Priority[ index ] ); } int CBugReporter::GetAreaCount() { return m_Area.Count(); } char const *CBugReporter::GetArea( int index ) { if ( index < 0 || index >= m_Area.Count() ) return "<>"; return m_BugStrings.String( m_Area[ index ] ); } int CBugReporter::GetAreaMapCount() { return m_AreaMap.Count(); } char const *CBugReporter::GetAreaMap( int index ) { if ( index < 0 || index >= m_AreaMap.Count() ) return "<>"; return m_BugStrings.String( m_AreaMap[ index ] ); } int CBugReporter::GetMapNumberCount() { return m_MapNumber.Count(); } char const *CBugReporter::GetMapNumber( int index ) { if ( index < 0 || index >= m_MapNumber.Count() ) return "<>"; return m_BugStrings.String( m_MapNumber[ index ] ); } int CBugReporter::GetReportTypeCount() { return m_ReportType.Count(); } char const *CBugReporter::GetReportType( int index ) { if ( index < 0 || index >= m_ReportType.Count() ) return "<>"; return m_BugStrings.String( m_ReportType[ index ] ); } char const *CBugReporter::GetRepositoryURL( void ) { return m_BugRootDirectory; } // only valid after calling CBugReporter::StartNewBugReport() char const *CBugReporter::GetSubmissionURL( void ) { return m_CurrentBugDirectory; } int CBugReporter::GetLevelCount(int area) { CUtlSymbol area_sym = m_AreaMap[area-1]; int area_index = m_LevelMap.Find(area_sym); if (m_LevelMap.IsValidIndex(area_index)) { CUtlVector *levels = m_LevelMap[area_index]; return levels->Count(); } return 0; } char const *CBugReporter::GetLevel(int area, int index ) { CUtlSymbol area_sym = m_AreaMap[area-1]; int area_index = m_LevelMap.Find(area_sym); if (m_LevelMap.IsValidIndex(area_index)) { CUtlVector *levels = m_LevelMap[area_index]; if (index >= 0 && index < levels->Count()) { return m_BugStrings.String((*levels)[index]); } } return ""; } void CBugReporter::StartNewBugReport() { if ( !m_pBug ) { m_pBug = new CBug(); } else { m_pBug->Clear(); } // Find the first available bug number by looking // for the next directory that doesn't exist. m_CurrentBugID = 0; struct tm t; do { VCRHook_LocalTime( &t ); Q_snprintf(m_CurrentBugDirectory, sizeof(m_CurrentBugDirectory), "%s%c%04i%02i%02i-%02i%02i%02i-%s", m_BugRootDirectory, CORRECT_PATH_SEPARATOR, t.tm_year + 1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, m_BugStrings.String(m_UserName)); if (_access(m_CurrentBugDirectory, 0) != 0) break; // sleep for a second or two then try again ThreadSleep(RandomInt(1000,2000)); } while ( 1 ); _mkdir(m_CurrentBugDirectory); } void CBugReporter::CancelNewBugReport() { if ( !m_pBug ) return; m_pBug->Clear(); m_CurrentBugID = 0; } static void OutputField( CUtlBuffer &outbuf, char const *pFieldName, char const *pFieldValue ) { if ( pFieldValue && pFieldValue[0] ) { char const *pTmpString = V_AddBackSlashesToSpecialChars( pFieldValue ); outbuf.Printf( "%s=\"%s\"\n", pFieldName, pTmpString ); delete[] pTmpString; } } bool CBugReporter::CommitBugReport( int& bugSubmissionId ) { bugSubmissionId = m_CurrentBugID; if ( !m_pBug ) return false; // Now create the bug descriptor file, and dump all the text keys in it CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER); OutputField( buf, "Title", m_pBug->title ); OutputField( buf, "Owner", m_pBug->owner ); OutputField( buf, "Submitter", m_pBug->submitter ); OutputField( buf, "Severity", m_pBug->severity ); // OutputField( buf, "Type", m_pBug->reporttype ); // OutputField( buf, "Priority", m_pBug->priority ); OutputField( buf, "Area", m_pBug->area ); OutputField( buf, "Level", m_pBug->mapnumber ); OutputField( buf, "Description", m_pBug->desc ); //OutputField( buf, "Level", m_pBug->level ); OutputField( buf, "Build", m_pBug->build ); OutputField( buf, "Position", m_pBug->position ); OutputField( buf, "Orientation", m_pBug->orientation ); OutputField( buf, "Screenshot", m_pBug->screenshot_unc ); OutputField( buf, "Savegame", m_pBug->savegame_unc ); OutputField( buf, "Bsp", m_pBug->bsp_unc ); OutputField( buf, "vmf", m_pBug->vmf_unc ); // if ( m_pBug->includedfiles.Count() > 0 ) // { // int c = m_pBug->includedfiles.Count(); // for ( int i = 0 ; i < c; ++i ) // { // buf.Printf( "include: %s\n", m_pBug->includedfiles[ i ].name ); // } // } OutputField( buf, "DriverInfo", m_pBug->driverinfo ); OutputField( buf, "Misc", m_pBug->misc ); // Write it out to the file // Need to use native calls to bypass steam filesystem char szBugFileName[1024]; Q_snprintf(szBugFileName, sizeof(szBugFileName), "%s%cbug.txt", m_CurrentBugDirectory, CORRECT_PATH_SEPARATOR ); FILE *fp = fopen(szBugFileName, "wb"); if (!fp) return false; fprintf(fp, "%s", (char *)buf.Base()); fclose(fp); // Clear the bug m_pBug->Clear(); return true; } void CBugReporter::SetTitle( char const *title ) { Assert( m_pBug ); Q_strncpy( m_pBug->title, title, sizeof( m_pBug->title ) ); } void CBugReporter::SetDescription( char const *description ) { Assert( m_pBug ); Q_strncpy( m_pBug->desc, description, sizeof( m_pBug->desc ) ); } void CBugReporter::SetSubmitter( char const *username /* = 0 */) { if ( !username ) { username = GetUserName(); } Assert( m_pBug ); Q_strncpy( m_pBug->submitter, username, sizeof( m_pBug->submitter ) ); } void CBugReporter::SetOwner( char const *username ) { Assert( m_pBug ); Q_strncpy( m_pBug->owner, username, sizeof( m_pBug->owner ) ); } void CBugReporter::SetSeverity( char const *severity ) { Assert( m_pBug ); Q_strncpy( m_pBug->severity, severity, sizeof( m_pBug->severity ) ); } void CBugReporter::SetPriority( char const *priority ) { Assert( m_pBug ); Q_strncpy( m_pBug->priority, priority, sizeof( m_pBug->priority ) ); } void CBugReporter::SetArea( char const *area ) { Assert( m_pBug ); char const *game = GetAreaMapForArea(area); Q_strncpy( m_pBug->area, game, sizeof( m_pBug->area ) ); } void CBugReporter::SetMapNumber( char const *mapnumber ) { Assert( m_pBug); Q_strncpy( m_pBug->mapnumber, mapnumber, sizeof( m_pBug->mapnumber ) ); } void CBugReporter::SetReportType( char const *reporttype ) { Assert( m_pBug ); Q_strncpy( m_pBug->reporttype, reporttype, sizeof( m_pBug->reporttype ) ); } void CBugReporter::SetLevel( char const *levelnamne ) { Assert( m_pBug ); Q_strncpy( m_pBug->level, levelnamne, sizeof( m_pBug->level ) ); } void CBugReporter::SetDriverInfo( char const *info ) { Assert( m_pBug ); Q_strncpy( m_pBug->driverinfo, info, sizeof( m_pBug->driverinfo ) ); } void CBugReporter::SetMiscInfo( char const *info ) { Assert( m_pBug ); Q_strncpy( m_pBug->misc, info, sizeof( m_pBug->misc ) ); } void CBugReporter::SetPosition( char const *position ) { Assert( m_pBug ); Q_strncpy( m_pBug->position, position, sizeof( m_pBug->position ) ); } void CBugReporter::SetOrientation( char const *pitch_yaw_roll ) { Assert( m_pBug ); Q_strncpy( m_pBug->orientation, pitch_yaw_roll, sizeof( m_pBug->orientation ) ); } void CBugReporter::SetBuildNumber( char const *build_num ) { Assert( m_pBug ); Q_strncpy( m_pBug->build, build_num, sizeof( m_pBug->build ) ); } void CBugReporter::SetScreenShot( char const *screenshot_unc_address ) { Assert( m_pBug ); Q_strncpy( m_pBug->screenshot_unc, screenshot_unc_address, sizeof( m_pBug->screenshot_unc ) ); } void CBugReporter::SetSaveGame( char const *savegame_unc_address ) { Assert( m_pBug ); Q_strncpy( m_pBug->savegame_unc, savegame_unc_address, sizeof( m_pBug->savegame_unc ) ); } void CBugReporter::SetBSPName( char const *bsp_unc_address ) { Assert( m_pBug ); Q_strncpy( m_pBug->bsp_unc, bsp_unc_address, sizeof( m_pBug->bsp_unc ) ); } void CBugReporter::SetVMFName( char const *vmf_unc_address ) { Assert( m_pBug ); Q_strncpy( m_pBug->vmf_unc, vmf_unc_address, sizeof( m_pBug->vmf_unc ) ); } void CBugReporter::AddIncludedFile( char const *filename ) { CBug::incfile includedfile; Q_strncpy( includedfile.name, filename, sizeof( includedfile.name ) ); m_pBug->includedfiles.AddToTail( includedfile ); } void CBugReporter::ResetIncludedFiles() { m_pBug->includedfiles.Purge(); } bool CBugReporter::PopulateChoiceList( char const *listname, CUtlVector< CUtlSymbol >& list ) { // Read choice lists from text file KeyValues *pKV = m_OptionsFile->FindKey( listname ); pKV = pKV->GetFirstSubKey(); while ( pKV ) { CUtlSymbol sym = m_BugStrings.AddString( pKV->GetName() ); list.AddToTail( sym ); pKV = pKV->GetNextKey(); } return true; } bool CBugReporter::SymbolLessThan(const CUtlSymbol &sym1, const CUtlSymbol &sym2) { const char *string1 = m_BugStrings.String(sym1); const char *string2 = m_BugStrings.String(sym2); return Q_stricmp(string1, string2) < 0; } // owner, not required <> // area <> bool CBugReporter::PopulateLists() { CUtlSymbol none = m_BugStrings.AddString( "<>" ); m_Area.AddToTail( none ); PopulateChoiceList( "Severity", m_Severity ); // Get developer names from text file KeyValues *pKV = m_OptionsFile->FindKey( "Names" ); pKV = pKV->GetFirstSubKey(); while ( pKV ) { // Fill in lookup table CUtlSymbol display_name_sym = m_BugStrings.AddString( pKV->GetName() ); CUtlSymbol user_name_sym = m_BugStrings.AddString( pKV->GetString() ); m_SortedDisplayNames.AddToTail( display_name_sym ); m_SortedUserNames.AddToTail( user_name_sym ); pKV = pKV->GetNextKey(); } pKV = m_OptionsFile->FindKey( "Area" ); pKV = pKV->GetFirstSubKey(); while(pKV) { char const *area = pKV->GetName(); char const *game = pKV->GetString(); char areamap[256]; Q_snprintf(areamap, sizeof(areamap), "@%s", game); CUtlSymbol area_sym = m_BugStrings.AddString(area); CUtlSymbol area_map_sym = m_BugStrings.AddString(areamap); m_Area.AddToTail( area_sym ); m_AreaMap.AddToTail( area_map_sym ); pKV = pKV->GetNextKey(); } pKV = m_OptionsFile->FindKey( "Level" ); pKV = pKV->GetFirstSubKey(); while(pKV) { char const *level = pKV->GetName(); char const *area = pKV->GetString(); char areamap[256]; Q_snprintf(areamap, sizeof(areamap), "@%s", area); CUtlSymbol level_sym = m_BugStrings.AddString(level); CUtlSymbol area_sym = m_BugStrings.Find(areamap); if (!area_sym.IsValid()) { area_sym = m_BugStrings.AddString(areamap); } unsigned index = m_LevelMap.Find(area_sym); CUtlVector *levels = 0; if (m_LevelMap.IsValidIndex(index)) { levels = m_LevelMap[index]; } else { levels = new CUtlVector; Assert(levels); m_LevelMap.Insert(area_sym, levels); } if (level) { levels->AddToTail(level_sym); } pKV = pKV->GetNextKey(); } return true; } EXPOSE_SINGLE_INTERFACE( CBugReporter, IBugReporter, INTERFACEVERSION_BUGREPORTER );