2858 lines
76 KiB
C++
2858 lines
76 KiB
C++
#include "debug/bugstar/BugstarIntegrationSwitch.h"
|
|
|
|
#if(BUGSTAR_INTEGRATION_ACTIVE)
|
|
// bugstar headers
|
|
#include "BugstarIntegration.h"
|
|
#include "debug/bugstar/MissionLookup.h"
|
|
|
|
// rage headers
|
|
#include "atl/guid.h"
|
|
#include "file/remote.h"
|
|
#include "parsercore/streamxml.h"
|
|
#include "string/stringutil.h"
|
|
#include "streaming/streamingallocator.h"
|
|
#include "system/bootmgr.h"
|
|
#include "system/exception.h"
|
|
|
|
// Game headers
|
|
#include "debug/Debug.h"
|
|
#include "camera/Viewports/Viewport.h"
|
|
#include "camera/Viewports/ViewportManager.h"
|
|
#include "debug/BlockView.h"
|
|
#include "peds/ped.h"
|
|
#include "peds/pedfactory.h"
|
|
#include "physics/physics.h"
|
|
#include "scene/world/gameworld.h"
|
|
#include "scene/streamer/SceneStreamerMgr.h"
|
|
#include "vehicles/vehicle.h"
|
|
#include "vehicles/vehiclefactory.h"
|
|
#include "objects/DummyObject.h"
|
|
#include "parser/manager.h" // XML
|
|
#include "parser/tree.h"
|
|
#include "system/exec.h"
|
|
#include "system/nelem.h"
|
|
#include "grcore/device.h"
|
|
#include "grcore/light.h"
|
|
#include "grcore/stateblock.h"
|
|
#include "qa/bugstarinterface.h"
|
|
|
|
#define BUGSTAR_IP_ADDRESS "rest.bugstar.rockstargames.com:8443"
|
|
|
|
#define BUGSTAR_DEFAULT_ART_TEAM "*Default Art*"
|
|
#define BUGSTAR_DEFAULT_AUDIO_TEAM "*Default Audio Code*"
|
|
#define BUGSTAR_DEFAULT_LEVELS_TEAM "*Default Levels*"
|
|
#define BUGSTAR_DEFAULT_AI_CODE_TEAM "*Default AI Code*"
|
|
#define BUGSTAR_DEFAULT_CODE_TEAM "*Default Code*"
|
|
|
|
// Full list at:-
|
|
// https://rsgedibgs1.rockstar.t2.corp:8443/BugstarRestService-1.0/rest/Projects/1546/States
|
|
// To add new ones, add a new member
|
|
CBugstarIntegration::StatusFilter CBugstarIntegration::m_StatusFilters[] =
|
|
{
|
|
{"Dev (Fixing)", true },
|
|
{"Dev (Fixed, Awaiting Build)", true },
|
|
{"Test (Verifying)", true },
|
|
{"Test (Not Seen)", true }
|
|
};
|
|
|
|
// Fill List at:-
|
|
// https://rsgedibgs8.rockstar.t2.corp:8443/BugstarRestService-1.0/rest/Projects/1546/Components
|
|
// (as a tree)
|
|
// To add new ones, add a new member
|
|
CBugstarIntegration::ComponentFilter CBugstarIntegration::m_ComponentFilters[] =
|
|
{
|
|
{"Animation", true },
|
|
{"Audio", true },
|
|
{"Art", true },
|
|
{"Art : Collision", true }, // Art ones copied from an email, if they're miss-spelled it'll go wrong
|
|
{"Art : General", true },
|
|
{"Art : Geometry", true },
|
|
{"Art : Interior", true },
|
|
{"Art : Legal", true },
|
|
{"Art : Lighting", true },
|
|
{"Art : LOD", true },
|
|
{"Art : Maps : Structures", true },
|
|
{"Art : Maps : Terrain", true },
|
|
{"Art : Positioning", true },
|
|
{"Art : Props", true },
|
|
{"Art : Seam", true },
|
|
{"Art : Texture", true },
|
|
{"Art : Timeline", true },
|
|
{"Art : VFX", true },
|
|
{"Art : Z-fighting", true },
|
|
{"Character", true },
|
|
{"Code", true },
|
|
{"Interface", true },
|
|
{"Road", true },
|
|
{"Script", true },
|
|
{"Slowdown", true },
|
|
{"Social Club", true },
|
|
{"Streaming", true },
|
|
{"Text", true },
|
|
{"Tools", true },
|
|
{"TRC", true },
|
|
{"Vehicles", true }
|
|
};
|
|
|
|
XPARAM(bugstarusername);
|
|
|
|
PARAM(SetBugstarProduct, "Force use of specified bugstar database");
|
|
PARAM(attachConsoleLogsToBugsInMP, "Attach console log file(s) in MP");
|
|
PARAM(forceRenewBugstarRequests, "Forces retrieving requests from bugstar rather than using cached files");
|
|
PARAM(bugstarStreamingVideo, "[bugstar] Initialize bugstar console streaming video. If a parameter is specified it uses that as the url rather than the default");
|
|
|
|
bool CBugstarIntegration::m_FilterByCurrentUser = false;
|
|
atHashString CBugstarIntegration::m_CurrentUsername;
|
|
s32 CBugstarIntegration::m_CurrentUserID = 0;
|
|
|
|
char CBugstarIntegration::m_sAreaCenter[MAX_COORDINATE_STRING_LENGTH];
|
|
float CBugstarIntegration::m_fAreaSize = 50.0;
|
|
float CBugstarIntegration::m_fBugMarkerSize = 1.0;
|
|
bool CBugstarIntegration::m_bBugMarkersOn = true;
|
|
bool CBugstarIntegration::m_bBugClassFilters[CMinimalBug::kBugCategoryNum] = { true, true, true, true, true, true, true };
|
|
char CBugstarIntegration::m_sFetchBugNumber[BUG_NUMBER_STRING_LENGTH];
|
|
char CBugstarIntegration::m_sUsername[BUG_LOGIN_STRING_LENGTH]="gamestats";
|
|
char CBugstarIntegration::m_sPassword[BUG_LOGIN_STRING_LENGTH]="game5tats";
|
|
char CBugstarIntegration::m_sAssertUsername[BUG_LOGIN_STRING_LENGTH]="gameasserts";
|
|
char CBugstarIntegration::m_sAssertPassword[BUG_LOGIN_STRING_LENGTH]="gamea$$ert5";
|
|
char CBugstarIntegration::m_sUsernameVis[BUG_LOGIN_STRING_LENGTH];
|
|
char CBugstarIntegration::m_sPasswordVis[BUG_LOGIN_STRING_LENGTH];
|
|
bool CBugstarIntegration::m_bProcessScreenshot = false;
|
|
int CBugstarIntegration::m_iScreenshotFrameDelay = 0;
|
|
|
|
//bug display
|
|
int CBugstarIntegration::m_iSelectedBugIndex = 0;
|
|
char CBugstarIntegration::m_sBugNumber[BUG_NUMBER_STRING_LENGTH];
|
|
char CBugstarIntegration::m_sBugSummary[BUG_SUMMARY_STRING_LENGTH];
|
|
|
|
int CBugstarIntegration::sm_currentUserId = -1;
|
|
atMap<atHashString, int> CBugstarIntegration::sm_teamIdMap;
|
|
|
|
atArray<CMinimalBug> CBugstarIntegration::m_Buglist;
|
|
atArray<CMinimalBug*> CBugstarIntegration::m_FilteredBugList;
|
|
CSavedBugstarRequestList CBugstarIntegration::m_savedBugstarRequestList;
|
|
CBugstarAssetLookup CBugstarIntegration::sm_buildLookup;
|
|
CBugstarAssetLookup CBugstarIntegration::sm_mapLookup;
|
|
char CBugstarIntegration::sm_streamingVideoId[128];
|
|
char CBugstarIntegration::sm_streamingVideoBaseUri[128];
|
|
char const* CBugstarIntegration::sm_streamingVideoRootDirectory = "x:\\BugstarStreamingVideo";
|
|
char const* CBugstarIntegration::sm_streamingVideoErrorMessage = nullptr;
|
|
bool CBugstarIntegration::sm_isStreamingVideoEnabled = false;
|
|
|
|
sysMessageQueue<CBugstarAssertUpdateQuery*, MAX_ASSERT_QUERIES> CBugstarAssertUpdateQuery::sm_messageQueue;
|
|
atMap<int, bool> CBugstarAssertUpdateQuery::sm_assertIdAlreadyAdded;
|
|
bool CBugstarAssertUpdateQuery::sm_BugstarWorking = true;
|
|
|
|
#define BUGSTAR_REST_SERVER "https://rest.bugstar.rockstargames.com:8443/BugstarRestService-1.0/rest/"
|
|
#define BUGSTAR_LOG_DIRECTORY "N:\\RSGEDI\\GameLogs\\GTA V\\"
|
|
|
|
static const char* const gDxDiagLogDirectory = "\\\\rockstar.t2.corp\\Network\\RSGEDI\\GameLogs\\GTA V\\";
|
|
static const char* const gDxDiagLogURL = "file://rockstar.t2.corp/Network/RSGEDI/GameLogs/GTA%20V/";
|
|
|
|
//Reduced from 60, use a lower timeout to avoid holding up queued http requests for too long
|
|
const static unsigned s_BugstarRequestDefaultTimeout = 30;
|
|
|
|
// ----- CSavedBugstarRequestList --------------------------------------------------------------
|
|
|
|
// I've given up trying to make this accurate as it doesnt matter that much
|
|
int GetTimeDifferenceInMinutes(fiDevice::SystemTime time1, fiDevice::SystemTime time2)
|
|
{
|
|
u32 minutes1 = time1.wMinute+(time1.wHour+(time1.wDay+(time1.wMonth+(time1.wYear-2005)*12)*31)*24)*60;
|
|
u32 minutes2 = time2.wMinute+(time2.wHour+(time2.wDay+(time2.wMonth+(time2.wYear-2005)*12)*31)*24)*60;
|
|
|
|
return minutes2 - minutes1;
|
|
}
|
|
|
|
static void GetLocalFilename(const char* pFilename, char fullFilename[RAGE_MAX_PATH])
|
|
{
|
|
// save file to local machine
|
|
sprintf(fullFilename, "common:/data/debug/%s", pFilename);
|
|
}
|
|
#define DATETEST_FILENAME "datetest.txt"
|
|
|
|
void CSavedBugstarRequestList::Init()
|
|
{
|
|
// given up trying to compare system time and file time. Now I'm going to create a file on the same filesystem and compare times
|
|
if (!sysBootManager::IsPackagedBuild())
|
|
{
|
|
char filename[RAGE_MAX_PATH];
|
|
GetLocalFilename(DATETEST_FILENAME, filename);
|
|
fiStream* pStream = ASSET.Create(filename, "");
|
|
if (pStream)
|
|
pStream->Close();
|
|
}
|
|
}
|
|
|
|
parTree* CSavedBugstarRequestList::LoadBugstarRequest(const char* pLocalFilename)
|
|
{
|
|
char filename[RAGE_MAX_PATH];
|
|
GetLocalFilename(pLocalFilename, filename);
|
|
|
|
fiStream* pStream = ASSET.Open(filename, "");
|
|
parTree* pTree = NULL;
|
|
if(pStream)
|
|
{
|
|
pTree = PARSER.LoadTree(pStream);
|
|
pStream->Close();
|
|
}
|
|
|
|
return pTree;
|
|
}
|
|
|
|
void CSavedBugstarRequestList::Get(const char* pLocalFilename, const char* pRequest, int renewTimeInMinutes, int bufferSize)
|
|
{
|
|
char filename[RAGE_MAX_PATH];
|
|
char datetestFilename[RAGE_MAX_PATH];
|
|
GetLocalFilename(pLocalFilename, filename);
|
|
GetLocalFilename(DATETEST_FILENAME, datetestFilename);
|
|
|
|
// check filetimes to see if we need to renew this load
|
|
const fiDevice* pDevice = fiDevice::GetDevice(filename);
|
|
|
|
if (!pDevice)
|
|
{
|
|
// Can't get a device for this, so the file won't exist either - let's forget about it.
|
|
return;
|
|
}
|
|
|
|
u64 fileTime64 = pDevice->GetFileTime(filename);
|
|
u64 dateTestFileTime64 = pDevice->GetFileTime(datetestFilename);
|
|
u64 diff = dateTestFileTime64 - fileTime64;
|
|
|
|
// convert to minutes
|
|
diff /= 600000000LL;
|
|
|
|
/* fiDevice::SystemTime fileTime;
|
|
fiDevice::SystemTime systemTime;
|
|
|
|
fiDevice::ConvertFileTimeToSystemTime(fileTime64, fileTime);
|
|
// Horrible cludge to get round the fact PS3 return UTC based filetimes
|
|
#if __PS3
|
|
CellRtcTick utcTick;
|
|
CellRtcTick localTick;
|
|
utcTick.tick = fileTime64 / 10LL; // divide by 10 to convert from win32 time to cell ticks
|
|
|
|
cellRtcConvertUtcToLocalTime(&utcTick, &localTick);
|
|
fileTime64 = localTick.tick * 10LL;
|
|
#endif
|
|
fiDevice::ConvertFileTimeToSystemTime(fileTime64, fileTime);
|
|
fiDevice::GetLocalSystemTime(systemTime);
|
|
int diff = GetTimeDifferenceInMinutes(fileTime, systemTime);*/
|
|
|
|
if(renewTimeInMinutes < diff
|
|
|| PARAM_forceRenewBugstarRequests.Get())
|
|
{
|
|
Displayf("File %s is %d minutes old , so resubmitting", filename, (int)diff);
|
|
CHttpQuerySaveToFile* pQuery = CBugstarIntegration::CreateSaveHttpQuery(filename);
|
|
pQuery->Init(pRequest, s_BugstarRequestDefaultTimeout, bufferSize);
|
|
}
|
|
}
|
|
|
|
void CSavedBugstarRequestList::PostXml(const char* pLocalFilename, const char* pRequest, const char* pXml, int xmlLength, int renewTimeInMinutes, int bufferSize)
|
|
{
|
|
char filename[RAGE_MAX_PATH];
|
|
GetLocalFilename(pLocalFilename, filename);
|
|
|
|
// check filetimes to see if we need to renew this load
|
|
const fiDevice* pDevice = fiDevice::GetDevice(filename);
|
|
u64 fileTime64 = pDevice->GetFileTime(filename);
|
|
fiDevice::SystemTime fileTime;
|
|
fiDevice::SystemTime systemTime;
|
|
fiDevice::ConvertFileTimeToSystemTime(fileTime64, fileTime);
|
|
fiDevice::GetLocalSystemTime(systemTime);
|
|
|
|
int diff = GetTimeDifferenceInMinutes(fileTime, systemTime);
|
|
|
|
if(renewTimeInMinutes < diff
|
|
|| PARAM_forceRenewBugstarRequests.Get())
|
|
{
|
|
Displayf("File %s is %d minutes old , so resubmitting", filename, diff);
|
|
CHttpQuerySaveToFile* pQuery = CBugstarIntegration::CreateSaveHttpQuery(filename);
|
|
pQuery->InitPost(pRequest, s_BugstarRequestDefaultTimeout, bufferSize);
|
|
pQuery->AppendXmlContent(pXml, xmlLength);
|
|
pQuery->CommitPost();
|
|
}
|
|
}
|
|
|
|
// ----- CBugstarAssertUpdateQuery -------------------------------------------------------------
|
|
|
|
CBugstarAssertUpdateQuery::CBugstarAssertUpdateQuery(const char* errorMsg, const char* detailedErrorMsg, const char* callstack, const char* gamerTag, const BugDesc& bug, bool showBug)
|
|
: m_errorMsg(errorMsg),
|
|
m_detailedErrorMsg(detailedErrorMsg),
|
|
m_callstack(callstack),
|
|
m_gamerTag(gamerTag),
|
|
m_bugDesc(bug),
|
|
m_showBug(showBug),
|
|
m_pNextAssert(NULL)
|
|
{
|
|
}
|
|
|
|
CBugstarAssertUpdateQuery::~CBugstarAssertUpdateQuery()
|
|
{
|
|
if(m_bugDesc.logBuffer)
|
|
delete[] m_bugDesc.logBuffer;
|
|
if(m_pNextAssert)
|
|
delete m_pNextAssert;
|
|
|
|
#if __WIN32PC
|
|
if(m_bugDesc.dxDiagBuffer)
|
|
delete[] m_bugDesc.dxDiagBuffer;
|
|
#endif
|
|
}
|
|
|
|
void CBugstarAssertUpdateQuery::AddQueryToQueue()
|
|
{
|
|
sm_messageQueue.Push(this);
|
|
}
|
|
|
|
void CBugstarAssertUpdateQuery::CreateBugDescription(char* pBuffer, int bufferSize, const char* logName)
|
|
{
|
|
if(m_pNextAssert == NULL)
|
|
{
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
formatf(pBuffer, bufferSize, "GamerTag: %s\n\nLog: %s\nSteps:\n\nOutcome:\n%s\n%s", m_gamerTag.c_str(), logName, m_detailedErrorMsg.c_str(), m_callstack.c_str());
|
|
}
|
|
else
|
|
{
|
|
formatf(pBuffer, bufferSize, "Log: %s\nSteps:\n\nOutcome:\n%s\n%s", logName, m_detailedErrorMsg.c_str(), m_callstack.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
formatf(pBuffer, bufferSize, "GamerTag: %s\n\nLog: %s\nSteps:\n\nOutcome:\nMultiple Asserts\n%s\n%s", m_gamerTag.c_str(), logName, m_detailedErrorMsg.c_str(), m_callstack.c_str());
|
|
}
|
|
else
|
|
{
|
|
formatf(pBuffer, bufferSize, "Log: %s\nSteps:\n\nOutcome:\nMultiple Asserts\n%s\n%s", logName, m_detailedErrorMsg.c_str(), m_callstack.c_str());
|
|
}
|
|
|
|
CBugstarAssertUpdateQuery* pQuery = m_pNextAssert;
|
|
while(pQuery)
|
|
{
|
|
// add additional callstacks
|
|
safecatf(pBuffer, bufferSize, "\n%s\n%s", pQuery->m_detailedErrorMsg.c_str(), pQuery->m_callstack.c_str());
|
|
pQuery = pQuery->m_pNextAssert;
|
|
}
|
|
}
|
|
}
|
|
|
|
int CBugstarAssertUpdateQuery::AddBug()
|
|
{
|
|
// Construct description
|
|
char logComment[128]="";
|
|
char bugDescription[8192];
|
|
CreateBugDescription(bugDescription, sizeof(bugDescription), logComment);
|
|
|
|
// No bugs exist yet, create a new one
|
|
Displayf("Adding bug %d", m_bugDesc.assertId);
|
|
int bugId = CBugstarIntegration::CreateNewAssertBug(m_errorMsg, bugDescription, m_bugDesc);
|
|
|
|
GetSubsequentBugs();
|
|
|
|
// The last bug in the list is the one we take the log from
|
|
CBugstarAssertUpdateQuery* pLogQuery = this;
|
|
while(pLogQuery->m_pNextAssert)
|
|
{
|
|
pLogQuery = pLogQuery->m_pNextAssert;
|
|
}
|
|
|
|
if(bugId != 0)
|
|
{
|
|
bool rebuildBugDescription = false;
|
|
if(pLogQuery->m_bugDesc.logBuffer)
|
|
{
|
|
char logFilename[128];
|
|
// saving log file to network
|
|
formatf(logFilename, sizeof(logFilename), "%s%d/assert.log", BUGSTAR_LOG_DIRECTORY, bugId);
|
|
// commented out until we can get the path into the bug easily
|
|
ASSET.CreateLeadingPath(logFilename);
|
|
fiStream* pStream = ASSET.Create(logFilename, "");
|
|
if(pStream)
|
|
{
|
|
pStream->Write(pLogQuery->m_bugDesc.logBuffer, pLogQuery->m_bugDesc.logSize);
|
|
pStream->Close();
|
|
|
|
formatf(logComment, sizeof(logComment), "\"%s\"", logFilename);
|
|
rebuildBugDescription = true;
|
|
}
|
|
}
|
|
|
|
// if multiple asserts
|
|
if(m_pNextAssert)
|
|
rebuildBugDescription = true;
|
|
|
|
if(rebuildBugDescription)
|
|
{
|
|
CreateBugDescription(bugDescription, sizeof(bugDescription), logComment);
|
|
CBugstarIntegration::ChangeField(bugId, "description", bugDescription);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#if __WIN32PC
|
|
if (bugId != 0 && m_bugDesc.dxDiagBuffer)
|
|
{
|
|
if (WriteDxDiagLog(bugId, 1))
|
|
{
|
|
char logComment[256];
|
|
|
|
Assert(strlen(gDxDiagLogDirectory) + strlen(gDxDiagLogURL) + 21 < 256); // +20 for 2 * digits in MAX_INT32, +1 for \0
|
|
|
|
sprintf(logComment, "DxDiag logs are in: \"%s%d\"\n%s%d", gDxDiagLogDirectory, bugId, gDxDiagLogURL, bugId);
|
|
|
|
CBugstarIntegration::AddComment(bugId, logComment);
|
|
}
|
|
}
|
|
#endif
|
|
return bugId;
|
|
}
|
|
|
|
void CBugstarAssertUpdateQuery::Execute()
|
|
{
|
|
bool bAlreadyAdded = false;
|
|
int bugId = 0;
|
|
int bugCount = 0;
|
|
|
|
// If the game fails to connect to bugstar it stops adding bugs
|
|
if(!sm_BugstarWorking)
|
|
return;
|
|
|
|
USE_DEBUG_MEMORY();
|
|
|
|
// If already added flag as already added and don't try and create a new bug
|
|
if(sm_assertIdAlreadyAdded.Access(m_bugDesc.assertId))
|
|
{
|
|
Displayf("bug %d already added", m_bugDesc.assertId);
|
|
bAlreadyAdded = true;
|
|
}
|
|
// set as already added
|
|
sm_assertIdAlreadyAdded.Insert(m_bugDesc.assertId, true);
|
|
|
|
// only get bug count if we havent already added it
|
|
if(!bAlreadyAdded)
|
|
bugCount = CBugstarIntegration::GetBugCountForAssertId(m_bugDesc.assertId);
|
|
|
|
// bug count of -1 indicates an error
|
|
if(bugCount == -1)
|
|
{
|
|
fiRemoteShowMessageBox("Assert Bot failed to communicate with bugstar, disabling the assert bot", sysParam::GetProgramName(), MB_OK, 0);
|
|
sm_BugstarWorking = false;
|
|
return;
|
|
}
|
|
else if (bugCount == 0 && !bAlreadyAdded)
|
|
{
|
|
bugId = AddBug();
|
|
if(bugId == 0)
|
|
{
|
|
fiRemoteShowMessageBox("Assert Bot failed to communicate with bugstar, disabling the assert bot", sysParam::GetProgramName(), MB_OK, 0);
|
|
sm_BugstarWorking = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Query bugstar for a list of the bugs that exist and check if there is one that isn't closed
|
|
rage::parTree* openBugTree = CBugstarIntegration::GetMostRecentOpenBug(m_bugDesc.assertId);
|
|
|
|
// clear out subsequent bugs so we aren't creating bugs for asserts we didnt before
|
|
GetSubsequentBugs();
|
|
|
|
if (openBugTree != NULL)
|
|
{
|
|
CMinimalBug bug;
|
|
CBugstarIntegration::ConvertToMinimalBug(openBugTree, bug);
|
|
bugId = bug.Number();
|
|
|
|
// Open bug exists, increment its Seen count
|
|
#if __WIN32PC
|
|
int count =
|
|
#endif
|
|
CBugstarIntegration::IncrementBugSeenCount(bugId, m_bugDesc.buildId, openBugTree);
|
|
|
|
#if __WIN32PC
|
|
WriteDxDiagLog(bugId, count);
|
|
#endif
|
|
|
|
delete openBugTree;
|
|
}
|
|
else if(!bAlreadyAdded)
|
|
{
|
|
bugId = AddBug();
|
|
}
|
|
}
|
|
|
|
// Check whether the user wishes to see the bug
|
|
if (bugId != 0 && m_showBug)
|
|
{
|
|
qaBug bug;
|
|
bug.SetID(bugId);
|
|
|
|
qaBugstarInterface BUGSTAR;
|
|
BUGSTAR.OpenBugForEdit(bug);
|
|
}
|
|
}
|
|
|
|
void CBugstarAssertUpdateQuery::GetSubsequentBugs()
|
|
{
|
|
CBugstarAssertUpdateQuery* pQuery = this;
|
|
CBugstarAssertUpdateQuery* pNextQuery;
|
|
while(sm_messageQueue.GetTail(pNextQuery))
|
|
{
|
|
if(pNextQuery->m_bugDesc.timeSinceLastBug >= 0.034f)
|
|
break;
|
|
pNextQuery = sm_messageQueue.Pop();
|
|
pQuery->SetNextAssert(pNextQuery);
|
|
pQuery = pNextQuery;
|
|
// If this bug has been set to shown then set parent bug to be shown
|
|
if(pQuery->m_showBug)
|
|
m_showBug = true;
|
|
}
|
|
}
|
|
|
|
#if __WIN32PC
|
|
bool CBugstarAssertUpdateQuery::WriteDxDiagLog(int bugId, int seen)
|
|
{
|
|
if (!m_bugDesc.dxDiagBuffer)
|
|
return false;
|
|
|
|
char logFilename[RAGE_MAX_PATH];
|
|
sprintf(logFilename, "%s%d/dxdiag-%04d.log", gDxDiagLogDirectory, bugId, seen);
|
|
ASSET.CreateLeadingPath(logFilename);
|
|
fiStream* pStream = ASSET.Create(logFilename, "");
|
|
if(pStream)
|
|
{
|
|
pStream->Write(m_bugDesc.dxDiagBuffer, m_bugDesc.dxDiagSize);
|
|
pStream->Close();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void CBugstarAssertUpdateQuery::InitAssertQueryQueue()
|
|
{
|
|
sysIpcCreateThread(&BugstarAssertUpdateThread, NULL, 40*1024, PRIO_NORMAL, "BugstarAssertLogging" );
|
|
}
|
|
|
|
void CBugstarAssertUpdateQuery::BugstarAssertUpdateThread(void*)
|
|
{
|
|
CBugstarAssertUpdateQuery* pQuery;
|
|
while( true )
|
|
{
|
|
pQuery = sm_messageQueue.Pop();
|
|
pQuery->Execute();
|
|
delete pQuery;
|
|
}
|
|
}
|
|
|
|
|
|
// ----- CBugstarIntegration -------------------------------------------------------------
|
|
//bool CreateGameData(const char *pGameName, const char* pGameVersion);
|
|
|
|
void CBugstarIntegration::Init( )
|
|
{
|
|
|
|
|
|
const char* pSetDbase = NULL;
|
|
PARAM_SetBugstarProduct.Get(pSetDbase);
|
|
|
|
if (pSetDbase)
|
|
{
|
|
qaBugstarInterface::SetProduct(pSetDbase);
|
|
}
|
|
else
|
|
{
|
|
qaBugstarInterface::SetProduct("GTA 5 DLC");
|
|
}
|
|
|
|
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
sysGetEnv("USERNAME",m_sUsernameVis,sizeof(m_sUsernameVis));
|
|
strcpy(m_sPasswordVis,"");
|
|
}
|
|
|
|
fwHttpQuery::InitHttpQueryQueue();
|
|
CBugstarAssertUpdateQuery::InitAssertQueryQueue();
|
|
|
|
CSavedBugstarRequestList().Init();
|
|
CMissionLookup::Init();
|
|
sm_mapLookup.Init(BUGSTAR_GTA_PROJECT_PATH "Maps", "maps.xml", "name");
|
|
sm_buildLookup.Init(BUGSTAR_GTA_PROJECT_PATH "Builds", "builds.xml", "buildId");
|
|
|
|
// Get the current username
|
|
const char* pUserName=NULL;
|
|
if(PARAM_bugstarusername.Get(pUserName))
|
|
{
|
|
m_CurrentUsername = pUserName;
|
|
}
|
|
else
|
|
{
|
|
char userName[64];
|
|
sysGetEnv("USERNAME",userName,64);
|
|
m_CurrentUsername = userName;
|
|
}
|
|
|
|
// Nonsense just to ensure that GetRockstarTargetManagerConfig is never dead stripped
|
|
if ((const char*(*)(void))pUserName == GetRockstarTargetManagerConfig)
|
|
{
|
|
m_CurrentUsername = (char*)GetRockstarTargetManagerConfig;
|
|
}
|
|
|
|
if (PARAM_bugstarStreamingVideo.Get())
|
|
{
|
|
InitStreamingVideo();
|
|
}
|
|
|
|
#if EXCEPTION_HANDLING && SYSTMCMD_ENABLE
|
|
sysException::SetBugstarConfig(GetRockstarTargetManagerConfig());
|
|
#endif
|
|
}
|
|
|
|
void CBugstarIntegration::Shutdown( )
|
|
{
|
|
CBugstarComponentLookup::Shutdown();
|
|
CBugstarStateLookup::Shutdown();
|
|
CBugstarUserLookup::Shutdown();
|
|
}
|
|
|
|
// Data for Rockstar Target Manager.
|
|
const char* CBugstarIntegration::GetRockstarTargetManagerConfig()
|
|
{
|
|
# define INCLUDE_ROOT_DIR (RSG_DURANGO || RSG_XENON)
|
|
# if INCLUDE_ROOT_DIR
|
|
# define INCLUDE_ROOT_DIR_ONLY(...) __VA_ARGS__
|
|
# else
|
|
# define INCLUDE_ROOT_DIR_ONLY(...)
|
|
# endif
|
|
static const char fmt[] =
|
|
"<bugstar_config>"
|
|
"<rest_server>" BUGSTAR_REST_SERVER "</rest_server>"
|
|
"<log_directory>" BUGSTAR_LOG_DIRECTORY "</log_directory>"
|
|
"<project_id>" BUGSTAR_GTA_PROJECT_ID "</project_id>"
|
|
"<build_id>%s</build_id>"
|
|
INCLUDE_ROOT_DIR_ONLY("<root_dir>%s</root_dir>")
|
|
"</bugstar_config>";
|
|
static char xml[sizeof(fmt)+128];
|
|
snprintf(xml, sizeof(xml), fmt, CDebug::GetVersionNumber()
|
|
INCLUDE_ROOT_DIR_ONLY(,CFileMgr::GetRootDirectory()));
|
|
Assert(strlen(xml) < sizeof(xml));
|
|
return xml;
|
|
# undef INCLUDE_ROOT_DIR_ONLY
|
|
# undef INCLUDE_ROOT_DIR
|
|
}
|
|
|
|
void CBugstarIntegration::ProcessScreenShot()
|
|
{
|
|
// Process screenshot requests
|
|
if (!m_bProcessScreenshot)
|
|
return;
|
|
|
|
if (m_iScreenshotFrameDelay > 0)
|
|
{
|
|
m_iScreenshotFrameDelay--;
|
|
return;
|
|
}
|
|
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
CMinimalBug* bug = list[m_iSelectedBugIndex];
|
|
const char *shotName = "X:/lastBug.jpg";
|
|
#if __D3D11
|
|
GRCDEVICE.LockContext();
|
|
#endif // __D3D11
|
|
GRCDEVICE.CaptureScreenShotToJpegFile(shotName);
|
|
#if __D3D11
|
|
GRCDEVICE.UnlockContext();
|
|
#endif // __D3D11
|
|
qaBugstarInterface BUGSTAR;
|
|
qaBug BUG;
|
|
BUGSTAR.CreateBug(BUG,shotName,bug->Number());
|
|
|
|
m_bProcessScreenshot = false;
|
|
}
|
|
|
|
|
|
|
|
atArray<CMinimalBug>& CBugstarIntegration::GetCurrentBugList()
|
|
{
|
|
return m_Buglist;
|
|
}
|
|
|
|
atArray<CMinimalBug*>& CBugstarIntegration::GetFilteredBugList()
|
|
{
|
|
return m_FilteredBugList;
|
|
}
|
|
|
|
|
|
#if __BANK
|
|
|
|
char s_DebugAssertMsg[128] = "";
|
|
int s_DebugAssertTimes = 1;
|
|
|
|
#if RSG_ORBIS
|
|
#pragma GCC diagnostic ignored "-Wformat-security"
|
|
#endif
|
|
|
|
void TestAssertBot()
|
|
{
|
|
if(s_DebugAssertTimes > 1)
|
|
{
|
|
int length = istrlen(s_DebugAssertMsg);
|
|
for(int i=0; i<s_DebugAssertTimes; i++)
|
|
{
|
|
sprintf(s_DebugAssertMsg+length, "%d", i+1);
|
|
// AF: Don't change this back to "%s", s_DebugAssertMsg as it breaks my assert bot testing as fmt keeps coming thru as "%s"
|
|
diagLogf(Channel_debug, DIAG_SEVERITY_ASSERT, __FILE__, __LINE__, s_DebugAssertMsg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// AF: Don't change this back to "%s", s_DebugAssertMsg as it breaks my assert bot testing as fmt keeps coming thru as "%s"
|
|
diagLogf(Channel_debug, DIAG_SEVERITY_ASSERT, __FILE__, __LINE__, s_DebugAssertMsg);
|
|
}
|
|
};
|
|
|
|
#if RSG_ORBIS
|
|
#pragma GCC diagnostic error "-Wformat-security"
|
|
#endif
|
|
|
|
void CBugstarIntegration::InitWidgets(bkBank& bank)
|
|
{
|
|
bank.PushGroup( "Login" );
|
|
bank.AddText( "Username", m_sUsernameVis, NELEM(m_sUsernameVis), false );
|
|
bank.AddText( "Password", m_sPasswordVis, NELEM(m_sPasswordVis), false );
|
|
bank.AddButton( "Login", datCallback(CFA(LoginAndClear)) );
|
|
bank.PopGroup( );
|
|
|
|
bank.PushGroup( "Assert Bot" );
|
|
bank.AddText( "Message", s_DebugAssertMsg, sizeof(s_DebugAssertMsg), false );
|
|
bank.AddSlider( "Times", &s_DebugAssertTimes, 1, 5, 1 );
|
|
bank.AddButton( "Assert", datCallback(CFA(TestAssertBot)) );
|
|
bank.PopGroup( );
|
|
|
|
bank.PushGroup( "List Bugs In Area" );
|
|
bank.AddButton( "Load Open BugList", datCallback(CFA(LoadDevBugList)) );
|
|
bank.AddText( "Current Area Center", m_sAreaCenter, NELEM(m_sAreaCenter), true );
|
|
bank.AddSlider( "Area Size", &m_fAreaSize, 0,MAX_BUG_AREA_SIZE,1.0f );
|
|
bank.PopGroup( );
|
|
|
|
bank.PushGroup( "Get Bug by Number" );
|
|
bank.AddText("Enter Bug #",m_sFetchBugNumber,NELEM(m_sFetchBugNumber),false);
|
|
bank.AddButton("Fetch",datCallback(CFA(LoadBugNumber)));
|
|
bank.PopGroup( );
|
|
|
|
bank.PushGroup( "Bugs",false );
|
|
bank.AddButton( "Next Bug", datCallback(CFA(NextBug)) );
|
|
bank.AddButton( "Previous Bug", datCallback(CFA(PreviousBug)) );
|
|
bank.AddButton( "Nearest Bug", datCallback(CFA(SelectNearestBug)) );
|
|
|
|
bank.AddButton( "Warp to Bug", datCallback(CFA(WarpToSelectedBug)) );
|
|
bank.AddText( "Bug #", m_sBugNumber, NELEM(m_sBugNumber), true, datCallback(CFA(CBugstarIntegration::UpdateSelectedBugDetails)) );
|
|
bank.AddText( "", m_sBugSummary, NELEM(m_sBugSummary), true, datCallback(CFA(CBugstarIntegration::UpdateSelectedBugDetails)) );
|
|
bank.PopGroup( );
|
|
|
|
bank.PushGroup( "Edit Bug",false );
|
|
bank.AddButton( "Add Screenshot", datCallback(CFA(AddScreenshot)) );
|
|
bank.PopGroup( );
|
|
|
|
bank.PushGroup( "Settings" ,false);
|
|
bank.AddSlider( "Set Bug Marker Size:", &m_fBugMarkerSize, 0,MAX_BUG_MARKER_SIZE,0.1f );
|
|
bank.AddToggle( "Show Bug Markers",&m_bBugMarkersOn);
|
|
bank.AddToggle( "Don't Attach Screenshots To Space Bar Bugs", &CDebug::ms_disableSpaceBugScreenshots);
|
|
bank.AddSeparator();
|
|
bank.AddTitle( "Filter Bug Classes" );
|
|
bank.AddToggle( "A",&m_bBugClassFilters[CMinimalBug::kA], datCallback(CFA(UpdateFilteredBugList)));
|
|
bank.AddToggle( "B",&m_bBugClassFilters[CMinimalBug::kB], datCallback(CFA(UpdateFilteredBugList)));
|
|
bank.AddToggle( "C",&m_bBugClassFilters[CMinimalBug::kC], datCallback(CFA(UpdateFilteredBugList)));
|
|
bank.AddToggle( "D",&m_bBugClassFilters[CMinimalBug::kD], datCallback(CFA(UpdateFilteredBugList)));
|
|
bank.AddToggle( "Task",&m_bBugClassFilters[CMinimalBug::kTask], datCallback(CFA(UpdateFilteredBugList)));
|
|
bank.AddToggle( "Todo",&m_bBugClassFilters[CMinimalBug::kTodo], datCallback(CFA(UpdateFilteredBugList)));
|
|
bank.AddToggle( "Wish",&m_bBugClassFilters[CMinimalBug::kWish], datCallback(CFA(UpdateFilteredBugList)));
|
|
|
|
bank.AddTitle( "Filter Bug Status" );
|
|
int statusFilterCount = sizeof(m_StatusFilters)/sizeof(m_StatusFilters[0]);
|
|
for(int i=0;i<statusFilterCount;i++)
|
|
{
|
|
bank.AddToggle(m_StatusFilters[i].name, &m_StatusFilters[i].bInclude, datCallback(CFA(UpdateFilteredBugList)));
|
|
}
|
|
|
|
bank.AddTitle( "Filter Bug Component" );
|
|
int componentFilterCount = sizeof(m_ComponentFilters)/sizeof(m_ComponentFilters[0]);
|
|
for(int i=0;i<componentFilterCount;i++)
|
|
{
|
|
bank.AddToggle(m_ComponentFilters[i].name, &m_ComponentFilters[i].bInclude, datCallback(CFA(UpdateFilteredBugList)));
|
|
}
|
|
|
|
bank.AddTitle( "Filter Bug User" );
|
|
bank.AddToggle("Bugs only for current user", &m_FilterByCurrentUser, datCallback(CFA(UpdateFilteredBugList)));
|
|
|
|
bank.PopGroup( );
|
|
|
|
// start the tcp worker thread now otherwise fetching bugs via RAG leads to a deadlock.
|
|
// Pressing 'Fetch' in RAG will enter a critical section, which calls the LoadBug() callback
|
|
// which fires off an HTTP query, which starts a thread. Starting a thread adds a bank widget
|
|
// which also enters a critical section using the same token, causing a deadlock.
|
|
// By starting the worker thread here, it avoids the problem.
|
|
netTcp::StartWorker(0);
|
|
}
|
|
|
|
void CBugstarIntegration::InitLiveEditWidgets(bkBank& bank)
|
|
{
|
|
bank.AddText("Enter Bug #",m_sFetchBugNumber,NELEM(m_sFetchBugNumber),false);
|
|
bank.AddButton("Fetch",datCallback(CFA(LoadBugNumber)));
|
|
bank.AddButton( "Add Screenshot", datCallback(CFA(AddScreenshot)) );
|
|
}
|
|
|
|
void CBugstarIntegration::LoginAndClear( )
|
|
{
|
|
//strcpy(m_sUsername,m_sUsernameVis);
|
|
|
|
sprintf(m_sUsername,"%s@rockstar.t2.corp",m_sUsernameVis);
|
|
strcpy(m_sPassword,m_sPasswordVis);
|
|
strcpy(m_sUsernameVis,"");
|
|
strcpy(m_sPasswordVis,"");
|
|
}
|
|
|
|
#endif // __BANK
|
|
|
|
|
|
void CBugstarIntegration::LoadDevBugList( )
|
|
{
|
|
if (LoadBugList())
|
|
{
|
|
UpdateFilteredBugList();
|
|
}
|
|
}
|
|
|
|
void CBugstarIntegration::LoadBugNumber( )
|
|
{
|
|
int number = 0;
|
|
|
|
number = atoi(m_sFetchBugNumber);
|
|
|
|
if(number > 0)
|
|
{
|
|
if (LoadBug(number))
|
|
{
|
|
// reset filtered buglist and add bug to list
|
|
m_FilteredBugList.ResetCount();
|
|
m_FilteredBugList.PushAndGrow(&m_Buglist[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
parTree* CBugstarIntegration::CreateXmlTreeFromHttpQuery(const char* search)
|
|
{
|
|
return CreateXmlTreeFromHttpQuery(search, m_sUsername, m_sPassword);
|
|
}
|
|
|
|
parTree* CBugstarIntegration::CreateXmlTreeFromHttpQuery(const char* search, const char* username, const char* password)
|
|
{
|
|
fwHttpQuery* pXmlSearch = CreateHttpQuery(username, password);
|
|
parTree* pTree = NULL;
|
|
|
|
if(pXmlSearch->Init(search))
|
|
{
|
|
while(pXmlSearch->Pending())
|
|
{
|
|
sysIpcSleep(100);
|
|
}
|
|
if(pXmlSearch->Succeeded())
|
|
pTree = pXmlSearch->CreateTree();
|
|
else
|
|
{
|
|
pXmlSearch->WriteBufferToFile("restError.html");
|
|
}
|
|
}
|
|
delete pXmlSearch;
|
|
return pTree;
|
|
}
|
|
|
|
parTree* CBugstarIntegration::CreateXmlTreeFromHttpQuery(const char* search, const char* postData)
|
|
{
|
|
return CreateXmlTreeFromHttpQuery(search, postData, m_sUsername, m_sPassword);
|
|
}
|
|
|
|
parTree* CBugstarIntegration::CreateXmlTreeFromHttpQuery(const char* search, const char* postData, const char* username, const char* password)
|
|
{
|
|
parTree* pTree = NULL;
|
|
|
|
// Create and run the query
|
|
fwHttpQuery* postQuery = CreateHttpQuery(username, password);
|
|
postQuery->InitPost(search, s_BugstarRequestDefaultTimeout);
|
|
postQuery->AppendXmlContent(postData, istrlen(postData));
|
|
|
|
if(postQuery->CommitPost())
|
|
{
|
|
while(postQuery->Pending())
|
|
{
|
|
sysIpcSleep(100);
|
|
}
|
|
if(postQuery->Succeeded())
|
|
{
|
|
pTree = postQuery->CreateTree();
|
|
}
|
|
else
|
|
{
|
|
postQuery->WriteBufferToFile("restError.html");
|
|
}
|
|
}
|
|
delete postQuery;
|
|
return pTree;
|
|
}
|
|
|
|
bool CBugstarIntegration::PostHttpQuery(const char* search, const char* postData, const char* username, const char* password)
|
|
{
|
|
// Create and run the query
|
|
bool rt = false;
|
|
fwHttpQuery* postQuery = CreateHttpQuery(username, password);
|
|
postQuery->InitPost(search, s_BugstarRequestDefaultTimeout);
|
|
postQuery->AppendXmlContent(postData, istrlen(postData));
|
|
|
|
if(postQuery->CommitPost())
|
|
{
|
|
while(postQuery->Pending())
|
|
{
|
|
sysIpcSleep(100);
|
|
}
|
|
if(postQuery->Succeeded())
|
|
{
|
|
rt = true;
|
|
}
|
|
else
|
|
{
|
|
postQuery->WriteBufferToFile("restError.html");
|
|
}
|
|
}
|
|
delete postQuery;
|
|
return rt;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CBugstarIntegration::LoadBugList()
|
|
{
|
|
// update the area center
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
|
|
Vec3V posn = gVpMan.GetGameViewport()->GetGrcViewport().GetCameraPosition();
|
|
int x = (int)posn.GetXf();
|
|
int y = (int)posn.GetYf();
|
|
int z = (int)posn.GetZf();
|
|
sprintf(m_sAreaCenter,"(%d,%d,%d)", x, y, z);
|
|
|
|
// sprintf(search,"%sBugs/Searches/OpenByLocation?radius=%d&x=%d&y=%d&z=%d&fields=Id,Summary,X,Y,Z,Priority,Category,Component,State",BUGSTAR_GTA_PROJECT_PATH,(int)m_fAreaSize,x,y,z);
|
|
sprintf(search,"%sBugs/Searches/NotClosedByLocation?radius=%d&x=%d&y=%d&z=%d&fields=Id,Summary,X,Y,Z,Priority,Category,Component,State,Developer&states=df,dfab,tns,tmi,tv",BUGSTAR_GTA_PROJECT_PATH,(int)m_fAreaSize,x,y,z);
|
|
|
|
parTree* pTree = CreateXmlTreeFromHttpQuery(search);
|
|
if(pTree)
|
|
{
|
|
m_Buglist.Reset();
|
|
|
|
{
|
|
parTreeNode* root = pTree->GetRoot();
|
|
parTreeNode::ChildNodeIterator i = root->BeginChildren();
|
|
|
|
int size = 0;
|
|
|
|
for(; i != root->EndChildren(); ++i) {
|
|
|
|
parTreeNode* id = (*i)->FindFromXPath("id");
|
|
if (id)
|
|
{
|
|
size++;
|
|
}
|
|
}
|
|
|
|
m_Buglist.Resize(size);
|
|
size = 0;
|
|
|
|
for(i = root->BeginChildren(); i != root->EndChildren(); ++i)
|
|
{
|
|
parTreeNode* id = (*i)->FindFromXPath("id"); // B* Number
|
|
|
|
if (id)
|
|
{
|
|
parTreeNode* summary = (*i)->FindFromXPath("summary"); // B* Description
|
|
parTreeNode* xloc = (*i)->FindFromXPath("x"); // Position
|
|
parTreeNode* yloc = (*i)->FindFromXPath("y");
|
|
parTreeNode* zloc = (*i)->FindFromXPath("z");
|
|
parTreeNode* priority = (*i)->FindFromXPath("priority"); // 1/2/3/4/5
|
|
parTreeNode* category = (*i)->FindFromXPath("category"); // A/B/C/D/Task/TODO/Wish
|
|
parTreeNode* component = (*i)->FindFromXPath("component"); // Art/Code:Graphics/Code:Systems/etc... (seems to return a number)
|
|
parTreeNode* state = (*i)->FindFromXPath("state"); // Dev(fixing)/etc.... (Seems to be "DEV")
|
|
parTreeNode* owner = (*i)->FindFromXPath("developer"); // Current Owner ID
|
|
|
|
CMinimalBug& addbug = m_Buglist[size];
|
|
|
|
addbug.SetNumber(atoi((char*)id->GetData()));
|
|
if(summary && summary->GetData())
|
|
addbug.SetSummary((char*)summary->GetData());
|
|
if(xloc && xloc->GetData())
|
|
addbug.SetX((float)atof((char*)xloc->GetData()));
|
|
if(yloc && yloc->GetData())
|
|
addbug.SetY((float)atof((char*)yloc->GetData()));
|
|
if(zloc && zloc->GetData())
|
|
addbug.SetZ((float)atof((char*)zloc->GetData()));
|
|
if(priority && priority->GetData())
|
|
addbug.SetSeverity(atoi((char*)priority->GetData()));
|
|
if(category && category->GetData())
|
|
addbug.SetCategory((char*)category->GetData());
|
|
if(component && component->GetData())
|
|
addbug.SetComponent((int)atoi((char*)component->GetData()));
|
|
if(state && state->GetData())
|
|
addbug.SetState((char*)state->GetData());
|
|
if(owner && owner->GetData())
|
|
addbug.SetOwner(atoi((char*)owner->GetData()));
|
|
|
|
}
|
|
size++;
|
|
}
|
|
}
|
|
|
|
delete pTree;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CBugstarIntegration::LoadBug(int bugNumber)
|
|
{
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
|
|
sprintf(search,"%sBugs/%d", BUGSTAR_GTA_PROJECT_PATH,bugNumber);
|
|
|
|
parTree* pTree = CreateXmlTreeFromHttpQuery(search);
|
|
if(pTree)
|
|
{
|
|
if(strcmp(pTree->GetRoot()->GetElement().GetName(), "bug"))
|
|
{
|
|
Errorf("Bugstar Integration - Bug cannot be found, make sure you're trying to access the correct Bugstar project (set in BugstarIntegration.h BUGSTAR_GTA_PROJECT_PATH)");
|
|
return false;
|
|
}
|
|
m_Buglist.Reset();
|
|
m_Buglist.Resize(1);
|
|
|
|
parTreeNode* root = pTree->GetRoot();
|
|
parTreeNode* id = root->FindFromXPath("id");
|
|
parTreeNode* summary = root->FindFromXPath("summary");
|
|
parTreeNode* xloc = root->FindFromXPath("x");
|
|
parTreeNode* yloc = root->FindFromXPath("y");
|
|
parTreeNode* zloc = root->FindFromXPath("z");
|
|
parTreeNode* priority = root->FindFromXPath("priority");
|
|
parTreeNode* category = root->FindFromXPath("category");
|
|
parTreeNode* component = root->FindFromXPath("component");
|
|
parTreeNode* state = root->FindFromXPath("state");
|
|
|
|
CMinimalBug& addbug = m_Buglist[0];
|
|
|
|
addbug.SetNumber(atoi((char*)id->GetData()));
|
|
if(summary && summary->GetData())
|
|
addbug.SetSummary((char*)summary->GetData());
|
|
if(xloc && xloc->GetData())
|
|
addbug.SetX((float)atof((char*)xloc->GetData()));
|
|
if(yloc && yloc->GetData())
|
|
addbug.SetY((float)atof((char*)yloc->GetData()));
|
|
if(zloc && zloc->GetData())
|
|
addbug.SetZ((float)atof((char*)zloc->GetData()));
|
|
if(priority && priority->GetData())
|
|
addbug.SetSeverity(atoi((char*)priority->GetData()));
|
|
if(category && category->GetData())
|
|
addbug.SetCategory((char*)category->GetData());
|
|
if(component && component->GetData())
|
|
addbug.SetComponent((int)atoi((char*)component->GetData()));
|
|
if(state && state->GetData())
|
|
addbug.SetState((char*)state->GetData());
|
|
|
|
delete pTree;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CBugstarIntegration::UpdateSelectedBugDetails()
|
|
{
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
sprintf(m_sBugNumber,"###");
|
|
sprintf(m_sBugSummary,"No bugs loaded");
|
|
|
|
if(list.size() > m_iSelectedBugIndex && m_iSelectedBugIndex >= 0)
|
|
{
|
|
CMinimalBug* bug = list[m_iSelectedBugIndex];
|
|
|
|
memset(m_sBugNumber,0,BUG_NUMBER_STRING_LENGTH);
|
|
sprintf(m_sBugNumber,"%d",bug->Number());
|
|
|
|
memset(m_sBugSummary,0,BUG_SUMMARY_STRING_LENGTH);
|
|
sprintf(m_sBugSummary,"%s",bug->Summary());
|
|
}
|
|
}
|
|
|
|
void CBugstarIntegration::NextBug( )
|
|
{
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
|
|
if(list.size() > 0)
|
|
{
|
|
if(list.size() > (m_iSelectedBugIndex + 1))
|
|
{
|
|
m_iSelectedBugIndex++;
|
|
}
|
|
else
|
|
{
|
|
m_iSelectedBugIndex = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_iSelectedBugIndex = -1;
|
|
}
|
|
UpdateSelectedBugDetails();
|
|
}
|
|
|
|
void CBugstarIntegration::PreviousBug( )
|
|
{
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
|
|
if(list.size() > 0)
|
|
{
|
|
if( (m_iSelectedBugIndex - 1) >= 0)
|
|
{
|
|
m_iSelectedBugIndex--;
|
|
}
|
|
else
|
|
{
|
|
m_iSelectedBugIndex = (list.size() -1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_iSelectedBugIndex = -1;
|
|
}
|
|
|
|
UpdateSelectedBugDetails();
|
|
}
|
|
|
|
void CBugstarIntegration::SelectNearestBug( )
|
|
{
|
|
Vector3 pos = CGameWorld::GetMainPlayerInfo()->GetPos();
|
|
|
|
int cur_nearest = -1;
|
|
float cur_dist = -1.0;
|
|
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
|
|
if(list.size() > 0)
|
|
{
|
|
CMinimalBug* bug = list[0];
|
|
Vector3 bug_pos(bug->X(),bug->Y(),bug->Z());
|
|
cur_dist = pos.Dist(bug_pos);
|
|
|
|
for(int i = 0; i < list.size(); i++)
|
|
{
|
|
CMinimalBug* bug2 = list[i];
|
|
Vector3 bug_pos(bug2->X(),bug2->Y(),bug2->Z());
|
|
float tmp = pos.Dist(bug_pos);
|
|
if(tmp < cur_dist)
|
|
{
|
|
cur_dist = tmp;
|
|
cur_nearest = i;
|
|
}
|
|
}
|
|
|
|
m_iSelectedBugIndex = cur_nearest;
|
|
}
|
|
|
|
UpdateSelectedBugDetails();
|
|
}
|
|
|
|
void CBugstarIntegration::WarpToSelectedBug( )
|
|
{
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
|
|
if(list.size() > m_iSelectedBugIndex && m_iSelectedBugIndex >= 0)
|
|
{
|
|
WarpToBug(list[m_iSelectedBugIndex]);
|
|
}
|
|
}
|
|
|
|
void CBugstarIntegration::WarpToBug(CMinimalBug* bug )
|
|
{
|
|
Vector3 vBugPosition(bug->X(),bug->Y(),bug->Z());
|
|
if (FindPlayerVehicle(CGameWorld::GetMainPlayerInfo(), true))
|
|
{
|
|
FindPlayerVehicle(CGameWorld::GetMainPlayerInfo(), true)->Teleport(vBugPosition, 0.0f, false);
|
|
}
|
|
else if(AssertVerify(FindPlayerPed()))
|
|
{
|
|
FindPlayerPed()->Teleport(vBugPosition, 0.0f);
|
|
}
|
|
|
|
g_SceneStreamerMgr.LoadScene(vBugPosition);
|
|
}
|
|
|
|
void CBugstarIntegration::AddScreenshot()
|
|
{
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
|
|
if(list.size() > m_iSelectedBugIndex && m_iSelectedBugIndex >= 0)
|
|
{
|
|
m_bProcessScreenshot = true;
|
|
m_iScreenshotFrameDelay = 3;
|
|
}
|
|
}
|
|
|
|
int CBugstarIntegration::GetBugCountForAssertId(unsigned int assertId)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
int bugCount = 0;
|
|
|
|
parTree* pResultTree = PerformFulltextSearch(assertId);
|
|
if (pResultTree)
|
|
{
|
|
// Process the results to get at the bug count
|
|
parTreeNode* root = pResultTree->GetRoot();
|
|
if (root)
|
|
{
|
|
parAttribute* countAttr = root->FindAttributeFromXPath("@count");
|
|
if (countAttr)
|
|
{
|
|
bugCount = atoi(countAttr->GetStringValue());
|
|
}
|
|
}
|
|
delete pResultTree;
|
|
}
|
|
else
|
|
{
|
|
bugCount = -1;
|
|
}
|
|
|
|
return bugCount;
|
|
}
|
|
|
|
int CBugstarIntegration::CreateNewAssertBug(const char* summary, const char* description, const CBugstarAssertUpdateQuery::BugDesc& bugDesc)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
int newBugId = 0;
|
|
char buffer[64];
|
|
|
|
// Generate the tree that represents the xml to post
|
|
parTree* pTree = rage_new parTree();
|
|
parTreeNode* pRoot = rage_new parTreeNode("bug");
|
|
pTree->SetRoot(pRoot);
|
|
|
|
parTreeNode* pSummary = rage_new parTreeNode("summary");
|
|
pSummary->SetData(summary, ustrlen(summary));
|
|
pSummary->AppendAsChildOf(pRoot);
|
|
|
|
parTreeNode* pDescription = rage_new parTreeNode("description");
|
|
pDescription->SetData(description, ustrlen(description));
|
|
pDescription->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "%f", bugDesc.position.x);
|
|
parTreeNode* pX = rage_new parTreeNode("x");
|
|
pX->SetData(buffer, ustrlen(buffer));
|
|
pX->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "%f", bugDesc.position.y);
|
|
parTreeNode* pY = rage_new parTreeNode("y");
|
|
pY->SetData(buffer, ustrlen(buffer));
|
|
pY->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "%f", bugDesc.position.z);
|
|
parTreeNode* pZ = rage_new parTreeNode("z");
|
|
pZ->SetData(buffer, ustrlen(buffer));
|
|
pZ->AppendAsChildOf(pRoot);
|
|
|
|
// Check whether we should assign this to default art or default code
|
|
int developerId = -1;
|
|
// The QA owner should be the local user
|
|
int testerId = GetCurrentUserId();
|
|
|
|
// Check that we got a valid user
|
|
if(testerId == -1)
|
|
{
|
|
delete pTree;
|
|
return 0;
|
|
}
|
|
|
|
developerId = testerId;
|
|
|
|
sprintf(buffer, "%d", developerId);
|
|
parTreeNode* pDeveloper = rage_new parTreeNode("developer");
|
|
pDeveloper->SetData(buffer, ustrlen(buffer));
|
|
pDeveloper->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "%d", testerId);
|
|
parTreeNode* pTester = rage_new parTreeNode("tester");
|
|
pTester->SetData(buffer, ustrlen(buffer));
|
|
pTester->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "%d", bugDesc.buildId);
|
|
parTreeNode* pBuild = rage_new parTreeNode("foundIn");
|
|
pBuild->SetData(buffer, ustrlen(buffer));
|
|
pBuild->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "%d", bugDesc.mapId);
|
|
parTreeNode* pMap = rage_new parTreeNode("map");
|
|
pMap->SetData(buffer, ustrlen(buffer));
|
|
pMap->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "%d", bugDesc.missionId);
|
|
parTreeNode* pMission = rage_new parTreeNode("mission");
|
|
pMission->SetData(buffer, ustrlen(buffer));
|
|
pMission->AppendAsChildOf(pRoot);
|
|
|
|
parTreeNode* pCategory = rage_new parTreeNode("category");
|
|
pCategory->SetData("A", 1);
|
|
pCategory->AppendAsChildOf(pRoot);
|
|
|
|
parTreeNode* pTagList = rage_new parTreeNode("tagList");
|
|
pTagList->AppendAsChildOf(pRoot);
|
|
|
|
parTreeNode* pAssertTag = rage_new parTreeNode("tag");
|
|
pAssertTag->SetData("assert", 7);
|
|
pAssertTag->AppendAsChildOf(pTagList);
|
|
#if __XENON
|
|
parTreeNode* pPlatformTag = rage_new parTreeNode("tag");
|
|
pPlatformTag->SetData("XBOX360", 8);
|
|
pPlatformTag->AppendAsChildOf(pTagList);
|
|
#elif __PS3
|
|
parTreeNode* pPlatformTag = rage_new parTreeNode("tag");
|
|
pPlatformTag->SetData("PS3", 4);
|
|
pPlatformTag->AppendAsChildOf(pTagList);
|
|
#elif __WIN32PC
|
|
parTreeNode* pPlatformTag = rage_new parTreeNode("tag");
|
|
pPlatformTag->SetData("PC", 3);
|
|
pPlatformTag->AppendAsChildOf(pTagList);
|
|
#elif RSG_DURANGO
|
|
parTreeNode* pPlatformTag = rage_new parTreeNode("tag");
|
|
pPlatformTag->SetData("DURANGO", 3);
|
|
pPlatformTag->AppendAsChildOf(pTagList);
|
|
#elif RSG_ORBIS
|
|
parTreeNode* pPlatformTag = rage_new parTreeNode("tag");
|
|
pPlatformTag->SetData("ORBIS", 3);
|
|
pPlatformTag->AppendAsChildOf(pTagList);
|
|
#else
|
|
#error "Not Implemented"
|
|
#endif
|
|
|
|
parTreeNode* pSeen = rage_new parTreeNode("seen");
|
|
pSeen->SetData("1", 2);
|
|
pSeen->AppendAsChildOf(pRoot);
|
|
|
|
parTreeNode* pCCList = rage_new parTreeNode("ccList");
|
|
pCCList->AppendAsChildOf(pRoot);
|
|
|
|
sprintf(buffer, "Assert ID: %u", bugDesc.assertId);
|
|
parTreeNode* pAssertComment = rage_new parTreeNode("addcomment");
|
|
parTreeNode* pText = rage_new parTreeNode("text");
|
|
pText->SetData(buffer, ustrlen(buffer));
|
|
pText->AppendAsChildOf(pAssertComment);
|
|
pAssertComment->AppendAsChildOf(pRoot);
|
|
|
|
// Convert the tree to an xml string
|
|
datGrowBuffer gb;
|
|
gb.Init(sysMemAllocator::GetMaster().GetAllocator(MEMTYPE_DEBUG_VIRTUAL), datGrowBuffer::NULL_TERMINATE);
|
|
|
|
char gbName[RAGE_MAX_PATH];
|
|
fiDevice::MakeGrowBufferFileName(gbName, RAGE_MAX_PATH, &gb);
|
|
fiStream* stream = ASSET.Create(gbName, "");
|
|
PARSER.SaveTree(stream, pTree, parManager::XML);
|
|
stream->Flush();
|
|
stream->Close();
|
|
|
|
// Run the query
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sBugs", BUGSTAR_GTA_PROJECT_PATH);
|
|
|
|
parTree* pResultTree = CreateXmlTreeFromHttpQuery(search, (char*)gb.GetBuffer(), m_sAssertUsername, m_sAssertPassword);
|
|
if (pResultTree)
|
|
{
|
|
// Process the results to get at the bug id
|
|
parTreeNode* root = pResultTree->GetRoot();
|
|
if (root)
|
|
{
|
|
parTreeNode* idNode = root->FindFromXPath("id");
|
|
if (idNode)
|
|
{
|
|
newBugId = atoi((char*)idNode->GetData());
|
|
}
|
|
}
|
|
delete pResultTree;
|
|
}
|
|
|
|
delete pTree;
|
|
return newBugId;
|
|
}
|
|
|
|
parTree* CBugstarIntegration::GetMostRecentOpenBug(unsigned int assertId)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
parTree* openBugTree = NULL;
|
|
CMinimalBug openMinimalBug;
|
|
|
|
// Get the list of bugs for this assert
|
|
parTree* pSearchTree = PerformFulltextSearch(assertId, false);
|
|
if (pSearchTree)
|
|
{
|
|
// Process the results to get at the bug count
|
|
parTreeNode* searchTreeRoot = pSearchTree->GetRoot();
|
|
if (searchTreeRoot)
|
|
{
|
|
//for(parTreeNode::ChildNodeIterator itr = searchTreeRoot->BeginChildren(); itr != searchTreeRoot->EndChildren(); ++itr)
|
|
for (int i = 0; i < searchTreeRoot->FindNumChildren(); ++i)
|
|
{
|
|
parTreeNode* child = searchTreeRoot->FindChildWithIndex(i);
|
|
parAttribute* idAttr = child->FindAttributeFromXPath("@id");
|
|
|
|
// Query bugstar for the full bug information based on the id attribute
|
|
//parAttribute* idAttr = (*itr)->FindAttributeFromXPath("@id");
|
|
parTree* bugTree = GetBug(atoi((char*)idAttr->GetStringValue()));
|
|
|
|
if (bugTree)
|
|
{
|
|
CMinimalBug minimalBug;
|
|
ConvertToMinimalBug(bugTree, minimalBug);
|
|
// bugs which aren't closed and aren't with admin for review are considered open
|
|
bool isBugOpen = ((strncmp (minimalBug.State(), "CLOSED", 6) != 0) && (strncmp (minimalBug.State(), "REVIEW", 6) != 0));
|
|
|
|
if (isBugOpen && difftime(minimalBug.CollectedOn(), openMinimalBug.CollectedOn()) > 0)
|
|
{
|
|
// If both bugs are in the same state, keep track of the more recent one
|
|
if (openBugTree != NULL)
|
|
{
|
|
delete openBugTree;
|
|
}
|
|
openBugTree = bugTree;
|
|
openMinimalBug = minimalBug;
|
|
}
|
|
else
|
|
{
|
|
// We don't need to keep track of this bug so delete the tree
|
|
delete bugTree;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete pSearchTree;
|
|
}
|
|
|
|
return openBugTree;
|
|
}
|
|
|
|
//
|
|
// name: PostBugChanges
|
|
// description: Edit bug by posting to REST interface
|
|
bool CBugstarIntegration::PostBugChanges(int bugId, parTree* pBugTree)
|
|
{
|
|
// Generate the query for sending the updated bug data
|
|
datGrowBuffer gb;
|
|
gb.Init(sysMemAllocator::GetMaster().GetAllocator(MEMTYPE_DEBUG_VIRTUAL), datGrowBuffer::NULL_TERMINATE);
|
|
|
|
char gbName[RAGE_MAX_PATH];
|
|
fiDevice::MakeGrowBufferFileName(gbName, RAGE_MAX_PATH, &gb);
|
|
fiStream* stream = ASSET.Create(gbName, "");
|
|
PARSER.SaveTree(stream, pBugTree, parManager::XML);
|
|
stream->Flush();
|
|
stream->Close();
|
|
|
|
// Run the query
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sBugs/%d", BUGSTAR_GTA_PROJECT_PATH, bugId);
|
|
|
|
parTree* pResultTree = CreateXmlTreeFromHttpQuery(search, (char*)gb.GetBuffer(), m_sAssertUsername, m_sAssertPassword);
|
|
if (pResultTree)
|
|
{
|
|
delete pResultTree;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// name: PostBugChanges
|
|
// description: Edit bug by posting to REST interface
|
|
bool CBugstarIntegration::PostBugFieldChange(int bugId, const char* field, const char* value)
|
|
{
|
|
// Run the query
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sBugs/%d/%s", BUGSTAR_GTA_PROJECT_PATH, bugId, field);
|
|
return PostHttpQuery(search, value, m_sAssertUsername, m_sAssertPassword);
|
|
}
|
|
|
|
int CBugstarIntegration::IncrementBugSeenCount(int bugId, int buildId, parTree* pBugTree)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
parTreeNode* verificationNode = pBugTree->GetRoot()->FindFromXPath("seen");
|
|
int currentVerificationCount = 0;
|
|
if (verificationNode != NULL)
|
|
currentVerificationCount = atoi((char*)verificationNode->GetData());
|
|
++currentVerificationCount;
|
|
|
|
char buffer[64];
|
|
sprintf(buffer, "%d", currentVerificationCount);
|
|
|
|
//return PostBugFieldChange(bugId, "seen", buffer);
|
|
|
|
parTreeNode* newVerificationNode = rage_new parTreeNode("seen");
|
|
newVerificationNode->SetData(buffer, ustrlen(buffer));
|
|
verificationNode->SwapWith(*newVerificationNode);
|
|
delete newVerificationNode;
|
|
|
|
// Only update build if build hasn't been set already
|
|
if(buildId != 0)
|
|
{
|
|
parTreeNode* buildNode = pBugTree->GetRoot()->FindFromXPath("foundIn");
|
|
if (buildNode == NULL)
|
|
{
|
|
sprintf(buffer, "%d", buildId);
|
|
parTreeNode* newBuildNode = rage_new parTreeNode("foundIn");
|
|
newBuildNode->SetData(buffer, ustrlen(buffer));
|
|
newBuildNode->AppendAsChildOf(pBugTree->GetRoot());
|
|
}
|
|
}
|
|
|
|
PostBugChanges(bugId, pBugTree);
|
|
|
|
return currentVerificationCount;
|
|
}
|
|
|
|
bool CBugstarIntegration::ChangeField(int bugId, const char* field, const char* value)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
bool rt = false;
|
|
parTree* pTree = GetBug(bugId);
|
|
if(pTree)
|
|
{
|
|
parTreeNode* node = pTree->GetRoot()->FindFromXPath(field);
|
|
if(node == NULL)
|
|
{
|
|
node = rage_new parTreeNode(field);
|
|
node->AppendAsChildOf(pTree->GetRoot());
|
|
}
|
|
node->SetData(value, ustrlen(value));
|
|
|
|
rt = PostBugChanges(bugId, pTree);
|
|
delete pTree;
|
|
}
|
|
return rt;
|
|
}
|
|
|
|
bool CBugstarIntegration::AddComment(int bugId, const char* comment)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
bool rt = false;
|
|
parTree* pTree = GetBug(bugId);
|
|
if(pTree)
|
|
{
|
|
parTreeNode* commentNode = rage_new parTreeNode("addcomment");
|
|
parTreeNode* pText = rage_new parTreeNode("text");
|
|
pText->SetData(comment, ustrlen(comment));
|
|
pText->AppendAsChildOf(commentNode);
|
|
commentNode->AppendAsChildOf(pTree->GetRoot());
|
|
|
|
|
|
rt = PostBugChanges(bugId, pTree);
|
|
delete pTree;
|
|
}
|
|
return rt;
|
|
}
|
|
|
|
bool CBugstarIntegration::ConvertToMinimalBug(parTree* pBugTree, CMinimalBug& bug)
|
|
{
|
|
if (pBugTree == NULL || pBugTree->GetRoot() == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
parTreeNode* pRoot = pBugTree->GetRoot();
|
|
|
|
parTreeNode* id = pRoot->FindFromXPath("id");
|
|
parTreeNode* summary = pRoot->FindFromXPath("summary");
|
|
parTreeNode* xloc = pRoot->FindFromXPath("x");
|
|
parTreeNode* yloc = pRoot->FindFromXPath("y");
|
|
parTreeNode* zloc = pRoot->FindFromXPath("z");
|
|
parTreeNode* state = pRoot->FindFromXPath("state");
|
|
parTreeNode* priority = pRoot->FindFromXPath("priority");
|
|
parTreeNode* category = pRoot->FindFromXPath("category");
|
|
parTreeNode* collectedOn = pRoot->FindFromXPath("collectedOn");
|
|
parTreeNode* fixedOn = pRoot->FindFromXPath("fixedOn");
|
|
parTreeNode* owner = pRoot->FindFromXPath("developer");
|
|
parTreeNode* qaOwner = pRoot->FindFromXPath("tester");
|
|
|
|
bug.SetNumber(atoi((char*)id->GetData()));
|
|
if(summary && summary->GetData())
|
|
bug.SetSummary((char*)summary->GetData());
|
|
if(xloc && xloc->GetData())
|
|
bug.SetX((float)atof((char*)xloc->GetData()));
|
|
if(yloc && yloc->GetData())
|
|
bug.SetY((float)atof((char*)yloc->GetData()));
|
|
if(zloc && zloc->GetData())
|
|
bug.SetZ((float)atof((char*)zloc->GetData()));
|
|
if(state && state->GetData())
|
|
bug.SetState((char*)state->GetData());
|
|
if(priority && priority->GetData())
|
|
bug.SetSeverity(atoi((char*)priority->GetData()));
|
|
if(category && category->GetData())
|
|
bug.SetCategory((char*)category->GetData());
|
|
if(collectedOn && collectedOn->GetData())
|
|
bug.SetCollectedOn((char*)collectedOn->GetData());
|
|
if(fixedOn && fixedOn->GetData())
|
|
bug.SetFixedOn((char*)fixedOn->GetData());
|
|
if(owner && owner->GetData())
|
|
bug.SetOwner(atoi((char*)owner->GetData()));
|
|
if(qaOwner && qaOwner->GetData())
|
|
bug.SetQaOwner(atoi((char*)qaOwner->GetData()));
|
|
|
|
return true;
|
|
}
|
|
|
|
fwHttpQuery* CBugstarIntegration::CreateHttpQuery()
|
|
{
|
|
return CreateHttpQuery(m_sUsername, m_sPassword);
|
|
}
|
|
|
|
fwHttpQuery* CBugstarIntegration::CreateHttpQuery(const char* username, const char* password)
|
|
{
|
|
return rage_new fwHttpQuery(BUGSTAR_IP_ADDRESS, BUGSTAR_REST_SERVER, username, password, true);
|
|
}
|
|
|
|
CHttpQuerySaveToFile* CBugstarIntegration::CreateSaveHttpQuery(const char* filename)
|
|
{
|
|
return rage_new CHttpQuerySaveToFile(BUGSTAR_IP_ADDRESS, BUGSTAR_REST_SERVER, m_sUsername, m_sPassword, filename, true);
|
|
}
|
|
|
|
void CBugstarIntegration::RenderTheBuglist( )
|
|
{
|
|
static sysSpinLockToken SpinLock;
|
|
SYS_SPINLOCK_ENTER(SpinLock);
|
|
|
|
if(m_bBugMarkersOn)
|
|
{
|
|
atArray<CMinimalBug*>& list = GetFilteredBugList();
|
|
|
|
for(int i = 0; i < list.size(); i++)
|
|
{
|
|
if(i == m_iSelectedBugIndex)
|
|
{
|
|
RenderBug(list[i],true);
|
|
}
|
|
else
|
|
{
|
|
RenderBug(list[i],false);
|
|
}
|
|
}
|
|
}
|
|
|
|
SYS_SPINLOCK_EXIT(SpinLock);
|
|
}
|
|
|
|
void CBugstarIntegration::RenderMarker(float x,float y, float z,float r,Color32 colour,bool highlight,const char* label)
|
|
{
|
|
bool bSolid = true;
|
|
if(highlight == true)
|
|
{
|
|
r = r*2;
|
|
//colour = Color32(200,200,200,255);
|
|
bSolid = false;
|
|
}
|
|
|
|
s32 num_slices = 2;
|
|
s32 num_segments = 4;
|
|
s32 bit_num_segments = 1;
|
|
s32 quart_num_slices = 1;
|
|
s32 quart_num_segments = 1;
|
|
|
|
s32 i;
|
|
float fSliceAngleInc = PI/(float)num_slices;
|
|
float fSegmentAngleInc = 2*PI/(float)num_segments;
|
|
|
|
s32 nFirstFaceSegment = (num_segments/2)+1 - bit_num_segments;
|
|
s32 nLastFaceSegment = (num_segments/2)+1 + bit_num_segments;
|
|
float fFaceSliceInc = 0.5f / (float)quart_num_slices;
|
|
float fFaceSegmentInc = 1.0f / (float)quart_num_segments;
|
|
|
|
s32 nSlice;
|
|
float fNormL, fNormH;
|
|
float fHeightL, fHeightH;
|
|
float fRadiusFracL, fRadiusFracH;
|
|
|
|
grcColor(colour);
|
|
bool wasOverride = grcStateBlock::SetWireframeOverride(!bSolid);
|
|
grcStateBlock::SetRasterizerState(grcStateBlock::RS_Default);
|
|
|
|
// do top slices
|
|
for(nSlice = 1; nSlice <= num_slices; nSlice++)
|
|
{
|
|
fNormL = rage::Cosf((nSlice-1)*fSliceAngleInc);
|
|
fRadiusFracL = rage::Sinf((nSlice-1)*fSliceAngleInc);
|
|
fHeightL = z + r*fNormL;
|
|
|
|
fNormH = rage::Cosf(nSlice*fSliceAngleInc);
|
|
fRadiusFracH = rage::Sinf(nSlice*fSliceAngleInc);
|
|
fHeightH = z + r*fNormH;
|
|
|
|
grcBegin(drawTriStrip, (num_segments+1) * 2);
|
|
|
|
for(i=0; i <= num_segments;i++)
|
|
{
|
|
|
|
float u,v;
|
|
if(i<nFirstFaceSegment)
|
|
u = 0.0f;
|
|
else if(i>nLastFaceSegment)
|
|
u = 1.0f;
|
|
else
|
|
u = (i-nFirstFaceSegment)*fFaceSegmentInc;
|
|
|
|
if(nSlice < quart_num_slices)
|
|
v = 0.0f;
|
|
else if(nSlice > num_slices-quart_num_slices)
|
|
v = 1.0f;
|
|
else
|
|
v = 0.5f + (nSlice)*fFaceSliceInc;
|
|
|
|
grcTexCoord2f(u, v);
|
|
grcNormal3f(fRadiusFracH*rage::Sinf(i*fSegmentAngleInc), fRadiusFracH*rage::Cosf(i*fSegmentAngleInc), fNormH);
|
|
grcVertex3f(x+r*fRadiusFracH*rage::Sinf(i*fSegmentAngleInc), y+r*fRadiusFracH*rage::Cosf(i*fSegmentAngleInc), fHeightH);
|
|
|
|
// lower vertex
|
|
if(i<nFirstFaceSegment)
|
|
u = 0.0f;
|
|
else if(i>nLastFaceSegment)
|
|
u = 1.0f;
|
|
else
|
|
u = (i-nFirstFaceSegment)*fFaceSegmentInc;
|
|
|
|
if(nSlice < quart_num_slices)
|
|
v = 0.0f;
|
|
else if(nSlice > num_slices-quart_num_slices)
|
|
v = 1.0f;
|
|
else
|
|
v = 0.5f + (nSlice-1)*fFaceSliceInc;
|
|
|
|
grcTexCoord2f(u, v);
|
|
grcNormal3f(fRadiusFracL*rage::Sinf(i*fSegmentAngleInc), fRadiusFracL*rage::Cosf(i*fSegmentAngleInc), fNormL);
|
|
grcVertex3f(x+r*fRadiusFracL*rage::Sinf(i*fSegmentAngleInc), y+r*fRadiusFracL*rage::Cosf(i*fSegmentAngleInc), fHeightL);
|
|
}
|
|
|
|
grcEnd();
|
|
}
|
|
grcStateBlock::SetWireframeOverride(wasOverride);
|
|
|
|
grcBindTexture(NULL);
|
|
grcColor(Color_white);
|
|
grcDrawLabelf(Vector3(x,y,z),label);
|
|
|
|
}
|
|
|
|
void CBugstarIntegration::RenderBug(CMinimalBug* bug,bool highlight)
|
|
{
|
|
float x = bug->X();
|
|
float y = bug->Y();
|
|
float z = bug->Z();
|
|
float r = m_fBugMarkerSize;
|
|
|
|
Color32 colour;
|
|
|
|
switch(bug->Severity())
|
|
{
|
|
case 0:
|
|
colour = Color32(255,0,0,255); // A
|
|
break;
|
|
case 1:
|
|
colour = Color32(255,150,0,255); // B
|
|
break;
|
|
case 2:
|
|
colour = Color32(255,222,100,255); // C
|
|
break;
|
|
case 3:
|
|
colour = Color32(188,255,50,255); // D
|
|
break;
|
|
case 4:
|
|
colour = Color32(0,255,0,255); // wish
|
|
break;
|
|
case 5:
|
|
colour = Color32(255,0,255,255); // todo
|
|
break;
|
|
case 6:
|
|
colour = Color32(0,255,230,255); // task
|
|
break;
|
|
default:
|
|
colour = Color32(0,0,0,255);
|
|
break;
|
|
|
|
}
|
|
grcBindTexture(NULL);
|
|
grcWorldIdentity();
|
|
grcLightState::SetEnabled(false);
|
|
|
|
char idlabel[64];
|
|
sprintf(idlabel,"%d",bug->Number());
|
|
|
|
RenderMarker(x,y,z,r,colour,highlight,idlabel);
|
|
|
|
grcStateBlock::SetBlendState(grcStateBlock::BS_Default);
|
|
grcStateBlock::SetDepthStencilState(CShaderLib::DSS_Default_Invert);
|
|
grcStateBlock::SetRasterizerState(grcStateBlock::RS_Default);
|
|
}
|
|
|
|
bool CBugstarIntegration::PassesStatusFilter(CMinimalBug *pBug)
|
|
{
|
|
char *pProperStatusString = CBugstarStateLookup::GetDescriptionFromState(pBug->State());
|
|
|
|
int statusFilterCount = sizeof(m_StatusFilters)/sizeof(m_StatusFilters[0]);
|
|
for(int i=0;i<statusFilterCount;i++)
|
|
{
|
|
StatusFilter *pStatFilter = &m_StatusFilters[i];
|
|
if( pStatFilter->bInclude == true)
|
|
{
|
|
if( strcmp(pProperStatusString, pStatFilter->name) == 0 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CBugstarIntegration::PassesComponentFilter(CMinimalBug *pBug)
|
|
{
|
|
// Special case, no component has been set to test against, so return true
|
|
if(pBug->Component() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
char *pComponentString = CBugstarComponentLookup::GetComponentString(pBug->Component());
|
|
if( pComponentString )
|
|
{
|
|
int componentFilterCount = sizeof(m_ComponentFilters)/sizeof(m_ComponentFilters[0]);
|
|
for(int i=0;i<componentFilterCount;i++)
|
|
{
|
|
ComponentFilter *pCompFilter = &m_ComponentFilters[i];
|
|
if( pCompFilter->bInclude == true)
|
|
{
|
|
// Only check as many chars as the filter contains, so if we select "Art", "Art : Monkeys" will still pass
|
|
if( strncmp(pComponentString, pCompFilter->name, strlen(pCompFilter->name) ) == 0 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check the bug against the current owner
|
|
bool CBugstarIntegration::PassesOwnerFilter(CMinimalBug *pBug)
|
|
{
|
|
// We're not filtering by user
|
|
if( !m_FilterByCurrentUser )
|
|
return true;
|
|
|
|
// Check bug owner is current User.. return true if so.
|
|
if(pBug->Owner() == m_CurrentUserID)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CBugstarIntegration::UpdateFilteredBugList()
|
|
{
|
|
if(!CBugstarComponentLookup::IsInitialised())
|
|
{
|
|
CBugstarComponentLookup::Init();
|
|
}
|
|
|
|
if(!CBugstarStateLookup::IsInitialised())
|
|
{
|
|
CBugstarStateLookup::Init();
|
|
}
|
|
|
|
if(!CBugstarUserLookup::IsInitialised())
|
|
{
|
|
CBugstarUserLookup::Init();
|
|
m_CurrentUserID = CBugstarUserLookup::GetUserIDFromNameHash(m_CurrentUsername.GetHash());
|
|
}
|
|
|
|
m_FilteredBugList.Reset();
|
|
|
|
int filteredBugsCount = 0;
|
|
for(int i = 0; i < m_Buglist.size(); i++)
|
|
{
|
|
CMinimalBug *pBug = &m_Buglist[i];
|
|
|
|
if (m_bBugClassFilters[pBug->Category()])
|
|
{
|
|
// Check the status filter
|
|
if(PassesStatusFilter(pBug) && PassesComponentFilter(pBug) && PassesOwnerFilter(pBug))
|
|
{
|
|
++filteredBugsCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (filteredBugsCount > 0)
|
|
{
|
|
m_FilteredBugList.Resize(filteredBugsCount);
|
|
int idx = 0;
|
|
|
|
for(int i = 0; i < m_Buglist.size(); i++)
|
|
{
|
|
CMinimalBug *pBug = &m_Buglist[i];
|
|
|
|
if (m_bBugClassFilters[pBug->Category()])
|
|
{
|
|
if(PassesStatusFilter(pBug) && PassesComponentFilter(pBug) && PassesOwnerFilter(pBug))
|
|
{
|
|
m_FilteredBugList[idx++] = &m_Buglist[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
m_iSelectedBugIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
m_iSelectedBugIndex = -1;
|
|
}
|
|
|
|
UpdateSelectedBugDetails();
|
|
}
|
|
|
|
parTree* CBugstarIntegration::PerformFulltextSearch(unsigned int assertId, bool countOnly)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
// Post something along the lines of the following:
|
|
// <Search query="tag:assert" countOnly="false"/>
|
|
parTree* pTree = rage_new parTree();
|
|
parTreeNode* pRoot = rage_new parTreeNode("Search");
|
|
pTree->SetRoot(pRoot);
|
|
|
|
// Create the query
|
|
char query[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(query,"comment:\"Assert ID: %u\"", assertId);
|
|
|
|
pRoot->GetElement().AddAttribute("query", query);
|
|
pRoot->GetElement().AddAttribute("countOnly", countOnly);
|
|
|
|
// Convert the tree to an xml string
|
|
datGrowBuffer gb;
|
|
gb.Init(sysMemAllocator::GetMaster().GetAllocator(MEMTYPE_DEBUG_VIRTUAL), datGrowBuffer::NULL_TERMINATE);
|
|
|
|
char gbName[RAGE_MAX_PATH];
|
|
fiDevice::MakeGrowBufferFileName(gbName, RAGE_MAX_PATH, &gb);
|
|
fiStream* stream = ASSET.Create(gbName, "");
|
|
PARSER.SaveTree(stream, pTree, parManager::XML);
|
|
stream->Flush();
|
|
stream->Close();
|
|
delete pTree;
|
|
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sFulltextSearch", BUGSTAR_GTA_PROJECT_PATH);
|
|
|
|
// Run the query
|
|
return CreateXmlTreeFromHttpQuery(search, (char*)gb.GetBuffer());
|
|
}
|
|
|
|
parTree* CBugstarIntegration::GetBug(unsigned int bugId)
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sBugs/%d", BUGSTAR_GTA_PROJECT_PATH, bugId);
|
|
|
|
return CreateXmlTreeFromHttpQuery(search);
|
|
}
|
|
|
|
int CBugstarIntegration::GetCurrentUserId()
|
|
{
|
|
// Check whether we already know what the id is
|
|
if (sm_currentUserId != -1)
|
|
{
|
|
return sm_currentUserId;
|
|
}
|
|
|
|
// Get the user name
|
|
char userName[64];
|
|
sysGetEnv("USERNAME", userName, 64);
|
|
|
|
sm_currentUserId = GetUserByName(userName);
|
|
return sm_currentUserId;
|
|
}
|
|
|
|
int CBugstarIntegration::GetUserByName(const char* username)
|
|
{
|
|
int userId = -1;
|
|
|
|
// Create the query
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sUsers/Search?username=%s", BUGSTAR_GTA_PROJECT_PATH, username);
|
|
|
|
parTree* pTree = CreateXmlTreeFromHttpQuery(search);
|
|
if(pTree)
|
|
{
|
|
parTreeNode* root = pTree->GetRoot();
|
|
parTreeNode* firstUser = root->GetChild();
|
|
if (firstUser)
|
|
{
|
|
parTreeNode* id = firstUser->FindFromXPath("id");
|
|
userId = atoi((char*)id->GetData());
|
|
}
|
|
delete pTree;
|
|
}
|
|
|
|
return userId;
|
|
}
|
|
|
|
int CBugstarIntegration::GetTeamByName(const char* teamname)
|
|
{
|
|
// is team name in map
|
|
if(sm_teamIdMap.Access(teamname))
|
|
{
|
|
return sm_teamIdMap[teamname];
|
|
}
|
|
|
|
int teamId = -1;
|
|
|
|
// Create the query
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sTeams/Search?name=%s", BUGSTAR_GTA_PROJECT_PATH, teamname);
|
|
|
|
parTree* pTree = CreateXmlTreeFromHttpQuery(search);
|
|
if(pTree)
|
|
{
|
|
parTreeNode* root = pTree->GetRoot();
|
|
parTreeNode* firstTeam = root->GetChild();
|
|
if (firstTeam)
|
|
{
|
|
parTreeNode* id = firstTeam->FindFromXPath("id");
|
|
teamId = atoi((char*)id->GetData());
|
|
}
|
|
delete pTree;
|
|
}
|
|
|
|
// store teamname in map
|
|
sm_teamIdMap[teamname] = teamId;
|
|
|
|
return teamId;
|
|
}
|
|
|
|
bool CBugstarIntegration::AddConsoleLogsToBug(void)
|
|
{
|
|
return NetworkInterface::IsGameInProgress() && PARAM_attachConsoleLogsToBugsInMP.Get();
|
|
}
|
|
|
|
bool CBugstarIntegration::GetStreamingVideoId(char* id, size_t idSize)
|
|
{
|
|
if (sm_isStreamingVideoEnabled == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (*sm_streamingVideoId == '\0')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsStreamingVideo() == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Assertf(strlen(sm_streamingVideoId) < idSize, "Bugstar streaming video buffer size %i is too small", int(idSize));
|
|
|
|
strncpy(id, sm_streamingVideoId, idSize);
|
|
|
|
// Null terminate the string in case the whole buffer was filled.
|
|
sm_streamingVideoId[idSize - 1] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
void CBugstarIntegration::InitStreamingVideo()
|
|
{
|
|
sm_isStreamingVideoEnabled = false;
|
|
*sm_streamingVideoBaseUri = '\0';
|
|
*sm_streamingVideoId = '\0';
|
|
|
|
USE_DEBUG_MEMORY();
|
|
|
|
if (fiRemoteServerIsRunning == false)
|
|
{
|
|
sm_streamingVideoErrorMessage = "sysTrayRfs is required for streaming video";
|
|
return;
|
|
}
|
|
|
|
if (ValidateStreamingVideoSettings() == false)
|
|
{
|
|
sm_streamingVideoErrorMessage = "Invalid streaming video setup";
|
|
return;
|
|
}
|
|
|
|
if (LoadStreamingVideoSettings() == false)
|
|
{
|
|
sm_streamingVideoErrorMessage = "Invalid streaming video settings file";
|
|
return;
|
|
}
|
|
|
|
if (SetStreamingVideoSettings() == false)
|
|
{
|
|
sm_streamingVideoErrorMessage = "Failed to set streaming video settings";
|
|
return;
|
|
}
|
|
|
|
sm_isStreamingVideoEnabled = true;
|
|
|
|
Displayf("Bugstar streaming video is enabled.");
|
|
}
|
|
|
|
bool CBugstarIntegration::ValidateStreamingVideoSettings()
|
|
{
|
|
|
|
#if RSG_ORBIS
|
|
return ValidateStreamingVideoSettingsOrbis();
|
|
#else
|
|
return true;
|
|
#endif
|
|
|
|
}
|
|
|
|
bool CBugstarIntegration::LoadStreamingVideoSettings()
|
|
{
|
|
char filePath[MAX_PATH];
|
|
|
|
formatf(filePath, "%s\\Settings.xml", sm_streamingVideoRootDirectory);
|
|
|
|
fiStream* stream = fiStream::Open(filePath, true);
|
|
|
|
if (stream == nullptr)
|
|
{
|
|
Errorf("Failed to load '%s'. Bugstar streaming video is disabled.", filePath);
|
|
return false;
|
|
}
|
|
|
|
parTree* tree = PARSER.LoadTree(stream, nullptr);
|
|
|
|
if (tree == nullptr)
|
|
{
|
|
Errorf("Failed to parse XML file '%s'. Tree failed to load. Bugstar streaming video is disabled.", filePath);
|
|
|
|
return false;
|
|
}
|
|
|
|
parTreeNode* root = tree->GetRoot();
|
|
|
|
if (root == nullptr)
|
|
{
|
|
Errorf("Failed to parse XML file '%s'. No root element. Bugstar streaming video is disabled.", filePath);
|
|
|
|
delete tree;
|
|
return false;
|
|
}
|
|
|
|
parElement& element = root->GetElement();
|
|
|
|
char const* rootName = element.GetName();
|
|
|
|
char const* rootTag = "BugstarStreamingVideo";
|
|
char const* baseUriTag = "BaseUri";
|
|
|
|
if (strcmp(rootName, rootTag) != 0)
|
|
{
|
|
Errorf("Failed to parse XML file '%s'. Missing root element '%s'. Bugstar streaming video is disabled.", filePath, rootTag);
|
|
delete tree;
|
|
return false;
|
|
}
|
|
|
|
for (parTreeNode* child = root->GetChild() ; child != nullptr ; child = child->GetSibling())
|
|
{
|
|
parElement& element = child->GetElement();
|
|
|
|
char const* name = element.GetName();
|
|
char const* data = child->GetData();
|
|
|
|
if (strcmp(name, baseUriTag) == 0)
|
|
{
|
|
safecpy(sm_streamingVideoBaseUri, data);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Check for compulsory values.
|
|
|
|
if (*sm_streamingVideoBaseUri == '\0')
|
|
{
|
|
Errorf("Failed to parse XML file '%s'. Missing element '%s'. Bugstar streaming video is disabled.", filePath, baseUriTag);
|
|
delete tree;
|
|
return false;
|
|
}
|
|
|
|
Displayf("Loaded bugstar streaming video settings '%s'.", filePath);
|
|
delete tree;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CBugstarIntegration::IsStreamingVideo()
|
|
{
|
|
|
|
#if RSG_ORBIS
|
|
return NetworkInterface::IsLiveStreamingEnabled();
|
|
#else
|
|
return true; // Assume it's streaming video by default.
|
|
#endif
|
|
|
|
}
|
|
|
|
char const* CBugstarIntegration::GetStreamingVideoMessage()
|
|
{
|
|
if (PARAM_bugstarStreamingVideo.Get() == false)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (sm_streamingVideoErrorMessage != nullptr)
|
|
{
|
|
return sm_streamingVideoErrorMessage;
|
|
}
|
|
|
|
if (IsStreamingVideoEnabled() == false)
|
|
{
|
|
return "B* streaming video disabled";
|
|
}
|
|
|
|
if (IsStreamingVideo() == false)
|
|
{
|
|
return "Enable streaming";
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool CBugstarIntegration::SetStreamingVideoSettings()
|
|
{
|
|
if (IsStreamingVideo() == true)
|
|
{
|
|
Errorf("Cannot set bugstar streaming video setting while currently streaming video.");
|
|
return false;
|
|
}
|
|
|
|
char guidStr[64];
|
|
|
|
rage::atGuid guid = rage::atGuid::Generate();
|
|
|
|
guid.GetAsString(guidStr, sizeof(guidStr), rage::sysGuidUtil::kGuidStringFormatNonDecorated);
|
|
|
|
char forename[64];
|
|
|
|
char* firstDot = strchr(m_sUsernameVis, '.');
|
|
|
|
if (firstDot != nullptr)
|
|
{
|
|
int forenameLength = int(firstDot - m_sUsernameVis);
|
|
|
|
memcpy(forename, m_sUsernameVis, forenameLength);
|
|
|
|
forename[forenameLength] = '\0';
|
|
}
|
|
else
|
|
{
|
|
strcpy(forename, m_sUsernameVis);
|
|
}
|
|
|
|
snprintf(sm_streamingVideoId, sizeof(sm_streamingVideoId), "%s.Continuous.%s", forename, guidStr);
|
|
|
|
sm_streamingVideoId[sizeof(sm_streamingVideoId) - 1] = '\0';
|
|
|
|
#if RSG_ORBIS
|
|
return SetStreamingVideoSettingsOrbis();
|
|
#else
|
|
return true;
|
|
#endif
|
|
|
|
}
|
|
|
|
#if RSG_ORBIS
|
|
bool CBugstarIntegration::ValidateStreamingVideoSettingsOrbis()
|
|
{
|
|
enum Comparison
|
|
{
|
|
COMPARISON_EQUALS = 0,
|
|
COMPARISON_GREATER_EQUALS,
|
|
};
|
|
|
|
struct OrbisSetting
|
|
{
|
|
char const* key;
|
|
Comparison comparison;
|
|
int value;
|
|
char const* name;
|
|
};
|
|
|
|
// These are the orbis settings and their expected values that we are validating.
|
|
|
|
static OrbisSetting const settings[] =
|
|
{
|
|
{ "0x32010000", COMPARISON_EQUALS, 0, "Live Streaming Mode" },
|
|
{ "0x32020000", COMPARISON_EQUALS, 0, "Social Feedback Mode" },
|
|
{ "0x32890000", COMPARISON_GREATER_EQUALS, 5, "Live Quality: Debug" },
|
|
};
|
|
|
|
bool foundSettings[COUNTOF(settings)];
|
|
|
|
for (int index = 0 ; index < COUNTOF(foundSettings) ; ++index)
|
|
{
|
|
foundSettings[index] = false;
|
|
}
|
|
|
|
char commandline[256];
|
|
|
|
char filePath[MAX_PATH];
|
|
|
|
formatf(filePath, "%s\\ExportedOrbisSettings.xml", sm_streamingVideoRootDirectory);
|
|
|
|
snprintf(commandline, sizeof(commandline), "orbis-ctrl.exe settings-export %s", filePath);
|
|
|
|
int commandResult = sysExec(commandline);
|
|
|
|
if (commandResult != 0)
|
|
{
|
|
Assertf(false, "Error: Failed to get orbis settings. command:'%s'", commandline);
|
|
return false;
|
|
}
|
|
|
|
fiStream* stream = fiStream::Open(filePath, true);
|
|
|
|
if (stream == nullptr)
|
|
{
|
|
Displayf("Failed to load '%s'. Bugstar streaming video is disabled.", filePath);
|
|
return false;
|
|
}
|
|
|
|
// Set the parser settings to truncate long attributes as there are some very long names and descriptions
|
|
// written out of orbis-ctrl that can exceed the maximum parcodegen attribute size.
|
|
parSettings parserSettings = parSettings::sm_StandardSettings;
|
|
|
|
parserSettings.SetFlag(parSettings::TRUNCATE_ATTRIBUTES, true);
|
|
|
|
parStreamInXml parserStream(stream, parserSettings);
|
|
|
|
parTree* tree = PARSER.LoadTree(&parserStream);
|
|
|
|
if (tree == nullptr)
|
|
{
|
|
Errorf("Failed to parse XML file '%s'. Tree failed to load. Bugstar streaming video is disabled.", filePath);
|
|
return false;
|
|
}
|
|
|
|
parTreeNode* root = tree->GetRoot();
|
|
|
|
if (root == nullptr)
|
|
{
|
|
Errorf("Failed to parse XML file '%s'. No root element. Bugstar streaming video is disabled.", filePath);
|
|
|
|
delete tree;
|
|
return false;
|
|
}
|
|
|
|
parElement& element = root->GetElement();
|
|
|
|
char const* rootName = element.GetName();
|
|
|
|
char const* rootTag = "settings";
|
|
char const* settingTag = "setting";
|
|
char const* keyAttr = "key";
|
|
char const* valueAttr = "value";
|
|
|
|
if (strcmp(rootName, rootTag) != 0)
|
|
{
|
|
Errorf("Failed to parse XML file '%s'. Missing root element '%s'. Bugstar streaming video is disabled.", filePath, rootTag);
|
|
delete tree;
|
|
return false;
|
|
}
|
|
|
|
for (parTreeNode* child = root->GetChild() ; child != nullptr ; child = child->GetSibling())
|
|
{
|
|
parElement& element = child->GetElement();
|
|
|
|
char const* name = element.GetName();
|
|
|
|
if (strcmp(name, settingTag) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
atArray<parAttribute> const& attributes = element.GetAttributes();
|
|
|
|
int attributeCount = attributes.GetCount();
|
|
|
|
// Get the key attribute.
|
|
|
|
int foundSettingIndex = -1;
|
|
|
|
for (int index = 0 ; index < attributeCount ; ++index)
|
|
{
|
|
parAttribute const& attr = attributes[index];
|
|
|
|
char const* name = attr.GetName();
|
|
|
|
if (stricmp(name, keyAttr) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// See if this key is one of the settings we are looking for.
|
|
|
|
char const* currentKey = attr.GetStringValue();
|
|
|
|
for (int settingIndex = 0 ; settingIndex < COUNTOF(settings) ; ++settingIndex)
|
|
{
|
|
if (foundSettings[settingIndex] == true)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
OrbisSetting const& currentSetting = settings[settingIndex];
|
|
|
|
if (stricmp(currentKey, currentSetting.key) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foundSettingIndex = settingIndex;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (foundSettingIndex == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
OrbisSetting const& currentSetting = settings[foundSettingIndex];
|
|
|
|
int valueInt = -1;
|
|
|
|
for (int index = 0 ; index < attributeCount ; ++index)
|
|
{
|
|
parAttribute const& attr = attributes[index];
|
|
|
|
char const* name = attr.GetName();
|
|
|
|
if (stricmp(name, valueAttr) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int type = attr.GetType();
|
|
|
|
switch (type)
|
|
{
|
|
|
|
case parAttribute::INT64:
|
|
{
|
|
valueInt = attr.GetIntValue();
|
|
}
|
|
break;
|
|
|
|
case parAttribute::STRING:
|
|
{
|
|
char const* value = attr.GetStringValue();
|
|
|
|
if (strncmp(value, "0x", 2) == 0)
|
|
{
|
|
sscanf(value, "0x%x", &valueInt);
|
|
}
|
|
else
|
|
{
|
|
sscanf(value, "%i", &valueInt);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
Errorf("Failed to parse value for orbis setting %s (%s). Bugstar streaming video is disabled.", currentSetting.key, currentSetting.name);
|
|
delete tree;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
bool passed = false;
|
|
|
|
switch (currentSetting.comparison)
|
|
{
|
|
|
|
case COMPARISON_EQUALS:
|
|
passed = valueInt == currentSetting.value;
|
|
break;
|
|
|
|
case COMPARISON_GREATER_EQUALS:
|
|
passed = valueInt >= currentSetting.value;
|
|
break;
|
|
|
|
}
|
|
|
|
if (passed == false)
|
|
{
|
|
Errorf("Error: Orbis setting %s (%s) has incorrect value %i. Streaming video is disabled.", currentSetting.key, currentSetting.name, valueInt);
|
|
delete tree;
|
|
return false;
|
|
}
|
|
|
|
foundSettings[foundSettingIndex] = true;
|
|
}
|
|
|
|
for (int index = 0 ; index < COUNTOF(foundSettings) ; ++index)
|
|
{
|
|
if (foundSettings[index] == false)
|
|
{
|
|
Errorf("Error: Did not find orbis setting %s (%s). Streaming video is disabled.", settings[index].key, settings[index].name);
|
|
delete tree;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Displayf("Validated orbis streaming video settings '%s'.", filePath);
|
|
|
|
delete tree;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CBugstarIntegration::AddSettingOrbis(parTreeNode* rootNode, char const* key, char const* dest, char const* size, char const* type, char const* value, char const* eng)
|
|
{
|
|
parTreeNode* node = rage_new parTreeNode("setting");
|
|
|
|
parElement& element = node->GetElement();
|
|
|
|
element.AddAttribute("key", key);
|
|
element.AddAttribute("dest", dest);
|
|
element.AddAttribute("size", size);
|
|
element.AddAttribute("type", type);
|
|
element.AddAttribute("value", value);
|
|
element.AddAttribute("eng", eng);
|
|
|
|
node->AppendAsChildOf(rootNode);
|
|
}
|
|
|
|
bool CBugstarIntegration::SetStreamingVideoSettingsOrbis()
|
|
{
|
|
parTree *tree = rage_new parTree();
|
|
|
|
parTreeNode* rootNode = rage_new parTreeNode("settings");
|
|
|
|
tree->SetRoot(rootNode);
|
|
|
|
parElement& rootElement = rootNode->GetElement();
|
|
|
|
rootElement.AddAttribute("version", 1);
|
|
rootElement.AddAttribute("target", "orbis");
|
|
|
|
char url[256];
|
|
|
|
formatf(url, "%s/%s", sm_streamingVideoBaseUri, sm_streamingVideoId);
|
|
|
|
AddSettingOrbis(rootNode, "0x32030000", "0x00", "256", "0x1", url, "Broadcast URL");
|
|
AddSettingOrbis(rootNode, "0x37070000", "0x00", "4", "0x0", "0x1", "SNS Contents Sharing Test from Share Button");
|
|
|
|
char filePath[MAX_PATH];
|
|
|
|
formatf(filePath, "%s\\GameOrbisSettings.xml", sm_streamingVideoRootDirectory);
|
|
|
|
PARSER.SaveTree(filePath, "", tree, rage::parManager::XML);
|
|
|
|
delete tree;
|
|
|
|
char commandline[256];
|
|
|
|
snprintf(commandline, sizeof(commandline), "orbis-ctrl.exe settings-import %s", filePath);
|
|
|
|
int commandResult = sysExec(commandline);
|
|
|
|
Assertf(commandResult == 0, "Failed to execute command '%s'", commandline);
|
|
|
|
if (commandResult == 0)
|
|
{
|
|
Displayf("Updated streaming video settings. url:'%s' command:'%s'", url, commandline);
|
|
}
|
|
else
|
|
{
|
|
Displayf("Error: Failed to update streaming video settings. command:'%s'", commandline);
|
|
}
|
|
|
|
return commandResult == 0;
|
|
}
|
|
#endif // RSG_ORBIS
|
|
|
|
//***********************************
|
|
// CBugstarComponentLookup
|
|
//***********************************
|
|
|
|
atArray<CBugstarComponentLookup::ComponentData> CBugstarComponentLookup::m_ComponentLookupData;
|
|
bool CBugstarComponentLookup::m_Initialised = false;
|
|
|
|
void CBugstarComponentLookup::Init()
|
|
{
|
|
CreateComponentLookupTable();
|
|
}
|
|
|
|
void CBugstarComponentLookup::Shutdown()
|
|
{
|
|
m_ComponentLookupData.Reset();
|
|
m_Initialised = false;
|
|
}
|
|
|
|
char *CBugstarComponentLookup::GetComponentString(int id)
|
|
{
|
|
for(int i=0;i<m_ComponentLookupData.size();i++)
|
|
{
|
|
if(m_ComponentLookupData[i].m_ID == id)
|
|
{
|
|
return m_ComponentLookupData[i].m_Name;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Create the component lookup table.
|
|
void CBugstarComponentLookup::CreateComponentLookupTable()
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
atArray<ComponentWorkData> workData;
|
|
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sComponents",BUGSTAR_GTA_PROJECT_PATH);
|
|
parTree* pTree = CBugstarIntegration::CreateXmlTreeFromHttpQuery(search);
|
|
if(pTree)
|
|
{
|
|
parTreeNode* root = pTree->GetRoot();
|
|
parTreeNode::ChildNodeIterator i = root->BeginChildren();
|
|
for(; i != root->EndChildren(); ++i)
|
|
{
|
|
parTreeNode* id = (*i)->FindFromXPath("id"); // The component ID (number as a string)
|
|
if(id)
|
|
{
|
|
ComponentWorkData thisData;
|
|
|
|
parTreeNode *name = (*i)->FindFromXPath("name"); // The name of this component (as a string)
|
|
parTreeNode *parentID = (*i)->FindFromXPath("parentId"); // The name of this components parent (number as a string)
|
|
|
|
thisData.m_ID = atoi((char*)id->GetData());
|
|
// ParentID is optional
|
|
thisData.m_ParentID = 0; // Let hope 0 is unused... suspect it is!
|
|
if(parentID)
|
|
{
|
|
thisData.m_ParentID = atoi((char*)parentID->GetData());
|
|
}
|
|
strncpy(thisData.m_Name, (char*)name->GetData(), MAX_COMPONENT_NAME_LENGTH);
|
|
workData.Grow() = thisData;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
m_ComponentLookupData.Reset();
|
|
|
|
for(int i=0;i<workData.size();i++)
|
|
{
|
|
ComponentWorkData *pData = &workData[i];
|
|
|
|
ComponentData thisData;
|
|
thisData.m_ID = pData->m_ID;
|
|
|
|
int nameIDX = 0;
|
|
char constructedName[2][512];
|
|
|
|
strcpy(constructedName[nameIDX], pData->m_Name);
|
|
while( pData->m_ParentID != 0 )
|
|
{
|
|
nameIDX = 1-nameIDX; // Toggle buffer to use
|
|
pData = FindComponentData(workData, pData->m_ParentID);
|
|
|
|
Assertf(pData, "Component Parent Doesn't Exist. It's going to go wrong :(");
|
|
|
|
sprintf(constructedName[nameIDX],"%s : %s", pData->m_Name, constructedName[1-nameIDX]);
|
|
}
|
|
strncpy(thisData.m_Name, constructedName[nameIDX], MAX_COMPONENT_DESCRIPTION_LENGTH );
|
|
m_ComponentLookupData.Grow() = thisData;
|
|
}
|
|
|
|
m_Initialised = true;
|
|
}
|
|
|
|
// Find a particular component from it's ID.
|
|
CBugstarComponentLookup::ComponentWorkData *CBugstarComponentLookup::FindComponentData(atArray<ComponentWorkData> &lookupData, int componentID)
|
|
{
|
|
for(int i=0;i<lookupData.size();i++)
|
|
{
|
|
ComponentWorkData *pData = &lookupData[i];
|
|
|
|
if(pData->m_ID == componentID)
|
|
{
|
|
return pData;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
//***********************************
|
|
|
|
//***********************************
|
|
// CBugstarStateLookup
|
|
//***********************************
|
|
|
|
atArray<CBugstarStateLookup::StateData> CBugstarStateLookup::m_StateLookupData;
|
|
bool CBugstarStateLookup::m_Initialised = false;
|
|
|
|
void CBugstarStateLookup::Init()
|
|
{
|
|
CreateStateLookupTable();
|
|
}
|
|
|
|
void CBugstarStateLookup::Shutdown()
|
|
{
|
|
m_StateLookupData.Reset();
|
|
m_Initialised = false;
|
|
}
|
|
|
|
void CBugstarStateLookup::CreateStateLookupTable()
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
m_StateLookupData.Reset();
|
|
|
|
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sStates",BUGSTAR_GTA_PROJECT_PATH);
|
|
parTree* pTree = CBugstarIntegration::CreateXmlTreeFromHttpQuery(search);
|
|
if(pTree)
|
|
{
|
|
parTreeNode* root = pTree->GetRoot();
|
|
parTreeNode::ChildNodeIterator i = root->BeginChildren();
|
|
for(; i != root->EndChildren(); ++i)
|
|
{
|
|
parTreeNode* id = (*i)->FindFromXPath("name"); // The name of this state
|
|
if(id)
|
|
{
|
|
StateData thisData;
|
|
|
|
parTreeNode *description = (*i)->FindFromXPath("description"); // The name of this state (the thing that is printed in B*)
|
|
|
|
thisData.m_Name = atHashString((char*)id->GetData());
|
|
strncpy(thisData.m_Description, (char*)description->GetData(), MAX_DESCRIPTION_LENGTH);
|
|
m_StateLookupData.Grow() = thisData;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_Initialised = true;
|
|
}
|
|
|
|
char *CBugstarStateLookup::GetDescriptionFromState(char *state)
|
|
{
|
|
atHashString stateHash(state);
|
|
|
|
for(int i=0;i<m_StateLookupData.size();i++)
|
|
{
|
|
if( m_StateLookupData[i].m_Name == stateHash )
|
|
{
|
|
return m_StateLookupData[i].m_Description;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
//***********************************
|
|
|
|
//***********************************
|
|
// CBugstarUserLookup
|
|
//***********************************
|
|
|
|
atArray<CBugstarUserLookup::UserData> CBugstarUserLookup::m_UserLookupData;
|
|
bool CBugstarUserLookup::m_Initialised = false;
|
|
|
|
void CBugstarUserLookup::Init()
|
|
{
|
|
CreateUserLookupTable();
|
|
}
|
|
|
|
void CBugstarUserLookup::Shutdown()
|
|
{
|
|
m_UserLookupData.Reset();
|
|
m_Initialised = false;
|
|
}
|
|
|
|
void CBugstarUserLookup::CreateUserLookupTable()
|
|
{
|
|
USE_DEBUG_MEMORY();
|
|
|
|
m_UserLookupData.Reset();
|
|
|
|
char search[BUG_LOGIN_SEARCH_LENGTH];
|
|
sprintf(search,"%sUsers",BUGSTAR_GTA_PROJECT_PATH);
|
|
parTree* pTree = CBugstarIntegration::CreateXmlTreeFromHttpQuery(search);
|
|
if(pTree)
|
|
{
|
|
parTreeNode* root = pTree->GetRoot();
|
|
parTreeNode::ChildNodeIterator i = root->BeginChildren();
|
|
for(; i != root->EndChildren(); ++i)
|
|
{
|
|
parTreeNode* id = (*i)->FindFromXPath("id"); // userID
|
|
if(id)
|
|
{
|
|
UserData thisData;
|
|
|
|
parTreeNode *userName = (*i)->FindFromXPath("username"); // The name of this user
|
|
|
|
thisData.m_Name = atHashString((char*)userName->GetData());
|
|
thisData.m_ID = atoi((char*)id->GetData());
|
|
m_UserLookupData.Grow() = thisData;
|
|
}
|
|
}
|
|
}
|
|
m_Initialised = true;
|
|
}
|
|
|
|
u32 CBugstarUserLookup::GetUserIDFromNameHash(u32 hash)
|
|
{
|
|
for(int i=0;i<m_UserLookupData.size();i++)
|
|
{
|
|
UserData &thisUserData = m_UserLookupData[i];
|
|
if(thisUserData.m_Name.GetHash() == hash)
|
|
{
|
|
return thisUserData.m_ID;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif //BUGSTAR_INTEGRATION_ACTIVE
|
|
|