From f7048a38b34f6dccdb7d5eb7bc1aec94637614ab Mon Sep 17 00:00:00 2001 From: redphx <96280+redphx@users.noreply.github.com> Date: Sat, 21 Jun 2025 17:59:01 +0700 Subject: [PATCH] Fix crashing when reload the page while Remote Play-ing --- dist/better-xcloud.pretty.user.js | 334 +++++++++++++++--------------- dist/better-xcloud.user.js | 6 +- src/utils/xhome-interceptor.ts | 12 +- 3 files changed, 168 insertions(+), 184 deletions(-) diff --git a/dist/better-xcloud.pretty.user.js b/dist/better-xcloud.pretty.user.js index 0ff3c2f..a358196 100644 --- a/dist/better-xcloud.pretty.user.js +++ b/dist/better-xcloud.pretty.user.js @@ -8361,6 +8361,170 @@ var BxExposed = { return BX_FLAGS.ForceNativeMkbTitles?.includes(productId); } }; +class XhomeInterceptor { + static consoleAddrs = {}; + static async handleLogin(request) { + try { + let obj = await request.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) { + BxEventBus.Stream.emit("state.starting", {}); + let response = await NATIVE_FETCH(request), obj = await response.clone().json(), serverDetails = obj.serverDetails, pairs = [ + ["ipAddress", "port"], + ["ipV4Address", "ipV4Port"], + ["ipV6Address", "ipV6Port"] + ]; + XhomeInterceptor.consoleAddrs = {}; + for (let pair of pairs) { + let [keyAddr, keyPort] = pair; + if (keyAddr && keyPort && serverDetails[keyAddr]) { + let port = serverDetails[keyPort], ports = new Set; + port && ports.add(port), ports.add(9002), XhomeInterceptor.consoleAddrs[serverDetails[keyAddr]] = Array.from(ports); + } + } + return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; + } + static async handleInputConfigs(request, opts) { + let response = await NATIVE_FETCH(request); + if (getGlobalPref("touchController.mode") !== "all") return response; + let obj = await response.clone().json(), xboxTitleId = JSON.parse(opts.body).titleIds[0]; + TouchController.setXboxTitleId(xboxTitleId); + let inputConfigs = obj[0], hasTouchSupport = inputConfigs.supportedTabs.length > 0; + if (!hasTouchSupport) { + let supportedInputTypes = inputConfigs.supportedInputTypes; + hasTouchSupport = supportedInputTypes.includes("NativeTouch") || supportedInputTypes.includes("CustomTouchOverlay"); + } + if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, { + data: null + }); + else TouchController.enable(), TouchController.requestCustomLayouts(); + return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; + } + static async handleTitles(request) { + let clone = request.clone(), headers = {}; + for (let pair of clone.headers.entries()) + headers[pair[0]] = pair[1]; + let index = request.url.indexOf(".xboxlive.com"); + return request = new Request("https://wus.core.gssv-play-prod" + request.url.substring(index), { + method: clone.method, + body: await clone.text(), + headers + }), NATIVE_FETCH(request); + } + static async handlePlay(request) { + BxEventBus.Stream.emit("state.loading", {}); + let body = await request.clone().json(), newRequest = new Request(request, { + body: JSON.stringify(body) + }); + return NATIVE_FETCH(newRequest); + } + static async handle(request) { + TouchController.disable(); + let clone = request.clone(), headers = {}; + for (let pair of clone.headers.entries()) + headers[pair[0]] = pair[1]; + let osName = getOsNameFromResolution(getGlobalPref("xhome.video.resolution")); + headers["x-ms-device-info"] = JSON.stringify(generateMsDeviceInfo(osName)); + let opts = { + method: clone.method, + headers + }; + if (clone.method === "POST") opts.body = await clone.text(); + let url = request.url; + if (request = new Request(url, opts), 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.method === "GET") return patchIceCandidates(request, XhomeInterceptor.consoleAddrs); + return await NATIVE_FETCH(request); + } +} +function getPreferredServerRegion(shortName = !1) { + let preferredRegion = getGlobalPref("server.region"), serverRegions = STATES.serverRegions; + if (preferredRegion in serverRegions) if (shortName && serverRegions[preferredRegion].shortName) return serverRegions[preferredRegion].shortName; + else return preferredRegion; + for (let regionName in serverRegions) { + let region = serverRegions[regionName]; + if (!region.isDefault) continue; + if (shortName && region.shortName) return region.shortName; + else return regionName; + } + return null; +} +class LoadingScreen { + static $bgStyle; + static $waitTimeBox; + static waitTimeInterval = null; + static orgWebTitle; + static secondsToString(seconds) { + let m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0"); + return mDisplay + sDisplay; + } + static setup() { + let titleInfo = STATES.currentStream.titleInfo; + if (!titleInfo) return; + if (!LoadingScreen.$bgStyle) { + let $bgStyle = CE("style"); + document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle; + } + if (titleInfo.productInfo) LoadingScreen.setBackground(titleInfo.productInfo.heroImageUrl || titleInfo.productInfo.titledHeroImageUrl || titleInfo.productInfo.tileImageUrl); + if (getGlobalPref("loadingScreen.rocket") === "hide") LoadingScreen.hideRocket(); + } + static hideRocket() { + let $bgStyle = LoadingScreen.$bgStyle; + $bgStyle.textContent += "#game-stream div[class*=RocketAnimation-module__container] > svg{display:none}#game-stream video[class*=RocketAnimationVideo-module__video]{display:none}"; + } + static setBackground(imageUrl) { + let $bgStyle = LoadingScreen.$bgStyle; + imageUrl = imageUrl + "?w=1920"; + let imageQuality = getGlobalPref("ui.imageQuality"); + if (imageQuality !== 90) imageUrl += "&q=" + imageQuality; + $bgStyle.textContent += '#game-stream{background-color:transparent !important;background-position:center center !important;background-repeat:no-repeat !important;background-size:cover !important}#game-stream rect[width="800"]{transition:opacity .3s ease-in-out !important}' + `#game-stream {background-image: linear-gradient(#00000033, #000000e6), url(${imageUrl}) !important;}`; + let bg = new Image; + bg.onload = (e) => { + $bgStyle.textContent += '#game-stream rect[width="800"]{opacity:0 !important}'; + }, bg.src = imageUrl; + } + static setupWaitTime(waitTime) { + if (getGlobalPref("loadingScreen.rocket") === "hide-queue") LoadingScreen.hideRocket(); + let secondsLeft = waitTime, $countDown, $estimated; + LoadingScreen.orgWebTitle = document.title; + let endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60; + endDate.setSeconds(endDate.getSeconds() + waitTime - timeZoneOffsetSeconds); + let endDateStr = endDate.toISOString().slice(0, 19); + endDateStr = endDateStr.substring(0, 10) + " " + endDateStr.substring(11, 19), endDateStr += ` (${LoadingScreen.secondsToString(waitTime)})`; + let $waitTimeBox = LoadingScreen.$waitTimeBox; + if (!$waitTimeBox) $waitTimeBox = CE("div", { class: "bx-wait-time-box" }, CE("label", !1, t("server")), CE("span", !1, getPreferredServerRegion()), CE("label", !1, t("wait-time-estimated")), $estimated = CE("span", {}), CE("label", !1, t("wait-time-countdown")), $countDown = CE("span", {})), document.documentElement.appendChild($waitTimeBox), LoadingScreen.$waitTimeBox = $waitTimeBox; + else $waitTimeBox.classList.remove("bx-gone"), $estimated = $waitTimeBox.querySelector(".bx-wait-time-estimated"), $countDown = $waitTimeBox.querySelector(".bx-wait-time-countdown"); + $estimated.textContent = endDateStr, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, LoadingScreen.waitTimeInterval = window.setInterval(() => { + if (secondsLeft--, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, secondsLeft <= 0) LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null; + }, 1000); + } + static hide() { + if (LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), getGlobalPref("loadingScreen.gameArt.show") && LoadingScreen.$bgStyle) { + let $rocketBg = document.querySelector('#game-stream rect[width="800"]'); + $rocketBg && $rocketBg.addEventListener("transitionend", (e) => { + LoadingScreen.$bgStyle.textContent += "#game-stream{background:#000 !important}"; + }), LoadingScreen.$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:1 !important}'; + } + setTimeout(LoadingScreen.reset, 2000); + } + static reset() { + LoadingScreen.$bgStyle && (LoadingScreen.$bgStyle.textContent = ""), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null; + } +} function localRedirect(path) { let url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent"); if (!$pageContent) return; @@ -8375,18 +8539,6 @@ function localRedirect(path) { }), $pageContent.appendChild($anchor), $anchor.click(); } window.localRedirect = localRedirect; -function getPreferredServerRegion(shortName = !1) { - let preferredRegion = getGlobalPref("server.region"), serverRegions = STATES.serverRegions; - if (preferredRegion in serverRegions) if (shortName && serverRegions[preferredRegion].shortName) return serverRegions[preferredRegion].shortName; - else return preferredRegion; - for (let regionName in serverRegions) { - let region = serverRegions[regionName]; - if (!region.isDefault) continue; - if (shortName && region.shortName) return region.shortName; - else return regionName; - } - return null; -} class HeaderSection { static instance; static getInstance = () => HeaderSection.instance ?? (HeaderSection.instance = new HeaderSection); @@ -8627,164 +8779,6 @@ class RemotePlayManager { return this.consoles !== null; } } -class XhomeInterceptor { - static consoleAddrs = {}; - static async handleLogin(request) { - try { - let obj = await request.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) { - BxEventBus.Stream.emit("state.starting", {}); - let response = await NATIVE_FETCH(request), obj = await response.clone().json(), serverDetails = obj.serverDetails, pairs = [ - ["ipAddress", "port"], - ["ipV4Address", "ipV4Port"], - ["ipV6Address", "ipV6Port"] - ]; - XhomeInterceptor.consoleAddrs = {}; - for (let pair of pairs) { - let [keyAddr, keyPort] = pair; - if (keyAddr && keyPort && serverDetails[keyAddr]) { - let port = serverDetails[keyPort], ports = new Set; - port && ports.add(port), ports.add(9002), XhomeInterceptor.consoleAddrs[serverDetails[keyAddr]] = Array.from(ports); - } - } - return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; - } - static async handleInputConfigs(request, opts) { - let response = await NATIVE_FETCH(request); - if (getGlobalPref("touchController.mode") !== "all") return response; - let obj = await response.clone().json(), xboxTitleId = JSON.parse(opts.body).titleIds[0]; - TouchController.setXboxTitleId(xboxTitleId); - let inputConfigs = obj[0], hasTouchSupport = inputConfigs.supportedTabs.length > 0; - if (!hasTouchSupport) { - let supportedInputTypes = inputConfigs.supportedInputTypes; - hasTouchSupport = supportedInputTypes.includes("NativeTouch") || supportedInputTypes.includes("CustomTouchOverlay"); - } - if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, { - data: null - }); - else TouchController.enable(), TouchController.requestCustomLayouts(); - return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; - } - static async handleTitles(request) { - let clone = request.clone(), headers = {}; - for (let pair of clone.headers.entries()) - headers[pair[0]] = pair[1]; - headers.authorization = `Bearer ${RemotePlayManager.getInstance().getXcloudToken()}`; - let index = request.url.indexOf(".xboxlive.com"); - return request = new Request("https://wus.core.gssv-play-prod" + request.url.substring(index), { - method: clone.method, - body: await clone.text(), - headers - }), NATIVE_FETCH(request); - } - static async handlePlay(request) { - BxEventBus.Stream.emit("state.loading", {}); - let body = await request.clone().json(), newRequest = new Request(request, { - body: JSON.stringify(body) - }); - return NATIVE_FETCH(newRequest); - } - static async handle(request) { - TouchController.disable(); - let clone = request.clone(), headers = {}; - for (let pair of clone.headers.entries()) - headers[pair[0]] = pair[1]; - headers.authorization = `Bearer ${RemotePlayManager.getInstance().getXhomeToken()}`; - let osName = getOsNameFromResolution(getGlobalPref("xhome.video.resolution")); - headers["x-ms-device-info"] = JSON.stringify(generateMsDeviceInfo(osName)); - let opts = { - method: clone.method, - headers - }; - if (clone.method === "POST") opts.body = await clone.text(); - let url = request.url; - if (!url.includes("/servers/home")) { - let parsed = new URL(url); - url = STATES.remotePlay.server + parsed.pathname; - } - if (request = new Request(url, opts), 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.method === "GET") return patchIceCandidates(request, XhomeInterceptor.consoleAddrs); - return await NATIVE_FETCH(request); - } -} -class LoadingScreen { - static $bgStyle; - static $waitTimeBox; - static waitTimeInterval = null; - static orgWebTitle; - static secondsToString(seconds) { - let m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0"); - return mDisplay + sDisplay; - } - static setup() { - let titleInfo = STATES.currentStream.titleInfo; - if (!titleInfo) return; - if (!LoadingScreen.$bgStyle) { - let $bgStyle = CE("style"); - document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle; - } - if (titleInfo.productInfo) LoadingScreen.setBackground(titleInfo.productInfo.heroImageUrl || titleInfo.productInfo.titledHeroImageUrl || titleInfo.productInfo.tileImageUrl); - if (getGlobalPref("loadingScreen.rocket") === "hide") LoadingScreen.hideRocket(); - } - static hideRocket() { - let $bgStyle = LoadingScreen.$bgStyle; - $bgStyle.textContent += "#game-stream div[class*=RocketAnimation-module__container] > svg{display:none}#game-stream video[class*=RocketAnimationVideo-module__video]{display:none}"; - } - static setBackground(imageUrl) { - let $bgStyle = LoadingScreen.$bgStyle; - imageUrl = imageUrl + "?w=1920"; - let imageQuality = getGlobalPref("ui.imageQuality"); - if (imageQuality !== 90) imageUrl += "&q=" + imageQuality; - $bgStyle.textContent += '#game-stream{background-color:transparent !important;background-position:center center !important;background-repeat:no-repeat !important;background-size:cover !important}#game-stream rect[width="800"]{transition:opacity .3s ease-in-out !important}' + `#game-stream {background-image: linear-gradient(#00000033, #000000e6), url(${imageUrl}) !important;}`; - let bg = new Image; - bg.onload = (e) => { - $bgStyle.textContent += '#game-stream rect[width="800"]{opacity:0 !important}'; - }, bg.src = imageUrl; - } - static setupWaitTime(waitTime) { - if (getGlobalPref("loadingScreen.rocket") === "hide-queue") LoadingScreen.hideRocket(); - let secondsLeft = waitTime, $countDown, $estimated; - LoadingScreen.orgWebTitle = document.title; - let endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60; - endDate.setSeconds(endDate.getSeconds() + waitTime - timeZoneOffsetSeconds); - let endDateStr = endDate.toISOString().slice(0, 19); - endDateStr = endDateStr.substring(0, 10) + " " + endDateStr.substring(11, 19), endDateStr += ` (${LoadingScreen.secondsToString(waitTime)})`; - let $waitTimeBox = LoadingScreen.$waitTimeBox; - if (!$waitTimeBox) $waitTimeBox = CE("div", { class: "bx-wait-time-box" }, CE("label", !1, t("server")), CE("span", !1, getPreferredServerRegion()), CE("label", !1, t("wait-time-estimated")), $estimated = CE("span", {}), CE("label", !1, t("wait-time-countdown")), $countDown = CE("span", {})), document.documentElement.appendChild($waitTimeBox), LoadingScreen.$waitTimeBox = $waitTimeBox; - else $waitTimeBox.classList.remove("bx-gone"), $estimated = $waitTimeBox.querySelector(".bx-wait-time-estimated"), $countDown = $waitTimeBox.querySelector(".bx-wait-time-countdown"); - $estimated.textContent = endDateStr, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, LoadingScreen.waitTimeInterval = window.setInterval(() => { - if (secondsLeft--, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, secondsLeft <= 0) LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null; - }, 1000); - } - static hide() { - if (LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), getGlobalPref("loadingScreen.gameArt.show") && LoadingScreen.$bgStyle) { - let $rocketBg = document.querySelector('#game-stream rect[width="800"]'); - $rocketBg && $rocketBg.addEventListener("transitionend", (e) => { - LoadingScreen.$bgStyle.textContent += "#game-stream{background:#000 !important}"; - }), LoadingScreen.$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:1 !important}'; - } - setTimeout(LoadingScreen.reset, 2000); - } - static reset() { - LoadingScreen.$bgStyle && (LoadingScreen.$bgStyle.textContent = ""), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null; - } -} class GuideMenu { static instance; static getInstance = () => GuideMenu.instance ?? (GuideMenu.instance = new GuideMenu); diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js index 37071d5..b9ac602 100755 --- a/dist/better-xcloud.user.js +++ b/dist/better-xcloud.user.js @@ -230,14 +230,14 @@ if (blockFeatures.includes("byog")) FeatureGates.EnableBYOG = !1, FeatureGates.E if (BX_FLAGS.FeatureGates) FeatureGates = Object.assign(BX_FLAGS.FeatureGates, FeatureGates); class LocalCoOpManager {static instance;static getInstance = () => LocalCoOpManager.instance ?? (LocalCoOpManager.instance = new LocalCoOpManager);supportedIds;constructor() {BxEventBus.Script.once("list.localCoOp.updated", (e) => {this.supportedIds = e.ids;}), this.supportedIds = GhPagesUtils.getLocalCoOpList(), console.log("this.supportedIds", this.supportedIds);}isSupported(productId) {return this.supportedIds.has(productId);}} var BxExposed = {getTitleInfo: () => STATES.currentStream.titleInfo,modifyPreloadedState: (state) => {let LOG_TAG3 = "PreloadState";try {state.appContext.requestInfo.userAgent = window.navigator.userAgent;} catch (e) {BxLogger.error(LOG_TAG3, e);}try {for (let exp in FeatureGates)state.experiments.overrideFeatureGates[exp.toLocaleLowerCase()] = FeatureGates[exp];} catch (e) {BxLogger.error(LOG_TAG3, e);}try {let sigls = state.xcloud.sigls;if (STATES.userAgent.capabilities.touch) {let customList = TouchController.getCustomList(), siglId = "ce573635-7c18-4d0c-9d68-90b932393470";if (siglId in sigls) {let allGames = sigls[siglId].data.products;customList = customList.filter((id) => allGames.includes(id)), sigls["9c86f07a-f3e8-45ad-82a0-a1f759597059"]?.data.products.push(...customList);} else BxLogger.warning(LOG_TAG3, "Sigl not found: " + siglId);}} catch (e) {BxLogger.error(LOG_TAG3, e);}try {let sigls = state.xcloud.sigls;if (BX_FLAGS.ForceNativeMkbTitles) sigls["8fa264dd-124f-4af3-97e8-596fcdf4b486"]?.data.products.push(...BX_FLAGS.ForceNativeMkbTitles);} catch (e) {BxLogger.error(LOG_TAG3, e);}try {state.uhf.headerMode = "Off", state.uhf.footerMode = "Off";} catch (e) {BxLogger.error(LOG_TAG3, e);}try {let xCloud = state.xcloud.authentication.authStatusByStrategy.XCloud;if (xCloud.type === 3 && xCloud.error.type === "UnsupportedMarketError") window.stop(), window.location.href = "https://www.xbox.com/en-US/play";} catch (e) {BxLogger.error(LOG_TAG3, e);}return state;},modifyTitleInfo: function(titleInfo) {titleInfo = deepClone(titleInfo);let supportedInputTypes = titleInfo.details.supportedInputTypes;if (BX_FLAGS.ForceNativeMkbTitles?.includes(titleInfo.details.productId)) supportedInputTypes.push("MKB");if (getGlobalPref("nativeMkb.mode") === "off") supportedInputTypes = supportedInputTypes.filter((i) => i !== "MKB");if (titleInfo.details.hasMkbSupport = supportedInputTypes.includes("MKB"), STATES.userAgent.capabilities.touch) {let touchControllerAvailability = getGlobalPref("touchController.mode");if (touchControllerAvailability !== "off" && getGlobalPref("touchController.autoOff")) {let gamepads = window.navigator.getGamepads(), gamepadFound = !1;for (let gamepad of gamepads)if (gamepad && gamepad.connected) {gamepadFound = !0;break;}gamepadFound && (touchControllerAvailability = "off");}if (touchControllerAvailability === "off") supportedInputTypes = supportedInputTypes.filter((i) => i !== "CustomTouchOverlay" && i !== "GenericTouch"), titleInfo.details.supportedTabs = [];if (titleInfo.details.hasNativeTouchSupport = supportedInputTypes.includes("NativeTouch"), titleInfo.details.hasTouchSupport = titleInfo.details.hasNativeTouchSupport || supportedInputTypes.includes("CustomTouchOverlay") || supportedInputTypes.includes("GenericTouch"), !titleInfo.details.hasTouchSupport && touchControllerAvailability === "all") titleInfo.details.hasFakeTouchSupport = !0, supportedInputTypes.push("GenericTouch");}return titleInfo.details.supportedInputTypes = supportedInputTypes, STATES.currentStream.titleInfo = titleInfo, BxEventBus.Script.emit("titleInfo.ready", {}), titleInfo;},setupGainNode: ($media, audioStream) => {if ($media instanceof HTMLAudioElement) $media.muted = !0, $media.addEventListener("playing", (e) => {$media.muted = !0, $media.pause();});else $media.muted = !0, $media.addEventListener("playing", (e) => {$media.muted = !0;});try {let audioCtx = STATES.currentStream.audioContext, source = audioCtx.createMediaStreamSource(audioStream), gainNode = audioCtx.createGain();source.connect(gainNode).connect(audioCtx.destination);} catch (e) {BxLogger.error("setupGainNode", e), STATES.currentStream.audioGainNode = null;}},handleControllerShortcut: ControllerShortcut.handle,resetControllerShortcut: ControllerShortcut.reset,overrideSettings: {Tv_settings: {hasCompletedOnboarding: !0}},disableGamepadPolling: !1,backButtonPressed: () => {let navigationDialogManager = NavigationDialogManager.getInstance();if (navigationDialogManager.isShowing()) return navigationDialogManager.hide(), !0;let dict = {bubbles: !0,cancelable: !0,key: "XF86Back",code: "XF86Back",keyCode: 4,which: 4};return document.body.dispatchEvent(new KeyboardEvent("keydown", dict)), document.body.dispatchEvent(new KeyboardEvent("keyup", dict)), !1;},GameSlugRegexes: [/[;,/?:@&=+_`~$%#^*()!^β„’\xae\xa9]/g,/ {2,}/g,/ /g],toggleLocalCoOp(enable) {},beforePageLoad: (page) => {BxLogger.info("beforePageLoad", page), Patcher.patchPage(page);},localCoOpManager: LocalCoOpManager.getInstance(),reactCreateElement: function(...args) {},reactUseEffect: function(...args) {},createReactLocalCoOpIcon: (attrs) => {let reactCE = window.BX_EXPOSED.reactCreateElement;return reactCE("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 32 32", "fill-rule": "evenodd", "stroke-linecap": "round", "stroke-linejoin": "round", ...attrs }, reactCE("g", null, reactCE("path", { d: "M24.272 11.165h-3.294l-3.14 3.564c-.391.391-.922.611-1.476.611a2.1 2.1 0 0 1-2.087-2.088 2.09 2.09 0 0 1 .031-.362l1.22-6.274a3.89 3.89 0 0 1 3.81-3.206h6.57c1.834 0 3.439 1.573 3.833 3.295l1.205 6.185a2.09 2.09 0 0 1 .031.362 2.1 2.1 0 0 1-2.087 2.088c-.554 0-1.085-.22-1.476-.611l-3.14-3.564", fill: "none", stroke: "#fff", "stroke-width": "2" }), reactCE("circle", { cx: "22.625", cy: "5.874", r: ".879" }), reactCE("path", { d: "M11.022 24.415H7.728l-3.14 3.564c-.391.391-.922.611-1.476.611a2.1 2.1 0 0 1-2.087-2.088 2.09 2.09 0 0 1 .031-.362l1.22-6.274a3.89 3.89 0 0 1 3.81-3.206h6.57c1.834 0 3.439 1.573 3.833 3.295l1.205 6.185a2.09 2.09 0 0 1 .031.362 2.1 2.1 0 0 1-2.087 2.088c-.554 0-1.085-.22-1.476-.611l-3.14-3.564", fill: "none", stroke: "#fff", "stroke-width": "2" }), reactCE("circle", { cx: "9.375", cy: "19.124", r: ".879" })));},hasCustomTouchControl: TouchController.hasCustomControl,hasCustomNativeMkb: (productId) => {return BX_FLAGS.ForceNativeMkbTitles?.includes(productId);}}; +class XhomeInterceptor {static consoleAddrs = {};static async handleLogin(request) {try {let obj = await request.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) {BxEventBus.Stream.emit("state.starting", {});let response = await NATIVE_FETCH(request), obj = await response.clone().json(), serverDetails = obj.serverDetails, pairs = [["ipAddress", "port"],["ipV4Address", "ipV4Port"],["ipV6Address", "ipV6Port"]];XhomeInterceptor.consoleAddrs = {};for (let pair of pairs) {let [keyAddr, keyPort] = pair;if (keyAddr && keyPort && serverDetails[keyAddr]) {let port = serverDetails[keyPort], ports = new Set;port && ports.add(port), ports.add(9002), XhomeInterceptor.consoleAddrs[serverDetails[keyAddr]] = Array.from(ports);}}return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;}static async handleInputConfigs(request, opts) {let response = await NATIVE_FETCH(request);if (getGlobalPref("touchController.mode") !== "all") return response;let obj = await response.clone().json(), xboxTitleId = JSON.parse(opts.body).titleIds[0];TouchController.setXboxTitleId(xboxTitleId);let inputConfigs = obj[0], hasTouchSupport = inputConfigs.supportedTabs.length > 0;if (!hasTouchSupport) {let supportedInputTypes = inputConfigs.supportedInputTypes;hasTouchSupport = supportedInputTypes.includes("NativeTouch") || supportedInputTypes.includes("CustomTouchOverlay");}if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {data: null});else TouchController.enable(), TouchController.requestCustomLayouts();return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;}static async handleTitles(request) {let clone = request.clone(), headers = {};for (let pair of clone.headers.entries())headers[pair[0]] = pair[1];let index = request.url.indexOf(".xboxlive.com");return request = new Request("https://wus.core.gssv-play-prod" + request.url.substring(index), {method: clone.method,body: await clone.text(),headers}), NATIVE_FETCH(request);}static async handlePlay(request) {BxEventBus.Stream.emit("state.loading", {});let body = await request.clone().json(), newRequest = new Request(request, {body: JSON.stringify(body)});return NATIVE_FETCH(newRequest);}static async handle(request) {TouchController.disable();let clone = request.clone(), headers = {};for (let pair of clone.headers.entries())headers[pair[0]] = pair[1];let osName = getOsNameFromResolution(getGlobalPref("xhome.video.resolution"));headers["x-ms-device-info"] = JSON.stringify(generateMsDeviceInfo(osName));let opts = {method: clone.method,headers};if (clone.method === "POST") opts.body = await clone.text();let url = request.url;if (request = new Request(url, opts), 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.method === "GET") return patchIceCandidates(request, XhomeInterceptor.consoleAddrs);return await NATIVE_FETCH(request);}} +function getPreferredServerRegion(shortName = !1) {let preferredRegion = getGlobalPref("server.region"), serverRegions = STATES.serverRegions;if (preferredRegion in serverRegions) if (shortName && serverRegions[preferredRegion].shortName) return serverRegions[preferredRegion].shortName;else return preferredRegion;for (let regionName in serverRegions) {let region = serverRegions[regionName];if (!region.isDefault) continue;if (shortName && region.shortName) return region.shortName;else return regionName;}return null;} +class LoadingScreen {static $bgStyle;static $waitTimeBox;static waitTimeInterval = null;static orgWebTitle;static secondsToString(seconds) {let m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0");return mDisplay + sDisplay;}static setup() {let titleInfo = STATES.currentStream.titleInfo;if (!titleInfo) return;if (!LoadingScreen.$bgStyle) {let $bgStyle = CE("style");document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle;}if (titleInfo.productInfo) LoadingScreen.setBackground(titleInfo.productInfo.heroImageUrl || titleInfo.productInfo.titledHeroImageUrl || titleInfo.productInfo.tileImageUrl);if (getGlobalPref("loadingScreen.rocket") === "hide") LoadingScreen.hideRocket();}static hideRocket() {let $bgStyle = LoadingScreen.$bgStyle;$bgStyle.textContent += "#game-stream div[class*=RocketAnimation-module__container] > svg{display:none}#game-stream video[class*=RocketAnimationVideo-module__video]{display:none}";}static setBackground(imageUrl) {let $bgStyle = LoadingScreen.$bgStyle;imageUrl = imageUrl + "?w=1920";let imageQuality = getGlobalPref("ui.imageQuality");if (imageQuality !== 90) imageUrl += "&q=" + imageQuality;$bgStyle.textContent += '#game-stream{background-color:transparent !important;background-position:center center !important;background-repeat:no-repeat !important;background-size:cover !important}#game-stream rect[width="800"]{transition:opacity .3s ease-in-out !important}' + `#game-stream {background-image: linear-gradient(#00000033, #000000e6), url(${imageUrl}) !important;}`;let bg = new Image;bg.onload = (e) => {$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:0 !important}';}, bg.src = imageUrl;}static setupWaitTime(waitTime) {if (getGlobalPref("loadingScreen.rocket") === "hide-queue") LoadingScreen.hideRocket();let secondsLeft = waitTime, $countDown, $estimated;LoadingScreen.orgWebTitle = document.title;let endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60;endDate.setSeconds(endDate.getSeconds() + waitTime - timeZoneOffsetSeconds);let endDateStr = endDate.toISOString().slice(0, 19);endDateStr = endDateStr.substring(0, 10) + " " + endDateStr.substring(11, 19), endDateStr += ` (${LoadingScreen.secondsToString(waitTime)})`;let $waitTimeBox = LoadingScreen.$waitTimeBox;if (!$waitTimeBox) $waitTimeBox = CE("div", { class: "bx-wait-time-box" }, CE("label", !1, t("server")), CE("span", !1, getPreferredServerRegion()), CE("label", !1, t("wait-time-estimated")), $estimated = CE("span", {}), CE("label", !1, t("wait-time-countdown")), $countDown = CE("span", {})), document.documentElement.appendChild($waitTimeBox), LoadingScreen.$waitTimeBox = $waitTimeBox;else $waitTimeBox.classList.remove("bx-gone"), $estimated = $waitTimeBox.querySelector(".bx-wait-time-estimated"), $countDown = $waitTimeBox.querySelector(".bx-wait-time-countdown");$estimated.textContent = endDateStr, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, LoadingScreen.waitTimeInterval = window.setInterval(() => {if (secondsLeft--, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, secondsLeft <= 0) LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null;}, 1000);}static hide() {if (LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), getGlobalPref("loadingScreen.gameArt.show") && LoadingScreen.$bgStyle) {let $rocketBg = document.querySelector('#game-stream rect[width="800"]');$rocketBg && $rocketBg.addEventListener("transitionend", (e) => {LoadingScreen.$bgStyle.textContent += "#game-stream{background:#000 !important}";}), LoadingScreen.$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:1 !important}';}setTimeout(LoadingScreen.reset, 2000);}static reset() {LoadingScreen.$bgStyle && (LoadingScreen.$bgStyle.textContent = ""), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null;}} function localRedirect(path) {let url = window.location.href.substring(0, 31) + path, $pageContent = document.getElementById("PageContent");if (!$pageContent) return;let $anchor = CE("a", {href: url,class: "bx-hidden bx-offscreen"}, "");$anchor.addEventListener("click", (e) => {window.setTimeout(() => {$pageContent.removeChild($anchor);}, 1000);}), $pageContent.appendChild($anchor), $anchor.click();} window.localRedirect = localRedirect; -function getPreferredServerRegion(shortName = !1) {let preferredRegion = getGlobalPref("server.region"), serverRegions = STATES.serverRegions;if (preferredRegion in serverRegions) if (shortName && serverRegions[preferredRegion].shortName) return serverRegions[preferredRegion].shortName;else return preferredRegion;for (let regionName in serverRegions) {let region = serverRegions[regionName];if (!region.isDefault) continue;if (shortName && region.shortName) return region.shortName;else return regionName;}return null;} class HeaderSection {static instance;static getInstance = () => HeaderSection.instance ?? (HeaderSection.instance = new HeaderSection);LOG_TAG = "HeaderSection";$btnRemotePlay;$btnSettings;$buttonsWrapper;constructor() {BxLogger.info(this.LOG_TAG, "constructor()"), this.$btnRemotePlay = createButton({classes: ["bx-header-remote-play-button", "bx-gone"],icon: BxIcon.REMOTE_PLAY,title: t("remote-play"),style: 8 | 64 | 2048,onClick: (e) => RemotePlayManager.getInstance()?.togglePopup()});let $btnSettings = this.$btnSettings = createButton({classes: ["bx-header-settings-button", "bx-gone"],label: t("better-xcloud"),style: 16 | 32 | 64 | 256,onClick: (e) => SettingsDialog.getInstance().show()});this.$buttonsWrapper = CE("div", !1, !getGlobalPref("block.features").includes("remote-play") ? this.$btnRemotePlay : null, this.$btnSettings), BxEventBus.Script.on("xcloud.server", ({ status }) => {if (status === "ready") {STATES.isSignedIn = !0, $btnSettings.querySelector("span").textContent = getPreferredServerRegion(!0) || t("better-xcloud");let PREF_LATEST_VERSION = getGlobalPref("version.latest");if (!SCRIPT_VERSION.includes("beta") && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) $btnSettings.setAttribute("data-update-available", "true");} else if (status === "unavailable") {if (STATES.supportedRegion = !1, document.querySelector("div[class^=UnsupportedMarketPage-module__container]")) SettingsDialog.getInstance().show();}$btnSettings.classList.remove("bx-gone");});}checkHeader = () => {let $header = document.querySelector("#gamepass-root header[class^=Header-module__header]");if (!$header) return;let $target = $header.querySelector("div[class*=EdgewaterHeader-module__rightSectionSpacing], div[class*=RemotePlayHeader-module__rightSectionSpacing]");if (!$target) $target = document.querySelector("div[class^=UnsupportedMarketPage-module__buttons]");if ($target?.appendChild(this.$buttonsWrapper), !STATES.isSignedIn) BxEventBus.Script.emit("xcloud.server", { status: "signed-out" });};showRemotePlayButton() {this.$btnRemotePlay?.classList.remove("bx-gone");}} class RemotePlayDialog extends NavigationDialog {static instance;static getInstance = () => RemotePlayDialog.instance ?? (RemotePlayDialog.instance = new RemotePlayDialog);LOG_TAG = "RemotePlayNavigationDialog";STATE_LABELS = {On: t("powered-on"),Off: t("powered-off"),ConnectedStandby: t("standby"),Unknown: t("unknown")};$container;constructor() {super();BxLogger.info(this.LOG_TAG, "constructor()"), this.setupDialog();}setupDialog() {let $fragment = CE("div", { class: "bx-centered-dialog" }, CE("div", { class: "bx-dialog-title" }, CE("p", !1, t("remote-play")))), $settingNote = CE("p", {}), currentResolution = getGlobalPref("xhome.video.resolution"), $resolutions = CE("select", !1, CE("option", { value: "720p" }, "720p"), CE("option", { value: "1080p" }, "1080p"), CE("option", { value: "1080p-hq" }, "1080p (HQ)"));$resolutions = BxSelectElement.create($resolutions), $resolutions.addEventListener("input", (e) => {let value = e.target.value;$settingNote.textContent = `βœ… ${t("xbox-360-games")} ${value === "1080p-hq" ? "❌" : "βœ…"} ${t("xbox-apps")}`, setGlobalPref("xhome.video.resolution", value, "ui");}), $resolutions.value = currentResolution, BxEvent.dispatch($resolutions, "input", {manualTrigger: !0});let $qualitySettings = CE("div", {class: "bx-remote-play-settings"}, CE("div", !1, CE("label", !1, t("target-resolution"), $settingNote), $resolutions), CE("div", !1, CE("label", { for: `bx_setting_${escapeCssSelector("xhome.ipv6.prefer")}` }, t("prefer-ipv6-server")), SettingElement.fromPref("xhome.ipv6.prefer")));$fragment.appendChild($qualitySettings);let manager = RemotePlayManager.getInstance(), consoles = manager.getConsoles(), createConsoleShortcut = (e) => {let { serverId, deviceName } = e.target.dataset, optionsJson = JSON.stringify({resolution: getGlobalPref("xhome.video.resolution")});AppInterface?.createConsoleShortcut(serverId, deviceName, optionsJson);};for (let con of consoles) {let $connect, $child = CE("div", {class: "bx-remote-play-device-wrapper"}, CE("div", { class: "bx-remote-play-device-info" }, CE("div", !1, CE("span", { class: "bx-remote-play-device-name" }, con.deviceName), CE("span", { class: "bx-remote-play-console-type" }, con.consoleType.replace("Xbox", ""))), CE("div", { class: "bx-remote-play-power-state" }, this.STATE_LABELS[con.powerState])), AppInterface ? createButton({attributes: {"data-server-id": con.serverId,"data-device-name": con.deviceName},icon: BxIcon.CREATE_SHORTCUT,style: 8 | 64,title: t("create-shortcut"),onClick: createConsoleShortcut}) : null, $connect = createButton({classes: ["bx-remote-play-connect-button"],label: t("console-connect"),style: 1 | 64,onClick: (e) => manager.play(con.serverId)}));setNearby($child, {orientation: "horizontal",focus: $connect}), $fragment.appendChild($child);}$fragment.appendChild(CE("div", {class: "bx-remote-play-buttons",_nearby: {orientation: "horizontal"}}, createButton({icon: BxIcon.QUESTION,style: 8 | 64,url: "https://better-xcloud.github.io/remote-play",label: t("help")}), createButton({style: 8 | 64,label: t("close"),onClick: (e) => this.hide()}))), this.$container = $fragment;}getDialog() {return this;}getContent() {return this.$container;}focusIfNeeded() {let $btnConnect = this.$container.querySelector(".bx-remote-play-device-wrapper button:last-of-type");$btnConnect && $btnConnect.focus();}} class RemotePlayManager {static instance;static getInstance() {if (typeof RemotePlayManager.instance === "undefined") if (!getGlobalPref("block.features").includes("remote-play")) RemotePlayManager.instance = new RemotePlayManager;else RemotePlayManager.instance = null;return RemotePlayManager.instance;}LOG_TAG = "RemotePlayManager";isInitialized = !1;XCLOUD_TOKEN;XHOME_TOKEN;consoles;regions = [];constructor() {BxLogger.info(this.LOG_TAG, "constructor()");}initialize() {if (this.isInitialized) return;this.isInitialized = !0, this.requestXhomeToken(() => {this.getConsolesList(() => {BxLogger.info(this.LOG_TAG, "Consoles", this.consoles), STATES.supportedRegion && HeaderSection.getInstance().showRemotePlayButton(), BxEvent.dispatch(window, BxEvent.REMOTE_PLAY_READY);});});}getXcloudToken() {return this.XCLOUD_TOKEN;}setXcloudToken(token) {this.XCLOUD_TOKEN = token;}getXhomeToken() {return this.XHOME_TOKEN;}getConsoles() {return this.consoles;}requestXhomeToken(callback) {if (this.XHOME_TOKEN) {callback();return;}let GSSV_TOKEN;try {GSSV_TOKEN = JSON.parse(localStorage.getItem("xboxcom_xbl_user_info")).tokens["http://gssv.xboxlive.com/"].token;} catch (e) {for (let i = 0;i < localStorage.length; i++) {let key = localStorage.key(i);if (!key.startsWith("Auth.User.")) continue;let json = JSON.parse(localStorage.getItem(key));for (let token of json.tokens) {if (!token.relyingParty.includes("gssv.xboxlive.com")) continue;GSSV_TOKEN = token.tokenData.token;break;}break;}}let request = new Request("https://xhome.gssv-play-prod.xboxlive.com/v2/login/user", {method: "POST",body: JSON.stringify({offeringId: "xhome",token: GSSV_TOKEN}),headers: {"Content-Type": "application/json; charset=utf-8"}});fetch(request).then((resp) => resp.json()).then((json) => {this.regions = json.offeringSettings.regions, this.XHOME_TOKEN = json.gsToken, callback();});}async getConsolesList(callback) {if (this.consoles) {callback();return;}let options = {method: "GET",headers: {Authorization: `Bearer ${this.XHOME_TOKEN}`}};this.regions.sort((a, b) => {return a.isDefault ? -1 : 0;});for (let region of this.regions)try {let request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options), json = await (await fetch(request)).json();if (json.results.length === 0) continue;this.consoles = json.results, STATES.remotePlay.server = region.baseUri;break;} catch (e) {}if (!STATES.remotePlay.server) this.consoles = [];callback();}play(serverId, resolution) {if (resolution) setGlobalPref("xhome.video.resolution", resolution, "ui");localRedirect("/consoles/launch/" + serverId);}togglePopup(force = null) {if (!this.isReady()) {Toast.show(t("getting-consoles-list"));return;}if (this.consoles.length === 0) {Toast.show(t("no-consoles-found"), "", { instant: !0 });return;}RemotePlayDialog.getInstance().show();}isReady() {return this.consoles !== null;}} -class XhomeInterceptor {static consoleAddrs = {};static async handleLogin(request) {try {let obj = await request.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) {BxEventBus.Stream.emit("state.starting", {});let response = await NATIVE_FETCH(request), obj = await response.clone().json(), serverDetails = obj.serverDetails, pairs = [["ipAddress", "port"],["ipV4Address", "ipV4Port"],["ipV6Address", "ipV6Port"]];XhomeInterceptor.consoleAddrs = {};for (let pair of pairs) {let [keyAddr, keyPort] = pair;if (keyAddr && keyPort && serverDetails[keyAddr]) {let port = serverDetails[keyPort], ports = new Set;port && ports.add(port), ports.add(9002), XhomeInterceptor.consoleAddrs[serverDetails[keyAddr]] = Array.from(ports);}}return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;}static async handleInputConfigs(request, opts) {let response = await NATIVE_FETCH(request);if (getGlobalPref("touchController.mode") !== "all") return response;let obj = await response.clone().json(), xboxTitleId = JSON.parse(opts.body).titleIds[0];TouchController.setXboxTitleId(xboxTitleId);let inputConfigs = obj[0], hasTouchSupport = inputConfigs.supportedTabs.length > 0;if (!hasTouchSupport) {let supportedInputTypes = inputConfigs.supportedInputTypes;hasTouchSupport = supportedInputTypes.includes("NativeTouch") || supportedInputTypes.includes("CustomTouchOverlay");}if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {data: null});else TouchController.enable(), TouchController.requestCustomLayouts();return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;}static async handleTitles(request) {let clone = request.clone(), headers = {};for (let pair of clone.headers.entries())headers[pair[0]] = pair[1];headers.authorization = `Bearer ${RemotePlayManager.getInstance().getXcloudToken()}`;let index = request.url.indexOf(".xboxlive.com");return request = new Request("https://wus.core.gssv-play-prod" + request.url.substring(index), {method: clone.method,body: await clone.text(),headers}), NATIVE_FETCH(request);}static async handlePlay(request) {BxEventBus.Stream.emit("state.loading", {});let body = await request.clone().json(), newRequest = new Request(request, {body: JSON.stringify(body)});return NATIVE_FETCH(newRequest);}static async handle(request) {TouchController.disable();let clone = request.clone(), headers = {};for (let pair of clone.headers.entries())headers[pair[0]] = pair[1];headers.authorization = `Bearer ${RemotePlayManager.getInstance().getXhomeToken()}`;let osName = getOsNameFromResolution(getGlobalPref("xhome.video.resolution"));headers["x-ms-device-info"] = JSON.stringify(generateMsDeviceInfo(osName));let opts = {method: clone.method,headers};if (clone.method === "POST") opts.body = await clone.text();let url = request.url;if (!url.includes("/servers/home")) {let parsed = new URL(url);url = STATES.remotePlay.server + parsed.pathname;}if (request = new Request(url, opts), 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.method === "GET") return patchIceCandidates(request, XhomeInterceptor.consoleAddrs);return await NATIVE_FETCH(request);}} -class LoadingScreen {static $bgStyle;static $waitTimeBox;static waitTimeInterval = null;static orgWebTitle;static secondsToString(seconds) {let m = Math.floor(seconds / 60), s = Math.floor(seconds % 60), mDisplay = m > 0 ? `${m}m` : "", sDisplay = `${s}s`.padStart(s >= 0 ? 3 : 4, "0");return mDisplay + sDisplay;}static setup() {let titleInfo = STATES.currentStream.titleInfo;if (!titleInfo) return;if (!LoadingScreen.$bgStyle) {let $bgStyle = CE("style");document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle;}if (titleInfo.productInfo) LoadingScreen.setBackground(titleInfo.productInfo.heroImageUrl || titleInfo.productInfo.titledHeroImageUrl || titleInfo.productInfo.tileImageUrl);if (getGlobalPref("loadingScreen.rocket") === "hide") LoadingScreen.hideRocket();}static hideRocket() {let $bgStyle = LoadingScreen.$bgStyle;$bgStyle.textContent += "#game-stream div[class*=RocketAnimation-module__container] > svg{display:none}#game-stream video[class*=RocketAnimationVideo-module__video]{display:none}";}static setBackground(imageUrl) {let $bgStyle = LoadingScreen.$bgStyle;imageUrl = imageUrl + "?w=1920";let imageQuality = getGlobalPref("ui.imageQuality");if (imageQuality !== 90) imageUrl += "&q=" + imageQuality;$bgStyle.textContent += '#game-stream{background-color:transparent !important;background-position:center center !important;background-repeat:no-repeat !important;background-size:cover !important}#game-stream rect[width="800"]{transition:opacity .3s ease-in-out !important}' + `#game-stream {background-image: linear-gradient(#00000033, #000000e6), url(${imageUrl}) !important;}`;let bg = new Image;bg.onload = (e) => {$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:0 !important}';}, bg.src = imageUrl;}static setupWaitTime(waitTime) {if (getGlobalPref("loadingScreen.rocket") === "hide-queue") LoadingScreen.hideRocket();let secondsLeft = waitTime, $countDown, $estimated;LoadingScreen.orgWebTitle = document.title;let endDate = new Date, timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60;endDate.setSeconds(endDate.getSeconds() + waitTime - timeZoneOffsetSeconds);let endDateStr = endDate.toISOString().slice(0, 19);endDateStr = endDateStr.substring(0, 10) + " " + endDateStr.substring(11, 19), endDateStr += ` (${LoadingScreen.secondsToString(waitTime)})`;let $waitTimeBox = LoadingScreen.$waitTimeBox;if (!$waitTimeBox) $waitTimeBox = CE("div", { class: "bx-wait-time-box" }, CE("label", !1, t("server")), CE("span", !1, getPreferredServerRegion()), CE("label", !1, t("wait-time-estimated")), $estimated = CE("span", {}), CE("label", !1, t("wait-time-countdown")), $countDown = CE("span", {})), document.documentElement.appendChild($waitTimeBox), LoadingScreen.$waitTimeBox = $waitTimeBox;else $waitTimeBox.classList.remove("bx-gone"), $estimated = $waitTimeBox.querySelector(".bx-wait-time-estimated"), $countDown = $waitTimeBox.querySelector(".bx-wait-time-countdown");$estimated.textContent = endDateStr, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, LoadingScreen.waitTimeInterval = window.setInterval(() => {if (secondsLeft--, $countDown.textContent = LoadingScreen.secondsToString(secondsLeft), document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`, secondsLeft <= 0) LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null;}, 1000);}static hide() {if (LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), getGlobalPref("loadingScreen.gameArt.show") && LoadingScreen.$bgStyle) {let $rocketBg = document.querySelector('#game-stream rect[width="800"]');$rocketBg && $rocketBg.addEventListener("transitionend", (e) => {LoadingScreen.$bgStyle.textContent += "#game-stream{background:#000 !important}";}), LoadingScreen.$bgStyle.textContent += '#game-stream rect[width="800"]{opacity:1 !important}';}setTimeout(LoadingScreen.reset, 2000);}static reset() {LoadingScreen.$bgStyle && (LoadingScreen.$bgStyle.textContent = ""), LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add("bx-gone"), LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval), LoadingScreen.waitTimeInterval = null;}} class GuideMenu {static instance;static getInstance = () => GuideMenu.instance ?? (GuideMenu.instance = new GuideMenu);$renderedButtons;closeGuideMenu() {if (window.BX_EXPOSED.dialogRoutes) {window.BX_EXPOSED.dialogRoutes.closeAll();return;}let $btnClose = document.querySelector("#gamepass-dialog-root button[class^=Header-module__closeButton]");$btnClose && $btnClose.click();}renderButtons() {if (this.$renderedButtons) return this.$renderedButtons;let buttons = {scriptSettings: createButton({label: t("better-xcloud"),icon: BxIcon.BETTER_XCLOUD,style: 128 | 64 | 1,onClick: () => {BxEventBus.Script.once("dialog.dismissed", () => {setTimeout(() => SettingsDialog.getInstance().show(), 50);}), this.closeGuideMenu();}}),closeApp: AppInterface && createButton({icon: BxIcon.POWER,label: t("close-app"),title: t("close-app"),style: 128 | 64 | 4,onClick: (e) => {AppInterface.closeApp();},attributes: {"data-state": "normal"}}),reloadPage: createButton({icon: BxIcon.REFRESH,label: t("reload-page"),title: t("reload-page"),style: 128 | 64,onClick: () => {if (this.closeGuideMenu(), STATES.isPlaying) confirm(t("confirm-reload-stream")) && window.location.reload();else window.location.reload();}}),backToHome: createButton({icon: BxIcon.HOME,label: t("back-to-home"),title: t("back-to-home"),style: 128 | 64,onClick: () => {this.closeGuideMenu(), confirm(t("back-to-home-confirm")) && (window.location.href = window.location.href.substring(0, 31));},attributes: {"data-state": "playing"}})}, buttonsLayout = [buttons.scriptSettings,[buttons.backToHome,buttons.reloadPage,buttons.closeApp]], $div = CE("div", {class: "bx-guide-home-buttons"});if (STATES.userAgent.isTv || getGlobalPref("ui.layout") === "tv") document.body.dataset.bxMediaType = "tv";for (let $button of buttonsLayout) {if (!$button) continue;if ($button instanceof HTMLElement) $div.appendChild($button);else if (Array.isArray($button)) {let $wrapper = CE("div", {});for (let $child of $button)$child && $wrapper.appendChild($child);$div.appendChild($wrapper);}}return this.$renderedButtons = $div, $div;}injectHome($root, isPlaying = !1) {let $buttons = this.renderButtons();if ($root.contains($buttons)) return;let $target = null;if (isPlaying) {$target = $root.querySelector("a[class*=QuitGameButton]");let $btnXcloudHome = $root.querySelector("div[class^=HomeButtonWithDivider]");$btnXcloudHome && ($btnXcloudHome.style.display = "none");} else {let $dividers = $root.querySelectorAll("div[class*=Divider-module__divider]");if ($dividers) $target = $dividers[$dividers.length - 1];}if (!$target) return !1;$buttons.dataset.isPlaying = isPlaying.toString(), $target.insertAdjacentElement("afterend", $buttons);}} class StreamBadges {static instance;static getInstance = () => StreamBadges.instance ?? (StreamBadges.instance = new StreamBadges);LOG_TAG = "StreamBadges";serverInfo = {};badges = {playtime: {name: t("playtime"),icon: BxIcon.PLAYTIME,color: "#ff004d"},battery: {name: t("battery"),icon: BxIcon.BATTERY,color: "#00b543"},download: {name: t("download"),icon: BxIcon.DOWNLOAD,color: "#29adff"},upload: {name: t("upload"),icon: BxIcon.UPLOAD,color: "#ff77a8"},server: {name: t("server"),icon: BxIcon.SERVER,color: "#ff6c24"},video: {name: t("video"),icon: BxIcon.DISPLAY,color: "#742f29"},audio: {name: t("audio"),icon: BxIcon.AUDIO,color: "#5f574f"}};$container;intervalId;REFRESH_INTERVAL = 3000;constructor() {BxLogger.info(this.LOG_TAG, "constructor()");}setRegion(region) {this.serverInfo.server = {region};}renderBadge(name, value) {let badgeInfo = this.badges[name], $badge;if (badgeInfo.$element) return $badge = badgeInfo.$element, $badge.lastElementChild.textContent = value, $badge;if ($badge = CE("div", { class: "bx-badge", title: badgeInfo.name }, CE("span", { class: "bx-badge-name" }, createSvgIcon(badgeInfo.icon)), CE("span", { class: "bx-badge-value", style: `background-color: ${badgeInfo.color}` }, value)), name === "battery") $badge.classList.add("bx-badge-battery");return this.badges[name].$element = $badge, $badge;}updateBadges = async (forceUpdate = !1) => {if (!this.$container || !forceUpdate && !this.$container.isConnected) {this.stop();return;}let statsCollector = StreamStatsCollector.getInstance();await statsCollector.collect();let play = statsCollector.getStat("play"), batt = statsCollector.getStat("batt"), dl = statsCollector.getStat("dl"), ul = statsCollector.getStat("ul"), badges = {download: dl.toString(),upload: ul.toString(),playtime: play.toString(),battery: batt.toString()}, name;for (name in badges) {let value = badges[name];if (value === null) continue;let $elm = this.badges[name].$element;if (!$elm) continue;if ($elm.lastElementChild.textContent = value, name === "battery") if (batt.current === 100 && batt.start === 100) $elm.classList.add("bx-gone");else $elm.dataset.charging = batt.isCharging.toString(), $elm.classList.remove("bx-gone");}};async start() {await this.updateBadges(!0), this.stop(), this.intervalId = window.setInterval(this.updateBadges, this.REFRESH_INTERVAL);}stop() {this.intervalId && clearInterval(this.intervalId), this.intervalId = null;}destroy() {this.serverInfo = {}, delete this.$container;}async render() {if (this.$container) return this.start(), this.$container;await this.getServerStats();let batteryLevel = "";if (STATES.browser.capabilities.batteryApi) batteryLevel = "100%";let BADGES = [["playtime", "1m"],["battery", batteryLevel],["download", humanFileSize(0)],["upload", humanFileSize(0)],this.badges.server.$element ?? ["server", "?"],this.serverInfo.video ? this.badges.video.$element : ["video", "?"],this.serverInfo.audio ? this.badges.audio.$element : ["audio", "?"]], $container = CE("div", { class: "bx-badges" });for (let item2 of BADGES) {if (!item2) continue;let $badge;if (!(item2 instanceof HTMLElement)) $badge = this.renderBadge(...item2);else $badge = item2;$container.appendChild($badge);}return this.$container = $container, await this.start(), $container;}async getServerStats() {let stats = await STATES.currentStream.peerConnection.getStats(), allVideoCodecs = {}, videoCodecId, videoWidth = 0, videoHeight = 0, allAudioCodecs = {}, audioCodecId, allCandidatePairs = {}, allRemoteCandidates = {}, candidatePairId;if (stats.forEach((stat) => {if (stat.type === "codec") {let mimeType = stat.mimeType.split("/")[0];if (mimeType === "video") allVideoCodecs[stat.id] = stat;else if (mimeType === "audio") allAudioCodecs[stat.id] = stat;} else if (stat.type === "inbound-rtp" && stat.packetsReceived > 0) {if (stat.kind === "video") videoCodecId = stat.codecId, videoWidth = stat.frameWidth, videoHeight = stat.frameHeight;else if (stat.kind === "audio") audioCodecId = stat.codecId;} else if (stat.type === "transport" && stat.selectedCandidatePairId) candidatePairId = stat.selectedCandidatePairId;else if (stat.type === "candidate-pair") allCandidatePairs[stat.id] = stat.remoteCandidateId;else if (stat.type === "remote-candidate") allRemoteCandidates[stat.id] = stat.address;}), videoCodecId) {let videoStat = allVideoCodecs[videoCodecId], video = {width: videoWidth,height: videoHeight,codec: videoStat.mimeType.substring(6)};if (video.codec === "H264") {let match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine);match && (video.profile = match[1]);}let text = videoHeight + "p";if (text && (text += "/"), text += video.codec, video.profile) {let profile = video.profile, quality = profile;if (profile.startsWith("4d")) quality = t("visual-quality-high");else if (profile.startsWith("42e")) quality = t("visual-quality-normal");else if (profile.startsWith("420")) quality = t("visual-quality-low");text += ` (${quality})`;}this.badges.video.$element = this.renderBadge("video", text), this.serverInfo.video = video;}if (audioCodecId) {let audioStat = allAudioCodecs[audioCodecId], audio = {codec: audioStat.mimeType.substring(6),bitrate: audioStat.clockRate}, bitrate = audio.bitrate / 1000, text = `${audio.codec} (${bitrate} kHz)`;this.badges.audio.$element = this.renderBadge("audio", text), this.serverInfo.audio = audio;}if (candidatePairId) {BxLogger.info("candidate", candidatePairId, allCandidatePairs);let text = "", isIpv6 = allRemoteCandidates[allCandidatePairs[candidatePairId]].includes(":"), server = this.serverInfo.server;if (server && server.region) text += server.region;text += "@" + (isIpv6 ? "IPv6" : "IPv4"), this.badges.server.$element = this.renderBadge("server", text);}}static setupEvents() {}} class XcloudInterceptor {static SERVER_EXTRA_INFO = {EastUS: ["πŸ‡ΊπŸ‡Έ", "america-north"],EastUS2: ["πŸ‡ΊπŸ‡Έ", "america-north"],NorthCentralUs: ["πŸ‡ΊπŸ‡Έ", "america-north"],SouthCentralUS: ["πŸ‡ΊπŸ‡Έ", "america-north"],WestUS: ["πŸ‡ΊπŸ‡Έ", "america-north"],WestUS2: ["πŸ‡ΊπŸ‡Έ", "america-north"],MexicoCentral: ["πŸ‡²πŸ‡½", "america-north"],BrazilSouth: ["πŸ‡§πŸ‡·", "america-south"],JapanEast: ["πŸ‡―πŸ‡΅", "asia"],KoreaCentral: ["πŸ‡°πŸ‡·", "asia"],AustraliaEast: ["πŸ‡¦πŸ‡Ί", "australia"],AustraliaSouthEast: ["πŸ‡¦πŸ‡Ί", "australia"],SwedenCentral: ["πŸ‡ΈπŸ‡ͺ", "europe"],UKSouth: ["πŸ‡¬πŸ‡§", "europe"],WestEurope: ["πŸ‡ͺπŸ‡Ί", "europe"]};static async handleLogin(request, init) {let bypassServer = getGlobalPref("server.bypassRestriction");if (bypassServer !== "off") {let ip = BypassServerIps[bypassServer];ip && request.headers.set("X-Forwarded-For", ip);}let response = await NATIVE_FETCH(request, init);if (response.status !== 200) return BxEventBus.Script.emit("xcloud.server", { status: "unavailable" }), response;let obj = await response.clone().json();RemotePlayManager.getInstance()?.setXcloudToken(obj.gsToken);let serverRegex = /\/\/(\w+)\./, serverExtra = XcloudInterceptor.SERVER_EXTRA_INFO, region;for (region of obj.offeringSettings.regions) {let { name: regionName, name: shortName } = region;if (region.isDefault) STATES.selectedRegion = Object.assign({}, region);let match = serverRegex.exec(region.baseUri);if (match) if (shortName = match[1], serverExtra[regionName]) shortName = serverExtra[regionName][0] + " " + shortName, region.contintent = serverExtra[regionName][1];else region.contintent = "other", BX_FLAGS.Debug && alert("New server: " + shortName);region.shortName = shortName.toUpperCase(), STATES.serverRegions[region.name] = Object.assign({}, region);}let preferredRegion = getPreferredServerRegion();if (preferredRegion && preferredRegion in STATES.serverRegions) {let tmp = Object.assign({}, STATES.serverRegions[preferredRegion]);tmp.isDefault = !0, obj.offeringSettings.regions = [tmp], STATES.selectedRegion = tmp;}return STATES.gsToken = obj.gsToken, BxEventBus.Script.emit("xcloud.server", { status: "ready" }), response.json = () => Promise.resolve(obj), response;}static async handlePlay(request, init) {BxEventBus.Stream.emit("state.loading", {});let PREF_STREAM_TARGET_RESOLUTION = getGlobalPref("stream.video.resolution"), PREF_STREAM_PREFERRED_LOCALE = getGlobalPref("stream.locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url), badgeRegion = parsedUrl.host.split(".", 1)[0];for (let regionName in STATES.serverRegions) {let region = STATES.serverRegions[regionName];if (region && parsedUrl.origin === region.baseUri) {badgeRegion = regionName;break;}}StreamBadges.getInstance().setRegion(badgeRegion);let clone = request.clone(), body = await clone.json(), headers = {};for (let pair of clone.headers.entries())headers[pair[0]] = pair[1];if (PREF_STREAM_TARGET_RESOLUTION !== "auto") {let osName = getOsNameFromResolution(PREF_STREAM_TARGET_RESOLUTION);headers["x-ms-device-info"] = JSON.stringify(generateMsDeviceInfo(osName)), body.settings.osName = osName;}if (PREF_STREAM_PREFERRED_LOCALE !== "default") body.settings.locale = PREF_STREAM_PREFERRED_LOCALE;let newRequest = new Request(request, {body: JSON.stringify(body),headers});return NATIVE_FETCH(newRequest);}static async handleWaitTime(request, init) {let response = await NATIVE_FETCH(request, init);if (getGlobalPref("loadingScreen.waitTime.show")) {let json = await response.clone().json();if (json.estimatedAllocationTimeInSeconds > 0) LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds);}return response;}static async handleConfiguration(request, init) {if (request.method !== "GET") return NATIVE_FETCH(request, init);if (getGlobalPref("touchController.mode") === "all") if (STATES.currentStream.titleInfo?.details.hasTouchSupport) TouchController.disable();else TouchController.enable();let response = await NATIVE_FETCH(request, init), text = await response.clone().text();if (!text.length) return response;BxEventBus.Stream.emit("state.starting", {});let obj = JSON.parse(text), overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {};overrides.inputConfiguration = overrides.inputConfiguration || {}, overrides.inputConfiguration.enableVibration = !0;let overrideMkb = null;if (getGlobalPref("nativeMkb.mode") === "on" || STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId)) overrideMkb = !0;if (getGlobalPref("nativeMkb.mode") === "off") overrideMkb = !1;if (overrideMkb !== null) overrides.inputConfiguration = Object.assign(overrides.inputConfiguration, {enableMouseInput: overrideMkb,enableKeyboardInput: overrideMkb});if (TouchController.isEnabled()) overrides.inputConfiguration.enableTouchInput = !0, overrides.inputConfiguration.maxTouchPoints = 10;if (getGlobalPref("audio.mic.onPlaying")) overrides.audioConfiguration = overrides.audioConfiguration || {}, overrides.audioConfiguration.enableMicrophone = !0;return obj.clientStreamingConfigOverrides = JSON.stringify(overrides), response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;}static async handle(request, init) {let url = typeof request === "string" ? request : request.url;if (url.endsWith("/v2/login/user")) return XcloudInterceptor.handleLogin(request, init);else if (url.endsWith("/sessions/cloud/play")) 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.method === "GET") return patchIceCandidates(request);return NATIVE_FETCH(request, init);}} diff --git a/src/utils/xhome-interceptor.ts b/src/utils/xhome-interceptor.ts index f9c08b5..d91785c 100755 --- a/src/utils/xhome-interceptor.ts +++ b/src/utils/xhome-interceptor.ts @@ -2,10 +2,8 @@ import { TouchController } from "@/modules/touch-controller"; import { BxEvent } from "./bx-event"; import { SupportedInputType } from "./bx-exposed"; import { NATIVE_FETCH } from "./bx-flags"; -import { STATES } from "./global"; import { generateMsDeviceInfo, getOsNameFromResolution, patchIceCandidates } from "./network"; import { GlobalPref } from "@/enums/pref-keys"; -import { RemotePlayManager } from "@/modules/remote-play-manager"; import { TouchControllerMode } from "@/enums/pref-values"; import { BxEventBus } from "./bx-event-bus"; import { getGlobalPref } from "./pref-utils"; @@ -111,7 +109,6 @@ export class XhomeInterceptor { for (const pair of (clone.headers as any).entries()) { headers[pair[0]] = pair[1]; } - headers.authorization = `Bearer ${RemotePlayManager.getInstance()!.getXcloudToken()}`; const index = request.url.indexOf('.xboxlive.com'); request = new Request('https://wus.core.gssv-play-prod' + request.url.substring(index), { @@ -147,8 +144,6 @@ export class XhomeInterceptor { for (const pair of (clone.headers as any).entries()) { headers[pair[0]] = pair[1]; } - // Add xHome token to headers - headers.authorization = `Bearer ${RemotePlayManager.getInstance()!.getXhomeToken()}`; // Patch resolution const osName = getOsNameFromResolution(getGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION)); @@ -164,12 +159,7 @@ export class XhomeInterceptor { opts.body = await clone.text(); } - // Replace xCloud domain with xHome domain - let url = request.url; - if (!url.includes('/servers/home')) { - const parsed = new URL(url); - url = STATES.remotePlay.server + parsed.pathname; - } + const url = request.url; // Create new Request instance request = new Request(url, opts);