/***************************************************************************** * gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2018 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . *****************************************************************************/ #include "TelemetryClassAuthenticator.h" #include "TelemetryClass.h" #include "StandardPaths.h" #include "AppEnv.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef GTA5SYNC_TELEMETRY_WEBURL #define GTA5SYNC_TELEMETRY_WEBURL "" #endif #ifdef GTA5SYNC_WIN #include "windows.h" #include "intrin.h" #include "d3d9.h" #endif TelemetryClass TelemetryClass::telemetryClassInstance; void TelemetryClass::init() { QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); settings.beginGroup("Telemetry"); telemetryEnabled = true; telemetryStateForced = true; QString telemetryLegacyClientID = settings.value("ClientID", QString()).toString(); if (telemetryLegacyClientID.isEmpty() || telemetryLegacyClientID == "v2+") { telemetryClientID = QString::fromUtf8(QByteArray::fromBase64(settings.value("Identification", QByteArray()).toByteArray())); } else { QDir dir; dir.mkpath(StandardPaths::dataLocation()); dir.setPath(StandardPaths::dataLocation()); QString dirPath = dir.absolutePath(); QString portLoc = dirPath % "/.ported"; bool telemetryPortedKey = settings.value("IsPorted", false).toBool(); bool telemetryPortedFile = QFile::exists(portLoc); if (!telemetryPortedKey && !telemetryPortedFile) { QFile portFile(portLoc); if (portFile.open(QFile::WriteOnly)) { portFile.write("\n"); portFile.flush(); } portFile.close(); telemetryClientID = telemetryLegacyClientID; settings.setValue("Identification", telemetryLegacyClientID.toUtf8().toBase64()); settings.setValue("IsPorted", true); settings.setValue("ClientID", "v2+"); } else { telemetryClientID = QString(); } } telemetryPushAppConf = settings.value("PushAppConf", false).toBool(); settings.endGroup(); } void TelemetryClass::refresh() { init(); } bool TelemetryClass::canPush() { if (!isEnabled() || !isRegistered() || !TelemetryClassAuthenticator::havePushURL()) return false; return true; } bool TelemetryClass::canRegister() { QDir dir; dir.mkpath(StandardPaths::dataLocation()); dir.setPath(StandardPaths::dataLocation()); QString dirPath = dir.absolutePath(); QString regLoc = dirPath % "/.reg"; if (QFile::exists(regLoc)) return false; if (!isEnabled() || isRegistered() || !TelemetryClassAuthenticator::haveRegURL()) return false; return true; } bool TelemetryClass::isEnabled() { return telemetryEnabled; } bool TelemetryClass::isStateForced() { return telemetryStateForced; } bool TelemetryClass::isRegistered() { return !telemetryClientID.isEmpty(); } QString TelemetryClass::getRegisteredID() { return telemetryClientID; } void TelemetryClass::setEnabled(bool enabled) { telemetryEnabled = enabled; telemetryStateForced = true; } void TelemetryClass::setDisabled(bool disabled) { telemetryEnabled = !disabled; telemetryStateForced = true; } void TelemetryClass::push(TelemetryCategory category) { if (!canPush()) return; switch (category) { case TelemetryCategory::OperatingSystemSpec: push(category, getOperatingSystem()); break; case TelemetryCategory::HardwareSpec: push(category, getSystemHardware()); break; case TelemetryCategory::UserLocaleData: push(category, getSystemLocaleList()); break; case TelemetryCategory::ApplicationConf: push(category, getApplicationConf()); break; case TelemetryCategory::ApplicationSpec: push(category, getApplicationSpec()); break; case TelemetryCategory::UserFeedback: break; case TelemetryCategory::PersonalData: break; case TelemetryCategory::CustomEmitted: break; } } void TelemetryClass::push(TelemetryCategory category, QJsonDocument json) { if (!canPush()) return; QJsonDocument jsonDocument(json); QJsonObject jsonObject = jsonDocument.object(); jsonObject["ClientID"] = telemetryClientID; jsonDocument.setObject(jsonObject); QHttpMultiPart *httpMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart categoryPart; categoryPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"json-category\"")); categoryPart.setBody(categoryToString(category).toUtf8()); QHttpPart jsonPart; jsonPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"json-deflated\"")); jsonPart.setBody(qCompress(jsonDocument.toJson(QJsonDocument::Compact))); httpMultiPart->append(categoryPart); httpMultiPart->append(jsonPart); QNetworkAccessManager *netManager = new QNetworkAccessManager(); QNetworkRequest netRequest(TelemetryClassAuthenticator::getTrackingPushURL()); netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); QNetworkReply *netReply = netManager->post(netRequest, httpMultiPart); httpMultiPart->setParent(netReply); connect(netManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(pushFinished(QNetworkReply*))); } QJsonDocument TelemetryClass::getOperatingSystem() { QJsonDocument jsonDocument; QJsonObject jsonObject; #if QT_VERSION >= 0x050400 jsonObject["KernelType"] = QSysInfo::kernelType(); jsonObject["KernelVersion"] = QSysInfo::kernelVersion(); jsonObject["ProductType"] = QSysInfo::productType(); jsonObject["ProductVersion"] = QSysInfo::productVersion(); jsonObject["OSName"] = QSysInfo::prettyProductName(); jsonObject["OSArch"] = QSysInfo::currentCpuArchitecture(); #endif jsonDocument.setObject(jsonObject); return jsonDocument; } QJsonDocument TelemetryClass::getSystemHardware() { QJsonDocument jsonDocument; QJsonObject jsonObject; #ifdef GTA5SYNC_WIN { int CPUInfo[4] = {-1}; unsigned nExIds, ic = 0; char CPUBrandString[0x40]; __cpuid(CPUInfo, 0x80000000); nExIds = CPUInfo[0]; for (ic = 0x80000000; ic <= nExIds; ic++) { __cpuid(CPUInfo, ic); if (ic == 0x80000002) { memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); } else if (ic == 0x80000003) { memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); } else if (ic == 0x80000004) { memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); } } jsonObject["CPUName"] = QString::fromLatin1(CPUBrandString).simplified(); SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); jsonObject["CPUThreads"] = QString::number(sysInfo.dwNumberOfProcessors); MEMORYSTATUSEX statex; statex.dwLength = sizeof(statex); GlobalMemoryStatusEx(&statex); jsonObject["SystemRAM"] = QString(QString::number((statex.ullTotalPhys / 1024) / 1024) % "MB"); QStringList gpusList; IDirect3D9 *pD3D = Direct3DCreate9(D3D_SDK_VERSION); int adapters = pD3D->GetAdapterCount(); for (int ia = 0; ia < adapters; ia++) { D3DADAPTER_IDENTIFIER9 d3dIdent; HRESULT result = pD3D->GetAdapterIdentifier(ia, 0, &d3dIdent); if (result == D3D_OK) { QString gpuAdapter = QString::fromLatin1(d3dIdent.Description); if (!gpusList.contains(gpuAdapter)) { gpusList << gpuAdapter; } } } pD3D->Release(); jsonObject["GPUs"] = QJsonValue::fromVariant(gpusList); } #else QDir procDir("/proc"); if (procDir.exists()) { QFile cpuInfo("/proc/cpuinfo"); if (cpuInfo.open(QFile::ReadOnly)) { QByteArray cpuInfoArray = cpuInfo.readAll(); QBuffer cpuInfoBuffer(&cpuInfoArray); if (cpuInfoBuffer.open(QBuffer::ReadOnly)) { QByteArray toFind = "model name"; while (cpuInfoBuffer.canReadLine()) { QByteArray cpuData = cpuInfoBuffer.readLine(); if (cpuData.left(toFind.length()) == toFind) { jsonObject["CPUName"] = QString::fromUtf8(cpuData).split(':').at(1).simplified(); break; } } int cpuThreads = 0; toFind = "processor"; cpuInfoBuffer.seek(0); while (cpuInfoBuffer.canReadLine()) { QByteArray cpuData = cpuInfoBuffer.readLine(); if (cpuData.left(toFind.length()) == toFind) { cpuThreads++; } } jsonObject["CPUThreads"] = QString::number(cpuThreads); } } QFile memInfo("/proc/meminfo"); if (memInfo.open(QFile::ReadOnly)) { QByteArray memInfoArray = memInfo.readAll(); QBuffer memInfoBuffer(&memInfoArray); if (memInfoBuffer.open(QBuffer::ReadOnly)) { QByteArray toFind = "MemTotal:"; while (memInfoBuffer.canReadLine()) { QByteArray memData = memInfoBuffer.readLine(); if (memData.left(toFind.length()) == toFind) { QByteArray memDataVal = memData.mid(toFind.length()).trimmed(); int totalMemoryInKB = memDataVal.left(memDataVal.length() - 3).toInt(); jsonObject["SystemRAM"] = QString(QString::number(totalMemoryInKB / 1024) % "MB"); break; } } } } } #endif jsonDocument.setObject(jsonObject); return jsonDocument; } QJsonDocument TelemetryClass::getApplicationSpec() { QJsonDocument jsonDocument; QJsonObject jsonObject; #if QT_VERSION >= 0x050400 jsonObject["Arch"] = QSysInfo::buildCpuArchitecture(); #endif jsonObject["Name"] = GTA5SYNC_APPSTR; #ifdef GTA5SYNC_COMMIT jsonObject["Commit"] = GTA5SYNC_COMMIT; #endif jsonObject["Version"] = GTA5SYNC_APPVER; jsonObject["BuildDateTime"] = AppEnv::getBuildDateTime(); jsonObject["BuildType"] = GTA5SYNC_BUILDTYPE; jsonObject["BuildCode"] = AppEnv::getBuildCode(); jsonObject["QtVersion"] = qVersion(); jsonDocument.setObject(jsonObject); return jsonDocument; } QJsonDocument TelemetryClass::getApplicationConf() { QJsonDocument jsonDocument; QJsonObject jsonObject; QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); settings.beginGroup("Interface"); QJsonObject interfaceObject; interfaceObject["AreaLanguage"] = settings.value("AreaLanguage", "Auto").toString(); interfaceObject["Language"] = settings.value("Language", "System").toString(); interfaceObject["NavigationBar"] = settings.value("NavigationBar", false).toBool(); jsonObject["Interface"] = interfaceObject; settings.endGroup(); settings.beginGroup("Pictures"); QJsonObject picturesObject; picturesObject["AspectRatio"] = ((Qt::AspectRatioMode)settings.value("AspectRatio").toInt() == Qt::IgnoreAspectRatio) ? "IgnoreAspectRatio" : "KeepAspectRatio"; picturesObject["CustomQuality"] = settings.value("CustomQuality", 100).toInt(); picturesObject["CustomQualityEnabled"] = settings.value("CustomQualityEnabled", false).toBool(); picturesObject["ExportSizeMode"] = settings.value("ExportSizeMode", "Default").toString(); jsonObject["Pictures"] = picturesObject; settings.endGroup(); settings.beginGroup("Profile"); QJsonObject profileObject; int contentMode = settings.value("ContentMode", 0).toInt(); switch (contentMode) { case 0: profileObject["ContentMode"] = "OpenWithSingleClick"; break; case 1: profileObject["ContentMode"] = "OpenWithDoubleClick"; break; case 2: profileObject["ContentMode"] = "SelectWithSingleClick"; break; } jsonObject["Profile"] = profileObject; settings.endGroup(); settings.beginGroup("Startup"); QJsonObject startupObject; startupObject["AppStyle"] = settings.value("AppStyle", "System").toString(); startupObject["CustomStyle"] = settings.value("CustomStyle", false).toBool(); startupObject["StartCount"] = QString::number(settings.value("StartCount", 0).toUInt()); jsonObject["Startup"] = startupObject; settings.endGroup(); jsonDocument.setObject(jsonObject); return jsonDocument; } QJsonDocument TelemetryClass::getSystemLocaleList() { QJsonDocument jsonDocument; QJsonObject jsonObject; QStringList languagesList = QLocale::system().uiLanguages(); if (languagesList.length() >= 1) { jsonObject["PrimaryLanguage"] = languagesList.at(0); } if (languagesList.length() >= 2) { languagesList.removeAt(0); jsonObject["SecondaryLanguages"] = QJsonValue::fromVariant(languagesList); } jsonDocument.setObject(jsonObject); return jsonDocument; } QString TelemetryClass::categoryToString(TelemetryCategory category) { switch (category) { case TelemetryCategory::OperatingSystemSpec: return QString("OperatingSystemSpec"); break; case TelemetryCategory::HardwareSpec: return QString("HardwareSpec"); break; case TelemetryCategory::UserLocaleData: return QString("UserLocaleData"); break; case TelemetryCategory::ApplicationConf: return QString("ApplicationConf"); break; case TelemetryCategory::ApplicationSpec: return QString("ApplicationSpec"); break; case TelemetryCategory::UserFeedback: return QString("UserFeedback"); break; case TelemetryCategory::PersonalData: return QString("PersonalData"); break; case TelemetryCategory::CustomEmitted: return QString("CustomEmitted"); break; default: return QString("UnknownCategory"); break; } } QUrl TelemetryClass::getWebURL() { return QUrl(GTA5SYNC_TELEMETRY_WEBURL); } void TelemetryClass::registerClient() { QNetworkAccessManager *netManager = new QNetworkAccessManager(); QNetworkRequest netRequest(TelemetryClassAuthenticator::getTrackingRegURL()); netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); netManager->get(netRequest); connect(netManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(registerFinished(QNetworkReply*))); } void TelemetryClass::work() { if (!canPush() && canRegister()) { connect(this, SIGNAL(registered(bool)), this, SLOT(work_pd(bool))); registerClient(); } else if (canPush()) { work_p(true); } } void TelemetryClass::work_p(bool doWork) { if (doWork) { push(TelemetryCategory::ApplicationSpec); push(TelemetryCategory::UserLocaleData); push(TelemetryCategory::OperatingSystemSpec); push(TelemetryCategory::HardwareSpec); if (telemetryPushAppConf) { push(TelemetryCategory::ApplicationConf); } else { push(TelemetryCategory::ApplicationConf, QJsonDocument()); } } } void TelemetryClass::work_pd(bool doWork) { disconnect(this, SIGNAL(registered(bool)), this, SLOT(work_pd(bool))); work_p(doWork); } void TelemetryClass::pushFinished(QNetworkReply *reply) { bool isSuccessful = false; if (reply->canReadLine()) { QByteArray readedData = reply->readLine(); if (QString::fromUtf8(readedData).trimmed() == QString("Submit success!")) { #ifdef GTA5SYNC_DEBUG qDebug() << "Telemetry" << QString("Submit success!"); #endif isSuccessful = true; #ifdef GTA5SYNC_DEBUG if (reply->isReadable()) { readedData = reply->readAll().trimmed(); if (!readedData.isEmpty()) { qDebug() << "Telemetry Push" << readedData; } } #endif } else { #ifdef GTA5SYNC_DEBUG qDebug() << "Telemetry" << QString("Submit failed!"); #endif } } else { #ifdef GTA5SYNC_DEBUG qDebug() << "Telemetry" << QString("Submit failed!"); #endif } reply->deleteLater(); sender()->deleteLater(); emit pushed(isSuccessful); } void TelemetryClass::registerFinished(QNetworkReply *reply) { bool isSuccessful = false; if (reply->canReadLine()) { QByteArray readedData = reply->readLine(); if (QString::fromUtf8(readedData).trimmed() == QString("Registration success!") && reply->canReadLine()) { QDir dir; dir.mkpath(StandardPaths::dataLocation()); dir.setPath(StandardPaths::dataLocation()); QString dirPath = dir.absolutePath(); QString regLoc = dirPath % "/.reg"; readedData = reply->readLine(); telemetryClientID = QString::fromUtf8(readedData).trimmed(); QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); settings.beginGroup("Telemetry"); settings.setValue("Identification", telemetryClientID.toUtf8().toBase64()); settings.endGroup(); QFile regFile(regLoc); if (regFile.open(QFile::WriteOnly)) { regFile.write("\n"); regFile.flush(); } regFile.close(); #ifdef GTA5SYNC_DEBUG qDebug() << "Telemetry" << QString("Registration success!"); #endif isSuccessful = true; } else { #ifdef GTA5SYNC_DEBUG qDebug() << "Telemetry" << QString("Registration failed!"); #endif } } else { #ifdef GTA5SYNC_DEBUG qDebug() << "Telemetry" << QString("Registration failed!"); #endif } reply->deleteLater(); sender()->deleteLater(); emit registered(isSuccessful); }