From 35beb0e03a7f7d464c63220b4c3d17553353e4f2 Mon Sep 17 00:00:00 2001 From: zortos293 <65777760+zortos293@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:58:36 +0000 Subject: [PATCH] Unify GFN identity to desktop persona everywhere --- opennow-stable/src/main/gfn/auth.ts | 40 ++------- opennow-stable/src/main/gfn/clientIdentity.ts | 87 +++++++++++++++++++ opennow-stable/src/main/gfn/cloudmatch.ts | 69 ++++----------- opennow-stable/src/main/gfn/games.ts | 54 ++++-------- opennow-stable/src/main/gfn/signaling.ts | 8 +- opennow-stable/src/main/gfn/subscription.ts | 39 +++------ 6 files changed, 147 insertions(+), 150 deletions(-) create mode 100644 opennow-stable/src/main/gfn/clientIdentity.ts diff --git a/opennow-stable/src/main/gfn/auth.ts b/opennow-stable/src/main/gfn/auth.ts index 03f6331b..96d4deff 100644 --- a/opennow-stable/src/main/gfn/auth.ts +++ b/opennow-stable/src/main/gfn/auth.ts @@ -17,6 +17,7 @@ import type { StreamRegion, SubscriptionInfo, } from "@shared/gfn"; +import { buildDesktopGfnHeaders, GFN_USER_AGENT } from "./clientIdentity"; import { fetchSubscription, fetchDynamicRegions } from "./subscription"; const SERVICE_URLS_ENDPOINT = "https://pcs.geforcenow.com/v1/serviceUrls"; @@ -29,9 +30,6 @@ const CLIENT_ID = "ZU7sPN-miLujMD95LfOQ453IB0AtjM8sMyvgJ9wCXEQ"; const SCOPES = "openid consent email tk_client age"; const DEFAULT_IDP_ID = "PDiAhv2kJTFeQ7WOPqiQ2tRZ7lGhR2X11dXvM4TZSxg"; -const GFN_USER_AGENT = - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 NVIDIACEFClient/HEAD/debb5919f6 GFN-PC/2.0.80.173"; - const REDIRECT_PORTS = [2259, 6460, 7119, 8870, 9096]; const TOKEN_REFRESH_WINDOW_MS = 10 * 60 * 1000; const CLIENT_TOKEN_REFRESH_WINDOW_MS = 5 * 60 * 1000; @@ -576,20 +574,10 @@ export class AuthService { token = session ? session.tokens.idToken ?? session.tokens.accessToken : undefined; } - const headers: Record = { - Accept: "application/json", - "nv-client-id": "ec7e38d4-03af-4b58-b131-cfb0495903ab", - "nv-client-type": "BROWSER", - "nv-client-version": "2.0.80.173", - "nv-client-streamer": "WEBRTC", - "nv-device-os": "WINDOWS", - "nv-device-type": "DESKTOP", - "User-Agent": GFN_USER_AGENT, - }; - - if (token) { - headers.Authorization = `GFNJWT ${token}`; - } + const headers = buildDesktopGfnHeaders({ + accept: "application/json", + token, + }); let response: Response; try { @@ -730,20 +718,10 @@ export class AuthService { token = session ? session.tokens.idToken ?? session.tokens.accessToken : undefined; } - const headers: Record = { - Accept: "application/json", - "nv-client-id": "ec7e38d4-03af-4b58-b131-cfb0495903ab", - "nv-client-type": "BROWSER", - "nv-client-version": "2.0.80.173", - "nv-client-streamer": "WEBRTC", - "nv-device-os": "WINDOWS", - "nv-device-type": "DESKTOP", - "User-Agent": GFN_USER_AGENT, - }; - - if (token) { - headers.Authorization = `GFNJWT ${token}`; - } + const headers = buildDesktopGfnHeaders({ + accept: "application/json", + token, + }); try { const response = await fetch(`${base}v2/serverInfo`, { diff --git a/opennow-stable/src/main/gfn/clientIdentity.ts b/opennow-stable/src/main/gfn/clientIdentity.ts new file mode 100644 index 00000000..6302f272 --- /dev/null +++ b/opennow-stable/src/main/gfn/clientIdentity.ts @@ -0,0 +1,87 @@ +export const GFN_CLIENT_ID = "ec7e38d4-03af-4b58-b131-cfb0495903ab"; +export const GFN_CLIENT_VERSION = "2.0.80.173"; +export const GFN_CLOUDMATCH_CLIENT_VERSION = "30.0"; +export const GFN_CLIENT_IDENTIFICATION = "GFN-PC"; +export const GFN_CLIENT_PLATFORM_NAME = "windows"; +export const GFN_CLIENT_TYPE = "NATIVE"; +export const GFN_CLIENT_STREAMER = "NVIDIA-CLASSIC"; +export const GFN_DEVICE_OS = "WINDOWS"; +export const GFN_DEVICE_TYPE = "DESKTOP"; +export const GFN_BROWSER_TYPE = "CHROME"; +export const GFN_DEVICE_MAKE = "UNKNOWN"; +export const GFN_DEVICE_MODEL = "UNKNOWN"; +export const GFN_ORIGIN = "https://play.geforcenow.com"; +export const GFN_REFERER = `${GFN_ORIGIN}/`; +export const GFN_USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 NVIDIACEFClient/HEAD/debb5919f6 GFN-PC/2.0.80.173"; +// CloudMatch still expects WebRTC transport metadata because the session path negotiates a WebRTC stream. +export const GFN_STREAM_TRANSPORT_METADATA = "WebRTC"; + +interface DesktopGfnHeadersOptions { + token?: string; + clientId?: string; + deviceId?: string; + accept?: string; + contentType?: string; + includeOrigin?: boolean; + includeBrowserType?: boolean; + includeDeviceDetails?: boolean; +} + +export function buildDesktopGfnHeaders( + options: DesktopGfnHeadersOptions = {}, +): Record { + const headers: Record = { + "User-Agent": GFN_USER_AGENT, + "nv-client-id": options.clientId ?? GFN_CLIENT_ID, + "nv-client-type": GFN_CLIENT_TYPE, + "nv-client-version": GFN_CLIENT_VERSION, + "nv-client-streamer": GFN_CLIENT_STREAMER, + "nv-device-os": GFN_DEVICE_OS, + "nv-device-type": GFN_DEVICE_TYPE, + }; + + if (options.accept) { + headers.Accept = options.accept; + } + + if (options.contentType) { + headers["Content-Type"] = options.contentType; + } + + if (options.includeOrigin) { + headers.Origin = GFN_ORIGIN; + headers.Referer = GFN_REFERER; + } + + if (options.includeBrowserType) { + headers["nv-browser-type"] = GFN_BROWSER_TYPE; + } + + if (options.includeDeviceDetails) { + headers["nv-device-make"] = GFN_DEVICE_MAKE; + headers["nv-device-model"] = GFN_DEVICE_MODEL; + } + + if (options.deviceId) { + headers["x-device-id"] = options.deviceId; + } + + if (options.token) { + headers.Authorization = `GFNJWT ${options.token}`; + } + + return headers; +} + +export function buildDesktopCloudMatchIdentity(): { + clientIdentification: string; + clientVersion: string; + clientPlatformName: string; +} { + return { + clientIdentification: GFN_CLIENT_IDENTIFICATION, + clientVersion: GFN_CLOUDMATCH_CLIENT_VERSION, + clientPlatformName: GFN_CLIENT_PLATFORM_NAME, + }; +} diff --git a/opennow-stable/src/main/gfn/cloudmatch.ts b/opennow-stable/src/main/gfn/cloudmatch.ts index 1882644b..3fdbc32b 100644 --- a/opennow-stable/src/main/gfn/cloudmatch.ts +++ b/opennow-stable/src/main/gfn/cloudmatch.ts @@ -25,12 +25,13 @@ import { resolveGfnKeyboardLayout, } from "@shared/gfn"; +import { + buildDesktopCloudMatchIdentity, + buildDesktopGfnHeaders, + GFN_STREAM_TRANSPORT_METADATA, +} from "./clientIdentity"; import type { CloudMatchRequest, CloudMatchResponse, GetSessionsResponse } from "./types"; import { SessionError } from "./errorCodes"; - -const GFN_USER_AGENT = - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 NVIDIACEFClient/HEAD/debb5919f6 GFN-PC/2.0.80.173"; -const GFN_CLIENT_VERSION = "2.0.80.173"; const SESSION_MODIFY_ACTION_AD_UPDATE = 6; const READY_SESSION_STATUSES = new Set([2, 3]); @@ -410,29 +411,15 @@ interface RequestHeadersOptions { function requestHeaders(options: RequestHeadersOptions): Record { const clientId = options.clientId ?? crypto.randomUUID(); const deviceId = options.deviceId ?? crypto.randomUUID(); - - const headers: Record = { - "User-Agent": GFN_USER_AGENT, - Authorization: `GFNJWT ${options.token}`, - "Content-Type": "application/json", - "nv-browser-type": "CHROME", - "nv-client-id": clientId, - "nv-client-streamer": "NVIDIA-CLASSIC", - "nv-client-type": "NATIVE", - "nv-client-version": GFN_CLIENT_VERSION, - "nv-device-make": "UNKNOWN", - "nv-device-model": "UNKNOWN", - "nv-device-os": process.platform === "win32" ? "WINDOWS" : process.platform === "darwin" ? "MACOS" : "LINUX", - "nv-device-type": "DESKTOP", - "x-device-id": deviceId, - }; - - if (options.includeOrigin !== false) { - headers["Origin"] = "https://play.geforcenow.com"; - headers["Referer"] = "https://play.geforcenow.com/"; - } - - return headers; + return buildDesktopGfnHeaders({ + contentType: "application/json", + includeBrowserType: true, + includeDeviceDetails: true, + includeOrigin: options.includeOrigin !== false, + clientId, + deviceId, + token: options.token, + }); } function parseResolution(input: string): { width: number; height: number } { @@ -471,12 +458,10 @@ function buildSessionRequestBody(input: SessionCreateRequest): CloudMatchRequest availableSupportedControllers: [], networkTestSessionId: null, parentSessionId: null, - clientIdentification: "GFN-PC", + ...buildDesktopCloudMatchIdentity(), deviceHashId: crypto.randomUUID(), - clientVersion: "30.0", sdkVersion: "1.0", streamerVersion: 1, - clientPlatformName: "windows", clientRequestMonitorSettings: [ { widthInPixels: width, @@ -496,7 +481,7 @@ function buildSessionRequestBody(input: SessionCreateRequest): CloudMatchRequest metaData: [ { key: "SubSessionId", value: crypto.randomUUID() }, { key: "wssignaling", value: "1" }, - { key: "GSStreamerType", value: "WebRTC" }, + { key: "GSStreamerType", value: GFN_STREAM_TRANSPORT_METADATA }, { key: "networkType", value: "Unknown" }, { key: "ClientImeSupport", value: "0" }, { @@ -1210,20 +1195,18 @@ function buildClaimRequestBody(sessionId: string, appId: string, settings: Strea sdrHdrMode: 0, networkTestSessionId: null, availableSupportedControllers: [], - clientVersion: "30.0", + ...buildDesktopCloudMatchIdentity(), deviceHashId: deviceId, internalTitle: null, - clientPlatformName: "windows", metaData: [ { key: "SubSessionId", value: subSessionId }, { key: "wssignaling", value: "1" }, - { key: "GSStreamerType", value: "WebRTC" }, + { key: "GSStreamerType", value: GFN_STREAM_TRANSPORT_METADATA }, { key: "networkType", value: "Unknown" }, { key: "ClientImeSupport", value: "0" }, ], surroundAudioInfo: 0, clientTimezoneOffset: timezoneMs, - clientIdentification: "GFN-PC", parentSessionId: null, appId: parseInt(appId, 10), streamerVersion: 1, @@ -1349,21 +1332,7 @@ export async function claimSession(input: SessionClaimRequest): Promise = { - "User-Agent": GFN_USER_AGENT, - Authorization: `GFNJWT ${input.token}`, - "Content-Type": "application/json", - Origin: "https://play.geforcenow.com", - Referer: "https://play.geforcenow.com/", - "nv-client-id": clientId, - "nv-client-streamer": "NVIDIA-CLASSIC", - "nv-client-type": "NATIVE", - "nv-client-version": GFN_CLIENT_VERSION, - "nv-device-os": process.platform === "win32" ? "WINDOWS" : process.platform === "darwin" ? "MACOS" : "LINUX", - "nv-device-type": "DESKTOP", - "x-device-id": deviceId, - }; + const headers = requestHeaders({ token: input.token, clientId, deviceId }); console.log(`[CloudMatch] claimSession PUT ${claimUrl}`); console.log(`[CloudMatch] claimSession body: ${JSON.stringify(payload)}`); diff --git a/opennow-stable/src/main/gfn/games.ts b/opennow-stable/src/main/gfn/games.ts index dcce7a9a..ef2de39c 100644 --- a/opennow-stable/src/main/gfn/games.ts +++ b/opennow-stable/src/main/gfn/games.ts @@ -8,21 +8,21 @@ import type { } from "@shared/gfn"; import { isOwnedLibraryStatus } from "@shared/gfn"; import { cacheManager } from "../services/cacheManager"; +import { + buildDesktopGfnHeaders, + GFN_CLIENT_IDENTIFICATION, + GFN_USER_AGENT, +} from "./clientIdentity"; const GRAPHQL_URL = "https://games.geforce.com/graphql"; const PANELS_QUERY_HASH = "f8e26265a5db5c20e1334a6872cf04b6e3970507697f6ae55a6ddefa5420daf0"; const APP_METADATA_QUERY_HASH = "39187e85b6dcf60b7279a5f233288b0a8b69a8b1dbcfb5b25555afdcb988f0d7"; const LIBRARY_WITH_TIME_QUERY_HASH = "039e8c0d553972975485fee56e59f2549d2fdb518e247a42ab5022056a74406f"; const DEFAULT_LOCALE = "en_US"; -const LCARS_CLIENT_ID = "ec7e38d4-03af-4b58-b131-cfb0495903ab"; -const GFN_CLIENT_VERSION = "2.0.80.173"; const DEFAULT_CATALOG_FETCH_COUNT = 120; const MAX_CATALOG_PAGES = 3; const DEFAULT_SORT_ID = "relevance"; -const GFN_USER_AGENT = - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 NVIDIACEFClient/HEAD/debb5919f6 GFN-PC/2.0.80.173"; - interface GraphQlResponse { data?: { panels: Array<{ @@ -175,23 +175,14 @@ function randomHuId(): string { } function buildHeaders(token?: string): HeadersInit { - return { - Accept: "application/json, text/plain, */*", - "Content-Type": "application/json", - Origin: "https://play.geforcenow.com", - Referer: "https://play.geforcenow.com/", - ...(token ? { Authorization: `GFNJWT ${token}` } : {}), - "nv-client-id": LCARS_CLIENT_ID, - "nv-client-type": "NATIVE", - "nv-client-version": GFN_CLIENT_VERSION, - "nv-client-streamer": "NVIDIA-CLASSIC", - "nv-device-os": "WINDOWS", - "nv-device-type": "DESKTOP", - "nv-device-make": "UNKNOWN", - "nv-device-model": "UNKNOWN", - "nv-browser-type": "CHROME", - "User-Agent": GFN_USER_AGENT, - }; + return buildDesktopGfnHeaders({ + accept: "application/json, text/plain, */*", + contentType: "application/json", + includeOrigin: true, + includeBrowserType: true, + includeDeviceDetails: true, + token, + }); } async function postGraphQl(query: string, variables: Record, token?: string): Promise { @@ -214,25 +205,18 @@ async function getVpcId(token: string, providerStreamingBaseUrl?: string): Promi const normalizedBase = base.endsWith("/") ? base : `${base}/`; const response = await fetch(`${normalizedBase}v2/serverInfo`, { - headers: { - Accept: "application/json", - Authorization: `GFNJWT ${token}`, - "nv-client-id": LCARS_CLIENT_ID, - "nv-client-type": "NATIVE", - "nv-client-version": GFN_CLIENT_VERSION, - "nv-client-streamer": "NVIDIA-CLASSIC", - "nv-device-os": "WINDOWS", - "nv-device-type": "DESKTOP", - "User-Agent": GFN_USER_AGENT, - }, + headers: buildDesktopGfnHeaders({ + accept: "application/json", + token, + }), }); if (!response.ok) { - return "GFN-PC"; + return GFN_CLIENT_IDENTIFICATION; } const payload = (await response.json()) as ServerInfoResponse; - return payload.requestStatus?.serverId ?? "GFN-PC"; + return payload.requestStatus?.serverId ?? GFN_CLIENT_IDENTIFICATION; } function parseFeatureLabel(value: unknown): string | null { diff --git a/opennow-stable/src/main/gfn/signaling.ts b/opennow-stable/src/main/gfn/signaling.ts index a09af67f..10489d1b 100644 --- a/opennow-stable/src/main/gfn/signaling.ts +++ b/opennow-stable/src/main/gfn/signaling.ts @@ -8,9 +8,7 @@ import type { MainToRendererSignalingEvent, SendAnswerRequest, } from "@shared/gfn"; - -const USER_AGENT = - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/131.0.0.0 Safari/537.36"; +import { GFN_ORIGIN, GFN_USER_AGENT } from "./clientIdentity"; interface SignalingMessage { ackid?: number; @@ -134,8 +132,8 @@ export class GfnSignalingClient { rejectUnauthorized: false, headers: { Host: urlHost, - Origin: "https://play.geforcenow.com", - "User-Agent": USER_AGENT, + Origin: GFN_ORIGIN, + "User-Agent": GFN_USER_AGENT, "Sec-WebSocket-Key": randomBytes(16).toString("base64"), }, }); diff --git a/opennow-stable/src/main/gfn/subscription.ts b/opennow-stable/src/main/gfn/subscription.ts index 601eb872..a81219e3 100644 --- a/opennow-stable/src/main/gfn/subscription.ts +++ b/opennow-stable/src/main/gfn/subscription.ts @@ -9,16 +9,11 @@ import type { StorageAddon, StreamRegion, } from "@shared/gfn"; +import { buildDesktopGfnHeaders } from "./clientIdentity"; /** MES API endpoint URL */ const MES_URL = "https://mes.geforcenow.com/v4/subscriptions"; -/** LCARS Client ID */ -const LCARS_CLIENT_ID = "ec7e38d4-03af-4b58-b131-cfb0495903ab"; - -/** GFN client version */ -const GFN_CLIENT_VERSION = "2.0.80.173"; - interface SubscriptionResponse { firstEntitlementStartDateTime?: string; type?: string; @@ -113,16 +108,11 @@ export async function fetchSubscription( url.searchParams.append("userId", userId); const response = await fetch(url.toString(), { - headers: { - Authorization: `GFNJWT ${token}`, - Accept: "application/json", - "nv-client-id": LCARS_CLIENT_ID, - "nv-client-type": "NATIVE", - "nv-client-version": GFN_CLIENT_VERSION, - "nv-client-streamer": "NVIDIA-CLASSIC", - "nv-device-os": "WINDOWS", - "nv-device-type": "DESKTOP", - }, + headers: buildDesktopGfnHeaders({ + accept: "application/json", + contentType: "application/json", + token, + }), }); if (!response.ok) { @@ -253,19 +243,10 @@ export async function fetchDynamicRegions( : `${streamingBaseUrl}/`; const url = `${base}v2/serverInfo`; - const headers: Record = { - Accept: "application/json", - "nv-client-id": LCARS_CLIENT_ID, - "nv-client-type": "BROWSER", - "nv-client-version": GFN_CLIENT_VERSION, - "nv-client-streamer": "WEBRTC", - "nv-device-os": "WINDOWS", - "nv-device-type": "DESKTOP", - }; - - if (token) { - headers.Authorization = `GFNJWT ${token}`; - } + const headers = buildDesktopGfnHeaders({ + accept: "application/json", + token, + }); let response: Response; try {