diff --git a/dist/better-xcloud.pretty.user.js b/dist/better-xcloud.pretty.user.js index 92f72e9..718fac3 100644 --- a/dist/better-xcloud.pretty.user.js +++ b/dist/better-xcloud.pretty.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Better xCloud // @namespace https://github.com/redphx -// @version 6.7.3 +// @version 6.7.4-beta // @description Improve Xbox Cloud Gaming (xCloud) experience // @author redphx // @license MIT @@ -194,7 +194,7 @@ class UserAgent { }); } } -var SCRIPT_VERSION = "6.7.3", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; +var SCRIPT_VERSION = "6.7.4-beta", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; UserAgent.init(); var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = { supportedRegion: !0, @@ -5738,6 +5738,22 @@ ${subsVar} = subs; if (!match) return !1; if (str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomTouchControl(productId)) && ${match[2]}`), match = /(\w+)&&(\w+\.push\(\w+\.MouseAndKeyboard\))/.exec(str), match) str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomNativeMkb(productId)) && ${match[2]}`); return str; + }, + patchStreamMetadata(str) { + let index = str.indexOf("}onVideoFrame("); + if (index >= 0 && (index = PatcherUtils.indexOf(str, "){", index, 30, !0)), index < 0) return !1; + let code = ` +try { + const obj = arguments[0]; + const baseMs = obj.frameSubmittedTimeMs; + const renderMs = obj.frameRenderedTimeMs - obj.frameDecodedTimeMs; + obj.frameDecodedTimeMs = baseMs + ${1}; + obj.frameRenderedTimeMs = obj.frameDecodedTimeMs + renderMs; + obj.expectedDisplayTime = obj.frameRenderedTimeMs; + arguments[0] = obj; +} catch (e) { alert(e) } +`; + return str = PatcherUtils.insertAt(str, index, code), str; } }, PATCH_ORDERS = PatcherUtils.filterPatches([ ...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? [ @@ -5813,6 +5829,7 @@ ${subsVar} = subs; "playVibration", "alwaysShowStreamHud", "injectStreamMenuUseEffect", + "patchStreamMetadata", getGlobalPref("audio.volume.booster.enabled") && !getGlobalPref("stream.video.combineAudio") && "patchAudioMediaStream", getGlobalPref("audio.volume.booster.enabled") && getGlobalPref("stream.video.combineAudio") && "patchCombinedAudioVideoMediaStream", getGlobalPref("ui.feedbackDialog.disabled") && "skipFeedbackDialog", diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js index af3aba0..88e21df 100755 --- a/dist/better-xcloud.user.js +++ b/dist/better-xcloud.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Better xCloud // @namespace https://github.com/redphx -// @version 6.7.3 +// @version 6.7.4-beta // @description Improve Xbox Cloud Gaming (xCloud) experience // @author redphx // @license MIT @@ -26,7 +26,7 @@ var ALL_PREFS = {global: ["audio.mic.onPlaying","audio.volume.booster.enabled"," var SMART_TV_UNIQUE_ID = "FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA", CHROMIUM_VERSION = "140.0.3485.54"; if (!!window.chrome || window.navigator.userAgent.includes("Chrome")) {let match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/);if (match) CHROMIUM_VERSION = match[1];} class UserAgent {static STORAGE_KEY = "BetterXcloud.UserAgent";static #config;static #isMobile = null;static #isSafari = null;static #isSafariMobile = null;static #USER_AGENTS = {"windows-edge": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROMIUM_VERSION} Safari/537.36 Edg/${CHROMIUM_VERSION}`,"macos-safari": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1","smarttv-generic": `${window.navigator.userAgent} Smart-TV`,"smarttv-tizen": `Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) ${CHROMIUM_VERSION}/7.0 TV Safari/537.36 ${SMART_TV_UNIQUE_ID}`,"vr-oculus": window.navigator.userAgent + " OculusBrowser VR"};static init() {if (UserAgent.#config = JSON.parse(window.localStorage.getItem(UserAgent.STORAGE_KEY) || "{}"), !UserAgent.#config.profile) UserAgent.#config.profile = BX_FLAGS.DeviceInfo.deviceType === "android-tv" || BX_FLAGS.DeviceInfo.deviceType === "webos" ? "vr-oculus" : "default";if (!UserAgent.#config.custom) UserAgent.#config.custom = "";UserAgent.spoof();}static updateStorage(profile, custom) {let config = UserAgent.#config;if (config.profile = profile, profile === "custom" && typeof custom !== "undefined") config.custom = custom;window.localStorage.setItem(UserAgent.STORAGE_KEY, JSON.stringify(config));}static getDefault() {return window.navigator.orgUserAgent || window.navigator.userAgent;}static get(profile) {let defaultUserAgent = window.navigator.userAgent;switch (profile) {case "default":return defaultUserAgent;case "custom":return UserAgent.#config.custom || defaultUserAgent;default:return UserAgent.#USER_AGENTS[profile] || defaultUserAgent;}}static isSafari() {if (this.#isSafari !== null) return this.#isSafari;let userAgent = UserAgent.getDefault().toLowerCase(), result = userAgent.includes("safari") && !userAgent.includes("chrom");return this.#isSafari = result, result;}static isSafariMobile() {if (this.#isSafariMobile !== null) return this.#isSafariMobile;let userAgent = UserAgent.getDefault().toLowerCase(), result = this.isSafari() && userAgent.includes("mobile");return this.#isSafariMobile = result, result;}static isMobile() {if (this.#isMobile !== null) return this.#isMobile;let userAgent = UserAgent.getDefault().toLowerCase(), result = /iphone|ipad|android/.test(userAgent);return this.#isMobile = result, result;}static spoof() {let profile = UserAgent.#config.profile;if (profile === "default") return;let newUserAgent = UserAgent.get(profile);if ("userAgentData" in window.navigator) window.navigator.orgUserAgentData = window.navigator.userAgentData, Object.defineProperty(window.navigator, "userAgentData", {});window.navigator.orgUserAgent = window.navigator.userAgent, Object.defineProperty(window.navigator, "userAgent", {value: newUserAgent});}} -var SCRIPT_VERSION = "6.7.3", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; +var SCRIPT_VERSION = "6.7.4-beta", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; UserAgent.init(); var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {supportedRegion: !0,serverRegions: {},selectedRegion: {},gsToken: "",isSignedIn: !1,isPlaying: !1,browser: {capabilities: {touch: browserHasTouchSupport,batteryApi: "getBattery" in window.navigator,deviceVibration: !!window.navigator.vibrate,mkb: AppInterface || !UserAgent.getDefault().toLowerCase().match(/(android|iphone|ipad)/),emulatedNativeMkb: !!AppInterface}},userAgent: {isTv,capabilities: {touch: userAgentHasTouchSupport,mkb: AppInterface || !userAgent.match(/(android|iphone|ipad)/)}},currentStream: {},remotePlay: {},pointerServerPort: 9269}; function deepClone(obj) {if (!obj) return {};if ("structuredClone" in window) return structuredClone(obj);return JSON.parse(JSON.stringify(obj));} @@ -194,9 +194,11 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {const settings = let subs = ${subsVar}; subs = subs.filter(val => !${JSON.stringify(filters)}.includes(val)); ${subsVar} = subs; -`;return str = PatcherUtils.insertAt(str, index, newCode), str;},exposeReactCreateComponent(str) {let index = str.indexOf(".prototype.isReactComponent={}");if (index > -1 && (index = PatcherUtils.indexOf(str, ".createElement=", index)), index < 0) return !1;if (str = PatcherUtils.insertAt(str, index - 1, "window.BX_EXPOSED.reactCreateElement="), index = PatcherUtils.indexOf(str, ".useEffect=", index), index < 0) return !1;return str = PatcherUtils.insertAt(str, index - 1, "window.BX_EXPOSED.reactUseEffect="), str;},gameCardCustomIcons(str) {let initialIndex = str.indexOf("const{supportedInputIcons:");if (initialIndex < 0) return !1;let returnIndex = PatcherUtils.lastIndexOf(str, "return ", str.indexOf("SupportedInputsBadge"));if (returnIndex < 0) return !1;let productIdIndex = PatcherUtils.lastIndexOf(str, ",productId:", initialIndex, 300);if (productIdIndex < 0) return !1;let params = PatcherUtils.findAndParseParams(str, productIdIndex - 200, 400);if (!params || !params.productId) return !1;let productIdVar = params.productId, supportedInputIconsVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, "supportedInputIcons:", initialIndex, 100, !0));if (!supportedInputIconsVar) return !1;let newCode = renderString(game_card_icons_default, {productId: productIdVar,supportedInputIcons: supportedInputIconsVar});return str = PatcherUtils.insertAt(str, returnIndex, newCode), str;},setImageQuality(str) {let index = str.indexOf("const{size:{width:");if (index > -1 && (index = PatcherUtils.indexOf(str, "=new URLSearchParams", index, 500)), index < 0) return !1;let paramVar = PatcherUtils.getVariableNameBefore(str, index);if (!paramVar) return !1;index = PatcherUtils.indexOf(str, "return", index, 200);let newCode = `${paramVar}.set('q', ${getGlobalPref("ui.imageQuality")});`;return str = PatcherUtils.insertAt(str, index, newCode), str;},setBackgroundImageQuality(str) {let index = str.indexOf("}?w=${");if (index > -1 && (index = PatcherUtils.indexOf(str, "}", index + 1, 10, !0)), index < 0) return !1;return str = PatcherUtils.insertAt(str, index, `&q=${getGlobalPref("ui.imageQuality")}`), str;},injectHeaderUseEffect(str) {let index = str.indexOf('className:"Header-module__header');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 300)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.header.rendered");},injectErrorPageUseEffect(str) {let index = str.indexOf('"PureErrorPage-module__container');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "})=>(0,", index, 200)), index < 0) return !1;return str = PatcherUtils.insertAt(str, index + 4, "{return "), str = PatcherUtils.injectUseEffect(str, index + 5, "Script", "ui.error.rendered"), str += "}", str;},injectStreamMenuUseEffect(str) {let index = str.indexOf('"StreamMenu-module__container');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Stream", "ui.streamMenu.rendered");},injectGuideHomeUseEffect(str) {let index = str.indexOf('"HomeLandingPage-module__authenticatedContentContainer');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideHome.rendered");},injectCreatePortal(str) {let index = str.indexOf(".createPortal=function");if (index > -1 && (index = PatcherUtils.indexOf(str, "{", index, 50, !0)), index < 0) return !1;return str = PatcherUtils.insertAt(str, index, create_portal_default), str;},injectAchievementsProgressUseEffect(str) {let index = str.indexOf('"AchievementsButton-module__progressBarContainer');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideAchievementProgress.rendered");},injectAchievementsDetailUseEffect(str) {let index = str.indexOf("GuideAchievementDetail.useParams()");if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "const", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideAchievementDetail.rendered");},patchCustomInputIcon(str) {let index = str.indexOf('.MouseAndKeyboard="MouseAndKeyboard"');if (index < 0) return !1;let productIdMatch = /const (\w+)=(\w+)=>{/.exec(str.substring(index, index + 200));if (!productIdMatch) return !1;str = str.replace(productIdMatch[0], productIdMatch[0] + `const productId = ${productIdMatch[2]};`);let match = /(\w+)&&(\w+\.push\(\w+\.Touch\))/.exec(str);if (!match) return !1;if (str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomTouchControl(productId)) && ${match[2]}`), match = /(\w+)&&(\w+\.push\(\w+\.MouseAndKeyboard\))/.exec(str), match) str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomNativeMkb(productId)) && ${match[2]}`);return str;}}, PATCH_ORDERS = PatcherUtils.filterPatches([...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? ["enableNativeMkb","disableAbsoluteMouse"] : [],"exposeReactCreateComponent","injectCreatePortal","broadcastPollingMode",getGlobalPref("ui.gameCard.waitTime.show") && "patchSetCurrentFocus","patchGamepadPolling","modifyPreloadedState","detectBrowserRouterReady","exposeStreamSession","supportLocalCoOp","disableStreamGate","exposeDialogRoutes",...getGlobalPref("ui.imageQuality") < 90 ? ["setImageQuality"] : [],"patchRequestInfoCrash","injectErrorPageUseEffect","streamPageBeforeLoad","remotePlayStreamPageBeforeLoad","injectGuideHomeUseEffect","injectAchievementsProgressUseEffect","injectAchievementsDetailUseEffect","guideAchievementsDefaultLocked","injectHeaderUseEffect","homePageBeforeLoad","patchCustomInputIcon","gameCardCustomIcons","productDetailPageBeforeLoad","enableTvRoutes","overrideStorageGetSettings",getGlobalPref("ui.layout") !== "default" && "websiteLayout",getGlobalPref("game.fortnite.forceConsole") && "forceFortniteConsole",...STATES.userAgent.capabilities.touch ? ["disableTouchContextMenu"] : [],...getGlobalPref("block.tracking") ? ["disableAiTrack","blockWebRtcStatsCollector","disableIndexDbLogging","disableTelemetryProvider"] : [],...!getGlobalPref("block.features").includes("remote-play") ? ["remotePlayKeepAlive","remotePlayDisableAchievementToast",STATES.userAgent.capabilities.touch && "patchUpdateInputConfigurationAsync"] : [],...BX_FLAGS.EnableXcloudLogging ? ["enableConsoleLogging","enableXcloudLogger"] : [] +`;return str = PatcherUtils.insertAt(str, index, newCode), str;},exposeReactCreateComponent(str) {let index = str.indexOf(".prototype.isReactComponent={}");if (index > -1 && (index = PatcherUtils.indexOf(str, ".createElement=", index)), index < 0) return !1;if (str = PatcherUtils.insertAt(str, index - 1, "window.BX_EXPOSED.reactCreateElement="), index = PatcherUtils.indexOf(str, ".useEffect=", index), index < 0) return !1;return str = PatcherUtils.insertAt(str, index - 1, "window.BX_EXPOSED.reactUseEffect="), str;},gameCardCustomIcons(str) {let initialIndex = str.indexOf("const{supportedInputIcons:");if (initialIndex < 0) return !1;let returnIndex = PatcherUtils.lastIndexOf(str, "return ", str.indexOf("SupportedInputsBadge"));if (returnIndex < 0) return !1;let productIdIndex = PatcherUtils.lastIndexOf(str, ",productId:", initialIndex, 300);if (productIdIndex < 0) return !1;let params = PatcherUtils.findAndParseParams(str, productIdIndex - 200, 400);if (!params || !params.productId) return !1;let productIdVar = params.productId, supportedInputIconsVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, "supportedInputIcons:", initialIndex, 100, !0));if (!supportedInputIconsVar) return !1;let newCode = renderString(game_card_icons_default, {productId: productIdVar,supportedInputIcons: supportedInputIconsVar});return str = PatcherUtils.insertAt(str, returnIndex, newCode), str;},setImageQuality(str) {let index = str.indexOf("const{size:{width:");if (index > -1 && (index = PatcherUtils.indexOf(str, "=new URLSearchParams", index, 500)), index < 0) return !1;let paramVar = PatcherUtils.getVariableNameBefore(str, index);if (!paramVar) return !1;index = PatcherUtils.indexOf(str, "return", index, 200);let newCode = `${paramVar}.set('q', ${getGlobalPref("ui.imageQuality")});`;return str = PatcherUtils.insertAt(str, index, newCode), str;},setBackgroundImageQuality(str) {let index = str.indexOf("}?w=${");if (index > -1 && (index = PatcherUtils.indexOf(str, "}", index + 1, 10, !0)), index < 0) return !1;return str = PatcherUtils.insertAt(str, index, `&q=${getGlobalPref("ui.imageQuality")}`), str;},injectHeaderUseEffect(str) {let index = str.indexOf('className:"Header-module__header');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 300)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.header.rendered");},injectErrorPageUseEffect(str) {let index = str.indexOf('"PureErrorPage-module__container');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "})=>(0,", index, 200)), index < 0) return !1;return str = PatcherUtils.insertAt(str, index + 4, "{return "), str = PatcherUtils.injectUseEffect(str, index + 5, "Script", "ui.error.rendered"), str += "}", str;},injectStreamMenuUseEffect(str) {let index = str.indexOf('"StreamMenu-module__container');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Stream", "ui.streamMenu.rendered");},injectGuideHomeUseEffect(str) {let index = str.indexOf('"HomeLandingPage-module__authenticatedContentContainer');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideHome.rendered");},injectCreatePortal(str) {let index = str.indexOf(".createPortal=function");if (index > -1 && (index = PatcherUtils.indexOf(str, "{", index, 50, !0)), index < 0) return !1;return str = PatcherUtils.insertAt(str, index, create_portal_default), str;},injectAchievementsProgressUseEffect(str) {let index = str.indexOf('"AchievementsButton-module__progressBarContainer');if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideAchievementProgress.rendered");},injectAchievementsDetailUseEffect(str) {let index = str.indexOf("GuideAchievementDetail.useParams()");if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "const", index, 200)), index < 0) return !1;return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideAchievementDetail.rendered");},patchCustomInputIcon(str) {let index = str.indexOf('.MouseAndKeyboard="MouseAndKeyboard"');if (index < 0) return !1;let productIdMatch = /const (\w+)=(\w+)=>{/.exec(str.substring(index, index + 200));if (!productIdMatch) return !1;str = str.replace(productIdMatch[0], productIdMatch[0] + `const productId = ${productIdMatch[2]};`);let match = /(\w+)&&(\w+\.push\(\w+\.Touch\))/.exec(str);if (!match) return !1;if (str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomTouchControl(productId)) && ${match[2]}`), match = /(\w+)&&(\w+\.push\(\w+\.MouseAndKeyboard\))/.exec(str), match) str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomNativeMkb(productId)) && ${match[2]}`);return str;},patchStreamMetadata(str) {let index = str.indexOf("}onVideoFrame(");if (index >= 0 && (index = PatcherUtils.indexOf(str, "){", index, 30, !0)), index < 0) return !1;let code = ` +try {const obj = arguments[0];const baseMs = obj.frameSubmittedTimeMs;const renderMs = obj.frameRenderedTimeMs - obj.frameDecodedTimeMs;obj.frameDecodedTimeMs = baseMs + ${1};obj.frameRenderedTimeMs = obj.frameDecodedTimeMs + renderMs;obj.expectedDisplayTime = obj.frameRenderedTimeMs;arguments[0] = obj;} catch (e) { alert(e) } +`;return str = PatcherUtils.insertAt(str, index, code), str;}}, PATCH_ORDERS = PatcherUtils.filterPatches([...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? ["enableNativeMkb","disableAbsoluteMouse"] : [],"exposeReactCreateComponent","injectCreatePortal","broadcastPollingMode",getGlobalPref("ui.gameCard.waitTime.show") && "patchSetCurrentFocus","patchGamepadPolling","modifyPreloadedState","detectBrowserRouterReady","exposeStreamSession","supportLocalCoOp","disableStreamGate","exposeDialogRoutes",...getGlobalPref("ui.imageQuality") < 90 ? ["setImageQuality"] : [],"patchRequestInfoCrash","injectErrorPageUseEffect","streamPageBeforeLoad","remotePlayStreamPageBeforeLoad","injectGuideHomeUseEffect","injectAchievementsProgressUseEffect","injectAchievementsDetailUseEffect","guideAchievementsDefaultLocked","injectHeaderUseEffect","homePageBeforeLoad","patchCustomInputIcon","gameCardCustomIcons","productDetailPageBeforeLoad","enableTvRoutes","overrideStorageGetSettings",getGlobalPref("ui.layout") !== "default" && "websiteLayout",getGlobalPref("game.fortnite.forceConsole") && "forceFortniteConsole",...STATES.userAgent.capabilities.touch ? ["disableTouchContextMenu"] : [],...getGlobalPref("block.tracking") ? ["disableAiTrack","blockWebRtcStatsCollector","disableIndexDbLogging","disableTelemetryProvider"] : [],...!getGlobalPref("block.features").includes("remote-play") ? ["remotePlayKeepAlive","remotePlayDisableAchievementToast",STATES.userAgent.capabilities.touch && "patchUpdateInputConfigurationAsync"] : [],...BX_FLAGS.EnableXcloudLogging ? ["enableConsoleLogging","enableXcloudLogger"] : [] ]), hideSections = getGlobalPref("ui.hideSections"), HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([hideSections.includes("genres") && "ignoreGenresSection",hideSections.includes("byog") && "ignoreByogSection",STATES.browser.capabilities.touch && hideSections.includes("touch") && "ignorePlayWithTouchSection",getGlobalPref("ui.imageQuality") < 90 && "setBackgroundImageQuality",hideSections.some((value) => ["native-mkb", "most-popular"].includes(value)) && "ignoreSiglSections",hideSections.includes("news") && "ignoreNewsSection",(getGlobalPref("block.features").includes("friends") || hideSections.includes("friends")) && "ignorePlayWithFriendsSection",hideSections.includes("all-games") && "ignoreAllGamesSection",...blockSomeNotifications() ? ["changeNotificationsSubscription"] : [] -]), STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches(["exposeInputChannel","patchXcloudTitleInfo","disableGamepadDisconnectedScreen","patchStreamHud","playVibration","alwaysShowStreamHud","injectStreamMenuUseEffect",getGlobalPref("audio.volume.booster.enabled") && !getGlobalPref("stream.video.combineAudio") && "patchAudioMediaStream",getGlobalPref("audio.volume.booster.enabled") && getGlobalPref("stream.video.combineAudio") && "patchCombinedAudioVideoMediaStream",getGlobalPref("ui.feedbackDialog.disabled") && "skipFeedbackDialog",...STATES.userAgent.capabilities.touch ? [getGlobalPref("touchController.mode") === "all" && "patchShowSensorControls",getGlobalPref("touchController.mode") === "all" && "exposeTouchLayoutManager",(getGlobalPref("touchController.mode") === "off" || getGlobalPref("touchController.autoOff")) && "disableTakRenderer",getGlobalPref("touchController.opacity.default") !== 100 && "patchTouchControlDefaultOpacity",getGlobalPref("touchController.mode") !== "off" && (getGlobalPref("mkb.enabled") || getGlobalPref("nativeMkb.mode") === "on") && "patchBabylonRendererClass"] : [],"patchPollGamepads",getGlobalPref("stream.video.combineAudio") && "streamCombineSources",...!getGlobalPref("block.features").includes("remote-play") ? ["remotePlayPostStreamRedirectUrl","patchRemotePlayMkb"] : [],...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? ["patchMouseAndKeyboardEnabled","disableNativeRequestPointerLock"] : [] +]), STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches(["exposeInputChannel","patchXcloudTitleInfo","disableGamepadDisconnectedScreen","patchStreamHud","playVibration","alwaysShowStreamHud","injectStreamMenuUseEffect","patchStreamMetadata",getGlobalPref("audio.volume.booster.enabled") && !getGlobalPref("stream.video.combineAudio") && "patchAudioMediaStream",getGlobalPref("audio.volume.booster.enabled") && getGlobalPref("stream.video.combineAudio") && "patchCombinedAudioVideoMediaStream",getGlobalPref("ui.feedbackDialog.disabled") && "skipFeedbackDialog",...STATES.userAgent.capabilities.touch ? [getGlobalPref("touchController.mode") === "all" && "patchShowSensorControls",getGlobalPref("touchController.mode") === "all" && "exposeTouchLayoutManager",(getGlobalPref("touchController.mode") === "off" || getGlobalPref("touchController.autoOff")) && "disableTakRenderer",getGlobalPref("touchController.opacity.default") !== 100 && "patchTouchControlDefaultOpacity",getGlobalPref("touchController.mode") !== "off" && (getGlobalPref("mkb.enabled") || getGlobalPref("nativeMkb.mode") === "on") && "patchBabylonRendererClass"] : [],"patchPollGamepads",getGlobalPref("stream.video.combineAudio") && "streamCombineSources",...!getGlobalPref("block.features").includes("remote-play") ? ["remotePlayPostStreamRedirectUrl","patchRemotePlayMkb"] : [],...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? ["patchMouseAndKeyboardEnabled","disableNativeRequestPointerLock"] : [] ]), PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches(["detectProductDetailPage" ]), ALL_PATCHES = [...PATCH_ORDERS, ...HOME_PAGE_PATCH_ORDERS, ...STREAM_PAGE_PATCH_ORDERS, ...PRODUCT_DETAIL_PAGE_PATCH_ORDERS]; class Patcher {static remainingPatches = {home: HOME_PAGE_PATCH_ORDERS,stream: STREAM_PAGE_PATCH_ORDERS,"remote-play-stream": STREAM_PAGE_PATCH_ORDERS,"product-detail": PRODUCT_DETAIL_PAGE_PATCH_ORDERS};static patchPage(page) {let remaining = Patcher.remainingPatches[page];if (!remaining) return;PATCH_ORDERS = PATCH_ORDERS.concat(remaining), delete Patcher.remainingPatches[page];}static patchNativeBind() {let nativeBind = Function.prototype.bind;Function.prototype.bind = function() {let valid = !1;if (this.name.length <= 2 && arguments.length === 2 && arguments[0] === null) {if (arguments[1] === 0 || typeof arguments[1] === "function") valid = !0;}if (!valid) return nativeBind.apply(this, arguments);if (typeof arguments[1] === "function") BxLogger.info(LOG_TAG2, "Restored Function.prototype.bind()"), Function.prototype.bind = nativeBind;let orgFunc = this, newFunc = (a, item2) => {Patcher.checkChunks(item2), orgFunc(a, item2);};return nativeBind.apply(newFunc, arguments);};}static checkChunks(item) {let patchesToCheck, appliedPatches, chunkData = item[1], patchesMap = {}, patcherCache = PatcherCache.getInstance();for (let chunkId in chunkData) {appliedPatches = [];let cachedPatches = patcherCache.getPatches(chunkId);if (cachedPatches) patchesToCheck = cachedPatches.slice(0), patchesToCheck.push(...PATCH_ORDERS);else patchesToCheck = PATCH_ORDERS.slice(0);if (!patchesToCheck.length) continue;let func = chunkData[chunkId], funcStr = func.toString(), patchedFuncStr = funcStr, modified = !1, chunkAppliedPatches = [];for (let patchIndex = 0;patchIndex < patchesToCheck.length; patchIndex++) {let patchName = patchesToCheck[patchIndex];if (appliedPatches.indexOf(patchName) > -1) continue;if (!PATCHES[patchName]) continue;let tmpStr = PATCHES[patchName].call(null, patchedFuncStr);if (!tmpStr) continue;modified = !0, patchedFuncStr = tmpStr, appliedPatches.push(patchName), chunkAppliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName);}if (modified) {BxLogger.info(LOG_TAG2, `✅ [${chunkId}] ${chunkAppliedPatches.join(", ")}`), PATCH_ORDERS.length && BxLogger.info(LOG_TAG2, "Remaining patches", PATCH_ORDERS), BX_FLAGS.Debug && console.time(LOG_TAG2);try {chunkData[chunkId] = eval(patchedFuncStr);} catch (e) {if (e instanceof Error) BxLogger.error(LOG_TAG2, "Error", appliedPatches, e.message, patchedFuncStr);}BX_FLAGS.Debug && console.timeEnd(LOG_TAG2);}if (appliedPatches.length) patchesMap[chunkId] = appliedPatches;}if (Object.keys(patchesMap).length) patcherCache.saveToCache(patchesMap);}static init() {Patcher.patchNativeBind();}} diff --git a/src/modules/patcher/patcher.ts b/src/modules/patcher/patcher.ts index e08657a..717790f 100755 --- a/src/modules/patcher/patcher.ts +++ b/src/modules/patcher/patcher.ts @@ -1241,6 +1241,33 @@ ${subsVar} = subs; return str; }, + /** + * Modify "metadataToSend" value, prevent dropping resolution when Decode Time is high + * https://github.com/redphx/better-xcloud/issues/791 + */ + patchStreamMetadata(str: string) { + let index = str.indexOf('}onVideoFrame('); + (index >= 0) && (index = PatcherUtils.indexOf(str, '){', index, 30, true)); + if (index < 0) { + return false; + } + + const fakeDtMs = 1; + const code = ` +try { + const obj = arguments[0]; + const baseMs = obj.frameSubmittedTimeMs; + const renderMs = obj.frameRenderedTimeMs - obj.frameDecodedTimeMs; + obj.frameDecodedTimeMs = baseMs + ${fakeDtMs}; + obj.frameRenderedTimeMs = obj.frameDecodedTimeMs + renderMs; + obj.expectedDisplayTime = obj.frameRenderedTimeMs; + arguments[0] = obj; +} catch (e) { alert(e) } +`; + str = PatcherUtils.insertAt(str, index, code); + return str; + }, + /* patchBasicGameInfo(str: string) { let index = str.indexOf('.ChildXboxTitleIds,offerings'); @@ -1386,6 +1413,8 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([ 'injectStreamMenuUseEffect', + 'patchStreamMetadata', + // 'exposeEventTarget', // Patch volume control for normal stream