From 2fbb3205399c45ed33d439cf52401b0601af0d07 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 30 Apr 2021 20:05:38 -0500 Subject: [PATCH] Refuse to stream if Moonlight is known incompatible with the host GFE version --- app/app.pro | 2 + app/backend/computermanager.cpp | 6 +- app/backend/computermanager.h | 2 + app/backend/nvcomputer.cpp | 4 ++ app/backend/nvcomputer.h | 1 + app/gui/PcView.qml | 7 +- app/gui/computermodel.cpp | 3 + app/gui/computermodel.h | 3 +- app/settings/compatfetcher.cpp | 112 ++++++++++++++++++++++++++++++++ app/settings/compatfetcher.h | 22 +++++++ app/streaming/session.cpp | 5 ++ 11 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 app/settings/compatfetcher.cpp create mode 100644 app/settings/compatfetcher.h diff --git a/app/app.pro b/app/app.pro index d9bd207f..3f6273fb 100644 --- a/app/app.pro +++ b/app/app.pro @@ -131,6 +131,7 @@ SOURCES += \ cli/commandlineparser.cpp \ cli/quitstream.cpp \ cli/startstream.cpp \ + settings/compatfetcher.cpp \ settings/mappingfetcher.cpp \ settings/streamingpreferences.cpp \ streaming/input/abstouch.cpp \ @@ -156,6 +157,7 @@ SOURCES += \ HEADERS += \ backend/nvapp.h \ + settings/compatfetcher.h \ settings/mappingfetcher.h \ utils.h \ backend/computerseeker.h \ diff --git a/app/backend/computermanager.cpp b/app/backend/computermanager.cpp index 77d95302..a0303b86 100644 --- a/app/backend/computermanager.cpp +++ b/app/backend/computermanager.cpp @@ -146,7 +146,8 @@ private: ComputerManager::ComputerManager(QObject *parent) : QObject(parent), m_PollingRef(0), - m_MdnsBrowser(nullptr) + m_MdnsBrowser(nullptr), + m_CompatFetcher(nullptr) { QSettings settings; @@ -159,6 +160,9 @@ ComputerManager::ComputerManager(QObject *parent) } settings.endArray(); + // Fetch latest compatibility data asynchronously + m_CompatFetcher.start(); + // To quit in a timely manner, we must block additional requests // after we receive the aboutToQuit() signal. This is neccessary // because NvHTTP uses aboutToQuit() to abort requests in progres diff --git a/app/backend/computermanager.h b/app/backend/computermanager.h index 9882d299..45f6d2f9 100644 --- a/app/backend/computermanager.h +++ b/app/backend/computermanager.h @@ -2,6 +2,7 @@ #include "nvcomputer.h" #include "nvpairingmanager.h" +#include "settings/compatfetcher.h" #include #include @@ -214,4 +215,5 @@ private: QMdnsEngine::Browser* m_MdnsBrowser; QMdnsEngine::Cache m_MdnsCache; QVector m_PendingResolution; + CompatFetcher m_CompatFetcher; }; diff --git a/app/backend/nvcomputer.cpp b/app/backend/nvcomputer.cpp index f3180912..f1c73f48 100644 --- a/app/backend/nvcomputer.cpp +++ b/app/backend/nvcomputer.cpp @@ -1,5 +1,6 @@ #include "nvcomputer.h" #include "nvapp.h" +#include "settings/compatfetcher.h" #include #include @@ -50,6 +51,7 @@ NvComputer::NvComputer(QSettings& settings) this->serverCodecModeSupport = 0; this->pendingQuit = false; this->gpuModel = nullptr; + this->isSupportedServerVersion = true; } void NvComputer::serialize(QSettings& settings) const @@ -143,6 +145,7 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv this->activeAddress = address; this->state = NvComputer::CS_ONLINE; this->pendingQuit = false; + this->isSupportedServerVersion = CompatFetcher::isGfeVersionSupported(this->gfeVersion); } bool NvComputer::wake() @@ -408,6 +411,7 @@ bool NvComputer::update(NvComputer& that) ASSIGN_IF_CHANGED(state); ASSIGN_IF_CHANGED(gfeVersion); ASSIGN_IF_CHANGED(appVersion); + ASSIGN_IF_CHANGED(isSupportedServerVersion); ASSIGN_IF_CHANGED(maxLumaPixelsHEVC); ASSIGN_IF_CHANGED(gpuModel); ASSIGN_IF_CHANGED_AND_NONNULL(serverCert); diff --git a/app/backend/nvcomputer.h b/app/backend/nvcomputer.h index d66ee5ec..adf5f83f 100644 --- a/app/backend/nvcomputer.h +++ b/app/backend/nvcomputer.h @@ -65,6 +65,7 @@ public: int maxLumaPixelsHEVC; int serverCodecModeSupport; QString gpuModel; + bool isSupportedServerVersion; // Persisted traits QString localAddress; diff --git a/app/gui/PcView.qml b/app/gui/PcView.qml index 6230a3ad..123314ce 100644 --- a/app/gui/PcView.qml +++ b/app/gui/PcView.qml @@ -220,7 +220,12 @@ CenteredGridView { onClicked: { if (model.online) { - if (model.paired) { + if (!model.serverSupported) { + errorDialog.text = qsTr("The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.").arg(model.name) + errorDialog.helpText = "" + errorDialog.open() + } + else if (model.paired) { // go to game view var component = Qt.createComponent("AppView.qml") var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name}) diff --git a/app/gui/computermodel.cpp b/app/gui/computermodel.cpp index f6156ddd..f55a53ec 100644 --- a/app/gui/computermodel.cpp +++ b/app/gui/computermodel.cpp @@ -42,6 +42,8 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const return !computer->macAddress.isEmpty(); case StatusUnknownRole: return computer->state == NvComputer::CS_UNKNOWN; + case ServerSupportedRole: + return computer->isSupportedServerVersion; default: return QVariant(); } @@ -68,6 +70,7 @@ QHash ComputerModel::roleNames() const names[BusyRole] = "busy"; names[WakeableRole] = "wakeable"; names[StatusUnknownRole] = "statusUnknown"; + names[ServerSupportedRole] = "serverSupported"; return names; } diff --git a/app/gui/computermodel.h b/app/gui/computermodel.h index fe743737..f8f391d7 100644 --- a/app/gui/computermodel.h +++ b/app/gui/computermodel.h @@ -14,7 +14,8 @@ class ComputerModel : public QAbstractListModel PairedRole, BusyRole, WakeableRole, - StatusUnknownRole + StatusUnknownRole, + ServerSupportedRole }; public: diff --git a/app/settings/compatfetcher.cpp b/app/settings/compatfetcher.cpp new file mode 100644 index 00000000..2539ba6c --- /dev/null +++ b/app/settings/compatfetcher.cpp @@ -0,0 +1,112 @@ +#include "compatfetcher.h" +#include "path.h" + +#include +#include + +#define COMPAT_VERSION "v1" +#define COMPAT_KEY "latestsupportedversion-" + +CompatFetcher::CompatFetcher(QObject *parent) : + QObject(parent), + m_Nam(this) +{ + // Never communicate over HTTP + m_Nam.setStrictTransportSecurityEnabled(true); + + // Allow HTTP redirects + m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); + + connect(&m_Nam, &QNetworkAccessManager::finished, + this, &CompatFetcher::handleCompatInfoFetched); +} + +void CompatFetcher::start() +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT) + // HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible); + QT_WARNING_POP +#endif + + QUrl url("https://moonlight-stream.org/compatibility/" COMPAT_VERSION); + QNetworkRequest request(url); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true); +#else + request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); +#endif + + // We'll get a callback when this is finished + m_Nam.get(request); +} + +bool CompatFetcher::isGfeVersionSupported(QString gfeVersion) +{ + QSettings settings; + + if (gfeVersion.isEmpty()) { + // If we don't have a GFE version, just allow it + return true; + } + + QString latestSupportedVersion = settings.value(COMPAT_KEY COMPAT_VERSION).toString(); + if (latestSupportedVersion.isEmpty()) { + // We don't have compat data yet, so just assume it's supported + return true; + } + + QStringList latestSupportedVersionQuad = latestSupportedVersion.split('.'); + QStringList gfeVersionQuad = gfeVersion.split('.'); + + for (int i = 0;; i++) { + int actualVerVal = 0; + int latestSupportedVal = 0; + + // Treat missing decimal places as 0 + if (i < gfeVersionQuad.count()) { + actualVerVal = gfeVersionQuad[i].toInt(); + } + if (i < latestSupportedVersionQuad.count()) { + latestSupportedVal = latestSupportedVersionQuad[i].toInt(); + } + + if (i >= gfeVersionQuad.count() && i >= latestSupportedVersionQuad.count()) { + // Equal versions - this is fine + return true; + } + + if (actualVerVal < latestSupportedVal) { + // Actual version is lower than latest supported - this is fine + return true; + } + else if (actualVerVal > latestSupportedVal) { + // Actual version is greater than latest supported - this is bad + return false; + } + } +} + +void CompatFetcher::handleCompatInfoFetched(QNetworkReply* reply) +{ + Q_ASSERT(reply->isFinished()); + + if (reply->error() == QNetworkReply::NoError) { + // Queue the reply for deletion + reply->deleteLater(); + + QString version = QString(reply->readAll()).trimmed(); + + QSettings settings; + settings.setValue(COMPAT_KEY COMPAT_VERSION, version); + + qInfo() << "Latest supported GFE server:" << version; + } + else { + qWarning() << "Failed to download latest compatibility data:" << reply->error(); + reply->deleteLater(); + } +} diff --git a/app/settings/compatfetcher.h b/app/settings/compatfetcher.h new file mode 100644 index 00000000..3ff661cd --- /dev/null +++ b/app/settings/compatfetcher.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class CompatFetcher : public QObject +{ + Q_OBJECT + +public: + explicit CompatFetcher(QObject *parent = nullptr); + + void start(); + + static bool isGfeVersionSupported(QString gfeVersion); + +private slots: + void handleCompatInfoFetched(QNetworkReply* reply); + +private: + QNetworkAccessManager m_Nam; +}; diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 8eaa877f..2bd0cd09 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -588,6 +588,11 @@ bool Session::validateLaunch(SDL_Window* testWindow) { QStringList warningList; + if (!m_Computer->isSupportedServerVersion) { + emit displayLaunchError(tr("The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.").arg(m_Computer->name)); + return false; + } + if (m_Preferences->absoluteMouseMode && !m_App.isAppCollectorGame) { emitLaunchWarning(tr("Your selection to enable remote desktop mouse mode may cause problems in games.")); }