Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 9 additions & 31 deletions opennow-stable/src/main/gfn/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -576,20 +574,10 @@ export class AuthService {
token = session ? session.tokens.idToken ?? session.tokens.accessToken : undefined;
}

const headers: Record<string, string> = {
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 {
Expand Down Expand Up @@ -730,20 +718,10 @@ export class AuthService {
token = session ? session.tokens.idToken ?? session.tokens.accessToken : undefined;
}

const headers: Record<string, string> = {
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`, {
Expand Down
87 changes: 87 additions & 0 deletions opennow-stable/src/main/gfn/clientIdentity.ts
Original file line number Diff line number Diff line change
@@ -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<string, string> {
const headers: Record<string, string> = {
"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,
};
}
69 changes: 19 additions & 50 deletions opennow-stable/src/main/gfn/cloudmatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down Expand Up @@ -410,29 +411,15 @@ interface RequestHeadersOptions {
function requestHeaders(options: RequestHeadersOptions): Record<string, string> {
const clientId = options.clientId ?? crypto.randomUUID();
const deviceId = options.deviceId ?? crypto.randomUUID();

const headers: Record<string, string> = {
"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 } {
Expand Down Expand Up @@ -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,
Expand All @@ -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" },
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1349,21 +1332,7 @@ export async function claimSession(input: SessionClaimRequest): Promise<SessionI
// For status=1 (still launching) we bypass the claim and fall through to the polling loop.
if (preClaimStatus !== 1) {
const payload = buildClaimRequestBody(input.sessionId, appId, settings);

const headers: Record<string, string> = {
"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)}`);
Expand Down
54 changes: 19 additions & 35 deletions opennow-stable/src/main/gfn/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
Expand Down Expand Up @@ -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<T>(query: string, variables: Record<string, unknown>, token?: string): Promise<T> {
Expand All @@ -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 {
Expand Down
8 changes: 3 additions & 5 deletions opennow-stable/src/main/gfn/signaling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"),
},
});
Expand Down
Loading
Loading