Compare commits

...

19 Commits

Author SHA1 Message Date
b733d55e9e Bump version to 6.4.7 2025-03-21 06:37:13 +07:00
317ac9017b Fix custom input icons not showing in game card 2025-03-21 06:28:59 +07:00
b8c62a1f4d Fix Remote Play's achievement notification 2025-03-21 05:37:38 +07:00
7332528f72 Remove "enableConsoleLogging" patch from Stream page 2025-03-21 05:29:14 +07:00
d063500aae Fix not detecting new xCloud version correctly 2025-03-21 05:26:09 +07:00
29ff1bc09c Bump version to 6.4.6 2025-03-11 17:49:46 +07:00
8998daf14c Always check for new version 2025-03-11 17:49:15 +07:00
8bdad8b319 Add Czech translations 2025-03-11 17:43:49 +07:00
5dd3ebdea1 Fix unable to connect to console using Remote Play in some cases 2025-03-11 17:27:59 +07:00
55d7796f96 Bump version to 6.4.5 2025-02-21 07:17:23 +07:00
0b02a758db Fix custom touch control not working in Remote Play (#674) 2025-02-21 07:10:54 +07:00
3b2abbf6bb Fix video in detail page not playing (#679) 2025-02-21 06:55:55 +07:00
43a66db697 Fix patches 2025-02-21 06:46:29 +07:00
a3130101f4 Bump version to 6.4.4 2025-02-14 09:04:49 +07:00
3483672554 Fix Remote Play stopped working 2025-02-14 09:04:33 +07:00
75d7443e0f Bump version to 6.4.3 2025-02-14 06:25:04 +07:00
b5d2d0fdec Enable "1080p (HQ)" setting for Remote Play 2025-02-14 06:24:52 +07:00
20afe92371 Bump version to 6.4.2 2025-02-14 06:06:01 +07:00
5738412f71 Fix crashing in LoadingScreen 2025-02-14 06:05:46 +07:00
18 changed files with 183 additions and 152 deletions

View File

@ -1,5 +1,5 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud // @name Better xCloud
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 6.4.1 // @version 6.4.7
// ==/UserScript== // ==/UserScript==

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud // @name Better xCloud
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 6.4.1 // @version 6.4.7
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
@ -192,7 +192,7 @@ class UserAgent {
}); });
} }
} }
var SCRIPT_VERSION = "6.4.1", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; var SCRIPT_VERSION = "6.4.7", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface;
UserAgent.init(); 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 = { 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, supportedRegion: !0,
@ -382,6 +382,7 @@ class GhPagesUtils {
var SUPPORTED_LANGUAGES = { var SUPPORTED_LANGUAGES = {
"en-US": "English (US)", "en-US": "English (US)",
"ca-CA": "Català", "ca-CA": "Català",
"cs-CZ": "čeština",
"da-DK": "dansk", "da-DK": "dansk",
"de-DE": "Deutsch", "de-DE": "Deutsch",
"en-ID": "Bahasa Indonesia", "en-ID": "Bahasa Indonesia",
@ -571,6 +572,7 @@ var SUPPORTED_LANGUAGES = {
"new-version-available": [ "new-version-available": [
e => `Version ${e.version} available`, e => `Version ${e.version} available`,
e => `Versió ${e.version} disponible`, e => `Versió ${e.version} disponible`,
e => `Verze ${e.version} dostupná`,
, ,
e => `Version ${e.version} verfügbar`, e => `Version ${e.version} verfügbar`,
e => `Versi ${e.version} tersedia`, e => `Versi ${e.version} tersedia`,
@ -616,6 +618,7 @@ var SUPPORTED_LANGUAGES = {
"press-key-to-toggle-mkb": [ "press-key-to-toggle-mkb": [
e => `Press ${e.key} to toggle this feature`, e => `Press ${e.key} to toggle this feature`,
e => `Premeu ${e.key} per alternar aquesta funció`, e => `Premeu ${e.key} per alternar aquesta funció`,
e => `Zmáčknete ${e.key} pro přepnutí této funkce`,
e => `Tryk på ${e.key} for at slå denne funktion til`, e => `Tryk på ${e.key} for at slå denne funktion til`,
e => `${e.key}: Funktion an-/ausschalten`, e => `${e.key}: Funktion an-/ausschalten`,
e => `Tekan ${e.key} untuk mengaktifkan fitur ini`, e => `Tekan ${e.key} untuk mengaktifkan fitur ini`,
@ -641,6 +644,7 @@ var SUPPORTED_LANGUAGES = {
e => `Recommended settings for ${e.device}`, e => `Recommended settings for ${e.device}`,
e => `Configuració recomanada per a ${e.device}`, e => `Configuració recomanada per a ${e.device}`,
, ,
,
e => `Empfohlene Einstellungen für ${e.device}`, e => `Empfohlene Einstellungen für ${e.device}`,
e => `Rekomendasi pengaturan untuk ${e.device}`, e => `Rekomendasi pengaturan untuk ${e.device}`,
e => `Ajustes recomendados para ${e.device}`, e => `Ajustes recomendados para ${e.device}`,
@ -751,6 +755,7 @@ var SUPPORTED_LANGUAGES = {
"touch-control-layout-by": [ "touch-control-layout-by": [
e => `Touch control layout by ${e.name}`, e => `Touch control layout by ${e.name}`,
e => `Format del control tàctil per ${e.name}`, e => `Format del control tàctil per ${e.name}`,
e => `Rozložení dotykového ovládání ${e.name}`,
e => `Touch-kontrol layout af ${e.name}`, e => `Touch-kontrol layout af ${e.name}`,
e => `Touch-Steuerungslayout von ${e.name}`, e => `Touch-Steuerungslayout von ${e.name}`,
e => `Tata letak Sentuhan layar oleh ${e.name}`, e => `Tata letak Sentuhan layar oleh ${e.name}`,
@ -2688,11 +2693,12 @@ function setPref(prefKey, value, origin) {
} }
function checkForUpdate() { function checkForUpdate() {
if (SCRIPT_VERSION.includes("beta")) return; if (SCRIPT_VERSION.includes("beta")) return;
fetch("https://api.github.com/repos/redphx/better-xcloud/releases/latest").then((response) => response.json()).then((json) => {
setGlobalPref("version.latest", json.tag_name.substring(1), "direct"), setGlobalPref("version.current", SCRIPT_VERSION, "direct");
});
let CHECK_INTERVAL_SECONDS = 7200, currentVersion = getGlobalPref("version.current"), lastCheck = getGlobalPref("version.lastCheck"), now = Math.round(+new Date / 1000); let CHECK_INTERVAL_SECONDS = 7200, currentVersion = getGlobalPref("version.current"), lastCheck = getGlobalPref("version.lastCheck"), now = Math.round(+new Date / 1000);
if (currentVersion === SCRIPT_VERSION && now - lastCheck < CHECK_INTERVAL_SECONDS) return; if (currentVersion === SCRIPT_VERSION && now - lastCheck < CHECK_INTERVAL_SECONDS) return;
setGlobalPref("version.lastCheck", now, "direct"), fetch("https://api.github.com/repos/redphx/better-xcloud/releases/latest").then((response) => response.json()).then((json) => { setGlobalPref("version.lastCheck", now, "direct"), Translations.updateTranslations(currentVersion === SCRIPT_VERSION);
setGlobalPref("version.latest", json.tag_name.substring(1), "direct"), setGlobalPref("version.current", SCRIPT_VERSION, "direct");
}), Translations.updateTranslations(currentVersion === SCRIPT_VERSION);
} }
function disablePwa() { function disablePwa() {
if (!(window.navigator.orgUserAgent || window.navigator.userAgent || "").toLowerCase()) return; if (!(window.navigator.orgUserAgent || window.navigator.userAgent || "").toLowerCase()) return;
@ -4981,7 +4987,7 @@ class TouchController {
} }
if (!layoutId) layoutId = TouchController.#customLayouts[xboxTitleId]?.default_layout || null; if (!layoutId) layoutId = TouchController.#customLayouts[xboxTitleId]?.default_layout || null;
if (!layoutId) { if (!layoutId) {
BxLogger.error(LOG_TAG, "Invalid layoutId, show default controller"), TouchController.#enabled && TouchController.#showDefault(); BxLogger.warning(LOG_TAG, "Invalid layoutId, show default controller"), TouchController.#enabled && TouchController.#showDefault();
return; return;
} }
let layoutChanged = TouchController.#currentLayoutId !== layoutId; let layoutChanged = TouchController.#currentLayoutId !== layoutId;
@ -5017,6 +5023,9 @@ class TouchController {
static getCustomList() { static getCustomList() {
return TouchController.#customList; return TouchController.#customList;
} }
static hasCustomControl(productId) {
return TouchController.#customList?.includes(productId);
}
static setup() { static setup() {
window.testTouchLayout = (layout) => { window.testTouchLayout = (layout) => {
let { touchLayoutManager } = window.BX_EXPOSED; let { touchLayoutManager } = window.BX_EXPOSED;
@ -5073,7 +5082,7 @@ var poll_gamepad_default = "var self=this;if(window.BX_EXPOSED.disableGamepadPol
var expose_stream_session_default = 'var self=this;window.BX_EXPOSED.streamSession=self;var orgSetMicrophoneState=self.setMicrophoneState.bind(self);self.setMicrophoneState=(state)=>{orgSetMicrophoneState(state),window.BxEventBus.Stream.emit("microphone.state.changed",{state})};window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));var updateDimensionsStr=self.updateDimensions.toString();if(updateDimensionsStr.startsWith("function "))updateDimensionsStr=updateDimensionsStr.substring(9);var renderTargetVar=updateDimensionsStr.match(/if\\((\\w+)\\){/)[1];updateDimensionsStr=updateDimensionsStr.replaceAll(renderTargetVar+".scroll","scroll");updateDimensionsStr=updateDimensionsStr.replace(`if(${renderTargetVar}){`,`\nif (${renderTargetVar}) {\nconst scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;\nconst scrollHeight = ${renderTargetVar}.dataset.height ? parseInt(${renderTargetVar}.dataset.height) : ${renderTargetVar}.scrollHeight;\n`);eval(`this.updateDimensions = function ${updateDimensionsStr}`);\n'; var expose_stream_session_default = 'var self=this;window.BX_EXPOSED.streamSession=self;var orgSetMicrophoneState=self.setMicrophoneState.bind(self);self.setMicrophoneState=(state)=>{orgSetMicrophoneState(state),window.BxEventBus.Stream.emit("microphone.state.changed",{state})};window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));var updateDimensionsStr=self.updateDimensions.toString();if(updateDimensionsStr.startsWith("function "))updateDimensionsStr=updateDimensionsStr.substring(9);var renderTargetVar=updateDimensionsStr.match(/if\\((\\w+)\\){/)[1];updateDimensionsStr=updateDimensionsStr.replaceAll(renderTargetVar+".scroll","scroll");updateDimensionsStr=updateDimensionsStr.replace(`if(${renderTargetVar}){`,`\nif (${renderTargetVar}) {\nconst scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;\nconst scrollHeight = ${renderTargetVar}.dataset.height ? parseInt(${renderTargetVar}.dataset.height) : ${renderTargetVar}.scrollHeight;\n`);eval(`this.updateDimensions = function ${updateDimensionsStr}`);\n';
var game_card_icons_default = `var supportedInputIcons=$supportedInputIcons$,{productId}=$param$;supportedInputIcons.shift();if(window.BX_EXPOSED.localCoOpManager.isSupported(productId))supportedInputIcons.push(window.BX_EXPOSED.createReactLocalCoOpIcon);`; var game_card_icons_default = `var supportedInputIcons=$supportedInputIcons$,{productId}=$param$;supportedInputIcons.shift();if(window.BX_EXPOSED.localCoOpManager.isSupported(productId))supportedInputIcons.push(window.BX_EXPOSED.createReactLocalCoOpIcon);`;
var local_co_op_enable_default = 'this.orgOnGamepadChanged=this.onGamepadChanged;this.orgOnGamepadInput=this.onGamepadInput;var match,onGamepadChangedStr=this.onGamepadChanged.toString();if(onGamepadChangedStr.startsWith("function "))onGamepadChangedStr=onGamepadChangedStr.substring(9);onGamepadChangedStr=onGamepadChangedStr.replaceAll("0","arguments[1]");eval(`this.patchedOnGamepadChanged = function ${onGamepadChangedStr}`);var onGamepadInputStr=this.onGamepadInput.toString();if(onGamepadInputStr.startsWith("function "))onGamepadInputStr=onGamepadInputStr.substring(9);match=onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);if(match){let gamepadIndexVar=match[0];onGamepadInputStr=onGamepadInputStr.replace("this.gamepadStates.get(",`this.gamepadStates.get(${gamepadIndexVar},`),eval(`this.patchedOnGamepadInput = function ${onGamepadInputStr}`),BxLogger.info("supportLocalCoOp","✅ Successfully patched local co-op support")}else BxLogger.error("supportLocalCoOp","❌ Unable to patch local co-op support");this.toggleLocalCoOp=(enable)=>{BxLogger.info("toggleLocalCoOp",enable?"Enabled":"Disabled"),this.onGamepadChanged=enable?this.patchedOnGamepadChanged:this.orgOnGamepadChanged,this.onGamepadInput=enable?this.patchedOnGamepadInput:this.orgOnGamepadInput;let gamepads=window.navigator.getGamepads();for(let gamepad of gamepads){if(!gamepad?.connected)continue;if(gamepad.id.includes("Better xCloud"))continue;gamepad._noToast=!0,window.dispatchEvent(new GamepadEvent("gamepaddisconnected",{gamepad})),window.dispatchEvent(new GamepadEvent("gamepadconnected",{gamepad}))}};window.BX_EXPOSED.toggleLocalCoOp=this.toggleLocalCoOp.bind(null);\n'; var local_co_op_enable_default = 'this.orgOnGamepadChanged=this.onGamepadChanged;this.orgOnGamepadInput=this.onGamepadInput;var match,onGamepadChangedStr=this.onGamepadChanged.toString();if(onGamepadChangedStr.startsWith("function "))onGamepadChangedStr=onGamepadChangedStr.substring(9);onGamepadChangedStr=onGamepadChangedStr.replaceAll("0","arguments[1]");eval(`this.patchedOnGamepadChanged = function ${onGamepadChangedStr}`);var onGamepadInputStr=this.onGamepadInput.toString();if(onGamepadInputStr.startsWith("function "))onGamepadInputStr=onGamepadInputStr.substring(9);match=onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);if(match){let gamepadIndexVar=match[0];onGamepadInputStr=onGamepadInputStr.replace("this.gamepadStates.get(",`this.gamepadStates.get(${gamepadIndexVar},`),eval(`this.patchedOnGamepadInput = function ${onGamepadInputStr}`),BxLogger.info("supportLocalCoOp","✅ Successfully patched local co-op support")}else BxLogger.error("supportLocalCoOp","❌ Unable to patch local co-op support");this.toggleLocalCoOp=(enable)=>{BxLogger.info("toggleLocalCoOp",enable?"Enabled":"Disabled"),this.onGamepadChanged=enable?this.patchedOnGamepadChanged:this.orgOnGamepadChanged,this.onGamepadInput=enable?this.patchedOnGamepadInput:this.orgOnGamepadInput;let gamepads=window.navigator.getGamepads();for(let gamepad of gamepads){if(!gamepad?.connected)continue;if(gamepad.id.includes("Better xCloud"))continue;gamepad._noToast=!0,window.dispatchEvent(new GamepadEvent("gamepaddisconnected",{gamepad})),window.dispatchEvent(new GamepadEvent("gamepadconnected",{gamepad}))}};window.BX_EXPOSED.toggleLocalCoOp=this.toggleLocalCoOp.bind(null);\n';
var remote_play_keep_alive_default = `try{if(JSON.parse(e).reason==="WarningForBeingIdle"&&!window.location.pathname.includes("/launch/")){this.sendKeepAlive();return}}catch(ex){console.log(ex)}`; var remote_play_keep_alive_default = `try{if(JSON.parse(e).reason==="WarningForBeingIdle"&&window.location.pathname.includes("/consoles/launch/")){this.sendKeepAlive();return}}catch(ex){console.log(ex)}`;
var vibration_adjust_default = `if(e?.gamepad?.connected){let gamepadSettings=window.BX_STREAM_SETTINGS.controllers[e.gamepad.id];if(gamepadSettings?.customization){let intensity=gamepadSettings.customization.vibrationIntensity;if(intensity<=0){e.repeat=0;return}else if(intensity<1)e.leftMotorPercent*=intensity,e.rightMotorPercent*=intensity,e.leftTriggerMotorPercent*=intensity,e.rightTriggerMotorPercent*=intensity}}`; var vibration_adjust_default = `if(e?.gamepad?.connected){let gamepadSettings=window.BX_STREAM_SETTINGS.controllers[e.gamepad.id];if(gamepadSettings?.customization){let intensity=gamepadSettings.customization.vibrationIntensity;if(intensity<=0){e.repeat=0;return}else if(intensity<1)e.leftMotorPercent*=intensity,e.rightMotorPercent*=intensity,e.leftTriggerMotorPercent*=intensity,e.rightTriggerMotorPercent*=intensity}}`;
var stream_hud_default = `var options=arguments[0];window.BX_EXPOSED.showStreamMenu=options.onShowStreamMenu;options.guideUI=null;window.BX_EXPOSED.reactUseEffect(()=>{window.BxEventBus.Stream.emit("ui.streamHud.rendered",{expanded:options.offset.x===0})});`; var stream_hud_default = `var options=arguments[0];window.BX_EXPOSED.showStreamMenu=options.onShowStreamMenu;options.guideUI=null;window.BX_EXPOSED.reactUseEffect(()=>{window.BxEventBus.Stream.emit("ui.streamHud.rendered",{expanded:options.offset.x===0})});`;
var create_portal_default = `var $dom=arguments[1];if($dom&&$dom instanceof HTMLElement&&$dom.id==="gamepass-dialog-root"){let showing=!1,$dialog=$dom.firstElementChild?.firstElementChild;if($dialog)showing=!$dialog.className.includes("pageChangeExit");window.BxEventBus.Script.emit(showing?"dialog.shown":"dialog.dismissed",{})}`; var create_portal_default = `var $dom=arguments[1];if($dom&&$dom instanceof HTMLElement&&$dom.id==="gamepass-dialog-root"){let showing=!1,$dialog=$dom.firstElementChild?.firstElementChild;if($dialog)showing=!$dialog.className.includes("pageChangeExit");window.BxEventBus.Script.emit(showing?"dialog.shown":"dialog.dismissed",{})}`;
@ -5133,11 +5142,6 @@ var LOG_TAG2 = "Patcher", PATCHES = {
if (index < 0 || PatcherUtils.indexOf(str, '"AppInsightsCore', index, 200) < 0) return !1; if (index < 0 || PatcherUtils.indexOf(str, '"AppInsightsCore', index, 200) < 0) return !1;
return PatcherUtils.replaceWith(str, index, text, ".track=function(e){},!!function("); return PatcherUtils.replaceWith(str, index, text, ".track=function(e){},!!function(");
}, },
disableTelemetry(str) {
let text = ".disableTelemetry=function(){return!1}";
if (!str.includes(text)) return !1;
return str.replace(text, ".disableTelemetry=function(){return!0}");
},
disableTelemetryProvider(str) { disableTelemetryProvider(str) {
let text = "this.enableLightweightTelemetry=!"; let text = "this.enableLightweightTelemetry=!";
if (!str.includes(text)) return !1; if (!str.includes(text)) return !1;
@ -5165,10 +5169,10 @@ var LOG_TAG2 = "Patcher", PATCHES = {
let layout = getGlobalPref("ui.layout") === "tv" ? "tv" : "default"; let layout = getGlobalPref("ui.layout") === "tv" ? "tv" : "default";
return str.replace(text, `?"${layout}":"${layout}"`); return str.replace(text, `?"${layout}":"${layout}"`);
}, },
remotePlayDirectConnectUrl(str) { remotePlayPostStreamRedirectUrl(str) {
let index = str.indexOf("/direct-connect"); let text = ".RemotePlayRoot.getLink()):";
if (index < 0) return !1; if (!str.includes(text)) return !1;
return str.replace(str.substring(index - 9, index + 15), "https://www.xbox.com/play"); return str = str.replace(text, ".Home.getLink()):"), str;
}, },
remotePlayKeepAlive(str) { remotePlayKeepAlive(str) {
let text = "onServerDisconnectMessage(e){"; let text = "onServerDisconnectMessage(e){";
@ -5185,20 +5189,9 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
remotePlayDisableAchievementToast(str) { remotePlayDisableAchievementToast(str) {
let text = ".AchievementUnlock:{"; let text = ".AchievementUnlock:{";
if (!str.includes(text)) return !1; if (!str.includes(text)) return !1;
let newCode = "if (!!window.BX_REMOTE_PLAY_CONFIG) return;"; let newCode = "if (window.location.pathname.includes('/consoles/launch/')) return;";
return str.replace(text, text + newCode); return str.replace(text, text + newCode);
}, },
remotePlayRecentlyUsedTitleIds(str) {
let text = "(e.data.recentlyUsedTitleIds)){";
if (!str.includes(text)) return !1;
let newCode = "if (window.BX_REMOTE_PLAY_CONFIG) return;";
return str.replace(text, text + newCode);
},
remotePlayWebTitle(str) {
let text = "titleTemplate:void 0,title:", index = str.indexOf(text);
if (index < 0) return !1;
return str = PatcherUtils.insertAt(str, index + text.length, `!!window.BX_REMOTE_PLAY_CONFIG ? "${t("remote-play")} - Better xCloud" :`), str;
},
blockWebRtcStatsCollector(str) { blockWebRtcStatsCollector(str) {
let text = "this.shouldCollectStats=!0"; let text = "this.shouldCollectStats=!0";
if (!str.includes(text)) return !1; if (!str.includes(text)) return !1;
@ -5223,14 +5216,14 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
return customizationCode += renderString(controller_customization_default, { xCloudGamepadVar }), codeBlock = PatcherUtils.insertAt(codeBlock, backetIndex, customizationCode), str = str.substring(0, index) + codeBlock + str.substring(setTimeoutIndex), str; return customizationCode += renderString(controller_customization_default, { xCloudGamepadVar }), codeBlock = PatcherUtils.insertAt(codeBlock, backetIndex, customizationCode), str = str.substring(0, index) + codeBlock + str.substring(setTimeoutIndex), str;
}, },
enableXcloudLogger(str) { enableXcloudLogger(str) {
let text = "this.telemetryProvider=e}log(e,t,r){"; let index = str.indexOf("this.telemetryProvider.trackErrorLike");
if (!str.includes(text)) return !1; if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "}log(", index, 1500)), index > -1 && (index = PatcherUtils.indexOf(str, "{", index, 30, !0)), index < 0) return !1;
let newCode = ` let newCode = `
const [logTag, logLevel, logMessage] = Array.from(arguments); const [logTag, logLevel, logMessage] = Array.from(arguments);
const logFunc = [console.debug, console.log, console.warn, console.error][logLevel]; const logFunc = [console.debug, console.log, console.warn, console.error][logLevel];
logFunc(logTag, '//', logMessage); logFunc(logTag, '//', logMessage);
`; `;
return str = str.replaceAll(text, text + newCode), str; return str = PatcherUtils.insertAt(str, index, newCode), str;
}, },
enableConsoleLogging(str) { enableConsoleLogging(str) {
let text = "static isConsoleLoggingAllowed(){"; let text = "static isConsoleLoggingAllowed(){";
@ -5559,11 +5552,6 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
if (index >= 0 && (index = str.indexOf('addEventListener("touchstart"', index)), index >= 0 && (index = PatcherUtils.lastIndexOf(str, "return ", index, 50)), index < 0) return !1; if (index >= 0 && (index = str.indexOf('addEventListener("touchstart"', index)), index >= 0 && (index = PatcherUtils.lastIndexOf(str, "return ", index, 50)), index < 0) return !1;
return str = PatcherUtils.replaceWith(str, index, "return", "return () => {};"), str; return str = PatcherUtils.replaceWith(str, index, "return", "return () => {};"), str;
}, },
optimizeGameSlugGenerator(str) {
let text = "/[;,/?:@&=+_`~$%#^*()!^\\u2122\\xae\\xa9]/g";
if (!str.includes(text)) return !1;
return str = str.replace(text, "window.BX_EXPOSED.GameSlugRegexes[0]"), str = str.replace("/ {2,}/g", "window.BX_EXPOSED.GameSlugRegexes[1]"), str = str.replace("/ /g", "window.BX_EXPOSED.GameSlugRegexes[2]"), str;
},
modifyPreloadedState(str) { modifyPreloadedState(str) {
let text = "=window.__PRELOADED_STATE__;"; let text = "=window.__PRELOADED_STATE__;";
if (!str.includes(text)) return !1; if (!str.includes(text)) return !1;
@ -5669,6 +5657,17 @@ ${subsVar} = subs;
let index = str.indexOf("GuideAchievementDetail.useParams()"); let index = str.indexOf("GuideAchievementDetail.useParams()");
if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "const", index, 200)), index < 0) return !1; if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "const", index, 200)), index < 0) return !1;
return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideAchievementDetail.rendered"); 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([ }, PATCH_ORDERS = PatcherUtils.filterPatches([
...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? [ ...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? [
@ -5680,7 +5679,6 @@ ${subsVar} = subs;
"broadcastPollingMode", "broadcastPollingMode",
getGlobalPref("ui.gameCard.waitTime.show") && "patchSetCurrentFocus", getGlobalPref("ui.gameCard.waitTime.show") && "patchSetCurrentFocus",
"patchGamepadPolling", "patchGamepadPolling",
"optimizeGameSlugGenerator",
"modifyPreloadedState", "modifyPreloadedState",
"detectBrowserRouterReady", "detectBrowserRouterReady",
"exposeStreamSession", "exposeStreamSession",
@ -5699,6 +5697,7 @@ ${subsVar} = subs;
"guideAchievementsDefaultLocked", "guideAchievementsDefaultLocked",
"injectHeaderUseEffect", "injectHeaderUseEffect",
"homePageBeforeLoad", "homePageBeforeLoad",
"patchCustomInputIcon",
"gameCardCustomIcons", "gameCardCustomIcons",
"productDetailPageBeforeLoad", "productDetailPageBeforeLoad",
"enableTvRoutes", "enableTvRoutes",
@ -5710,17 +5709,13 @@ ${subsVar} = subs;
] : [], ] : [],
...getGlobalPref("block.tracking") ? [ ...getGlobalPref("block.tracking") ? [
"disableAiTrack", "disableAiTrack",
"disableTelemetry",
"blockWebRtcStatsCollector", "blockWebRtcStatsCollector",
"disableIndexDbLogging", "disableIndexDbLogging",
"disableTelemetryProvider" "disableTelemetryProvider"
] : [], ] : [],
...getGlobalPref("xhome.enabled") ? [ ...getGlobalPref("xhome.enabled") ? [
"remotePlayDirectConnectUrl",
"remotePlayKeepAlive", "remotePlayKeepAlive",
"remotePlayWebTitle",
"remotePlayDisableAchievementToast", "remotePlayDisableAchievementToast",
"remotePlayRecentlyUsedTitleIds",
STATES.userAgent.capabilities.touch && "patchUpdateInputConfigurationAsync" STATES.userAgent.capabilities.touch && "patchUpdateInputConfigurationAsync"
] : [], ] : [],
...BX_FLAGS.EnableXcloudLogging ? [ ...BX_FLAGS.EnableXcloudLogging ? [
@ -5757,10 +5752,10 @@ ${subsVar} = subs;
getGlobalPref("touchController.opacity.default") !== 100 && "patchTouchControlDefaultOpacity", getGlobalPref("touchController.opacity.default") !== 100 && "patchTouchControlDefaultOpacity",
getGlobalPref("touchController.mode") !== "off" && (getGlobalPref("mkb.enabled") || getGlobalPref("nativeMkb.mode") === "on") && "patchBabylonRendererClass" getGlobalPref("touchController.mode") !== "off" && (getGlobalPref("mkb.enabled") || getGlobalPref("nativeMkb.mode") === "on") && "patchBabylonRendererClass"
] : [], ] : [],
BX_FLAGS.EnableXcloudLogging && "enableConsoleLogging",
"patchPollGamepads", "patchPollGamepads",
getGlobalPref("stream.video.combineAudio") && "streamCombineSources", getGlobalPref("stream.video.combineAudio") && "streamCombineSources",
...getGlobalPref("xhome.enabled") ? [ ...getGlobalPref("xhome.enabled") ? [
"remotePlayPostStreamRedirectUrl",
"patchRemotePlayMkb", "patchRemotePlayMkb",
"remotePlayConnectMode" "remotePlayConnectMode"
] : [], ] : [],
@ -5846,11 +5841,12 @@ class PatcherCache {
PATCH_ORDERS = this.cleanupPatches(PATCH_ORDERS), STREAM_PAGE_PATCH_ORDERS = this.cleanupPatches(STREAM_PAGE_PATCH_ORDERS), PRODUCT_DETAIL_PAGE_PATCH_ORDERS = this.cleanupPatches(PRODUCT_DETAIL_PAGE_PATCH_ORDERS), BxLogger.info(LOG_TAG2, "PATCH_ORDERS", PATCH_ORDERS.slice(0)); PATCH_ORDERS = this.cleanupPatches(PATCH_ORDERS), STREAM_PAGE_PATCH_ORDERS = this.cleanupPatches(STREAM_PAGE_PATCH_ORDERS), PRODUCT_DETAIL_PAGE_PATCH_ORDERS = this.cleanupPatches(PRODUCT_DETAIL_PAGE_PATCH_ORDERS), BxLogger.info(LOG_TAG2, "PATCH_ORDERS", PATCH_ORDERS.slice(0));
} }
getSignature() { getSignature() {
let scriptVersion = SCRIPT_VERSION, patches = JSON.stringify(ALL_PATCHES), webVersion = "", $link = document.querySelector('link[data-chunk="client"][href*="/client."]'); let scriptVersion = SCRIPT_VERSION, patches = JSON.stringify(ALL_PATCHES), webVersion = "", $link = document.querySelector('link[data-chunk="client"][as="script"][href*="/client."]');
if ($link) { if ($link) {
let match = /\/client\.([^\.]+)\.js/.exec($link.href); let match = /\/client\.([^\.]+)\.js/.exec($link.href);
match && (webVersion = match[1]); match && (webVersion = match[1]);
} else webVersion = document.querySelector("meta[name=gamepass-app-version]")?.content ?? ""; }
if (!webVersion) webVersion = document.querySelector("meta[name=gamepass-app-version]")?.content ?? "";
return hashCode(scriptVersion + webVersion + patches); return hashCode(scriptVersion + webVersion + patches);
} }
clear() { clear() {
@ -8161,7 +8157,8 @@ var FeatureGates = {
EnableUpdateRequiredPage: !1, EnableUpdateRequiredPage: !1,
ShowForcedUpdateScreen: !1, ShowForcedUpdateScreen: !1,
EnableTakControlResizing: !0, EnableTakControlResizing: !0,
EnableLazyLoadedHome: !1 EnableLazyLoadedHome: !1,
EnableRemotePlay: getGlobalPref("xhome.enabled")
}, nativeMkbMode = getGlobalPref("nativeMkb.mode"); }, nativeMkbMode = getGlobalPref("nativeMkb.mode");
if (nativeMkbMode !== "default") FeatureGates.EnableMouseAndKeyboard = nativeMkbMode === "on"; if (nativeMkbMode !== "default") FeatureGates.EnableMouseAndKeyboard = nativeMkbMode === "on";
var blockFeatures = getGlobalPref("block.features"); var blockFeatures = getGlobalPref("block.features");
@ -8299,6 +8296,10 @@ var BxExposed = {
createReactLocalCoOpIcon: (attrs) => { createReactLocalCoOpIcon: (attrs) => {
let reactCE = window.BX_EXPOSED.reactCreateElement; 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" }))); 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);
} }
}; };
function localRedirect(path) { function localRedirect(path) {
@ -8384,7 +8385,7 @@ class RemotePlayDialog extends NavigationDialog {
BxLogger.info(this.LOG_TAG, "constructor()"), this.setupDialog(); BxLogger.info(this.LOG_TAG, "constructor()"), this.setupDialog();
} }
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")); 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) => { $resolutions = BxSelectElement.create($resolutions), $resolutions.addEventListener("input", (e) => {
let value = e.target.value; let value = e.target.value;
$settingNote.textContent = value === "1080p" ? "✅ " + t("can-stream-xbox-360-games") : "❌ " + t("cant-stream-xbox-360-games"), setGlobalPref("xhome.video.resolution", value, "ui"); $settingNote.textContent = value === "1080p" ? "✅ " + t("can-stream-xbox-360-games") : "❌ " + t("cant-stream-xbox-360-games"), setGlobalPref("xhome.video.resolution", value, "ui");
@ -8514,6 +8515,9 @@ class RemotePlayManager {
Authorization: `Bearer ${this.XHOME_TOKEN}` Authorization: `Bearer ${this.XHOME_TOKEN}`
} }
}; };
this.regions.sort((a, b) => {
return a.isDefault ? -1 : 0;
});
for (let region of this.regions) for (let region of this.regions)
try { try {
let request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options), json = await (await fetch(request)).json(); let request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options), json = await (await fetch(request)).json();
@ -8528,7 +8532,7 @@ class RemotePlayManager {
if (resolution) setGlobalPref("xhome.video.resolution", resolution, "ui"); if (resolution) setGlobalPref("xhome.video.resolution", resolution, "ui");
STATES.remotePlay.config = { STATES.remotePlay.config = {
serverId serverId
}, window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config, localRedirect("/launch/fortnite/BT5P2X999VH2#remote-play"); }, window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config, localRedirect("/launch/fortnite/BT5P2X999VH2#remote-play"), setTimeout(() => localRedirect("/consoles/launch/" + serverId), 100);
} }
togglePopup(force = null) { togglePopup(force = null) {
if (!this.isReady()) { if (!this.isReady()) {
@ -8597,7 +8601,7 @@ class XhomeInterceptor {
if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, { if (hasTouchSupport) TouchController.disable(), BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
data: null data: null
}); });
else TouchController.enable(), TouchController.requestCustomLayouts(xboxTitleId); else TouchController.enable(), TouchController.requestCustomLayouts();
return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response; return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;
} }
static async handleTitles(request) { static async handleTitles(request) {
@ -8662,7 +8666,8 @@ class LoadingScreen {
let $bgStyle = CE("style"); let $bgStyle = CE("style");
document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle; document.documentElement.appendChild($bgStyle), LoadingScreen.$bgStyle = $bgStyle;
} }
if (LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl), getGlobalPref("loadingScreen.rocket") === "hide") LoadingScreen.hideRocket(); if (titleInfo.productInfo) LoadingScreen.setBackground(titleInfo.productInfo.heroImageUrl || titleInfo.productInfo.titledHeroImageUrl || titleInfo.productInfo.tileImageUrl);
if (getGlobalPref("loadingScreen.rocket") === "hide") LoadingScreen.hideRocket();
} }
static hideRocket() { static hideRocket() {
let $bgStyle = LoadingScreen.$bgStyle; let $bgStyle = LoadingScreen.$bgStyle;
@ -9094,9 +9099,12 @@ function updateIceCandidates(candidates, options) {
let pattern = new RegExp(/a=candidate:(?<foundation>\d+) (?<component>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<port>\d+) (?<the_rest>.*)/), lst = []; let pattern = new RegExp(/a=candidate:(?<foundation>\d+) (?<component>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<port>\d+) (?<the_rest>.*)/), lst = [];
for (let item2 of candidates) { for (let item2 of candidates) {
if (item2.candidate == "a=end-of-candidates") continue; if (item2.candidate == "a=end-of-candidates") continue;
let groups = pattern.exec(item2.candidate).groups; let match = pattern.exec(item2.candidate);
if (match && match.groups) {
let groups = match.groups;
lst.push(groups); lst.push(groups);
} }
}
if (options.preferIpv6Server) lst.sort((a, b) => { if (options.preferIpv6Server) lst.sort((a, b) => {
let firstIp = a.ip, secondIp = b.ip; let firstIp = a.ip, secondIp = b.ip;
return !firstIp.includes(":") && secondIp.includes(":") ? 1 : -1; return !firstIp.includes(":") && secondIp.includes(":") ? 1 : -1;
@ -9579,7 +9587,7 @@ function patchVideoApi() {
return nativePlay.apply(this); return nativePlay.apply(this);
} }
let $parent = this.parentElement; let $parent = this.parentElement;
if (!this.src && $parent.dataset.testid === "media-container") this.addEventListener("loadedmetadata", showFunc, { once: !0 }); if (!this.src && $parent?.dataset.testid === "media-container") this.addEventListener("loadedmetadata", showFunc, { once: !0 });
return nativePlay.apply(this); return nativePlay.apply(this);
}; };
} }
@ -10256,7 +10264,7 @@ BxEventBus.Script.on("ui.header.rendered", () => {
HeaderSection.getInstance().checkHeader(); HeaderSection.getInstance().checkHeader();
}); });
BxEventBus.Stream.on("state.loading", () => { BxEventBus.Stream.on("state.loading", () => {
if (window.location.pathname.includes("/launch/") && STATES.currentStream.titleInfo) STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title); if (window.location.pathname.includes("/launch/") && STATES.currentStream.titleInfo) STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.productInfo.title);
else STATES.currentStream.titleSlug = "remote-play"; else STATES.currentStream.titleSlug = "remote-play";
}); });
getGlobalPref("loadingScreen.gameArt.show") && BxEventBus.Script.on("titleInfo.ready", LoadingScreen.setup); getGlobalPref("loadingScreen.gameArt.show") && BxEventBus.Script.on("titleInfo.ready", LoadingScreen.setup);

File diff suppressed because one or more lines are too long

View File

@ -204,7 +204,7 @@ BxEventBus.Script.on('ui.header.rendered', () => {
BxEventBus.Stream.on('state.loading', () => { BxEventBus.Stream.on('state.loading', () => {
// Get title ID for screenshot's name // Get title ID for screenshot's name
if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) { if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) {
STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title); STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.productInfo.title);
} else { } else {
STATES.currentStream.titleSlug = 'remote-play'; STATES.currentStream.titleSlug = 'remote-play';
} }

View File

@ -35,7 +35,9 @@ export class LoadingScreen {
LoadingScreen.$bgStyle = $bgStyle; LoadingScreen.$bgStyle = $bgStyle;
} }
LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl); if (titleInfo.productInfo) {
LoadingScreen.setBackground(titleInfo.productInfo.heroImageUrl || titleInfo.productInfo.titledHeroImageUrl || titleInfo.productInfo.tileImageUrl);
}
if (getGlobalPref(GlobalPref.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE) { if (getGlobalPref(GlobalPref.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE) {
LoadingScreen.hideRocket(); LoadingScreen.hideRocket();

View File

@ -16,7 +16,6 @@ import codeCreatePortal from "./patches/create-portal.js" with { type: "text" };
import { GlobalPref, StorageKey } from "@/enums/pref-keys.js"; import { GlobalPref, StorageKey } from "@/enums/pref-keys.js";
import { getGlobalPref } from "@/utils/pref-utils.js"; import { getGlobalPref } from "@/utils/pref-utils.js";
import { GamePassCloudGallery } from "@/enums/game-pass-gallery"; import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
import { t } from "@/utils/translation";
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values"; import { BlockFeature, NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values";
import { PatcherUtils } from "./patcher-utils.js"; import { PatcherUtils } from "./patcher-utils.js";
@ -40,6 +39,7 @@ const PATCHES = {
}, },
// Set disableTelemetry() to true // Set disableTelemetry() to true
/*
disableTelemetry(str: string) { disableTelemetry(str: string) {
let text = '.disableTelemetry=function(){return!1}'; let text = '.disableTelemetry=function(){return!1}';
if (!str.includes(text)) { if (!str.includes(text)) {
@ -48,6 +48,7 @@ const PATCHES = {
return str.replace(text, '.disableTelemetry=function(){return!0}'); return str.replace(text, '.disableTelemetry=function(){return!0}');
}, },
*/
disableTelemetryProvider(str: string) { disableTelemetryProvider(str: string) {
let text = 'this.enableLightweightTelemetry=!'; let text = 'this.enableLightweightTelemetry=!';
@ -92,14 +93,14 @@ const PATCHES = {
return str.replace(text, `?"${layout}":"${layout}"`); return str.replace(text, `?"${layout}":"${layout}"`);
}, },
// Replace "/direct-connect" with "/play" remotePlayPostStreamRedirectUrl(str: string) {
remotePlayDirectConnectUrl(str: string) { let text = '.RemotePlayRoot.getLink()):';
const index = str.indexOf('/direct-connect'); if (!str.includes(text)) {
if (index < 0) {
return false; return false;
} }
return str.replace(str.substring(index - 9, index + 15), 'https://www.xbox.com/play'); str = str.replace(text, '.Home.getLink()):');
return str;
}, },
remotePlayKeepAlive(str: string) { remotePlayKeepAlive(str: string) {
@ -132,33 +133,10 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
return false; return false;
} }
const newCode = `if (!!window.BX_REMOTE_PLAY_CONFIG) return;`; const newCode = `if (window.location.pathname.includes('/consoles/launch/')) return;`;
return str.replace(text, text + newCode); return str.replace(text, text + newCode);
}, },
// Remote Play: Prevent adding "Fortnite" to the "Jump back in" list
remotePlayRecentlyUsedTitleIds(str: string) {
let text = '(e.data.recentlyUsedTitleIds)){';
if (!str.includes(text)) {
return false;
}
const newCode = `if (window.BX_REMOTE_PLAY_CONFIG) return;`;
return str.replace(text, text + newCode);
},
// Remote Play: change web page's title
remotePlayWebTitle(str: string) {
let text = 'titleTemplate:void 0,title:';
const index = str.indexOf(text);
if (index < 0) {
return false;
}
str = PatcherUtils.insertAt(str, index + text.length, `!!window.BX_REMOTE_PLAY_CONFIG ? "${t('remote-play')} - Better xCloud" :`);
return str;
},
// Block WebRTC stats collector // Block WebRTC stats collector
blockWebRtcStatsCollector(str: string) { blockWebRtcStatsCollector(str: string) {
let text = 'this.shouldCollectStats=!0'; let text = 'this.shouldCollectStats=!0';
@ -226,8 +204,11 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
}, },
enableXcloudLogger(str: string) { enableXcloudLogger(str: string) {
let text = 'this.telemetryProvider=e}log(e,t,r){'; let index = str.indexOf('this.telemetryProvider.trackErrorLike');
if (!str.includes(text)) { index > -1 && (index = PatcherUtils.lastIndexOf(str, '}log(', index, 1500));
index > -1 && (index = PatcherUtils.indexOf(str, '{', index, 30, true));
if (index < 0) {
return false; return false;
} }
@ -237,7 +218,7 @@ const logFunc = [console.debug, console.log, console.warn, console.error][logLev
logFunc(logTag, '//', logMessage); logFunc(logTag, '//', logMessage);
`; `;
str = str.replaceAll(text, text + newCode); str = PatcherUtils.insertAt(str, index, newCode);
return str; return str;
}, },
@ -933,20 +914,6 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
return str; return str;
}, },
// Optimize Game slug generator by using cached RegEx
optimizeGameSlugGenerator(str: string) {
let text = '/[;,/?:@&=+_`~$%#^*()!^\\u2122\\xae\\xa9]/g';
if (!str.includes(text)) {
return false;
}
str = str.replace(text, 'window.BX_EXPOSED.GameSlugRegexes[0]');
str = str.replace('/ {2,}/g', 'window.BX_EXPOSED.GameSlugRegexes[1]');
str = str.replace('/ /g', 'window.BX_EXPOSED.GameSlugRegexes[2]');
return str;
},
modifyPreloadedState(str: string) { modifyPreloadedState(str: string) {
let text = '=window.__PRELOADED_STATE__;'; let text = '=window.__PRELOADED_STATE__;';
if (!str.includes(text)) { if (!str.includes(text)) {
@ -1202,6 +1169,36 @@ ${subsVar} = subs;
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideAchievementDetail.rendered'); return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideAchievementDetail.rendered');
}, },
patchCustomInputIcon(str: string) {
let index = str.indexOf('.MouseAndKeyboard="MouseAndKeyboard"');
if (index < 0) {
return false;
}
// Get productId
const productIdMatch = /const (\w+)=(\w+)=>{/.exec(str.substring(index, index + 200));
if (!productIdMatch) {
return false;
}
// Define productId variable
str = str.replace(productIdMatch[0], productIdMatch[0] + `const productId = ${productIdMatch[2]};`);
let match = /(\w+)&&(\w+\.push\(\w+\.Touch\))/.exec(str);
if (!match) {
return false;
}
str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomTouchControl(productId)) && ${match[2]}`);
match = /(\w+)&&(\w+\.push\(\w+\.MouseAndKeyboard\))/.exec(str);
if (match) {
str = str.replace(match[0], `(${match[1]} || window.BX_EXPOSED.hasCustomNativeMkb(productId)) && ${match[2]}`);
}
return str;
},
/* /*
patchBasicGameInfo(str: string) { patchBasicGameInfo(str: string) {
let index = str.indexOf('.ChildXboxTitleIds,offerings'); let index = str.indexOf('.ChildXboxTitleIds,offerings');
@ -1243,8 +1240,6 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'patchGamepadPolling', 'patchGamepadPolling',
'optimizeGameSlugGenerator',
'modifyPreloadedState', 'modifyPreloadedState',
'detectBrowserRouterReady', 'detectBrowserRouterReady',
@ -1275,6 +1270,8 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'homePageBeforeLoad', 'homePageBeforeLoad',
'patchCustomInputIcon',
'gameCardCustomIcons', 'gameCardCustomIcons',
// 'gameCardPassTitle', // 'gameCardPassTitle',
@ -1293,7 +1290,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
...(getGlobalPref(GlobalPref.BLOCK_TRACKING) ? [ ...(getGlobalPref(GlobalPref.BLOCK_TRACKING) ? [
'disableAiTrack', 'disableAiTrack',
'disableTelemetry', // 'disableTelemetry',
'blockWebRtcStatsCollector', 'blockWebRtcStatsCollector',
'disableIndexDbLogging', 'disableIndexDbLogging',
@ -1302,11 +1299,8 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
] : []) as PatchArray, ] : []) as PatchArray,
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [ ...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
'remotePlayDirectConnectUrl',
'remotePlayKeepAlive', 'remotePlayKeepAlive',
'remotePlayWebTitle',
'remotePlayDisableAchievementToast', 'remotePlayDisableAchievementToast',
'remotePlayRecentlyUsedTitleIds',
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync', STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
] : []) as PatchArray, ] : []) as PatchArray,
@ -1367,13 +1361,12 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
(getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getGlobalPref(GlobalPref.MKB_ENABLED) || getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass', (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getGlobalPref(GlobalPref.MKB_ENABLED) || getGlobalPref(GlobalPref.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass',
] : []) as PatchArray, ] : []) as PatchArray,
BX_FLAGS.EnableXcloudLogging && 'enableConsoleLogging',
'patchPollGamepads', 'patchPollGamepads',
getGlobalPref(GlobalPref.STREAM_COMBINE_SOURCES) && 'streamCombineSources', getGlobalPref(GlobalPref.STREAM_COMBINE_SOURCES) && 'streamCombineSources',
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [ ...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
'remotePlayPostStreamRedirectUrl',
'patchRemotePlayMkb', 'patchRemotePlayMkb',
'remotePlayConnectMode', 'remotePlayConnectMode',
] : []) as PatchArray, ] : []) as PatchArray,
@ -1583,11 +1576,13 @@ export class PatcherCache {
// Get client.js's hash // Get client.js's hash
let webVersion = ''; let webVersion = '';
const $link = document.querySelector<HTMLLinkElement>('link[data-chunk="client"][href*="/client."]'); const $link = document.querySelector<HTMLLinkElement>('link[data-chunk="client"][as="script"][href*="/client."]');
if ($link) { if ($link) {
const match = /\/client\.([^\.]+)\.js/.exec($link.href); const match = /\/client\.([^\.]+)\.js/.exec($link.href);
match && (webVersion = match[1]); match && (webVersion = match[1]);
} else { }
if (!webVersion) {
// Get version from <meta> // Get version from <meta>
// Sometimes this value is missing // Sometimes this value is missing
webVersion = (document.querySelector<HTMLMetaElement>('meta[name=gamepass-app-version]'))?.content ?? ''; webVersion = (document.querySelector<HTMLMetaElement>('meta[name=gamepass-app-version]'))?.content ?? '';

View File

@ -3,7 +3,7 @@ declare const e: string;
try { try {
const msg = JSON.parse(e); const msg = JSON.parse(e);
if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) { if (msg.reason === 'WarningForBeingIdle' && window.location.pathname.includes('/consoles/launch/')) {
$this$.sendKeepAlive(); $this$.sendKeepAlive();
// @ts-ignore // @ts-ignore
return; return;

View File

@ -156,6 +156,11 @@ export class RemotePlayManager {
}, },
}; };
// Start with "isDefault" = true first
this.regions.sort((a: RemotePlayRegion, b: RemotePlayRegion) => {
return a.isDefault ? -1 : 0;
})
// Test servers one by one // Test servers one by one
for (const region of this.regions) { for (const region of this.regions) {
try { try {
@ -195,6 +200,7 @@ export class RemotePlayManager {
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config; window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
localRedirect('/launch/fortnite/BT5P2X999VH2#remote-play'); localRedirect('/launch/fortnite/BT5P2X999VH2#remote-play');
setTimeout(() => localRedirect('/consoles/launch/' + serverId), 100);
} }
togglePopup(force = null) { togglePopup(force = null) {

View File

@ -209,7 +209,7 @@ export class TouchController {
} }
if (!layoutId) { if (!layoutId) {
BxLogger.error(LOG_TAG, 'Invalid layoutId, show default controller'); BxLogger.warning(LOG_TAG, 'Invalid layoutId, show default controller');
TouchController.#enabled && TouchController.#showDefault(); TouchController.#enabled && TouchController.#showDefault();
return; return;
} }
@ -267,6 +267,10 @@ export class TouchController {
return TouchController.#customList; return TouchController.#customList;
} }
static hasCustomControl(productId: string): boolean {
return TouchController.#customList?.includes(productId);
}
static setup() { static setup() {
// Function for testing touch control // Function for testing touch control
window.testTouchLayout = (layout: any) => { window.testTouchLayout = (layout: any) => {

View File

@ -44,14 +44,14 @@ export class RemotePlayDialog extends NavigationDialog {
let $resolutions : HTMLSelectElement | NavigationElement = CE('select', false, let $resolutions : HTMLSelectElement | NavigationElement = CE('select', false,
CE('option', { value: StreamResolution.DIM_720P }, '720p'), CE('option', { value: StreamResolution.DIM_720P }, '720p'),
CE('option', { value: StreamResolution.DIM_1080P }, '1080p'), CE('option', { value: StreamResolution.DIM_1080P }, '1080p'),
// CE('option', { value: StreamResolution.DIM_1080P_HQ }, `1080p (HQ)`), CE('option', { value: StreamResolution.DIM_1080P_HQ }, `1080p (HQ)`),
); );
$resolutions = BxSelectElement.create($resolutions as HTMLSelectElement); $resolutions = BxSelectElement.create($resolutions as HTMLSelectElement);
$resolutions.addEventListener('input', (e: Event) => { $resolutions.addEventListener('input', (e: Event) => {
const value = (e.target as HTMLSelectElement).value; const value = (e.target as HTMLSelectElement).value;
$settingNote.textContent = value === '1080p' ? '✅ ' + t('can-stream-xbox-360-games') : '❌ ' + t('cant-stream-xbox-360-games'); $settingNote.textContent = value === StreamResolution.DIM_1080P ? '✅ ' + t('can-stream-xbox-360-games') : '❌ ' + t('cant-stream-xbox-360-games');
setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, value, 'ui'); setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, value, 'ui');
}); });

View File

@ -36,7 +36,7 @@ type XcloudTitleInfo = {
hasMkbSupport: boolean; hasMkbSupport: boolean;
}; };
product: { productInfo: {
title: string; title: string;
heroImageUrl: string; heroImageUrl: string;
titledHeroImageUrl: string; titledHeroImageUrl: string;

View File

@ -261,4 +261,9 @@ export const BxExposed = {
), ),
); );
} : () => {}, } : () => {},
hasCustomTouchControl: TouchController.hasCustomControl,
hasCustomNativeMkb: (productId: string) => {
return BX_FLAGS.ForceNativeMkbTitles?.includes(productId);
}
}; };

View File

@ -10,6 +10,7 @@ export let FeatureGates: { [key: string]: boolean } = {
ShowForcedUpdateScreen: false, ShowForcedUpdateScreen: false,
EnableTakControlResizing: true, // Experimenting EnableTakControlResizing: true, // Experimenting
EnableLazyLoadedHome: false, EnableLazyLoadedHome: false,
EnableRemotePlay: getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED),
}; };
// Enable Native Mouse & Keyboard // Enable Native Mouse & Keyboard

View File

@ -55,9 +55,9 @@ export function patchVideoApi() {
return nativePlay.apply(this); return nativePlay.apply(this);
} }
const $parent = this.parentElement!!; const $parent = this.parentElement;
// Video tag is stream player // Video tag is stream player
if (!this.src && $parent.dataset.testid === 'media-container') { if (!this.src && $parent?.dataset.testid === 'media-container') {
this.addEventListener('loadedmetadata', showFunc, { once: true }); this.addEventListener('loadedmetadata', showFunc, { once: true });
} }

View File

@ -49,9 +49,12 @@ function updateIceCandidates(candidates: any, options: { preferIpv6Server: boole
continue; continue;
} }
const groups: { [index: string]: string | number } = pattern.exec(item.candidate)!.groups!; const match = pattern.exec(item.candidate);
if (match && match.groups) {
const groups: { [index: string]: string | number } = match.groups;
lst.push(groups); lst.push(groups);
} }
}
if (options.preferIpv6Server) { if (options.preferIpv6Server) {
lst.sort((a, b) => { lst.sort((a, b) => {

View File

@ -7,6 +7,7 @@ export const SUPPORTED_LANGUAGES = {
'en-US': 'English (US)', 'en-US': 'English (US)',
'ca-CA': 'Català', 'ca-CA': 'Català',
'cs-CZ': 'čeština',
'da-DK': 'dansk', 'da-DK': 'dansk',
'de-DE': 'Deutsch', 'de-DE': 'Deutsch',
'en-ID': 'Bahasa Indonesia', 'en-ID': 'Bahasa Indonesia',
@ -198,6 +199,7 @@ const Texts = {
"new-version-available": [ "new-version-available": [
(e: any) => `Version ${e.version} available`, (e: any) => `Version ${e.version} available`,
(e: any) => `Versió ${e.version} disponible`, (e: any) => `Versió ${e.version} disponible`,
(e: any) => `Verze ${e.version} dostupná`,
, ,
(e: any) => `Version ${e.version} verfügbar`, (e: any) => `Version ${e.version} verfügbar`,
(e: any) => `Versi ${e.version} tersedia`, (e: any) => `Versi ${e.version} tersedia`,
@ -243,6 +245,7 @@ const Texts = {
"press-key-to-toggle-mkb": [ "press-key-to-toggle-mkb": [
(e: any) => `Press ${e.key} to toggle this feature`, (e: any) => `Press ${e.key} to toggle this feature`,
(e: any) => `Premeu ${e.key} per alternar aquesta funció`, (e: any) => `Premeu ${e.key} per alternar aquesta funció`,
(e: any) => `Zmáčknete ${e.key} pro přepnutí této funkce`,
(e: any) => `Tryk på ${e.key} for at slå denne funktion til`, (e: any) => `Tryk på ${e.key} for at slå denne funktion til`,
(e: any) => `${e.key}: Funktion an-/ausschalten`, (e: any) => `${e.key}: Funktion an-/ausschalten`,
(e: any) => `Tekan ${e.key} untuk mengaktifkan fitur ini`, (e: any) => `Tekan ${e.key} untuk mengaktifkan fitur ini`,
@ -268,6 +271,7 @@ const Texts = {
(e: any) => `Recommended settings for ${e.device}`, (e: any) => `Recommended settings for ${e.device}`,
(e: any) => `Configuració recomanada per a ${e.device}`, (e: any) => `Configuració recomanada per a ${e.device}`,
, ,
,
(e: any) => `Empfohlene Einstellungen für ${e.device}`, (e: any) => `Empfohlene Einstellungen für ${e.device}`,
(e: any) => `Rekomendasi pengaturan untuk ${e.device}`, (e: any) => `Rekomendasi pengaturan untuk ${e.device}`,
(e: any) => `Ajustes recomendados para ${e.device}`, (e: any) => `Ajustes recomendados para ${e.device}`,
@ -378,6 +382,7 @@ const Texts = {
"touch-control-layout-by": [ "touch-control-layout-by": [
(e: any) => `Touch control layout by ${e.name}`, (e: any) => `Touch control layout by ${e.name}`,
(e: any) => `Format del control tàctil per ${e.name}`, (e: any) => `Format del control tàctil per ${e.name}`,
(e: any) => `Rozložení dotykového ovládání ${e.name}`,
(e: any) => `Touch-kontrol layout af ${e.name}`, (e: any) => `Touch-kontrol layout af ${e.name}`,
(e: any) => `Touch-Steuerungslayout von ${e.name}`, (e: any) => `Touch-Steuerungslayout von ${e.name}`,
(e: any) => `Tata letak Sentuhan layar oleh ${e.name}`, (e: any) => `Tata letak Sentuhan layar oleh ${e.name}`,

View File

@ -16,6 +16,15 @@ export function checkForUpdate() {
return; return;
} }
// Always check for new version
fetch('https://api.github.com/repos/redphx/better-xcloud/releases/latest')
.then(response => response.json())
.then(json => {
// Store the latest version
setGlobalPref(GlobalPref.VERSION_LATEST, json.tag_name.substring(1), 'direct');
setGlobalPref(GlobalPref.VERSION_CURRENT, SCRIPT_VERSION, 'direct');
});
const CHECK_INTERVAL_SECONDS = 2 * 3600; // check every 2 hours const CHECK_INTERVAL_SECONDS = 2 * 3600; // check every 2 hours
const currentVersion = getGlobalPref(GlobalPref.VERSION_CURRENT); const currentVersion = getGlobalPref(GlobalPref.VERSION_CURRENT);
@ -28,13 +37,6 @@ export function checkForUpdate() {
// Start checking // Start checking
setGlobalPref(GlobalPref.VERSION_LAST_CHECK, now, 'direct'); setGlobalPref(GlobalPref.VERSION_LAST_CHECK, now, 'direct');
fetch('https://api.github.com/repos/redphx/better-xcloud/releases/latest')
.then(response => response.json())
.then(json => {
// Store the latest version
setGlobalPref(GlobalPref.VERSION_LATEST, json.tag_name.substring(1), 'direct');
setGlobalPref(GlobalPref.VERSION_CURRENT, SCRIPT_VERSION, 'direct');
});
// Update translations // Update translations
Translations.updateTranslations(currentVersion === SCRIPT_VERSION); Translations.updateTranslations(currentVersion === SCRIPT_VERSION);

View File

@ -95,7 +95,7 @@ export class XhomeInterceptor {
}); });
} else { } else {
TouchController.enable(); TouchController.enable();
TouchController.requestCustomLayouts(xboxTitleId); TouchController.requestCustomLayouts();
} }
response.json = () => Promise.resolve(obj); response.json = () => Promise.resolve(obj);