diff --git a/src/utils/network.ts b/src/utils/network.ts index 0c0460c..d14b44a 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -1,16 +1,13 @@ import { BxEvent } from "@utils/bx-event"; import { BX_FLAGS, NATIVE_FETCH } from "@utils/bx-flags"; -import { LoadingScreen } from "@modules/loading-screen"; import { PrefKey, getPref } from "@utils/preferences"; -import { RemotePlay } from "@modules/remote-play"; -import { StreamBadges } from "@modules/stream/stream-badges"; import { TouchController } from "@modules/touch-controller"; import { STATES } from "@utils/global"; -import { getPreferredServerRegion } from "@utils/region"; import { GamePassCloudGallery } from "../enums/game-pass-gallery"; -import { InputType } from "./bx-exposed"; import { FeatureGates } from "./feature-gates"; import { BxLogger } from "./bx-logger"; +import { XhomeInterceptor } from "./xhome-interceptor"; +import { XcloudInterceptor } from "./xcloud-interceptor"; enum RequestType { XCLOUD = 'xcloud', @@ -101,7 +98,7 @@ function updateIceCandidates(candidates: any, options: any) { } -async function patchIceCandidates(request: Request, consoleAddrs?: {[index: string]: number}) { +export async function patchIceCandidates(request: Request, consoleAddrs?: {[index: string]: number}) { const response = await NATIVE_FETCH(request); const text = await response.clone().text(); @@ -125,383 +122,6 @@ async function patchIceCandidates(request: Request, consoleAddrs?: {[index: stri return response; } - -class XhomeInterceptor { - static #consoleAddrs: {[index: string]: number} = {}; - - static async #handleLogin(request: Request) { - try { - const clone = (request as Request).clone(); - - const obj = await clone.json(); - obj.offeringId = 'xhome'; - - request = new Request('https://xhome.gssv-play-prod.xboxlive.com/v2/login/user', { - method: 'POST', - body: JSON.stringify(obj), - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (e) { - alert(e); - console.log(e); - } - - return NATIVE_FETCH(request); - } - - static async #handleConfiguration(request: Request | URL) { - const response = await NATIVE_FETCH(request); - - const obj = await response.clone().json() - console.log(obj); - - const serverDetails = obj.serverDetails; - if (serverDetails.ipAddress) { - XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = serverDetails.port; - } - - if (serverDetails.ipV4Address) { - XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port; - } - - if (serverDetails.ipV6Address) { - XhomeInterceptor.#consoleAddrs[serverDetails.ipV6Address] = serverDetails.ipV6Port; - } - - response.json = () => Promise.resolve(obj); - response.text = () => Promise.resolve(JSON.stringify(obj)); - - return response; - } - - static async #handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) { - const response = await NATIVE_FETCH(request); - - if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'all') { - return response; - } - - const obj = await response.clone().json() as any; - - const xboxTitleId = JSON.parse(opts.body).titleIds[0]; - STATES.currentStream.xboxTitleId = xboxTitleId; - - const inputConfigs = obj[0]; - - let hasTouchSupport = inputConfigs.supportedTabs.length > 0; - if (!hasTouchSupport) { - const supportedInputTypes = inputConfigs.supportedInputTypes; - hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY); - } - - if (hasTouchSupport) { - TouchController.disable(); - - BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, { - data: null, - }); - } else { - TouchController.enable(); - TouchController.getCustomLayouts(xboxTitleId); - } - - response.json = () => Promise.resolve(obj); - response.text = () => Promise.resolve(JSON.stringify(obj)); - - return response; - } - - static async #handleTitles(request: Request) { - const clone = request.clone(); - - const headers: {[index: string]: any} = {}; - for (const pair of (clone.headers as any).entries()) { - headers[pair[0]] = pair[1]; - } - headers.authorization = `Bearer ${RemotePlay.XCLOUD_TOKEN}`; - - const index = request.url.indexOf('.xboxlive.com'); - request = new Request('https://wus.core.gssv-play-prod' + request.url.substring(index), { - method: clone.method, - body: await clone.text(), - headers: headers, - }); - - return NATIVE_FETCH(request); - } - - static async #handlePlay(request: RequestInfo | URL) { - const clone = (request as Request).clone(); - const body = await clone.json(); - - // body.settings.useIceConnection = true; - - const newRequest = new Request(request, { - body: JSON.stringify(body), - }); - - return NATIVE_FETCH(newRequest); - } - - static async handle(request: Request) { - TouchController.disable(); - - const clone = request.clone(); - - const headers: {[index: string]: string} = {}; - for (const pair of (clone.headers as any).entries()) { - headers[pair[0]] = pair[1]; - } - // Add xHome token to headers - headers.authorization = `Bearer ${RemotePlay.XHOME_TOKEN}`; - - // Patch resolution - const deviceInfo = RemotePlay.BASE_DEVICE_INFO; - if (getPref(PrefKey.REMOTE_PLAY_RESOLUTION) === '720p') { - deviceInfo.dev.os.name = 'android'; - } - - headers['x-ms-device-info'] = JSON.stringify(deviceInfo); - - const opts: {[index: string]: any} = { - method: clone.method, - headers: headers, - }; - - if (clone.method === 'POST') { - opts.body = await clone.text(); - } - - let newUrl = request.url; - if (!newUrl.includes('/servers/home')) { - const index = request.url.indexOf('.xboxlive.com'); - newUrl = STATES.remotePlay.server + request.url.substring(index + 13); - } - - request = new Request(newUrl, opts); - let url = (typeof request === 'string') ? request : request.url; - - // Get console IP - if (url.includes('/configuration')) { - return XhomeInterceptor.#handleConfiguration(request); - } else if (url.endsWith('/sessions/home/play')) { - return XhomeInterceptor.#handlePlay(request); - } else if (url.includes('inputconfigs')) { - return XhomeInterceptor.#handleInputConfigs(request, opts); - } else if (url.includes('/login/user')) { - return XhomeInterceptor.#handleLogin(request); - } else if (url.endsWith('/titles')) { - return XhomeInterceptor.#handleTitles(request); - } else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') { - return patchIceCandidates(request, XhomeInterceptor.#consoleAddrs); - } - - return await NATIVE_FETCH(request); - } -} - - -class XcloudInterceptor { - static async #handleLogin(request: RequestInfo | URL, init?: RequestInit) { - const response = await NATIVE_FETCH(request, init); - const obj = await response.clone().json(); - - // Preload Remote Play - getPref(PrefKey.REMOTE_PLAY_ENABLED) && BX_FLAGS.PreloadRemotePlay && RemotePlay.preload(); - - // Store xCloud token - RemotePlay.XCLOUD_TOKEN = obj.gsToken; - - // Get server list - const serverEmojis = { - AustraliaEast: 'πŸ‡¦πŸ‡Ί', - AustraliaSouthEast: 'πŸ‡¦πŸ‡Ί', - BrazilSouth: 'πŸ‡§πŸ‡·', - EastUS: 'πŸ‡ΊπŸ‡Έ', - EastUS2: 'πŸ‡ΊπŸ‡Έ', - JapanEast: 'πŸ‡―πŸ‡΅', - KoreaCentral: 'πŸ‡°πŸ‡·', - MexicoCentral: 'πŸ‡²πŸ‡½', - NorthCentralUs: 'πŸ‡ΊπŸ‡Έ', - SouthCentralUS: 'πŸ‡ΊπŸ‡Έ', - UKSouth: 'πŸ‡¬πŸ‡§', - WestEurope: 'πŸ‡ͺπŸ‡Ί', - WestUS: 'πŸ‡ΊπŸ‡Έ', - WestUS2: 'πŸ‡ΊπŸ‡Έ', - }; - - const serverRegex = /\/\/(\w+)\./; - - for (let region of obj.offeringSettings.regions) { - const regionName = region.name as keyof typeof serverEmojis; - let shortName = region.name; - - let match = serverRegex.exec(region.baseUri); - if (match) { - shortName = match[1]; - if (serverEmojis[regionName]) { - shortName = serverEmojis[regionName] + ' ' + shortName; - } - } - - region.shortName = shortName.toUpperCase(); - STATES.serverRegions[region.name] = Object.assign({}, region); - } - - BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY); - - const preferredRegion = getPreferredServerRegion(); - if (preferredRegion in STATES.serverRegions) { - const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); - tmp.isDefault = true; - - obj.offeringSettings.regions = [tmp]; - } - - response.json = () => Promise.resolve(obj); - return response; - } - - static async #handlePlay(request: RequestInfo | URL, init?: RequestInit) { - const PREF_STREAM_TARGET_RESOLUTION = getPref(PrefKey.STREAM_TARGET_RESOLUTION); - const PREF_STREAM_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE); - - const url = (typeof request === 'string') ? request : (request as Request).url; - const parsedUrl = new URL(url); - - let badgeRegion: string = parsedUrl.host.split('.', 1)[0]; - for (let regionName in STATES.serverRegions) { - const region = STATES.serverRegions[regionName]; - if (parsedUrl.origin == region.baseUri) { - badgeRegion = regionName; - break; - } - } - StreamBadges.getInstance().setRegion(badgeRegion); - - const clone = (request as Request).clone(); - const body = await clone.json(); - - // Force stream's resolution - if (PREF_STREAM_TARGET_RESOLUTION !== 'auto') { - const osName = (PREF_STREAM_TARGET_RESOLUTION === '720p') ? 'android' : 'windows'; - body.settings.osName = osName; - } - - // Override "locale" value - if (PREF_STREAM_PREFERRED_LOCALE !== 'default') { - body.settings.locale = PREF_STREAM_PREFERRED_LOCALE; - } - - const newRequest = new Request(request, { - body: JSON.stringify(body), - }); - - return NATIVE_FETCH(newRequest); - } - - static async #handleWaitTime(request: RequestInfo | URL, init?: RequestInit) { - const response = await NATIVE_FETCH(request, init); - - if (getPref(PrefKey.UI_LOADING_SCREEN_WAIT_TIME)) { - const json = await response.clone().json(); - if (json.estimatedAllocationTimeInSeconds > 0) { - // Setup wait time overlay - LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds); - } - } - - return response; - } - - static async #handleConfiguration(request: RequestInfo | URL, init?: RequestInit) { - if ((request as Request).method !== 'GET') { - return NATIVE_FETCH(request, init); - } - - // Touch controller for all games - if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all') { - const titleInfo = STATES.currentStream.titleInfo; - if (titleInfo?.details.hasTouchSupport) { - TouchController.disable(); - } else { - TouchController.enable(); - } - } - - // Intercept configurations - const response = await NATIVE_FETCH(request, init); - const text = await response.clone().text(); - if (!text.length) { - return response; - } - - const obj = JSON.parse(text); - let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {}; - - overrides.inputConfiguration = overrides.inputConfiguration || {}; - overrides.inputConfiguration.enableVibration = true; - - let overrideMkb: boolean | null = null; - - if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' || (STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId))) { - overrideMkb = true; - } - - if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'off') { - overrideMkb = false; - } - - if (overrideMkb !== null) { - overrides.inputConfiguration = Object.assign(overrides.inputConfiguration, { - enableMouseInput: overrideMkb, - enableKeyboardInput: overrideMkb, - }); - } - - // Enable touch controller - if (TouchController.isEnabled()) { - overrides.inputConfiguration.enableTouchInput = true; - overrides.inputConfiguration.maxTouchPoints = 10; - } - - // Enable mic - if (getPref(PrefKey.AUDIO_MIC_ON_PLAYING)) { - overrides.audioConfiguration = overrides.audioConfiguration || {}; - overrides.audioConfiguration.enableMicrophone = true; - } - - obj.clientStreamingConfigOverrides = JSON.stringify(overrides); - - response.json = () => Promise.resolve(obj); - response.text = () => Promise.resolve(JSON.stringify(obj)); - - return response; - } - - static async handle(request: RequestInfo | URL, init?: RequestInit) { - let url = (typeof request === 'string') ? request : (request as Request).url; - - // Server list - if (url.endsWith('/v2/login/user')) { - return XcloudInterceptor.#handleLogin(request, init); - } else if (url.endsWith('/sessions/cloud/play')) { // Get session - return XcloudInterceptor.#handlePlay(request, init); - } else if (url.includes('xboxlive.com') && url.includes('/waittime/')) { - return XcloudInterceptor.#handleWaitTime(request, init); - } else if (url.endsWith('/configuration')) { - return XcloudInterceptor.#handleConfiguration(request, init); - } else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') { - return patchIceCandidates(request as Request); - } - - return NATIVE_FETCH(request, init); - } -} - - export function interceptHttpRequests() { let BLOCKED_URLS: string[] = []; if (getPref(PrefKey.BLOCK_TRACKING)) { diff --git a/src/utils/xcloud-interceptor.ts b/src/utils/xcloud-interceptor.ts new file mode 100644 index 0000000..ad8bd24 --- /dev/null +++ b/src/utils/xcloud-interceptor.ts @@ -0,0 +1,209 @@ +import { LoadingScreen } from "@modules/loading-screen"; +import { RemotePlay } from "@modules/remote-play"; +import { StreamBadges } from "@modules/stream/stream-badges"; +import { TouchController } from "@modules/touch-controller"; +import { BxEvent } from "./bx-event"; +import { NATIVE_FETCH, BX_FLAGS } from "./bx-flags"; +import { STATES } from "./global"; +import { patchIceCandidates } from "./network"; +import { getPref, PrefKey } from "./preferences"; +import { getPreferredServerRegion } from "./region"; + +export +class XcloudInterceptor { + static async #handleLogin(request: RequestInfo | URL, init?: RequestInit) { + const response = await NATIVE_FETCH(request, init); + const obj = await response.clone().json(); + + // Preload Remote Play + getPref(PrefKey.REMOTE_PLAY_ENABLED) && BX_FLAGS.PreloadRemotePlay && RemotePlay.preload(); + + // Store xCloud token + RemotePlay.XCLOUD_TOKEN = obj.gsToken; + + // Get server list + const serverEmojis = { + AustraliaEast: 'πŸ‡¦πŸ‡Ί', + AustraliaSouthEast: 'πŸ‡¦πŸ‡Ί', + BrazilSouth: 'πŸ‡§πŸ‡·', + EastUS: 'πŸ‡ΊπŸ‡Έ', + EastUS2: 'πŸ‡ΊπŸ‡Έ', + JapanEast: 'πŸ‡―πŸ‡΅', + KoreaCentral: 'πŸ‡°πŸ‡·', + MexicoCentral: 'πŸ‡²πŸ‡½', + NorthCentralUs: 'πŸ‡ΊπŸ‡Έ', + SouthCentralUS: 'πŸ‡ΊπŸ‡Έ', + UKSouth: 'πŸ‡¬πŸ‡§', + WestEurope: 'πŸ‡ͺπŸ‡Ί', + WestUS: 'πŸ‡ΊπŸ‡Έ', + WestUS2: 'πŸ‡ΊπŸ‡Έ', + }; + + const serverRegex = /\/\/(\w+)\./; + + for (let region of obj.offeringSettings.regions) { + const regionName = region.name as keyof typeof serverEmojis; + let shortName = region.name; + + let match = serverRegex.exec(region.baseUri); + if (match) { + shortName = match[1]; + if (serverEmojis[regionName]) { + shortName = serverEmojis[regionName] + ' ' + shortName; + } + } + + region.shortName = shortName.toUpperCase(); + STATES.serverRegions[region.name] = Object.assign({}, region); + } + + BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY); + + const preferredRegion = getPreferredServerRegion(); + if (preferredRegion in STATES.serverRegions) { + const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); + tmp.isDefault = true; + + obj.offeringSettings.regions = [tmp]; + } + + response.json = () => Promise.resolve(obj); + return response; + } + + static async #handlePlay(request: RequestInfo | URL, init?: RequestInit) { + const PREF_STREAM_TARGET_RESOLUTION = getPref(PrefKey.STREAM_TARGET_RESOLUTION); + const PREF_STREAM_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE); + + const url = (typeof request === 'string') ? request : (request as Request).url; + const parsedUrl = new URL(url); + + let badgeRegion: string = parsedUrl.host.split('.', 1)[0]; + for (let regionName in STATES.serverRegions) { + const region = STATES.serverRegions[regionName]; + if (parsedUrl.origin == region.baseUri) { + badgeRegion = regionName; + break; + } + } + StreamBadges.getInstance().setRegion(badgeRegion); + + const clone = (request as Request).clone(); + const body = await clone.json(); + + // Force stream's resolution + if (PREF_STREAM_TARGET_RESOLUTION !== 'auto') { + const osName = (PREF_STREAM_TARGET_RESOLUTION === '720p') ? 'android' : 'windows'; + body.settings.osName = osName; + } + + // Override "locale" value + if (PREF_STREAM_PREFERRED_LOCALE !== 'default') { + body.settings.locale = PREF_STREAM_PREFERRED_LOCALE; + } + + const newRequest = new Request(request, { + body: JSON.stringify(body), + }); + + return NATIVE_FETCH(newRequest); + } + + static async #handleWaitTime(request: RequestInfo | URL, init?: RequestInit) { + const response = await NATIVE_FETCH(request, init); + + if (getPref(PrefKey.UI_LOADING_SCREEN_WAIT_TIME)) { + const json = await response.clone().json(); + if (json.estimatedAllocationTimeInSeconds > 0) { + // Setup wait time overlay + LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds); + } + } + + return response; + } + + static async #handleConfiguration(request: RequestInfo | URL, init?: RequestInit) { + if ((request as Request).method !== 'GET') { + return NATIVE_FETCH(request, init); + } + + // Touch controller for all games + if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all') { + const titleInfo = STATES.currentStream.titleInfo; + if (titleInfo?.details.hasTouchSupport) { + TouchController.disable(); + } else { + TouchController.enable(); + } + } + + // Intercept configurations + const response = await NATIVE_FETCH(request, init); + const text = await response.clone().text(); + if (!text.length) { + return response; + } + + const obj = JSON.parse(text); + let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {}; + + overrides.inputConfiguration = overrides.inputConfiguration || {}; + overrides.inputConfiguration.enableVibration = true; + + let overrideMkb: boolean | null = null; + + if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' || (STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId))) { + overrideMkb = true; + } + + if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'off') { + overrideMkb = false; + } + + if (overrideMkb !== null) { + overrides.inputConfiguration = Object.assign(overrides.inputConfiguration, { + enableMouseInput: overrideMkb, + enableKeyboardInput: overrideMkb, + }); + } + + // Enable touch controller + if (TouchController.isEnabled()) { + overrides.inputConfiguration.enableTouchInput = true; + overrides.inputConfiguration.maxTouchPoints = 10; + } + + // Enable mic + if (getPref(PrefKey.AUDIO_MIC_ON_PLAYING)) { + overrides.audioConfiguration = overrides.audioConfiguration || {}; + overrides.audioConfiguration.enableMicrophone = true; + } + + obj.clientStreamingConfigOverrides = JSON.stringify(overrides); + + response.json = () => Promise.resolve(obj); + response.text = () => Promise.resolve(JSON.stringify(obj)); + + return response; + } + + static async handle(request: RequestInfo | URL, init?: RequestInit) { + let url = (typeof request === 'string') ? request : (request as Request).url; + + // Server list + if (url.endsWith('/v2/login/user')) { + return XcloudInterceptor.#handleLogin(request, init); + } else if (url.endsWith('/sessions/cloud/play')) { // Get session + return XcloudInterceptor.#handlePlay(request, init); + } else if (url.includes('xboxlive.com') && url.includes('/waittime/')) { + return XcloudInterceptor.#handleWaitTime(request, init); + } else if (url.endsWith('/configuration')) { + return XcloudInterceptor.#handleConfiguration(request, init); + } else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') { + return patchIceCandidates(request as Request); + } + + return NATIVE_FETCH(request, init); + } +} diff --git a/src/utils/xhome-interceptor.ts b/src/utils/xhome-interceptor.ts new file mode 100644 index 0000000..3375661 --- /dev/null +++ b/src/utils/xhome-interceptor.ts @@ -0,0 +1,184 @@ +import { RemotePlay } from "@/modules/remote-play"; +import { TouchController } from "@/modules/touch-controller"; +import { BxEvent } from "./bx-event"; +import { InputType } from "./bx-exposed"; +import { NATIVE_FETCH } from "./bx-flags"; +import { STATES } from "./global"; +import { getPref, PrefKey } from "./preferences"; +import { patchIceCandidates } from "./network"; + +export class XhomeInterceptor { + static #consoleAddrs: {[index: string]: number} = {}; + + static async #handleLogin(request: Request) { + try { + const clone = (request as Request).clone(); + + const obj = await clone.json(); + obj.offeringId = 'xhome'; + + request = new Request('https://xhome.gssv-play-prod.xboxlive.com/v2/login/user', { + method: 'POST', + body: JSON.stringify(obj), + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (e) { + alert(e); + console.log(e); + } + + return NATIVE_FETCH(request); + } + + static async #handleConfiguration(request: Request | URL) { + const response = await NATIVE_FETCH(request); + + const obj = await response.clone().json() + console.log(obj); + + const serverDetails = obj.serverDetails; + if (serverDetails.ipAddress) { + XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = serverDetails.port; + } + + if (serverDetails.ipV4Address) { + XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port; + } + + if (serverDetails.ipV6Address) { + XhomeInterceptor.#consoleAddrs[serverDetails.ipV6Address] = serverDetails.ipV6Port; + } + + response.json = () => Promise.resolve(obj); + response.text = () => Promise.resolve(JSON.stringify(obj)); + + return response; + } + + static async #handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) { + const response = await NATIVE_FETCH(request); + + if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'all') { + return response; + } + + const obj = await response.clone().json() as any; + + const xboxTitleId = JSON.parse(opts.body).titleIds[0]; + STATES.currentStream.xboxTitleId = xboxTitleId; + + const inputConfigs = obj[0]; + + let hasTouchSupport = inputConfigs.supportedTabs.length > 0; + if (!hasTouchSupport) { + const supportedInputTypes = inputConfigs.supportedInputTypes; + hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY); + } + + if (hasTouchSupport) { + TouchController.disable(); + + BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, { + data: null, + }); + } else { + TouchController.enable(); + TouchController.getCustomLayouts(xboxTitleId); + } + + response.json = () => Promise.resolve(obj); + response.text = () => Promise.resolve(JSON.stringify(obj)); + + return response; + } + + static async #handleTitles(request: Request) { + const clone = request.clone(); + + const headers: {[index: string]: any} = {}; + for (const pair of (clone.headers as any).entries()) { + headers[pair[0]] = pair[1]; + } + headers.authorization = `Bearer ${RemotePlay.XCLOUD_TOKEN}`; + + const index = request.url.indexOf('.xboxlive.com'); + request = new Request('https://wus.core.gssv-play-prod' + request.url.substring(index), { + method: clone.method, + body: await clone.text(), + headers: headers, + }); + + return NATIVE_FETCH(request); + } + + static async #handlePlay(request: RequestInfo | URL) { + const clone = (request as Request).clone(); + const body = await clone.json(); + + // body.settings.useIceConnection = true; + + const newRequest = new Request(request, { + body: JSON.stringify(body), + }); + + return NATIVE_FETCH(newRequest); + } + + static async handle(request: Request) { + TouchController.disable(); + + const clone = request.clone(); + + const headers: {[index: string]: string} = {}; + for (const pair of (clone.headers as any).entries()) { + headers[pair[0]] = pair[1]; + } + // Add xHome token to headers + headers.authorization = `Bearer ${RemotePlay.XHOME_TOKEN}`; + + // Patch resolution + const deviceInfo = RemotePlay.BASE_DEVICE_INFO; + if (getPref(PrefKey.REMOTE_PLAY_RESOLUTION) === '720p') { + deviceInfo.dev.os.name = 'android'; + } + + headers['x-ms-device-info'] = JSON.stringify(deviceInfo); + + const opts: {[index: string]: any} = { + method: clone.method, + headers: headers, + }; + + if (clone.method === 'POST') { + opts.body = await clone.text(); + } + + let newUrl = request.url; + if (!newUrl.includes('/servers/home')) { + const index = request.url.indexOf('.xboxlive.com'); + newUrl = STATES.remotePlay.server + request.url.substring(index + 13); + } + + request = new Request(newUrl, opts); + let url = (typeof request === 'string') ? request : request.url; + + // Get console IP + if (url.includes('/configuration')) { + return XhomeInterceptor.#handleConfiguration(request); + } else if (url.endsWith('/sessions/home/play')) { + return XhomeInterceptor.#handlePlay(request); + } else if (url.includes('inputconfigs')) { + return XhomeInterceptor.#handleInputConfigs(request, opts); + } else if (url.includes('/login/user')) { + return XhomeInterceptor.#handleLogin(request); + } else if (url.endsWith('/titles')) { + return XhomeInterceptor.#handleTitles(request); + } else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') { + return patchIceCandidates(request, XhomeInterceptor.#consoleAddrs); + } + + return await NATIVE_FETCH(request); + } +}