diff options
Diffstat (limited to 'engine/bugreporter.cpp')
| -rw-r--r-- | engine/bugreporter.cpp | 3081 |
1 files changed, 3081 insertions, 0 deletions
diff --git a/engine/bugreporter.cpp b/engine/bugreporter.cpp new file mode 100644 index 0000000..093041e --- /dev/null +++ b/engine/bugreporter.cpp @@ -0,0 +1,3081 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#undef PROTECT_FILEIO_FUNCTIONS +#ifndef _LINUX +#undef fopen +#endif +#if defined( WIN32 ) && !defined( _X360 ) +#include "winlite.h" +#include <winsock2.h> // INADDR_ANY defn +#include <direct.h> +#elif defined(POSIX) +#include <sys/stat.h> + +#ifdef OSX +#include <copyfile.h> +#import <mach/mach_host.h> +#import <sys/sysctl.h> +#elif defined(LINUX) +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#endif +#define GetLastError() errno +#elif defined( _X360 ) +#else +#error +#endif + +#include <time.h> + +#include "client.h" +#include <vgui_controls/Frame.h> +#include <vgui/ISystem.h> +#include <vgui/ISurface.h> +#include <vgui/IInput.h> +#include <vgui/IVGui.h> +#include <KeyValues.h> +#include <vgui_controls/BuildGroup.h> +#include <vgui_controls/Tooltip.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/CheckButton.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/PropertySheet.h> +#include <vgui_controls/FileOpenDialog.h> +#include "vgui_controls/DirectorySelectDialog.h" +#include <vgui_controls/ProgressBar.h> +#include <vgui_controls/Slider.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/TextEntry.h> +#include "enginebugreporter.h" +#include "vgui_baseui_interface.h" +#include <vgui_controls/FileOpenDialog.h> +#include "ivideomode.h" +#include "cl_main.h" +#include "gl_model_private.h" +#include "tier2/tier2.h" +#include "tier1/utlstring.h" +#include "tier1/callqueue.h" +#include "vstdlib/jobthread.h" + +#include "utlsymbol.h" +#include "utldict.h" +#include "filesystem.h" +#include "filesystem_engine.h" +#include "icliententitylist.h" +#include "bugreporter/bugreporter.h" +#include "icliententity.h" +#include "tier0/vcrmode.h" +#include "tier0/platform.h" +#include "net.h" +#include "host_phonehome.h" +#include "tier0/icommandline.h" +#include "stdstring.h" +#include "sv_main.h" +#include "server.h" +#include "eiface.h" +#include "gl_matsysiface.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "FindSteamServers.h" +#include "vstdlib/random.h" +#ifndef SWDS +#include "cl_steamauth.h" +#endif + +#include "zip/XZip.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define DENY_SOUND "common/bugreporter_failed" +#define SUCCEED_SOUND "common/bugreporter_succeeded" + +// Fixme, move these to buguiddata.res script file? +#ifdef WIN32 +#define BUG_REPOSITORY_URL "\\\\fileserver\\bugs" +#elif defined(OSX) +#define BUG_REPOSITORY_URL "/Volumes/bugs" +#elif defined(LINUX) +#define BUG_REPOSITORY_URL "\\\\fileserver\\bugs" +#else +//#error +#endif +#define REPOSITORY_VALIDATION_FILE "info.txt" + +#define BUG_REPORTER_DLLNAME "bugreporter_filequeue" +#define BUG_REPORTER_PUBLIC_DLLNAME "bugreporter_public" + +#if defined( _DEBUG ) +#define PUBLIC_BUGREPORT_WAIT_TIME 3 +#else +#define PUBLIC_BUGREPORT_WAIT_TIME 15 +#endif + +// 16Mb max zipped size +#define MAX_ZIP_SIZE (1024 * 1024 * 16 ) + +extern ConVar skill; +extern float g_fFramesPerSecond; + +static ConVar bugreporter_includebsp( "bugreporter_includebsp", "1", 0, "Include .bsp for internal bug submissions." ); +static ConVar bugreporter_uploadasync( "bugreporter_uploadasync", "0", FCVAR_ARCHIVE, "Upload attachments asynchronously" ); + + +using namespace vgui; + +unsigned long GetRam() +{ +#ifdef WIN32 + MEMORYSTATUS stat; + GlobalMemoryStatus( &stat ); + return (stat.dwTotalPhys / (1024 * 1024)); +#elif defined(OSX) + int mib[2] = { CTL_HW, HW_MEMSIZE }; + u_int namelen = sizeof(mib) / sizeof(mib[0]); + uint64_t memsize; + size_t len = sizeof(memsize); + + if (sysctl(mib, namelen, &memsize, &len, NULL, 0) >= 0) + { + return memsize / (1024*1024); + } + else + return 0; +#elif defined( LINUX ) + unsigned long Ram = 0; + FILE *fh = fopen( "/proc/meminfo", "r" ); + if( fh ) + { + char buf[ 256 ]; + const char szMemTotal[] = "MemTotal:"; + + while( fgets( buf, sizeof( buf ), fh ) ) + { + if ( !Q_strnicmp( buf, szMemTotal, sizeof( szMemTotal ) - 1 ) ) + { + // Should already be in kB + Ram = atoi( buf + sizeof( szMemTotal ) - 1 ) / 1024; + break; + } + } + + fclose( fh ); + } + return Ram; +#else + Assert( !"Impl GetRam" ); + return 0; +#endif +} + +const char *GetInternalBugReporterDLL( void ) +{ + char const *pBugReportedDLL = NULL; + if ( CommandLine()->CheckParm("-bugreporterdll", &pBugReportedDLL ) ) + return pBugReportedDLL; + + return BUG_REPORTER_DLLNAME; +} + +void DisplaySystemVersion( char *osversion, int maxlen ) +{ +#ifdef WIN32 + osversion[ 0 ] = 0; + OSVERSIONINFOEX osvi; + BOOL bOsVersionInfoEx; + + // Try calling GetVersionEx using the OSVERSIONINFOEX structure. + // + // If that fails, try using the OSVERSIONINFO structure. + + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi); + + if( !bOsVersionInfoEx ) + { + // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) + { + Q_strncpy( osversion, "Unable to get Version", maxlen ); + return; + } + } + + switch (osvi.dwPlatformId) + { + case VER_PLATFORM_WIN32_NT: + + // Test for the product. + + if ( osvi.dwMajorVersion <= 4 ) + { + Q_strncat ( osversion, "NT ", maxlen, COPY_ALL_CHARACTERS ); + } + + if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 ) + { + Q_strncat ( osversion, "2000 ", maxlen, COPY_ALL_CHARACTERS ); + } + + if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 ) + { + Q_strncat ( osversion, "XP ", maxlen, COPY_ALL_CHARACTERS ); + } + + // Display version, service pack (if any), and build number. + + char build[256]; + Q_snprintf (build, sizeof( build ), "%s (Build %d) version %d.%d", + osvi.szCSDVersion, + osvi.dwBuildNumber & 0xFFFF, + osvi.dwMajorVersion, + osvi.dwMinorVersion ); + Q_strncat ( osversion, build, maxlen, COPY_ALL_CHARACTERS ); + break; + + case VER_PLATFORM_WIN32_WINDOWS: + + if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) + { + Q_strncat ( osversion, "95 ", maxlen, COPY_ALL_CHARACTERS ); + if ( osvi.szCSDVersion[1] == 'C' || osvi.szCSDVersion[1] == 'B' ) + { + Q_strncat ( osversion, "OSR2 ", maxlen, COPY_ALL_CHARACTERS ); + } + } + + if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) + { + Q_strncat ( osversion, "98 ", maxlen, COPY_ALL_CHARACTERS ); + if ( osvi.szCSDVersion[1] == 'A' ) + { + Q_strncat ( osversion, "SE ", maxlen, COPY_ALL_CHARACTERS ); + } + } + + if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) + { + Q_strncat ( osversion, "Me ", maxlen, COPY_ALL_CHARACTERS ); + } + break; + + case VER_PLATFORM_WIN32s: + + Q_strncat ( osversion, "Win32s ", maxlen, COPY_ALL_CHARACTERS ); + break; + } +#elif defined(OSX) + FILE *fpVersionInfo = popen( "/usr/bin/sw_vers", "r" ); + const char *pszSearchString = "ProductVersion:\t"; + const int cchSearchString = Q_strlen( pszSearchString ); + char rgchVersionLine[1024]; + + if ( !fpVersionInfo ) + Q_strncat ( osversion, "OSXU ", maxlen, COPY_ALL_CHARACTERS ); + else + { + while ( fgets( rgchVersionLine, sizeof(rgchVersionLine), fpVersionInfo ) ) + { + if ( !Q_strnicmp( rgchVersionLine, pszSearchString, cchSearchString ) ) + { + const char *pchVersion = rgchVersionLine + cchSearchString; + if ( !Q_strnicmp( pchVersion, "10.", Q_strlen( "10." ) ) ) + { + pchVersion += 3; // move past "10." + if( *pchVersion == '4' && *(pchVersion+1) == '.' ) + { + Q_strncat ( osversion, "OSX104 ", maxlen, COPY_ALL_CHARACTERS ); + break; + } + else if ( *pchVersion == '5' && *(pchVersion+1) == '.' ) + { + Q_strncat ( osversion, "OSX105 ", maxlen, COPY_ALL_CHARACTERS ); + break; + } + else if ( *pchVersion == '6' && *(pchVersion+1) == '.' ) + { + Q_strncat ( osversion, "OSX106 ", maxlen, COPY_ALL_CHARACTERS ); + break; + } + else if ( *pchVersion == '7' && *(pchVersion+1) == '.' ) + { + Q_strncat ( osversion, "OSX107 ", maxlen, COPY_ALL_CHARACTERS ); + break; + } + } + break; + } + } + pclose( fpVersionInfo ); + } +#elif defined(LINUX) + FILE *fpKernelVer = fopen( "/proc/version_signature", "r" ); + + if ( !fpKernelVer ) + { + Q_strncat ( osversion, "Linux ", maxlen, COPY_ALL_CHARACTERS ); + } + else + { + fgets( osversion, maxlen, fpKernelVer ); + osversion[ maxlen - 1 ] = 0; + + char *szlf = Q_strrchr( osversion, '\n' ); + if( szlf ) + *szlf = '\0'; + + fclose( fpKernelVer ); + } +#endif +} + +static int GetNumberForMap() +{ + if ( !host_state.worldmodel ) + return 1; + + char mapname[256]; + CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) ); + + KeyValues *resfilekeys = new KeyValues( "mapnumber" ); + if ( resfilekeys->LoadFromFile( g_pFileSystem, "scripts/bugreport_mapnumber.txt", "GAME" ) ) + { + KeyValues *entry = resfilekeys->GetFirstSubKey(); + + while ( entry ) + { + if ( !Q_stricmp( entry->GetName(), mapname ) ) + { + return entry->GetInt() + 1; + } + + entry = entry->GetNextKey(); + } + } + resfilekeys->deleteThis(); + + char szNameCopy[ 128 ]; + + const char *pszResult = Q_strrchr( mapname, '_' ); + if( !pszResult ) + //I don't know where the number of this map is, if there even is one. + return 1; + + Q_strncpy( szNameCopy, pszResult + 1, sizeof( szNameCopy ) ); + if ( !szNameCopy[0] ) + return 1; + +// in case we can't use tchar.h, this will do the same thing + char *pcEndOfName = szNameCopy; + while(*pcEndOfName != 0) + { + if(*pcEndOfName < '0' || *pcEndOfName > '9') + *pcEndOfName = 0; + pcEndOfName++; + } + + //add 1 because pvcs has 0 as the first map number, not 1 (and it is not 0-based). + return atoi(szNameCopy) + 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Generic dialog for displaying animating steam progress logo +// used when we are doing a possibly length steam op that has no progress measure (login/create user/etc) +//----------------------------------------------------------------------------- +class CBugReportUploadProgressDialog : public vgui::Frame +{ +public: + CBugReportUploadProgressDialog(vgui::Panel *parent, const char *name, const char *title, const char *message); + ~CBugReportUploadProgressDialog(); + + virtual void PerformLayout(); + + void SetProgress( float progress ); + +private: + typedef vgui::Frame BaseClass; + + vgui::ProgressBar *m_pProgress; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CBugReportUploadProgressDialog::CBugReportUploadProgressDialog(Panel *parent, const char *name, const char *title, const char *message) : Frame(parent, name) +{ + SetSize(300, 160); + SetSizeable(false); + MoveToFront(); + SetTitle(title, true); + + m_pProgress = new vgui::ProgressBar( this, "ProgressBar" ); + + LoadControlSettings("Resource\\BugReporterUploadProgress.res"); + SetControlString("InfoLabel", message); + + MoveToCenterOfScreen(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CBugReportUploadProgressDialog::~CBugReportUploadProgressDialog() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : percent - +//----------------------------------------------------------------------------- +void CBugReportUploadProgressDialog::SetProgress( float progress ) +{ + Assert( m_pProgress ); + m_pProgress->SetProgress( progress ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBugReportUploadProgressDialog::PerformLayout() +{ + SetMinimizeButtonVisible(false); + SetCloseButtonVisible(false); + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Generic dialog for displaying animating steam progress logo +// used when we are doing a possibly length steam op that has no progress measure (login/create user/etc) +//----------------------------------------------------------------------------- +class CBugReportFinishedDialog : public vgui::Frame +{ +public: + CBugReportFinishedDialog(vgui::Panel *parent, const char *name, const char *title, const char *message); + + virtual void PerformLayout(); + + virtual void OnCommand( const char *command ); + +private: + typedef vgui::Frame BaseClass; + + vgui::Button *m_pOk; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CBugReportFinishedDialog::CBugReportFinishedDialog(Panel *parent, const char *name, const char *title, const char *message) : Frame(parent, name) +{ + SetSize(300, 160); + SetSizeable(false); + MoveToFront(); + SetTitle(title, true); + + m_pOk = new vgui::Button( this, "CloseBtn", "#OK", this, "Close" ); + + LoadControlSettings("Resource\\BugReporterUploadFinished.res"); + SetControlString("InfoLabel", message); + + MoveToCenterOfScreen(); +} + +void CBugReportFinishedDialog::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "Close" ) ) + { + MarkForDeletion(); + OnClose(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBugReportFinishedDialog::PerformLayout() +{ + SetMinimizeButtonVisible(false); + SetCloseButtonVisible(true); + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CBugUIPanel : public vgui::Frame +{ + DECLARE_CLASS_SIMPLE( CBugUIPanel, vgui::Frame ); + +public: + CBugUIPanel( bool bIsPublic, vgui::Panel *parent ); + ~CBugUIPanel(); + + virtual void OnTick(); + + // Command issued + virtual void OnCommand(const char *command); + virtual void Close(); + + virtual void Activate(); + + virtual void SetVisible( bool state ) + { + bool changed = state != IsVisible(); + BaseClass::SetVisible( state ); + + if ( changed && state ) + { + m_pTitle->RequestFocus(); + } + } + + bool Init(); + void Shutdown(); + + virtual void OnKeyCodePressed( KeyCode code ); + + void ParseDefaultParams( void ); + void ParseCommands( const CCommand &args ); + + inline bool IsTakingSnapshot() const + { + return m_bTakingSnapshot; + } + + +protected: + vgui::TextEntry *m_pTitle; + vgui::TextEntry *m_pDescription; + + vgui::Button *m_pTakeShot; + vgui::Button *m_pSaveGame; + vgui::Button *m_pSaveBSP; + vgui::Button *m_pSaveVMF; + vgui::Button *m_pChooseVMFFolder; + vgui::Button *m_pIncludeFile; + vgui::Button *m_pClearIncludes; + + vgui::Label *m_pScreenShotURL; + vgui::Label *m_pSaveGameURL; + vgui::Label *m_pBSPURL; + vgui::Label *m_pVMFURL; + vgui::Label *m_pPosition; + vgui::Label *m_pOrientation; + vgui::Label *m_pLevelName; + vgui::Label *m_pBuildNumber; + + vgui::Label *m_pSubmitter; + + vgui::ComboBox *m_pAssignTo; + vgui::ComboBox *m_pSeverity; + vgui::ComboBox *m_pReportType; + vgui::ComboBox *m_pPriority; + vgui::ComboBox *m_pGameArea; + vgui::ComboBox *m_pMapNumber; + + vgui::Button *m_pSubmit; + vgui::Button *m_pCancel; + vgui::Button *m_pClearForm; + + vgui::TextEntry *m_pIncludedFiles; + + vgui::TextEntry *m_pEmail; + vgui::Label *m_pSubmitterLabel; + + IBugReporter *m_pBugReporter; + CSysModule *m_hBugReporter; + + MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath ); + MESSAGE_FUNC_CHARPTR( OnDirectorySelected, "DirectorySelected", dir ); + MESSAGE_FUNC( OnChooseVMFFolder, "OnChooseVMFFolder" ); + MESSAGE_FUNC_PTR( OnChooseArea, "TextChanged", panel); +protected: + + void DetermineSubmitterName(); + + void PopulateControls(); + + void OnTakeSnapshot(); + void OnSaveGame(); + void OnSaveBSP(); + void OnSaveVMF(); + void OnSubmit(); + void OnClearForm(); + void OnIncludeFile(); + void OnClearIncludedFiles(); + + int GetArea(); + + bool IsValidSubmission( bool verbose ); + bool IsValidEmailAddress( char const *email ); + + void WipeData(); + + void GetDataFileBase( char const *suffix, char *buf, int bufsize ); + const char *GetRepositoryURL( void ); + const char *GetSubmissionURL( int bugid ); + + bool AddFileToZip( char const *relative ); + bool AddBugTextToZip( char const *textfilename, char const *text, int textlen ); + + void CheckContinueQueryingSteamForCSERList(); + + struct includedfile + { + char name[ 256 ]; + char fixedname[ 256 ]; + }; + + bool UploadBugSubmission( + char const *levelname, + int bugId, + + char const *savefile, + char const *screenshot, + char const *bsp, + char const *vmf, + CUtlVector< includedfile >& includedfiles ); + bool UploadFile( char const *local, char const *remote, bool bDeleteLocal = false ); + + void DenySound(); + void SuccessSound( int bugid ); + + bool AutoFillToken( char const *token, bool partial ); + bool Compare( char const *token, char const *value, bool partial ); + + char const *GetSubmitter(); + + void OnFinishBugReport(); + + bool m_bCanSubmit; + bool m_bLoggedIn; + bool m_bCanSeeRepository; + bool m_bValidated; + bool m_bUseNameForSubmitter; + + unsigned char m_fAutoAddScreenshot; + enum AutoAddScreenshot { eAutoAddScreenshot_Detect = 0, eAutoAddScreenshot_Add = 1, eAutoAddScreenshot_DontAdd = 2 }; + + char m_szScreenShotName[ 256 ]; + char m_szSaveGameName[ 256 ]; + char m_szBSPName[ 256 ]; + char m_szVMFName[ 256 ]; + char m_szLevel[ 256 ]; + CUtlVector< includedfile > m_IncludedFiles; + + bool m_bTakingSnapshot; + bool m_bHidGameUIForSnapshot; + int m_nSnapShotFrame; + + char m_szVMFContentDirFullpath[ MAX_PATH ]; + + vgui::DHANDLE< vgui::FileOpenDialog > m_hFileOpenDialog; + vgui::DHANDLE< vgui::DirectorySelectDialog > m_hDirectorySelectDialog; + // If true, then once directory for vmfs is selected, we act like the Include .vmf button got pressed, too + bool m_bAddVMF; + + HZIP m_hZip; + + CBugReportUploadProgressDialog *m_pProgressDialog; + vgui::DHANDLE< CBugReportFinishedDialog > m_hFinishedDialog; + bool m_bWaitForFinish; + float m_flPauseTime; + CSteamID m_SteamID; + netadr_t m_cserIP; + bool m_bQueryingSteamForCSER; + bool m_bIsPublic; + CUtlString m_sDllName; +}; + +//----------------------------------------------------------------------------- +// Purpose: Basic help dialog +//----------------------------------------------------------------------------- +CBugUIPanel::CBugUIPanel( bool bIsPublic, vgui::Panel *parent ) : + BaseClass( parent, "BugUIPanel"), + m_bIsPublic( bIsPublic ), + m_bAddVMF( false ) +{ + m_sDllName = m_bIsPublic ? + BUG_REPORTER_PUBLIC_DLLNAME : + GetInternalBugReporterDLL(); + + m_hZip = (HZIP)0; + + m_hDirectorySelectDialog = NULL; + m_hFileOpenDialog = NULL; + m_pBugReporter = NULL; + m_hBugReporter = 0; + m_bQueryingSteamForCSER = false; + + // Default server address (hardcoded in case not running on steam) + char const *cserIP = "207.173.177.12:27013"; + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: If you need to override the CSER Ip, make sure you tweak the code in + // CheckContinueQueryingSteamForCSERList!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + NET_StringToAdr( cserIP, &m_cserIP ); + + m_bValidated = false; + m_szScreenShotName[ 0 ] = 0; + m_szSaveGameName[ 0 ] = 0; + m_szBSPName[ 0 ] = 0; + m_szVMFName[ 0 ] = 0; + m_szLevel[ 0 ] = 0; + m_szVMFContentDirFullpath[ 0 ] = 0; + m_IncludedFiles.Purge(); + + m_nSnapShotFrame = -1; + m_bTakingSnapshot = false; + m_bHidGameUIForSnapshot = false; + + m_bCanSubmit = false; + m_bLoggedIn = false; + m_bCanSeeRepository = false; + + m_pProgressDialog = NULL; + m_flPauseTime = 0.0f; + m_bWaitForFinish = false; + m_bUseNameForSubmitter = false; + + m_fAutoAddScreenshot = eAutoAddScreenshot_Detect; + + SetTitle("Bug Reporter", true); + + m_pTitle = new vgui::TextEntry( this, "BugTitle" ); + m_pTitle->SetMaximumCharCount( 60 ); // Titles can be 80 chars, but we put the mapname in front, so limit to 60 + m_pDescription = new vgui::TextEntry( this, "BugDescription" ); + m_pDescription->SetMultiline( true ); + m_pDescription->SetCatchEnterKey( true ); + m_pDescription->SetVerticalScrollbar( true ); + + m_pEmail = new vgui::TextEntry( this, "BugEmail" );; + m_pEmail->SetMaximumCharCount( 80 ); + + m_pSubmitterLabel = new vgui::Label( this, "BugSubitterLabelPublic", "" ); + + m_pScreenShotURL = new vgui::Label( this, "BugScreenShotURL", "" ); + m_pSaveGameURL = new vgui::Label( this, "BugSaveGameURL", "" ); + m_pBSPURL = new vgui::Label( this, "BugBSPURL", "" ); + m_pVMFURL = new vgui::Label( this, "BugVMFURL", "" ); + m_pIncludedFiles = new vgui::TextEntry( this, "BugIncludedFiles" ); + m_pIncludedFiles->SetMultiline( true ); + m_pIncludedFiles->SetVerticalScrollbar( true ); + m_pIncludedFiles->SetEditable( false ); + + m_pTakeShot = new vgui::Button( this, "BugTakeShot", "Take shot" ); + m_pSaveGame = new vgui::Button( this, "BugSaveGame", "Save game" ); + m_pSaveBSP = new vgui::Button( this, "BugSaveBSP", "Include .bsp" ); + m_pSaveVMF = new vgui::Button( this, "BugSaveVMF", "Include .vmf" ); + m_pChooseVMFFolder = new vgui::Button( this, "BugChooseVMFFolder", "Choose folder" ); + m_pIncludeFile = new vgui::Button( this, "BugIncludeFile", "Include file..." ); + m_pClearIncludes = new vgui::Button( this, "BugClearIncludedFiles", "Clear files" ); + + m_pPosition = new vgui::Label( this, "BugPosition", "" ); + m_pOrientation = new vgui::Label( this, "BugOrientation", "" ); + m_pLevelName = new vgui::Label( this, "BugLevel", "" ); + m_pBuildNumber = new vgui::Label( this, "BugBuild", "" ); + + m_pSubmitter = new Label(this, "BugSubmitter", "" ); + m_pAssignTo = new ComboBox(this, "BugOwner", 10, false); + m_pSeverity = new ComboBox(this, "BugSeverity", 10, false); + m_pReportType = new ComboBox(this, "BugReportType", 10, false); + m_pPriority = new ComboBox(this, "BugPriority", 10, false); + m_pGameArea = new ComboBox(this, "BugArea", 10, false); + m_pMapNumber = new ComboBox(this, "BugMapNumber", 10, false); + + m_pSubmit = new vgui::Button( this, "BugSubmit", "Submit" ); + m_pCancel = new vgui::Button( this, "BugCancel", "Cancel" ); + m_pClearForm = new vgui::Button( this, "BugClearForm", "Clear Form" ); + + vgui::ivgui()->AddTickSignal( GetVPanel(), 0 ); + + if ( m_bIsPublic ) + { + LoadControlSettings("Resource\\BugUIPanel_Public.res"); + } + else + { + LoadControlSettings("Resource\\BugUIPanel_Filequeue.res"); + } + + m_pChooseVMFFolder->SetCommand( new KeyValues( "OnChooseVMFFolder" ) ); + m_pChooseVMFFolder->AddActionSignalTarget( this ); + + int w = GetWide(); + int h = GetTall(); + + int x = ( videomode->GetModeStereoWidth() - w ) / 2; + int y = ( videomode->GetModeStereoHeight() - h ) / 2; + + // Hidden by default + SetVisible( false ); + + SetSizeable( false ); + SetMoveable( true ); + + SetPos( x, y ); +} + +bool CBugUIPanel::Init() +{ + Color clr( 50, 100, 255, 255 ); + + Assert( !m_pBugReporter ); + + m_hBugReporter = g_pFileSystem->LoadModule( m_sDllName); + + if( m_bIsPublic ) + { + // Hack due to constructor called before phonehome->Init... + m_hBugReporter = g_pFileSystem->LoadModule( m_sDllName ); + + LoadControlSettings("Resource\\BugUIPanel_Public.res"); + + int w = GetWide(); + int h = GetTall(); + + int x = ( videomode->GetModeStereoWidth() - w ) / 2; + int y = ( videomode->GetModeStereoHeight() - h ) / 2; + + + SetPos( x, y ); + } + + if ( m_hBugReporter ) + { + CreateInterfaceFn factory = Sys_GetFactory( m_hBugReporter ); + if ( factory ) + { + m_pBugReporter = (IBugReporter *)factory( INTERFACEVERSION_BUGREPORTER, NULL ); + if( m_pBugReporter ) + { + extern CreateInterfaceFn g_AppSystemFactory; + if ( m_pBugReporter->Init( g_AppSystemFactory ) ) + { + m_bCanSubmit = true; + m_bLoggedIn = true; + } + else + { + m_pBugReporter = NULL; + ConColorMsg( clr, "m_pBugReporter->Init() failed\n" ); + return false; + } + } + else + { + ConColorMsg( clr, "Couldn't get interface '%s' from '%s'\n", INTERFACEVERSION_BUGREPORTER, m_sDllName.String() ); + return false; + } + } + else + { + ConColorMsg( clr, "Couldn't get factory '%s'\n", m_sDllName.String() ); + return false; + } + } + else + { + ConColorMsg( clr, "Couldn't load '%s'\n", m_sDllName.String() ); + return false; + } + + if ( m_bCanSubmit ) + { + PopulateControls(); + } + + + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + { + m_pSaveBSP->SetVisible( false ); + m_pBSPURL->SetVisible( false ); + + m_pChooseVMFFolder->SetVisible( false ); + m_pSaveVMF->SetVisible( false ); + m_pVMFURL->SetVisible( false ); + + m_pIncludeFile->SetVisible( false ); + m_pClearIncludes->SetVisible( false ); + + m_pAssignTo->SetVisible( false ); + m_pSeverity->SetVisible( false ); + m_pPriority->SetVisible( false ); + m_pGameArea->SetVisible( false ); + m_pMapNumber->SetVisible( false ); + + m_pIncludedFiles->SetVisible( false ); + m_pSubmitter->SetVisible( false ); + + if ( Steam3Client().SteamUser() ) + { + m_pSubmitterLabel->SetText( Steam3Client().SteamUser()->GetSteamID().Render() ); + } + else + { + m_pSubmitterLabel->SetText( "PublicUser" ); + } + + m_pSubmitterLabel->SetVisible( true ); + + m_bQueryingSteamForCSER = true; + } + else + { + m_pEmail->SetVisible( false ); + m_pSubmitterLabel->SetVisible( true ); + + } + + Q_snprintf( m_szVMFContentDirFullpath, sizeof( m_szVMFContentDirFullpath ), "%s/maps", com_gamedir ); + Q_strlower( m_szVMFContentDirFullpath ); + Q_FixSlashes( m_szVMFContentDirFullpath ); + + m_pBuildNumber->SetText( va( "%d", build_number() ) ); + + return false; +} + +void CBugUIPanel::Shutdown() +{ + if ( m_pBugReporter ) + { + m_pBugReporter->Shutdown(); + } + + m_pBugReporter = NULL; + if ( m_hBugReporter ) + { + Sys_UnloadModule( m_hBugReporter ); + m_hBugReporter = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBugUIPanel::~CBugUIPanel() +{ + +} + +void CBugUIPanel::OnTick() +{ + BaseClass::OnTick(); + + CheckContinueQueryingSteamForCSERList(); + + if ( !IsVisible() ) + { + if ( !m_bTakingSnapshot ) + { + return; + } + + if ( host_framecount < m_nSnapShotFrame + 2 ) + return;; + + m_bTakingSnapshot = false; + m_nSnapShotFrame = -1; + + m_pScreenShotURL->SetText( m_szScreenShotName ); + + if (m_bHidGameUIForSnapshot) + { + EngineVGui()->ActivateGameUI(); + } + m_bHidGameUIForSnapshot = false; + + SetVisible( true ); + MoveToFront(); + } + + if ( !m_bCanSubmit ) + { + if ( m_pBugReporter && !m_pBugReporter->IsPublicUI() ) + { + if ( !m_bCanSeeRepository ) + { + Warning( "Bug UI disabled: Couldn't see repository\n" ); + } + else if ( !m_bLoggedIn ) + { + Warning( "Bug UI disabled: Couldn't log in to PVCS Tracker\n" ); + } + else + { + Assert( 0 ); + } + } + + SetVisible( false ); + return; + } + + m_pSubmit->SetEnabled( IsValidSubmission( false ) ); + + if (m_flPauseTime > 0.0f ) + { + if ( m_flPauseTime <= system()->GetFrameTime()) + { + m_flPauseTime = 0.0f; + + if ( m_pProgressDialog ) + { + m_pProgressDialog->Close(); + } + m_pProgressDialog = NULL; + + OnFinishBugReport(); + + m_bWaitForFinish = true; + + if ( !m_hFinishedDialog.Get() ) + { + m_hFinishedDialog = new CBugReportFinishedDialog(NULL, "FinishDialog", "#Steam_FinishedBug_WorkingTitle", "#Steam_FinishedBug_Text" ); + m_hFinishedDialog->Activate(); + vgui::input()->SetAppModalSurface(m_hFinishedDialog->GetVPanel()); + } + } + else + { + if ( m_pProgressDialog ) + { + float percent = clamp( 1.0f - (float)( m_flPauseTime - system()->GetFrameTime() ) / (float)PUBLIC_BUGREPORT_WAIT_TIME, 0.0f, 1.0f ); + m_pProgressDialog->SetProgress( percent ); + } + } + } + + if ( m_bWaitForFinish && !m_hFinishedDialog.Get() ) + { + m_bWaitForFinish = false; + Close(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *suffix - +// *buf - +// bufsize - +//----------------------------------------------------------------------------- +void CBugUIPanel::GetDataFileBase( char const *suffix, char *buf, int bufsize ) +{ + struct tm t; + VCRHook_LocalTime( &t ); + + char who[ 128 ]; + Q_strncpy( who, suffix, sizeof( who ) ); + Q_strlower( who ); + + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + { + Q_snprintf( buf, bufsize, "%i_%02i_%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday ); + } + else + { + Q_snprintf( buf, bufsize, "%i_%02i_%02i_%s", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, who ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CBugUIPanel::GetRepositoryURL( void ) +{ + const char *pURL = m_pBugReporter->GetRepositoryURL(); + if ( pURL ) + return pURL; + + return BUG_REPOSITORY_URL; +} + +const char *CBugUIPanel::GetSubmissionURL( int bugid ) +{ + const char *pURL = m_pBugReporter->GetSubmissionURL(); + if ( pURL ) + return pURL; + + static char url[512]; + + Q_snprintf(url, sizeof(url), "%s/%i", GetRepositoryURL(), bugid); + + return url; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBugUIPanel::OnTakeSnapshot() +{ + GetDataFileBase( GetSubmitter(), m_szScreenShotName, sizeof( m_szScreenShotName ) ); + + m_nSnapShotFrame = host_framecount; + m_bTakingSnapshot = true; + + if ( EngineVGui()->IsGameUIVisible() ) + { + m_bHidGameUIForSnapshot = true; + EngineVGui()->HideGameUI(); + } + else + { + m_bHidGameUIForSnapshot = false; + } + + SetVisible( false ); + + // Internal reports at 100% quality .jpg + int quality = 100; + + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + { + quality = 40; + } + + Cbuf_AddText( va( "jpeg \"%s\" %i\n", m_szScreenShotName, quality ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBugUIPanel::OnSaveGame() +{ + GetDataFileBase( GetSubmitter(), m_szSaveGameName, sizeof( m_szSaveGameName ) ); + + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + { + // External users send us a "minisave" file which doesn't contain data from other previously encoudntered/connected levels + Cbuf_AddText( va( "minisave %s\n", m_szSaveGameName ) ); + } + else + { + Cbuf_AddText( va( "save %s notmostrecent copymap\n", m_szSaveGameName ) ); + } + + m_pSaveGameURL->SetText( m_szSaveGameName ); +} + +void CBugUIPanel::OnSaveBSP() +{ + GetDataFileBase( GetSubmitter(), m_szBSPName, sizeof( m_szBSPName ) ); + m_pBSPURL->SetText( m_szBSPName ); +} + +void CBugUIPanel::OnSaveVMF() +{ + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + return; + + char level[ 256 ]; + m_pLevelName->GetText( level, sizeof( level ) ); + + // See if .vmf exists in assumed location + char localfile[ 512 ]; + Q_snprintf( localfile, sizeof( localfile ), "%s/%s.vmf", m_szVMFContentDirFullpath, level ); + if ( !g_pFileSystem->FileExists( localfile, NULL ) ) + { + if ( !m_hDirectorySelectDialog.Get() ) + { + m_hDirectorySelectDialog = new DirectorySelectDialog( this, "Choose .vmf folder" ); + } + + m_bAddVMF = true; + m_hDirectorySelectDialog->SetStartDirectory( m_szVMFContentDirFullpath ); + m_hDirectorySelectDialog->DoModal(); + return; + } + + GetDataFileBase( GetSubmitter(), m_szVMFName, sizeof( m_szVMFName ) ); + m_pVMFURL->SetText( m_szVMFName ); +} + +void CBugUIPanel::OnChooseVMFFolder() +{ + if ( !m_hDirectorySelectDialog.Get() ) + { + m_hDirectorySelectDialog = new DirectorySelectDialog( this, "Choose .vmf folder" ); + } + + m_bAddVMF = false; + m_hDirectorySelectDialog->SetStartDirectory( m_szVMFContentDirFullpath ); + m_hDirectorySelectDialog->DoModal(); +} + +void CBugUIPanel::OnChooseArea(vgui::Panel *panel) +{ + if (panel != m_pGameArea) + return; + + if (!Q_strcmp(BUG_REPORTER_DLLNAME, GetInternalBugReporterDLL())) + { + int area_index = m_pGameArea->GetActiveItem(); + int c = m_pBugReporter->GetLevelCount(area_index); + int item = -1; + const char *currentLevel = cl.IsActive() ? cl.m_szLevelBaseName : "console"; + + m_pMapNumber->DeleteAllItems(); + + for ( int i = 0; i < c; i++ ) + { + const char *level = m_pBugReporter->GetLevel(area_index, i ); + int id = m_pMapNumber->AddItem( level, NULL ); + if (!Q_strcmp(currentLevel, level)) + { + item = id; + } + } + + if (item >= 0) + { + m_pMapNumber->ActivateItem( item ); + } + else + { + m_pMapNumber->ActivateItemByRow(0); + } + } +} + +void CBugUIPanel::OnDirectorySelected( char const *dir ) +{ + Q_strncpy( m_szVMFContentDirFullpath, dir, sizeof( m_szVMFContentDirFullpath ) ); + Q_strlower( m_szVMFContentDirFullpath ); + Q_FixSlashes( m_szVMFContentDirFullpath ); + Q_StripTrailingSlash( m_szVMFContentDirFullpath ); + + if ( m_hDirectorySelectDialog != 0 ) + { + m_hDirectorySelectDialog->MarkForDeletion(); + } + + // See if .vmf exists in assumed location + if ( m_bAddVMF ) + { + GetDataFileBase( GetSubmitter(), m_szVMFName, sizeof( m_szVMFName ) ); + m_pVMFURL->SetText( m_szVMFName ); + } + m_bAddVMF = false; +} + +void CBugUIPanel::OnFileSelected( char const *fullpath ) +{ + bool baseDirFile = false; + + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + return; + + if ( !fullpath || !fullpath[ 0 ] ) + return; + + char relativepath[ 512 ]; + if ( !g_pFileSystem->FullPathToRelativePath( fullpath, relativepath, sizeof( relativepath ) ) ) + { + if ( Q_stristr( fullpath, com_basedir ) ) + { + Q_snprintf( relativepath, sizeof( relativepath ), "..%s", fullpath + strlen(com_basedir) ); + baseDirFile = true; + } + else + { + Msg("Only files beneath the base game directory can be included\n" ); + return; + } + } + + char ext[ 10 ]; + Q_ExtractFileExtension( relativepath, ext, sizeof( ext ) ); + + if ( m_hFileOpenDialog != 0 ) + { + m_hFileOpenDialog->MarkForDeletion(); + } + + includedfile inc; + Q_strncpy( inc.name, relativepath, sizeof( inc.name ) ); + + if ( baseDirFile ) + { + Q_snprintf( inc.fixedname, sizeof( inc.fixedname ), "%s", inc.name+3 ); // strip the "..\" + } + else + { + Q_snprintf( inc.fixedname, sizeof( inc.fixedname ), "%s", inc.name ); + } + Q_FixSlashes( inc.fixedname ); + + m_IncludedFiles.AddToTail( inc ); + + char concat[ 8192 ]; + concat[ 0 ] = 0; + for ( int i = 0 ; i < m_IncludedFiles.Count(); ++i ) + { + Q_strncat( concat, m_IncludedFiles[ i ].name, sizeof( concat), COPY_ALL_CHARACTERS ); + Q_strncat( concat, "\n", sizeof( concat), COPY_ALL_CHARACTERS ); + } + m_pIncludedFiles->SetText( concat ); +} + +void CBugUIPanel::OnIncludeFile() +{ + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + return; + + if ( !m_hFileOpenDialog.Get() ) + { + m_hFileOpenDialog = new FileOpenDialog( this, "Choose file to include", true ); + if ( m_hFileOpenDialog != 0 ) + { + m_hFileOpenDialog->AddFilter("*.*", "All Files (*.*)", true); + } + } + if ( m_hFileOpenDialog ) + { + char startPath[ MAX_PATH ]; + Q_strncpy( startPath, com_gamedir, sizeof( startPath ) ); + Q_FixSlashes( startPath ); + m_hFileOpenDialog->SetStartDirectory( startPath ); + m_hFileOpenDialog->DoModal( false ); + } + + //GetDataFileBase( GetSubmitter(), m_szVMFName, sizeof( m_szVMFName ) ); +} + +void CBugUIPanel::OnClearIncludedFiles() +{ + m_IncludedFiles.Purge(); + m_pIncludedFiles->SetText( "" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Shows the panel +//----------------------------------------------------------------------------- +void CBugUIPanel::Activate() +{ + if ( !m_bValidated ) + { + if ( Init() ) + { + m_bValidated = true; + DetermineSubmitterName(); + } + } + + if ( m_pGameArea->GetItemCount() != 0 ) + { + int iArea = GetArea(); + if ( iArea != 0 ) + m_pGameArea->ActivateItem( iArea ); + } + + { + int c = m_pMapNumber->GetItemCount(); + const char *currentLevel = cl.IsActive() ? cl.m_szLevelBaseName : "console"; + int item = -1; + + for ( int i = 0; i < c; i++ ) + { + int id = m_pMapNumber->GetItemIDFromRow(i); + char level[256]; + m_pMapNumber->GetItemText(id, level, sizeof(level)); + if (!Q_strcmp(currentLevel, level)) + { + item = id; + } + } + + if (item >= 0) + { + m_pMapNumber->ActivateItem( item ); + } + else + { + m_pMapNumber->ActivateItemByRow(0); + } + } + + if ( cl.IsActive() ) + { + Vector org = MainViewOrigin(); + QAngle ang; + VectorAngles( MainViewForward(), ang ); + + IClientEntity *localPlayer = entitylist->GetClientEntity( cl.m_nPlayerSlot + 1 ); + if ( localPlayer ) + { + org = localPlayer->GetAbsOrigin(); + } + + m_pPosition->SetText( va( "%f %f %f", org.x, org.y, org.z ) ); + m_pOrientation->SetText( va( "%f %f %f", ang.x, ang.y, ang.z ) ); + + m_pLevelName->SetText( cl.m_szLevelBaseName ); + m_pSaveGame->SetEnabled( ( cl.m_nMaxClients == 1 ) ? true : false ); + m_pSaveBSP->SetEnabled( true ); + m_pSaveVMF->SetEnabled( true ); + m_pChooseVMFFolder->SetEnabled( true ); + } + else + { + m_pPosition->SetText( "console" ); + m_pOrientation->SetText( "console" ); + m_pLevelName->SetText( "console" ); + m_pSaveGame->SetEnabled( false ); + m_pSaveBSP->SetEnabled( false ); + m_pSaveVMF->SetEnabled( false ); + m_pChooseVMFFolder->SetEnabled( false ); + } + + BaseClass::Activate(); + + m_pTitle->RequestFocus(); + m_pTitle->SelectAllText( true ); + + // Automatically make a screenshot if requested. + if (!m_szScreenShotName[0] ) + { + bool bAutoAddScreenshot = false; + + // For public bugreports should be explicitly requested + if ( m_bIsPublic ) + { + if ( m_fAutoAddScreenshot == CBugUIPanel::eAutoAddScreenshot_Add ) + { + bAutoAddScreenshot = true; + } + } + // For internal bugreports shouldn't be explicitly denied + else + { + if ( m_fAutoAddScreenshot != CBugUIPanel::eAutoAddScreenshot_DontAdd ) + { + bAutoAddScreenshot = true; + } + } + + if ( bAutoAddScreenshot ) + { + OnTakeSnapshot(); + } + } +} + +void CBugUIPanel::WipeData() +{ + m_fAutoAddScreenshot = eAutoAddScreenshot_Detect; + + m_szScreenShotName[ 0 ] = 0; + m_pScreenShotURL->SetText( "Screenshot file" ); + m_szSaveGameName[ 0 ] = 0; + m_pSaveGameURL->SetText( "Save game file" ); + m_szBSPName[ 0 ] = 0; + m_pBSPURL->SetText( ".bsp file" ); + m_szVMFName[ 0 ] = 0; + m_pVMFURL->SetText( ".vmf file" ); + m_IncludedFiles.Purge(); + m_pIncludedFiles->SetText( "" ); + +// m_pTitle->SetText( "" ); + m_pDescription->SetText( "" ); + m_pEmail->SetText( "" ); +} + +//----------------------------------------------------------------------------- +// Purpose: method to make sure the email address is acceptable to be used by steam +//----------------------------------------------------------------------------- +bool CBugUIPanel::IsValidEmailAddress( char const *email ) +{ + // basic size check + if (!email || strlen(email) < 5) + return false; + + // make sure all the characters in the string are valid + {for (const char *sz = email; *sz; sz++) + { + if (!V_isalnum(*sz) && *sz != '.' && *sz != '-' && *sz != '@' && *sz != '_') + return false; + }} + + // make sure it has letters, then the '@', then letters, then '.', then letters + const char *sz = email; + if (!V_isalnum(*sz)) + return false; + sz = strstr(sz, "@"); + if (!sz) + return false; + sz++; + if (!V_isalnum(*sz)) + return false; + sz = strstr(sz, "."); + if (!sz) + return false; + sz++; + if (!V_isalnum(*sz)) + return false; + + return true; +} + +bool CBugUIPanel::IsValidSubmission( bool verbose ) +{ + if ( !m_pBugReporter ) + return false; + + bool isPublic = m_pBugReporter->IsPublicUI(); + + char title[ 256 ]; + char desc[ 4096 ]; + + title[ 0 ] = 0; + desc[ 0 ] = 0; + + m_pTitle->GetText( title, sizeof( title ) ); + if ( !title[ 0 ] ) + { + if ( verbose ) + { + Warning( "Bug must have a title\n" ); + } + + return false; + } + + // Only require description in public UI + if ( isPublic ) + { + m_pDescription->GetText( desc, sizeof( desc ) ); + if ( !desc[ 0 ] ) + { + if ( verbose ) + { + Warning( "Bug must have a description\n" ); + } + return false; + } + } + + + if ( !isPublic && m_pSeverity->GetActiveItem() < 0 ) + { + if ( verbose ) + { + Warning( "Severity not set!\n" ); + } + return false; + } + + if ( !isPublic && m_pAssignTo->GetActiveItem() < 0 ) + { + if ( verbose ) + { + Warning( "Owner not set!\n" ); + } + return false; + } + + char owner[ 256 ]; + Q_strncpy( owner, m_pBugReporter->GetDisplayName( m_pAssignTo->GetActiveItem() ), sizeof( owner ) ); + if ( !isPublic && !Q_stricmp( owner, "<<Unassigned>>" ) ) + { + if ( verbose ) + { + Warning( "Owner not set!\n" ); + } + return false; + } + + if ( !isPublic && m_pPriority->GetActiveItem() < 0 ) + { + if ( verbose ) + { + Warning( "Priority not set!\n" ); + } + return false; + } + + if ( !isPublic && m_pReportType->GetActiveItem() < 0 ) + { + if ( verbose ) + { + Warning( "Report Type not set!\n" ); + } + return false; + } + + if ( !isPublic && m_pGameArea->GetActiveItem() < 0 ) + { + if ( verbose ) + { + Warning( "Area not set!\n" ); + } + return false; + } + + // Public must have a selection and it can't be the "<<Choose One>>" + if ( isPublic && m_pReportType->GetActiveItem() <= 0 ) + { + if ( verbose ) + { + Warning( "Report type not set!\n" ); + } + return false; + } + + if ( isPublic ) + { + char email[ 80 ]; + m_pEmail->GetText( email, sizeof( email ) ); + if ( email[ 0 ] != 0 && + !IsValidEmailAddress( email ) ) + { + return false; + } + } + + return true; +} + +bool CBugUIPanel::AddBugTextToZip( char const *textfilename, char const *text, int textlen ) +{ + if ( !m_hZip ) + { + // Create using OS pagefile memory... + m_hZip = CreateZipZ( 0, MAX_ZIP_SIZE, ZIP_MEMORY ); + Assert( m_hZip ); + if ( !m_hZip ) + { + return false; + } + } + + ZipAdd( m_hZip, textfilename, (void *)text, textlen, ZIP_MEMORY ); + + return true; +} + + +bool CBugUIPanel::AddFileToZip( char const *relative ) +{ + if ( !m_hZip ) + { + // Create using OS pagefile memory... + m_hZip = CreateZipZ( 0, MAX_ZIP_SIZE, ZIP_MEMORY ); + Assert( m_hZip ); + if ( !m_hZip ) + { + return false; + } + } + + char fullpath[ 512 ]; + if ( g_pFileSystem->RelativePathToFullPath( relative, "GAME", fullpath, sizeof( fullpath ) ) ) + { + char extension[ 32 ]; + Q_ExtractFileExtension( relative, extension, sizeof( extension ) ); + char basename[ 256 ]; + Q_FileBase( relative, basename, sizeof( basename ) ); + char outname[ 512 ]; + Q_snprintf( outname, sizeof( outname ), "%s.%s", basename, extension ); + + ZipAdd( m_hZip, outname, fullpath, 0, ZIP_FILENAME ); + + return true; + } + + return false; +} + +void CBugUIPanel::OnSubmit() +{ + if ( !m_bCanSubmit ) + { + return; + } + + if ( !IsValidSubmission( true ) ) + { + // Play deny sound + DenySound(); + return; + } + + bool isPublic = m_pBugReporter->IsPublicUI(); + + char title[ 80 ]; + char desc[ 4096 ]; + char severity[ 128 ]; + char area[ 128 ]; + char mapnumber[ 128 ]; + char priority[ 128 ]; + char assignedto[ 128 ]; + char level[ 128 ]; + char position[ 128 ]; + char orientation[ 128 ]; + char build[ 128 ]; + char reporttype[ 128 ]; + char email[ 80 ]; + + title[ 0 ] = 0; + desc[ 0 ] = 0; + severity[ 0 ] = 0; + area[ 0 ] = 0; + mapnumber[ 0] = 0; + priority[ 0 ] = 0; + assignedto[ 0 ] = 0; + level[ 0 ] = 0; + orientation[ 0 ] = 0; + position[ 0 ] = 0; + build[ 0 ] = 0; + reporttype [ 0 ] = 0; + email[ 0 ] = 0; + + Assert( m_pBugReporter ); + + // Stuff bug data files up to server + m_pBugReporter->StartNewBugReport(); + + + char temp[ 80 ]; + m_pTitle->GetText( temp, sizeof( temp ) ); + + if ( host_state.worldmodel ) + { + char mapname[256]; + CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) ); + + Q_snprintf( title, sizeof( title ), "%s: %s", mapname, temp ); + } + else + { + Q_snprintf( title, sizeof( title ), "%s", temp ); + } + + Msg( "title: %s\n", title ); + + m_pDescription->GetText( desc, sizeof( desc ) ); + + Msg( "description: %s\n", desc ); + + m_pLevelName->GetText( level, sizeof( level ) ); + m_pPosition->GetText( position, sizeof( position ) ); + m_pOrientation->GetText( orientation, sizeof( orientation ) ); + m_pBuildNumber->GetText( build, sizeof( build ) ); + + Q_strncat( build, " (Steam)", sizeof(build), COPY_ALL_CHARACTERS ); + + MaterialAdapterInfo_t info; + materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), info ); + char driverinfo[ 2048 ]; + + char const *dxlevel = "Unk"; + if ( g_pMaterialSystemHardwareConfig ) + { + dxlevel = COM_DXLevelToString( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() ) ; + } + + char osversion[ 256 ]; + DisplaySystemVersion( osversion, sizeof( osversion ) ); + + Q_snprintf( driverinfo, sizeof( driverinfo ), + "OS Version: %s\n" + "Driver Name: %s\n" + "VendorId / DeviceId: 0x%x / 0x%x\n" + "SubSystem / Rev: 0x%x / 0x%x\n" + "DXLevel: %s\nVid: %i x %i\n" + "Framerate: %.3f\n", + osversion, + info.m_pDriverName, + info.m_VendorID, + info.m_DeviceID, + info.m_SubSysID, + info.m_Revision, + dxlevel ? dxlevel : "Unk", + videomode->GetModeWidth(), videomode->GetModeHeight(), + g_fFramesPerSecond ); + + Msg( "%s", driverinfo ); + + int latency = 0; + if ( cl.m_NetChannel ) + { + latency = (int)( 1000.0f * cl.m_NetChannel->GetAvgLatency( FLOW_OUTGOING ) ); + } + + ConVarRef host_thread_mode( "host_thread_mode" ); + ConVarRef sv_alternateticks( "sv_alternateticks" ); + ConVarRef ai_strong_optimizations( "ai_strong_optimizations" ); + + char misc[ 1024 ]; + Q_snprintf( misc, sizeof( misc ), "Convars:\n\tskill: %i\n\tnet: rate %i update %i cmd %i latency %i msec\n\thost_thread_mode: %i\n\tsv_alternateticks: %i\n\tai_strong_optimizations: %i\n", + skill.GetInt(), + cl_rate->GetInt(), + (int)cl_updaterate->GetFloat(), + (int)cl_cmdrate->GetFloat(), + latency, + host_thread_mode.GetInt(), + sv_alternateticks.GetInt(), + ai_strong_optimizations.GetInt() + ); + + if ( cl.IsActive() && g_ServerGlobalVariables.mapversion != 0 && host_state.worldmodel ) + { + // Note, this won't work in multiplayer, oh well... + extern CGlobalVars g_ServerGlobalVariables; + char misc2[ 256 ]; + + long mapfiletime = g_pFileSystem->GetFileTime( modelloader->GetName( host_state.worldmodel ), "GAME" ); + if ( !isPublic && mapfiletime != 0L ) + { + char filetimebuf[ 64 ]; + g_pFileSystem->FileTimeToString( filetimebuf, sizeof( filetimebuf ), mapfiletime ); + + Q_snprintf( misc2, sizeof( misc2 ), "Map version: %i\nFile timestamp: %s", g_ServerGlobalVariables.mapversion, filetimebuf ); + Q_strncat( misc, misc2, sizeof( misc ), COPY_ALL_CHARACTERS ); + } + else + { + Q_snprintf( misc2, sizeof( misc2 ), "Map version: %i\n", g_ServerGlobalVariables.mapversion ); + Q_strncat( misc, misc2, sizeof( misc ), COPY_ALL_CHARACTERS ); + } + } + + if ( sv.IsActive() && serverGameClients ) + { + char gamedlldata[ 2048 ]; + Q_memset( gamedlldata, 0, sizeof( gamedlldata ) ); + serverGameClients->GetBugReportInfo( gamedlldata, sizeof( gamedlldata ) ); + Q_strncat( misc, gamedlldata, sizeof( misc ), COPY_ALL_CHARACTERS ); + } + + { + char misc2[ 128 ]; + Q_snprintf( misc2, sizeof( misc2 ), "gamedir: %s\n", com_gamedir ); + Q_strncat( misc, misc2, sizeof( misc ), COPY_ALL_CHARACTERS ); + //Q_snprintf( misc2, sizeof( misc2 ), "game: %s\n", COM_GetModDirectory() ); + //Q_strncat( misc, misc2, sizeof( misc ), COPY_ALL_CHARACTERS ); + } + + Msg( "%s", misc ); + + if ( !isPublic ) + { + m_pSeverity->GetText( severity, sizeof( severity ) ); + Msg( "severity %s\n", severity ); + + m_pGameArea->GetText( area, sizeof( area ) ); + Msg( "area %s\n", area ); + + m_pMapNumber->GetText( mapnumber, sizeof( mapnumber) ); + Msg( "map number %s\n", mapnumber); + + m_pPriority->GetText( priority, sizeof( priority ) ); + Msg( "priority %s\n", priority ); + + m_pAssignTo->GetText( assignedto, sizeof( assignedto ) ); + Msg( "owner %s\n", assignedto ); + } + + if ( isPublic ) + { + m_pEmail->GetText( email, sizeof( email ) ); + if ( Q_strlen( email ) > 0 ) + { + Msg( "email %s\n", email ); + } + else + { + Msg( "Not sending email address\n" ); + } + + m_pBugReporter->SetOwner( email ); + char submitter[ 80 ]; + m_pSubmitterLabel->GetText( submitter, sizeof( submitter ) ); + m_pBugReporter->SetSubmitter( submitter ); + } + else + { + m_pBugReporter->SetOwner( m_pBugReporter->GetUserNameForDisplayName( assignedto ) ); + + if ( m_bUseNameForSubmitter ) + { + char submitter[ 80 ]; + m_pSubmitter->GetText( submitter, sizeof( submitter ) ); + m_pBugReporter->SetSubmitter( submitter ); + } + else + { + m_pBugReporter->SetSubmitter( NULL ); + } + } + + m_pReportType->GetText( reporttype, sizeof( reporttype ) ); + Msg( "report type %s\n", reporttype ); + + Msg( "submitter %s\n", m_pBugReporter->GetUserName() ); + + Msg( "level %s\n", level ); + Msg( "position %s\n", position ); + Msg( "orientation %s\n", orientation ); + Msg( "build %s\n", build ); + + if ( m_szSaveGameName[ 0 ] ) + { + Msg( "save file save/%s.sav\n", m_szSaveGameName ); + } + else + { + Msg( "no save game\n" ); + } + + if ( m_szScreenShotName[ 0 ] ) + { + Msg( "screenshot screenshots/%s.jpg\n", m_szScreenShotName ); + } + else + { + Msg( "no screenshot\n" ); + } + + if ( !isPublic ) + { + if ( m_szBSPName[ 0 ] ) + { + Msg( "bsp file maps/%s.bsp\n", m_szBSPName ); + } + + if ( m_szVMFName[ 0 ] ) + { + Msg( "vmf file maps/%s.vmf\n", m_szVMFName ); + } + + if ( m_IncludedFiles.Count() > 0 ) + { + for ( int i = 0; i < m_IncludedFiles.Count(); ++i ) + { + Msg( "Include: %s\n", m_IncludedFiles[ i ].name ); + } + } + } + + + m_pBugReporter->SetTitle( title ); + m_pBugReporter->SetDescription( desc ); + m_pBugReporter->SetLevel( level ); + m_pBugReporter->SetPosition( position ); + m_pBugReporter->SetOrientation( orientation ); + m_pBugReporter->SetBuildNumber( build ); + + m_pBugReporter->SetSeverity( severity ); + m_pBugReporter->SetPriority( priority ); + m_pBugReporter->SetArea( area ); + m_pBugReporter->SetMapNumber( mapnumber ); + m_pBugReporter->SetReportType( reporttype ); + m_pBugReporter->SetDriverInfo( driverinfo ); + m_pBugReporter->SetMiscInfo( misc ); + + m_pBugReporter->SetCSERAddress( m_cserIP ); + m_pBugReporter->SetExeName( "hl2.exe" ); + m_pBugReporter->SetGameDirectory( com_gamedir ); + + const CPUInformation& pi = *GetCPUInformation(); + + m_pBugReporter->SetRAM( GetRam() ); + + double fFrequency = pi.m_Speed / 1000000.0; + + m_pBugReporter->SetCPU( (int)fFrequency ); + + m_pBugReporter->SetProcessor( pi.m_szProcessorID ); + + int nDxLevel = g_pMaterialSystemHardwareConfig->GetDXSupportLevel(); + int vHigh = nDxLevel / 10; + int vLow = nDxLevel - vHigh * 10; + + m_pBugReporter->SetDXVersion( vHigh, vLow, info.m_VendorID, info.m_DeviceID ); + + m_pBugReporter->SetOSVersion( osversion ); + + m_pBugReporter->ResetIncludedFiles(); + m_pBugReporter->SetZipAttachmentName( "" ); + + char fn[ 512 ]; + if ( m_pBugReporter->IsPublicUI() ) + { + bool attachedSave = false; + bool attachedScreenshot = false; + + CUtlBuffer buginfo( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + buginfo.Printf( "Title: %s\n", title ); + buginfo.Printf( "Description: %s\n\n", desc ); + buginfo.Printf( "Level: %s\n", level ); + buginfo.Printf( "Position: %s\n", position ); + buginfo.Printf( "Orientation: %s\n", orientation ); + buginfo.Printf( "BuildNumber: %s\n", build ); + buginfo.Printf( "DriverInfo: %s\n", driverinfo ); + buginfo.Printf( "Misc: %s\n", misc ); + buginfo.Printf( "Exe: %s\n", "hl2.exe" ); + char gd[ 256 ]; + Q_FileBase( com_gamedir, gd, sizeof( gd ) ); + buginfo.Printf( "GameDirectory: %s\n", gd ); + buginfo.Printf( "Ram: %lu\n", GetRam() ); + buginfo.Printf( "CPU: %i\n", (int)fFrequency ); + buginfo.Printf( "Processor: %s\n", pi.m_szProcessorID ); + buginfo.Printf( "DXLevel: %d\n", nDxLevel ); + buginfo.Printf( "OSVersion: %s\n", osversion ); + // Terminate it + buginfo.PutChar( 0 ); + + + int maxlen = buginfo.TellPut() * 2 + 1; + char *fixed = new char [ maxlen ]; + Assert( fixed ); + if ( fixed ) + { + Q_memset( fixed, 0, maxlen ); + + char *i = (char *)buginfo.Base(); + char *o = fixed; + while ( *i && ( o - fixed ) < maxlen - 1 ) + { + if ( *i == '\n' ) + { + *o++ = '\r'; + } + *o++ = *i++; + } + *o = '\0'; + + AddBugTextToZip( "info.txt", fixed, Q_strlen( fixed ) + 1 ); + + delete[] fixed; + } + else + { + Sys_Error( "Unable to allocate %i bytes for bug description\n", maxlen ); + } + + // Only attach .sav files in single player + if ( ( cl.m_nMaxClients == 1 ) && m_szSaveGameName[ 0 ] ) + { + Q_snprintf( fn, sizeof( fn ), "save/%s.sav", m_szSaveGameName ); + Q_FixSlashes( fn ); + attachedSave = AddFileToZip( fn ); + } + if ( m_szScreenShotName[ 0 ] ) + { + Q_snprintf( fn, sizeof( fn ), "screenshots/%s.jpg", m_szScreenShotName ); + Q_FixSlashes( fn ); + attachedScreenshot = AddFileToZip( fn ); + } + + // End users can only send save games and screenshots to valve + // Don't bother uploading any attachment if it's just the info.txt file, either... + if ( m_hZip && ( attachedSave || attachedScreenshot ) ) + { + Assert( m_hZip ); + void *mem; + unsigned long len; + + ZipGetMemory( m_hZip, &mem, &len ); + if ( mem != NULL + && len > 0 ) + { + // Store .zip file + FileHandle_t fh = g_pFileSystem->Open( "bug.zip", "wb" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + g_pFileSystem->Write( mem, len, fh ); + g_pFileSystem->Close( fh ); + + m_pBugReporter->SetZipAttachmentName( "bug.zip" ); + } + } + } + + if ( m_hZip ) + { + CloseZip( m_hZip ); + m_hZip = (HZIP)0; + } + + uint64 un64SteamID = m_SteamID.ConvertToUint64(); + m_pBugReporter->SetSteamUserID( &un64SteamID, sizeof( un64SteamID ) ); + } + else + { + // Notify other players that we've submitted a bug + if ( cl.IsActive() && cl.m_nMaxClients > 1 ) + { + char buf[256]; + Q_snprintf( buf, sizeof(buf), "say \"Bug Submitted: \'%s\'\"", title ); + Cbuf_AddText( buf ); + } + + if ( m_szSaveGameName[ 0 ] ) + { + Q_snprintf( fn, sizeof( fn ), "%s/BugId/%s.sav", GetRepositoryURL(), m_szSaveGameName ); + Q_FixSlashes( fn ); + m_pBugReporter->SetSaveGame( fn ); + } + if ( m_szScreenShotName[ 0 ] ) + { + Q_snprintf( fn, sizeof( fn ), "%s/BugId/%s.jpg", GetRepositoryURL(), m_szScreenShotName ); + Q_FixSlashes( fn ); + m_pBugReporter->SetScreenShot( fn ); + } + if ( m_szBSPName[ 0 ] ) + { + Q_snprintf( fn, sizeof( fn ), "%s/BugId/%s.bsp", GetRepositoryURL(), m_szBSPName ); + Q_FixSlashes( fn ); + m_pBugReporter->SetBSPName( fn ); + } + if ( m_szVMFName[ 0 ] ) + { + Q_snprintf( fn, sizeof( fn ), "%s/BugId/%s.vmf", GetRepositoryURL(), m_szVMFName ); + Q_FixSlashes( fn ); + m_pBugReporter->SetVMFName( fn ); + } + + + if ( m_IncludedFiles.Count() > 0 ) + { + for ( int i = 0; i < m_IncludedFiles.Count(); ++i ) + { + Q_snprintf( fn, sizeof( fn ), "%s/BugId/%s", GetRepositoryURL(), m_IncludedFiles[ i ].fixedname ); + Q_FixSlashes( fn ); + m_pBugReporter->AddIncludedFile( fn ); + } + } + } + + Q_strncpy( m_szLevel, level, sizeof( m_szLevel ) ); + + if ( m_pBugReporter->IsPublicUI() ) + { + m_pProgressDialog = new CBugReportUploadProgressDialog(NULL, "ProgressDialog", "#Steam_SubmittingBug_WorkingTitle", "#Steam_SubmittingBug_WorkingText" ); + m_pProgressDialog->Activate(); + vgui::input()->SetAppModalSurface(m_pProgressDialog->GetVPanel()); + m_flPauseTime = (float)system()->GetFrameTime() + PUBLIC_BUGREPORT_WAIT_TIME; + } + else + { + OnFinishBugReport(); + } +} + +void CBugUIPanel::OnFinishBugReport() +{ + int bugId = -1; + + bool success = m_pBugReporter->CommitBugReport( bugId ); + if ( success ) + { + // The public UI handles uploading on it's own... + if ( !m_pBugReporter->IsPublicUI() ) + { + if ( !UploadBugSubmission( m_szLevel, bugId, m_szSaveGameName, m_szScreenShotName, m_szBSPName, m_szVMFName, m_IncludedFiles ) ) + { + Warning( "Unable to upload saved game and screenshot to bug repository!\n" ); + success = false; + } + } + } + else + { + Warning( "Unable to post bug report to database\n" ); + } + + if ( !success ) + { + // Play deny sound + DenySound(); + m_bWaitForFinish = false; + } + else + { + // Close + WipeData(); + SuccessSound( bugId ); + + if ( !m_pBugReporter->IsPublicUI() ) + { + Close(); + } + } +} + +void NonFileSystem_CreatePath (const char *path) +{ + char temppath[512]; + Q_strncpy( temppath, path, sizeof(temppath) ); + + for (char *ofs = temppath+1 ; *ofs ; ofs++) + { + if (*ofs == '/' || *ofs == '\\') + { // create the directory + char old = *ofs; + *ofs = 0; + _mkdir (temppath); + *ofs = old; + } + } +} + +#ifdef LINUX +#define COPYFILE_ALL 0 +#define BSIZE 65535 +int copyfile( const char *local, const char *remote, void *ignored, int ignoredFlags ) +{ + ssize_t bytes; + int fps, fpd; + char buffer[BSIZE]; + + if ( (fps = open( local , O_RDONLY ) ) == -1 ) + return -1; + + if ( (fpd = open( remote, O_WRONLY | O_CREAT | O_TRUNC, 0644 ) ) == -1 ) + return -1; + + while((bytes = read(fps, buffer, BSIZE)) > 0) + write(fpd, buffer, bytes); + + close(fpd); + close(fps); + return 0; +} +#endif + +bool CBugUIPanel::UploadFile( char const *local, char const *remote, bool bDeleteLocal ) +{ + Msg( "Uploading %s to %s\n", local, remote ); + FileHandle_t hLocal = g_pFileSystem->Open( local, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE == hLocal ) + { + Warning( "CBugUIPanel::UploadFile: Unable to open local path '%s'\n", local ); + return false; + } + + int nLocalFileSize = g_pFileSystem->Size( hLocal ); + if ( nLocalFileSize <= 0 ) + { + Warning( "CBugUIPanel::UploadFile: Local file has 0 size '%s'\n", local ); + g_pFileSystem->Close( hLocal ); + return false; + } + NonFileSystem_CreatePath( remote ); + + bool bResult; + if ( !g_pFileSystem->IsSteam() ) + { + g_pFileSystem->Close( hLocal ); +#ifdef WIN32 + bResult = CopyFile( local, remote, false ) ? true : false; +#elif POSIX + bResult = (0 == copyfile( local, remote, NULL, COPYFILE_ALL )); +#else +#error +#endif + } + else + { + + FILE *r = fopen( va( "%s", remote ), "wb" ); + if ( !r ) + { + Warning( "CBugUIPanel::UploadFile: Unable to open remote path '%s'\n", remote ); + g_pFileSystem->Close( hLocal ); + return false; + } + + int nCopyBufferSize = 2 * 1024 * 1024; + + byte *pCopyBuf = new byte[ nCopyBufferSize ]; + Assert( pCopyBuf ); + if ( !pCopyBuf ) + { + Warning( "CBugUIPanel::UploadFile: Unable to allocate copy buffer of %d bytes\n", nCopyBufferSize ); + fclose( r ); + g_pFileSystem->Close( hLocal ); + return false; + } + + int nRemainingBytes = nLocalFileSize; + while ( nRemainingBytes > 0 ) + { + int nBytesToCopy = MIN( nRemainingBytes, nCopyBufferSize ); + g_pFileSystem->Read( pCopyBuf, nBytesToCopy, hLocal ); + fwrite( pCopyBuf, nBytesToCopy, 1, r ); + nRemainingBytes -= nBytesToCopy; + } + + fclose( r ); + g_pFileSystem->Close( hLocal ); + + delete[] pCopyBuf; + + bResult = true; + } + + if ( !bResult ) + { + Warning( "Failed to upload %s, error %d\n", local, GetLastError() ); + } + else if ( bDeleteLocal ) + { + unlink( local ); + } + return bResult; +} + +CCallQueue g_UploadQueue; + +bool CBugUIPanel::UploadBugSubmission( char const *levelname, int bugId, char const *savefile, char const *screenshot, char const *bsp, char const *vmf, + CUtlVector< includedfile >& files ) +{ + bool bret = true; + bool bAsync = bugreporter_uploadasync.GetBool(); + + char localfile[ 512 ]; + char remotefile[ 512 ]; + + if ( savefile && savefile[ 0 ] ) + { + Q_snprintf( localfile, sizeof( localfile ), "%s/save/%s.sav", com_gamedir, savefile ); + Q_snprintf( remotefile, sizeof( remotefile ), "%s/%s.sav", GetSubmissionURL(bugId), savefile ); + Q_FixSlashes( localfile ); + Q_FixSlashes( remotefile ); + + g_UploadQueue.QueueCall( this, &CBugUIPanel::UploadFile, CUtlEnvelope<const char *>(localfile), CUtlEnvelope<const char *>(remotefile), false ); + } + + if ( screenshot && screenshot[ 0 ] ) + { + Q_snprintf( localfile, sizeof( localfile ), "%s/screenshots/%s.jpg", com_gamedir, screenshot ); + Q_snprintf( remotefile, sizeof( remotefile ), "%s/%s.jpg", GetSubmissionURL(bugId), screenshot ); + Q_FixSlashes( localfile ); + Q_FixSlashes( remotefile ); + + g_UploadQueue.QueueCall( this, &CBugUIPanel::UploadFile, CUtlEnvelope<const char *>(localfile), CUtlEnvelope<const char *>(remotefile), false ); + } + + if ( bsp && bsp[ 0 ] ) + { + Q_snprintf( localfile, sizeof( localfile ), "maps/%s.bsp", levelname ); + char *pszMapPath; + FileHandle_t hBsp = g_pFileSystem->OpenEx( localfile, "rb", 0, 0, &pszMapPath ); + if ( !hBsp ) + { + Q_snprintf( localfile, sizeof( localfile ), "%s/maps/%s.bsp", com_gamedir, levelname ); + } + else + { + V_strncpy( localfile, pszMapPath, sizeof( localfile ) ); + delete pszMapPath; + g_pFileSystem->Close( hBsp ); + } + Q_snprintf( remotefile, sizeof( remotefile ), "%s/%s.bsp", GetSubmissionURL(bugId), bsp ); + Q_FixSlashes( localfile ); + Q_FixSlashes( remotefile ); + + g_UploadQueue.QueueCall( this, &CBugUIPanel::UploadFile, CUtlEnvelope<const char *>(localfile), CUtlEnvelope<const char *>(remotefile), false ); + } + + if ( vmf && vmf[ 0 ] ) + { + Q_snprintf( localfile, sizeof( localfile ), "%s/%s.vmf", m_szVMFContentDirFullpath, levelname ); + if ( g_pFileSystem->FileExists( localfile, NULL ) ) + { + Q_snprintf( remotefile, sizeof( remotefile ), "%s/%s.vmf", GetSubmissionURL(bugId), vmf ); + Q_FixSlashes( localfile ); + Q_FixSlashes( remotefile ); + + g_UploadQueue.QueueCall( this, &CBugUIPanel::UploadFile, CUtlEnvelope<const char *>(localfile), CUtlEnvelope<const char *>(remotefile), false ); + } + else + { + Msg( "Unable to locate .vmf file %s\n", localfile ); + } + } + + if ( files.Count() > 0 ) + { + bAsync = false; + int c = files.Count(); + for ( int i = 0 ; i < c; ++i ) + { + Q_snprintf( localfile, sizeof( localfile ), "%s/%s", com_gamedir, files[ i ].name ); + Q_snprintf( remotefile, sizeof( remotefile ), "%s/%s", GetSubmissionURL(bugId), files[ i ].fixedname ); + Q_FixSlashes( localfile ); + Q_FixSlashes( remotefile ); + + g_UploadQueue.QueueCall( this, &CBugUIPanel::UploadFile, CUtlEnvelope<const char *>(localfile), CUtlEnvelope<const char *>(remotefile), false ); + } + } + + if ( !bAsync ) + { + g_UploadQueue.CallQueued(); + } + else + { + ThreadExecuteSolo( "BugUpload", &g_UploadQueue, &CCallQueue::CallQueued ); + } + + return bret; +} + +void CBugUIPanel::Close() +{ + WipeData(); + BaseClass::Close(); +} + +void CBugUIPanel::OnCommand( char const *command ) +{ + if ( !Q_strcasecmp( command, "submit" ) ) + { + OnSubmit(); + } + else if ( !Q_strcasecmp( command, "cancel" ) ) + { + Close(); + WipeData(); + } + else if ( !Q_strcasecmp( command, "snapshot" ) ) + { + OnTakeSnapshot(); + } + else if ( !Q_strcasecmp( command, "savegame" ) ) + { + OnSaveGame(); + + //Adrian: We always want the BSP you used when saving the game. + //But only if you're not the public bug reporter! + if ( bugreporter_includebsp.GetBool() && + m_pBugReporter->IsPublicUI() == false ) + { + OnSaveBSP(); + } + } + else if ( !Q_strcasecmp( command, "savebsp" ) ) + { + OnSaveBSP(); + } + else if ( !Q_strcasecmp( command, "savevmf" ) ) + { + OnSaveVMF(); + } + else if ( !Q_strcasecmp( command, "clearform" ) ) + { + OnClearForm(); + } + else if ( !Q_strcasecmp( command, "addfile" ) ) + { + OnIncludeFile(); + } + else if ( !Q_strcasecmp( command, "clearfiles" ) ) + { + OnClearIncludedFiles(); + } + else + { + BaseClass::OnCommand( command ); + } +} + +void CBugUIPanel::OnClearForm() +{ + WipeData(); + + m_pTitle->SetText( "" ); + m_pDescription->SetText( "" ); + + m_pAssignTo->ActivateItem( 0 ); + m_pSeverity->ActivateItem( 0 ); + m_pReportType->ActivateItem( 0 ); + m_pPriority->ActivateItem( 2 ); + m_pGameArea->ActivateItem( 0 ); + m_pMapNumber->ActivateItem( 0 ); +} + +void CBugUIPanel::DetermineSubmitterName() +{ + if ( !m_pBugReporter ) + return; + + if ( m_pBugReporter->IsPublicUI() ) + { + m_pSubmitter->SetText( "PublicUser" ); + m_bCanSeeRepository = true; + m_bCanSubmit = true; + } + else + { + Color clr( 100, 200, 255, 255 ); + + const char *pUserName = m_pBugReporter->GetUserName(); + const char *pUserDisplayName = m_pBugReporter->GetUserName_Display(); + + if ( pUserName && pUserName[0] && pUserDisplayName && pUserDisplayName[0] ) + { + ConColorMsg( clr, "Username '%s' -- '%s'\n", pUserName, pUserDisplayName ); + } + else if ( cl.IsActive() ) + { + m_bUseNameForSubmitter = true; + pUserDisplayName = cl_name.GetString(); + ConColorMsg( clr, "Using '%s' as bug submission name.\n", pUserDisplayName ); + } + else + { + ConColorMsg( clr, "Failed to determine bug submission name.\n" ); + } + + // See if we can see the bug repository right now + char fn[ 512 ]; + Q_snprintf( fn, sizeof( fn ), "%s/%s", GetRepositoryURL(), REPOSITORY_VALIDATION_FILE ); + Q_FixSlashes( fn ); + + FILE *fp = fopen( fn, "rb" ); + if ( fp ) + { + ConColorMsg( clr, "Bug Repository '%s'\n", GetRepositoryURL() ); + fclose( fp ); + + m_bCanSeeRepository = true; + } + else + { + Warning( "Unable to see '%s', check permissions and network connectivity\n", fn ); + m_bCanSubmit = false; + } + + m_pSubmitter->SetText( pUserDisplayName ); + } +} + +void CBugUIPanel::PopulateControls() +{ + if ( !m_pBugReporter ) + return; + + m_pAssignTo->DeleteAllItems(); + int i; + int c = m_pBugReporter->GetDisplayNameCount(); + int defitem = 0; + for ( i = 0; i < c; i++ ) + { + char const *name = m_pBugReporter->GetDisplayName( i ); + if (!V_strcasecmp(name, "Triage")) + defitem = i; + m_pAssignTo->AddItem(name , NULL ); + } + m_pAssignTo->ActivateItem( defitem ); + + defitem = 0; + m_pSeverity->DeleteAllItems(); + c = m_pBugReporter->GetSeverityCount(); + for ( i = 0; i < c; i++ ) + { + char const *severity = m_pBugReporter->GetSeverity( i ); + if (!V_strcasecmp(severity, "Zero")) + defitem = i; + m_pSeverity->AddItem( severity, NULL ); + } + m_pSeverity->ActivateItem( defitem ); + + m_pReportType->DeleteAllItems(); + c = m_pBugReporter->GetReportTypeCount(); + for ( i = 0; i < c; i++ ) + { + m_pReportType->AddItem( m_pBugReporter->GetReportType( i ), NULL ); + } + m_pReportType->ActivateItem( 0 ); + + m_pPriority->DeleteAllItems(); + c = m_pBugReporter->GetPriorityCount(); + for ( i = 0; i < c; i++ ) + { + m_pPriority->AddItem( m_pBugReporter->GetPriority( i ), NULL ); + } + m_pPriority->ActivateItem( 2 ); + + m_pGameArea->DeleteAllItems(); + c = m_pBugReporter->GetAreaCount(); + for ( i = 0; i < c; i++ ) + { + m_pGameArea->AddItem( m_pBugReporter->GetArea( i ), NULL ); + } + + int area_index = GetArea(); + m_pGameArea->ActivateItem( area_index ); +} + +char const *CBugUIPanel::GetSubmitter() +{ + if ( !m_bCanSubmit ) + return ""; + return m_pBugReporter->GetUserName(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBugUIPanel::DenySound() +{ + Cbuf_AddText( va( "play %s\n", DENY_SOUND ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBugUIPanel::SuccessSound( int bugId ) +{ + Color clr( 50, 255, 100, 255 ); + + if ( m_pBugReporter && m_pBugReporter->IsPublicUI() ) + { + ConColorMsg( clr, "Bug submission succeeded\n" ); + } + else + { + ConColorMsg( clr, "Bug submission succeeded for bug (%i)\n", bugId ); + } + Cbuf_AddText( va( "play %s\n", SUCCEED_SOUND ) ); +} + +void CBugUIPanel::OnKeyCodePressed(KeyCode code) +{ + if ( code == KEY_ESCAPE || + GetBaseButtonCode( code ) == KEY_XBUTTON_B ) + { + Close(); + WipeData(); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + + +// Load game-specific bug reporter defaults as params +void CBugUIPanel::ParseDefaultParams( void ) +{ + const char *szDefaults = "scripts/bugreporter_defaults.txt"; + + FileHandle_t hLocal = g_pFileSystem->Open( szDefaults, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE == hLocal ) + { + return; + } + + // load file into a null-terminated buffer + int fileSize = g_pFileSystem->Size(hLocal); + char *buffer = (char*)MemAllocScratch(fileSize + 1); + + Assert(buffer); + + g_pFileSystem->Read(buffer, fileSize, hLocal); // read into local buffer + buffer[fileSize] = 0; // null terminate file as EOF + g_pFileSystem->Close( hLocal ); // close file after reading + + char token[64]; + const char *pfile = COM_ParseFile(buffer, token, sizeof( token ) ); + + while ( pfile ) + { + bool success = AutoFillToken( token, false ); + if ( !success ) + { + // Try partials + success = AutoFillToken( token, true ); + if ( !success ) + { + Msg( "Unable to determine where to set default bug parameter '%s', ignoring...\n", token ); + } + } + + pfile = COM_ParseFile(pfile, token, sizeof( token ) ); + } +} + +void CBugUIPanel::ParseCommands( const CCommand &args ) +{ + if ( !m_bCanSubmit ) + return; + + for ( int i = 1; i < args.ArgC(); i++ ) + { + bool success = AutoFillToken( args[ i ], false ); + if ( !success ) + { + // Try partials + success = AutoFillToken( args[ i ], true ); + if ( !success ) + { + Msg( "Unable to determine where to set bug parameter '%s', ignoring...\n", args[ i ] ); + } + } + } +} + +bool CBugUIPanel::Compare( char const *value, char const *token, bool partial ) +{ + if ( !partial ) + { + if ( !Q_stricmp( value, token ) ) + return true; + } + else + { + if ( Q_stristr( value, token ) ) + { + return true; + } + } + + return false; +} + + +bool CBugUIPanel::AutoFillToken( char const *token, bool partial ) +{ + // Search for token in all dropdown lists and fill it in if we find it + if ( !m_pBugReporter ) + return true; + + int i; + int c; + + c = m_pBugReporter->GetDisplayNameCount(); + for ( i = 0; i < c; i++ ) + { + if ( Compare( m_pBugReporter->GetDisplayName( i ), token, partial ) ) + { + m_pAssignTo->ActivateItem( i ); + return true; + } + } + + c = m_pBugReporter->GetSeverityCount(); + for ( i = 0; i < c; i++ ) + { + if ( Compare( m_pBugReporter->GetSeverity( i ), token, partial ) ) + { + m_pSeverity->ActivateItem( i ); + return true; + } + } + + c = m_pBugReporter->GetReportTypeCount(); + for ( i = 0; i < c; i++ ) + { + if ( Compare( m_pBugReporter->GetReportType( i ), token, partial ) ) + { + m_pReportType->ActivateItem( i ); + return true; + } + } + + c = m_pBugReporter->GetPriorityCount(); + for ( i = 0; i < c; i++ ) + { + if ( Compare( m_pBugReporter->GetPriority( i ), token, partial ) ) + { + m_pPriority->ActivateItem( i ); + return true; + } + } + + c = m_pBugReporter->GetAreaCount(); + for ( i = 0; i < c; i++ ) + { + if ( Compare( m_pBugReporter->GetArea( i ), token, partial ) ) + { + m_pGameArea->ActivateItem( i ); + return true; + } + } + + if ( !Q_stricmp( token, "screenshot" ) ) + { + m_fAutoAddScreenshot = eAutoAddScreenshot_Add; + return true; + } + + if ( !Q_stricmp( token, "noscreenshot" ) ) + { + m_fAutoAddScreenshot = eAutoAddScreenshot_DontAdd; + return true; + } + + return false; +} + +void CBugUIPanel::CheckContinueQueryingSteamForCSERList() +{ + if ( !m_bQueryingSteamForCSER || !Steam3Client().SteamUtils() ) + { + return; + } + + uint32 unIP; + uint16 usPort; + Steam3Client().SteamUtils()->GetCSERIPPort( &unIP, &usPort ); + if ( unIP ) + { + m_cserIP.SetIPAndPort( unIP, usPort ); + m_bQueryingSteamForCSER = false; + } +} + +static CBugUIPanel *g_pBugUI = NULL; + +class CEngineBugReporter : public IEngineBugReporter +{ +public: + virtual void Init( void ); + virtual void Shutdown( void ); + + virtual void InstallBugReportingUI( vgui::Panel *parent, IEngineBugReporter::BR_TYPE type ); + virtual bool ShouldPause() const; + + virtual bool IsVisible() const; //< true iff the bug panel is active and on screen right now + + void Restart( IEngineBugReporter::BR_TYPE type ); +private: + PHandle m_ParentPanel; +}; + +static CEngineBugReporter g_BugReporter; +IEngineBugReporter *bugreporter = &g_BugReporter; + +CON_COMMAND( _bugreporter_restart, "Restarts bug reporter .dll" ) +{ + if ( args.ArgC() <= 1 ) + { + Msg( "__bugreporter_restart <internal | external | autoselect>\n" ); + return; + } + + IEngineBugReporter::BR_TYPE type = IEngineBugReporter::BR_PUBLIC; + + if ( !Q_stricmp( args.Arg( 1 ), "internal" ) ) + { + type = IEngineBugReporter::BR_INTERNAL; + } + else if ( !Q_stricmp( args.Arg( 1 ), "autoselect" ) ) + { + type = IEngineBugReporter::BR_AUTOSELECT; + } + + g_BugReporter.Restart( type ); +} + +void CEngineBugReporter::Init( void ) +{ + m_ParentPanel = 0; +} + +void CEngineBugReporter::Shutdown( void ) +{ + if ( g_pBugUI ) + { + g_pBugUI->Shutdown(); + g_pBugUI = NULL; + } +} + +void CEngineBugReporter::InstallBugReportingUI( vgui::Panel *parent, IEngineBugReporter::BR_TYPE type ) +{ + if ( g_pBugUI ) + return; + + bool bIsPublic = true; + + char fn[ 512 ]; + + Q_snprintf( fn, sizeof( fn ), "%s%s", GetInternalBugReporterDLL(), DLL_EXT_STRING ); + + bool bCanUseInternal = g_pFileSystem->FileExists( fn, "EXECUTABLE_PATH" ); + + switch ( type ) + { + case IEngineBugReporter::BR_PUBLIC: + // Nothing + break; + default: + case IEngineBugReporter::BR_AUTOSELECT: + { + // check + bIsPublic = phonehome->IsExternalBuild() ? true : false; + if ( bCanUseInternal ) + { + // if command line param specifies internal, use that + if ( CommandLine()->FindParm( "-internalbuild" ) ) + { + bIsPublic = false; + } +#if !defined( _X360 ) + // otherwise, if Steam is running and connected to beta, autoselect the internal bug db + else if ( k_EUniverseBeta == GetSteamUniverse() ) + { + bIsPublic = false; + } +#endif + } + } + break; + + case IEngineBugReporter::BR_INTERNAL: + { + if ( bCanUseInternal ) + { + bIsPublic = false; + } + } + break; + } + + g_pBugUI = new CBugUIPanel( bIsPublic, parent ); + Assert( g_pBugUI ); + + m_ParentPanel = parent; +} + +void CEngineBugReporter::Restart( IEngineBugReporter::BR_TYPE type ) +{ + Shutdown(); + Msg( "Changing to bugreporter(%s)\n", + ( type == IEngineBugReporter::BR_AUTOSELECT ) ? + ( "autoselect" ) : + ( ( type == IEngineBugReporter::BR_PUBLIC ) ? + "public" : + "valve" ) ); + InstallBugReportingUI( m_ParentPanel, type ); +} + +bool CEngineBugReporter::ShouldPause() const +{ + return g_pBugUI && ( g_pBugUI->IsVisible() || g_pBugUI->IsTakingSnapshot() ) && (cl.m_nMaxClients == 1); +} + + +bool CEngineBugReporter::IsVisible() const +{ + return g_pBugUI && g_pBugUI->IsVisible(); +} + +CON_COMMAND_F( bug, "Show/hide the bug reporting UI.", FCVAR_DONTRECORD ) +{ + if ( !g_pBugUI ) + return; + + bool bWasVisible = g_pBugUI->IsVisible(); + + if ( bWasVisible ) + { + // hide + g_pBugUI->Close(); + } + + // make sure the gameUI is open so the bugreporter is visible + EngineVGui()->ActivateGameUI(); + + g_pBugUI->Activate(); + + + g_pBugUI->ParseDefaultParams(); + if (args.ArgC() > 1 ) + { + g_pBugUI->ParseCommands( args ); + } +} + + +int CBugUIPanel::GetArea() +{ + char mapname[256] = ""; + int iNewTitleLength = 80; + + if ( host_state.worldmodel ) + { + CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) ); + iNewTitleLength = (80 - (strlen( mapname )+2)); + } + m_pTitle->SetMaximumCharCount( iNewTitleLength ); + + char *gamedir = com_gamedir; + gamedir = Q_strrchr( gamedir, CORRECT_PATH_SEPARATOR ) + 1; + + for ( int i = 0; i < m_pBugReporter->GetAreaMapCount(); i++ ) + { + char szAreaMap[MAX_PATH]; + V_strcpy_safe( szAreaMap, m_pBugReporter->GetAreaMap( i ) ); + char *pszAreaDir = Q_strrchr( szAreaMap, '@' ); + char *pszAreaPrefix = Q_strrchr( szAreaMap, '%' ); + int iDirLength = 0; + if ( pszAreaDir && pszAreaPrefix ) + { + iDirLength = pszAreaPrefix - pszAreaDir - 1; + pszAreaDir++; + pszAreaPrefix++; + } + else if ( pszAreaDir && !pszAreaPrefix ) + { + pszAreaDir++; + iDirLength = Q_strlen( szAreaMap ) - (pszAreaDir - szAreaMap); + } + else + { + return 0; + } + + char szDirectory[MAX_PATH]; + Q_memmove( szDirectory, pszAreaDir, iDirLength ); + szDirectory[iDirLength] = 0; + + if ( pszAreaDir && pszAreaPrefix ) + { + if ( !Q_strcmp( szDirectory, gamedir) + && Q_strstr( mapname, pszAreaPrefix ) ) + { + return i+1; + } + } + else if ( pszAreaDir && !pszAreaPrefix ) + { + if ( !Q_strcmp( szDirectory, gamedir ) ) + { + return i+1; + } + } + } + return 0; +} |